diff --git a/boot_kernel.sh b/boot_game.sh similarity index 71% rename from boot_kernel.sh rename to boot_game.sh index 7cd250cb20..0daecc35a5 100755 --- a/boot_kernel.sh +++ b/boot_game.sh @@ -3,4 +3,4 @@ # Directory of this script DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -$DIR/build/game/gk -fakeiso -debug +$DIR/build/game/gk -boot -fakeiso -debug "$@" diff --git a/common/goos/CMakeLists.txt b/common/goos/CMakeLists.txt index 10aef75320..2000a6f58e 100644 --- a/common/goos/CMakeLists.txt +++ b/common/goos/CMakeLists.txt @@ -5,5 +5,5 @@ ELSE() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") ENDIF() -add_library(goos SHARED Object.cpp TextDB.cpp Reader.cpp Interpreter.cpp PrettyPrinter.cpp) +add_library(goos SHARED Object.cpp ParseHelpers.cpp TextDB.cpp Reader.cpp Interpreter.cpp PrettyPrinter.cpp ParseHelpers.cpp ParseHelpers.h) target_link_libraries(goos common_util fmt) \ No newline at end of file diff --git a/common/goos/Interpreter.cpp b/common/goos/Interpreter.cpp index 53356b7646..4d3bb0e2f4 100644 --- a/common/goos/Interpreter.cpp +++ b/common/goos/Interpreter.cpp @@ -5,6 +5,7 @@ #include #include "Interpreter.h" +#include "ParseHelpers.h" #include namespace goos { @@ -381,44 +382,9 @@ void Interpreter::vararg_check( const Arguments& args, const std::vector>& unnamed, const std::unordered_map>>& named) { - assert(args.rest.empty()); - if (unnamed.size() != args.unnamed.size()) { - throw_eval_error(form, "Got " + std::to_string(args.unnamed.size()) + - " arguments, but expected " + std::to_string(unnamed.size())); - } - - for (size_t i = 0; i < unnamed.size(); i++) { - if (unnamed[i].has_value() && unnamed[i] != args.unnamed[i].type) { - throw_eval_error(form, "Argument " + std::to_string(i) + " has type " + - object_type_to_string(args.unnamed[i].type) + " but " + - object_type_to_string(unnamed[i].value()) + " was expected"); - } - } - - for (const auto& kv : named) { - auto kv2 = args.named.find(kv.first); - if (kv2 == args.named.end()) { - // argument not given. - if (kv.second.first) { - // but was required - throw_eval_error(form, "Required named argument \"" + kv.first + "\" was not found"); - } - } else { - // argument given. - if (kv.second.second.has_value() && kv.second.second != kv2->second.type) { - // but is wrong type - throw_eval_error(form, "Argument \"" + kv.first + "\" has type " + - object_type_to_string(kv2->second.type) + " but " + - object_type_to_string(kv.second.second.value()) + - " was expected"); - } - } - } - - for (const auto& kv : args.named) { - if (named.find(kv.first) == named.end()) { - throw_eval_error(form, "Got unrecognized keyword argument \"" + kv.first + "\""); - } + std::string err; + if (!va_check(args, unnamed, named, &err)) { + throw_eval_error(form, err); } } diff --git a/common/goos/ParseHelpers.cpp b/common/goos/ParseHelpers.cpp new file mode 100644 index 0000000000..0951cfd126 --- /dev/null +++ b/common/goos/ParseHelpers.cpp @@ -0,0 +1,101 @@ +#include "ParseHelpers.h" + +namespace goos { + +bool get_va(const goos::Object& rest, std::string* err_string, goos::Arguments* result) { + goos::Arguments args; + // loop over forms in list + goos::Object current = rest; + while (!current.is_empty_list()) { + auto arg = current.as_pair()->car; + + // did we get a ":keyword" + if (arg.is_symbol() && arg.as_symbol()->name.at(0) == ':') { + auto key_name = arg.as_symbol()->name.substr(1); + + // check for multiple definition of key + if (args.named.find(key_name) != args.named.end()) { + *err_string = "Key argument " + key_name + " multiply defined"; + return false; + } + + // check for well-formed :key value expression + current = current.as_pair()->cdr; + if (current.is_empty_list()) { + *err_string = "Key argument didn't have a value"; + return false; + } + + args.named[key_name] = current.as_pair()->car; + } else { + // not a keyword. Add to unnamed or rest, depending on what we expect + args.unnamed.push_back(arg); + } + current = current.as_pair()->cdr; + } + *result = args; + return true; +} + +bool va_check( + const goos::Arguments& args, + const std::vector>& unnamed, + const std::unordered_map>>& named, + std::string* err_string) { + assert(args.rest.empty()); + if (unnamed.size() != args.unnamed.size()) { + *err_string = "Got " + std::to_string(args.unnamed.size()) + " arguments, but expected " + + std::to_string(unnamed.size()); + return false; + } + + for (size_t i = 0; i < unnamed.size(); i++) { + if (unnamed[i].has_value() && unnamed[i] != args.unnamed[i].type) { + *err_string = "Argument " + std::to_string(i) + " has type " + + object_type_to_string(args.unnamed[i].type) + " but " + + object_type_to_string(unnamed[i].value()) + " was expected"; + return false; + } + } + + for (const auto& kv : named) { + auto kv2 = args.named.find(kv.first); + if (kv2 == args.named.end()) { + // argument not given. + if (kv.second.first) { + // but was required + *err_string = "Required named argument \"" + kv.first + "\" was not found"; + return false; + } + } else { + // argument given. + if (kv.second.second.has_value() && kv.second.second != kv2->second.type) { + // but is wrong type + *err_string = "Argument \"" + kv.first + "\" has type " + + object_type_to_string(kv2->second.type) + " but " + + object_type_to_string(kv.second.second.value()) + " was expected"; + return false; + } + } + } + + for (const auto& kv : args.named) { + if (named.find(kv.first) == named.end()) { + *err_string = "Got unrecognized keyword argument \"" + kv.first + "\""; + return false; + } + } + + return true; +} + +int list_length(const goos::Object& list) { + int len = 0; + for_each_in_list(list, [&](const goos::Object& x) { + (void)x; + len++; + }); + return len; +} + +} // namespace goos diff --git a/common/goos/ParseHelpers.h b/common/goos/ParseHelpers.h new file mode 100644 index 0000000000..bb3771363c --- /dev/null +++ b/common/goos/ParseHelpers.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include +#include "Object.h" + +namespace goos { +bool get_va(const goos::Object& rest, std::string* err_string, goos::Arguments* result); +bool va_check( + const goos::Arguments& args, + const std::vector>& unnamed, + const std::unordered_map>>& named, + std::string* err_string); + +template +void for_each_in_list(const goos::Object& list, T f) { + const goos::Object* iter = &list; + while (iter->is_pair()) { + auto lap = iter->as_pair(); + f(lap->car); + iter = &lap->cdr; + } + + assert(iter->is_empty_list()); +} + +int list_length(const goos::Object& list); +} // namespace goos \ No newline at end of file diff --git a/common/link_types.h b/common/link_types.h index d393d9066c..9766c29b80 100644 --- a/common/link_types.h +++ b/common/link_types.h @@ -44,4 +44,12 @@ struct LinkHeaderV2 { uint32_t version; // always 2 }; +// Header for link data used for V4 +struct LinkHeaderV4 { + uint32_t type_tag; // always -1 + uint32_t length; // length of V2 link data found after object. + uint32_t version; // always 4 + uint32_t code_size; // length of object data before link data starts +}; + #endif // JAK1_LINK_TYPES_H diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index b63ce4ae01..267ba2224a 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -21,7 +21,8 @@ add_executable(decompiler Function/TypeInspector.cpp data/tpage.cpp data/game_text.cpp - data/StrFileReader.cpp) + data/StrFileReader.cpp + data/game_count.cpp data/LinkedWordReader.h) target_link_libraries(decompiler goos diff --git a/decompiler/ObjectFile/LinkedObjectFileCreation.cpp b/decompiler/ObjectFile/LinkedObjectFileCreation.cpp index 73d95a59f7..4cca65276e 100644 --- a/decompiler/ObjectFile/LinkedObjectFileCreation.cpp +++ b/decompiler/ObjectFile/LinkedObjectFileCreation.cpp @@ -27,14 +27,6 @@ struct LinkHeaderCommon { uint16_t version; // what version (2, 3, 4) }; -// Header for link data used for V4 -struct LinkHeaderV4 { - uint32_t type_tag; // always -1 - uint32_t length; // length of V2 link data found after object. - uint32_t version; // always 4 - uint32_t code_size; // length of object data before link data starts -}; - // Per-segment info for V3 and V5 link data struct SegmentInfo { uint32_t relocs; // offset of relocation table diff --git a/decompiler/ObjectFile/ObjectFileDB.cpp b/decompiler/ObjectFile/ObjectFileDB.cpp index 7411883889..b8f91d2285 100644 --- a/decompiler/ObjectFile/ObjectFileDB.cpp +++ b/decompiler/ObjectFile/ObjectFileDB.cpp @@ -13,6 +13,7 @@ #include "decompiler/data/tpage.h" #include "decompiler/data/game_text.h" #include "decompiler/data/StrFileReader.h" +#include "decompiler/data/game_count.h" #include "LinkedObjectFileCreation.h" #include "decompiler/config.h" #include "third-party/minilzo/minilzo.h" @@ -715,6 +716,26 @@ std::string ObjectFileDB::process_game_text() { return write_game_text(text_by_language_by_id); } +std::string ObjectFileDB::process_game_count() { + spdlog::info("- Finding game count file..."); + bool found = false; + std::string result; + + for_each_obj([&](ObjectFileData& data) { + if (data.name_in_dgo == "game-cnt") { + assert(!found); + found = true; + result = write_game_count(::process_game_count(data)); + } + }); + + if (!found) { + spdlog::warn("did not find game-cnt file"); + } + + return result; +} + void ObjectFileDB::analyze_functions() { spdlog::info("- Analyzing Functions..."); Timer timer; @@ -916,3 +937,10 @@ void ObjectFileDB::analyze_functions() { // } // } } + +void ObjectFileDB::dump_raw_objects(const std::string& output_dir) { + for_each_obj([&](ObjectFileData& data) { + auto dest = output_dir + "/" + data.to_unique_name(); + file_util::write_binary_file(dest, data.data.data(), data.data.size()); + }); +} \ No newline at end of file diff --git a/decompiler/ObjectFile/ObjectFileDB.h b/decompiler/ObjectFile/ObjectFileDB.h index 823d1bb113..2de5798aae 100644 --- a/decompiler/ObjectFile/ObjectFileDB.h +++ b/decompiler/ObjectFile/ObjectFileDB.h @@ -55,11 +55,13 @@ class ObjectFileDB { void process_labels(); void find_code(); void find_and_write_scripts(const std::string& output_dir); + void dump_raw_objects(const std::string& output_dir); void write_object_file_words(const std::string& output_dir, bool dump_v3_only); void write_disassembly(const std::string& output_dir, bool disassemble_objects_without_functions); void analyze_functions(); void process_tpages(); + std::string process_game_count(); std::string process_game_text(); ObjectFileData& lookup_record(const ObjectFileRecord& rec); diff --git a/decompiler/config.cpp b/decompiler/config.cpp index 1de39f650b..014d17cb68 100644 --- a/decompiler/config.cpp +++ b/decompiler/config.cpp @@ -30,6 +30,8 @@ void set_config(const std::string& path_to_config_file) { gConfig.analyze_functions = cfg.at("analyze_functions").get(); gConfig.process_tpages = cfg.at("process_tpages").get(); gConfig.process_game_text = cfg.at("process_game_text").get(); + gConfig.process_game_count = cfg.at("process_game_count").get(); + gConfig.dump_objs = cfg.at("dump_objs").get(); std::vector asm_functions_by_name = cfg.at("asm_functions_by_name").get>(); diff --git a/decompiler/config.h b/decompiler/config.h index d910d24df9..c84519933d 100644 --- a/decompiler/config.h +++ b/decompiler/config.h @@ -23,6 +23,8 @@ struct Config { bool analyze_functions = false; bool process_tpages = false; bool process_game_text = false; + bool process_game_count = false; + bool dump_objs = false; std::unordered_set asm_functions_by_name; // ... }; diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index c803ea3f97..0e7e10af8d 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -10142,20 +10142,21 @@ ((money-count int32 :offset-assert 0) (buzzer-count int32 :offset-assert 4) ) + :pack-me :method-count-assert 9 :size-assert #x8 :flag-assert #x900000008 ) -; ;; progress-h -; (deftype game-count-info (basic) -; ((length int32 :offset-assert 4) -; (data UNKNOWN :dynamic :offset-assert 8) -; ) -; :method-count-assert 9 -; :size-assert #x8 -; :flag-assert #x900000008 -; ) +;; progress-h +(deftype game-count-info (basic) + ((length int32 :offset-assert 4) + (data count-info :inline :dynamic :offset-assert 8) + ) + :method-count-assert 9 + :size-assert #x8 + :flag-assert #x900000008 + ) ; ;; progress-h ; (deftype task-info-data (basic) @@ -33563,7 +33564,7 @@ (define-extern reset-actors function) (define-extern get-task-control function) ;;(define-extern *kernel-boot-message* object) ;; unknown type -(define-extern play function) +(define-extern play (function none)) (define-extern stop function) (define-extern auto-save-check function) ;;(define-extern auto-save object) ;; unknown type diff --git a/decompiler/config/jak1_ntsc_black_label.jsonc b/decompiler/config/jak1_ntsc_black_label.jsonc index 568615ea51..1b4afe09c6 100644 --- a/decompiler/config/jak1_ntsc_black_label.jsonc +++ b/decompiler/config/jak1_ntsc_black_label.jsonc @@ -60,6 +60,8 @@ "process_tpages":true, "process_game_text":true, + "process_game_count":true, + "dump_objs":false, // to write out data of each object file "write_hexdump":false, diff --git a/decompiler/data/LinkedWordReader.h b/decompiler/data/LinkedWordReader.h new file mode 100644 index 0000000000..23575f455b --- /dev/null +++ b/decompiler/data/LinkedWordReader.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include +#include "common/common_types.h" +#include "decompiler/ObjectFile/LinkedWord.h" + +class LinkedWordReader { + public: + explicit LinkedWordReader(const std::vector* words) : m_words(words) {} + const std::string& get_type_tag() { + if (m_words->at(m_offset).kind == LinkedWord::TYPE_PTR) { + auto& result = m_words->at(m_offset).symbol_name; + m_offset++; + return result; + } else { + assert(false); + } + } + + template + T get_word() { + static_assert(sizeof(T) == 4, "size of word in get_word"); + T result; + assert(m_words->at(m_offset).kind == LinkedWord::PLAIN_DATA); + memcpy(&result, &m_words->at(m_offset).data, 4); + m_offset++; + return result; + } + + u32 words_left() { + assert(m_words->size() >= m_offset); + return m_words->size() - m_offset; + } + + private: + const std::vector* m_words = nullptr; + u32 m_offset = 0; +}; \ No newline at end of file diff --git a/decompiler/data/game_count.cpp b/decompiler/data/game_count.cpp new file mode 100644 index 0000000000..aec6bb1e5d --- /dev/null +++ b/decompiler/data/game_count.cpp @@ -0,0 +1,40 @@ +#include "third-party/fmt/core.h" +#include "decompiler/ObjectFile/ObjectFileDB.h" +#include "game_count.h" +#include "LinkedWordReader.h" + +GameCountResult process_game_count(ObjectFileData& data) { + GameCountResult result; + auto& words = data.linked_data.words_by_seg.at(0); + auto reader = LinkedWordReader(&words); + auto type = reader.get_type_tag(); + assert(type == "game-count-info"); + auto length = reader.get_word(); + + for (s32 i = 0; i < length; i++) { + GameCountResult::CountInfo info; + info.money_count = reader.get_word(); + info.buzzer_count = reader.get_word(); + result.info.push_back(info); + } + + result.mystery_data[0] = reader.get_word(); + result.mystery_data[1] = reader.get_word(); + assert(reader.words_left() == 0); + return result; +} + +std::string write_game_count(const GameCountResult& result) { + std::string str; + str += + ";; this file contains money/buzzer counts for each level.\n;; The last pair is unknown data " + "and possibly a bug that it is included\n\n"; + + for (auto& x : result.info) { + str += fmt::format("(:money {} :buzzer {})\n", x.money_count, x.buzzer_count); + } + + str += fmt::format("(:unknown-1 {} :unknown-2 {})\n", result.mystery_data[0], + result.mystery_data[1]); + return str; +} \ No newline at end of file diff --git a/decompiler/data/game_count.h b/decompiler/data/game_count.h new file mode 100644 index 0000000000..e662a43c14 --- /dev/null +++ b/decompiler/data/game_count.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include "common/common_types.h" + +struct GameCountResult { + struct CountInfo { + s32 money_count; + s32 buzzer_count; + }; + + std::vector info; + u32 mystery_data[2]; +}; + +struct ObjectFileData; +GameCountResult process_game_count(ObjectFileData& data); +std::string write_game_count(const GameCountResult& result); \ No newline at end of file diff --git a/decompiler/data/game_text.h b/decompiler/data/game_text.h index 44b6689406..a4c411cbe4 100644 --- a/decompiler/data/game_text.h +++ b/decompiler/data/game_text.h @@ -2,7 +2,7 @@ #include #include -class ObjectFileData; +struct ObjectFileData; struct GameTextResult { int total_text = 0; diff --git a/decompiler/main.cpp b/decompiler/main.cpp index 4c834859fc..38f8a44ea1 100644 --- a/decompiler/main.cpp +++ b/decompiler/main.cpp @@ -46,6 +46,12 @@ int main(int argc, char** argv) { file_util::write_text_file(file_util::combine_path(out_folder, "obj.txt"), db.generate_obj_listing()); + if (get_config().dump_objs) { + auto path = file_util::combine_path(out_folder, "raw_obj"); + file_util::create_dir_if_needed(path); + db.dump_raw_objects(path); + } + db.process_link_data(); db.find_code(); db.process_labels(); @@ -71,6 +77,11 @@ int main(int argc, char** argv) { db.process_tpages(); } + if (get_config().process_game_count) { + auto result = db.process_game_count(); + file_util::write_text_file(file_util::get_file_path({"assets", "game_count.txt"}), result); + } + if (get_config().write_disassembly) { db.write_disassembly(out_folder, get_config().disassemble_objects_without_functions); } diff --git a/game/kernel/kboot.cpp b/game/kernel/kboot.cpp index 9df6682a8f..3eb61fa121 100644 --- a/game/kernel/kboot.cpp +++ b/game/kernel/kboot.cpp @@ -184,5 +184,5 @@ void KernelCheckAndDispatch() { * DONE, EXACT */ void KernelShutdown() { - MasterExit = 1; // GOAL Kernel Dispatch loop will stop now. + MasterExit = 2; // GOAL Kernel Dispatch loop will stop now. } diff --git a/game/kernel/klink.cpp b/game/kernel/klink.cpp index 716f4ba81d..8da53c1a56 100644 --- a/game/kernel/klink.cpp +++ b/game/kernel/klink.cpp @@ -217,8 +217,12 @@ void link_control::begin(Ptr object_file, } } else { - // not yet implemented - assert(false); + auto header_v4 = (const LinkHeaderV4*)header; + auto old_object_data = m_object_data; + m_link_block_ptr = + old_object_data + header_v4->code_size + sizeof(LinkHeaderV4) + BASIC_OFFSET; + m_object_data = old_object_data + sizeof(LinkHeaderV4); + m_code_size = header_v4->code_size; } if ((m_flags & LINK_FLAG_FORCE_DEBUG) && MasterDebug && !DiskBoot) { @@ -768,7 +772,14 @@ void link_control::finish() { output_segment_load(m_object_name, m_link_block_ptr, m_flags); } } else { - printf("UNHANDELD OBJECT FILE VERSION IN FINISH\n"); + if (m_flags & LINK_FLAG_EXECUTE) { + auto entry = m_entry; + auto name = basename_goal(m_object_name); + strcpy(Ptr(LINK_CONTROL_NAME_ADDR).c(), name); + call_method_of_type_arg2(entry.offset, Ptr(*((entry - 4).cast())), + GOAL_RELOC_METHOD, m_heap.offset, + Ptr(LINK_CONTROL_NAME_ADDR).offset); + } } *EnableMethodSet = *EnableMethodSet - m_keep_debug; diff --git a/game/kernel/kmachine.h b/game/kernel/kmachine.h index f60148326e..abe6c8d696 100644 --- a/game/kernel/kmachine.h +++ b/game/kernel/kmachine.h @@ -23,6 +23,7 @@ constexpr u32 GLOBAL_HEAP_END = 0x1ffc000; //! Location of kglobalheap, kdebugheap kheapinfo structures. constexpr u32 GLOBAL_HEAP_INFO_ADDR = 0x13AD00; constexpr u32 DEBUG_HEAP_INFO_ADDR = 0x13AD10; +constexpr u32 LINK_CONTROL_NAME_ADDR = 0x13AD80; //! Where to place the debug heap constexpr u32 DEBUG_HEAP_START = 0x5000000; diff --git a/game/kernel/kscheme.h b/game/kernel/kscheme.h index 218bfc2211..11002924d4 100644 --- a/game/kernel/kscheme.h +++ b/game/kernel/kscheme.h @@ -102,6 +102,7 @@ u64 loado(u32 file_name_in, u32 heap_in); u64 unload(u32 name); Ptr make_function_symbol_from_c(const char* name, void* f); u64 call_goal_function_by_name(const char* name); +u64 call_method_of_type_arg2(u32 arg, Ptr type, u32 method_id, u32 a1, u32 a2); Ptr alloc_and_init_type(Ptr sym, u32 method_count); Ptr set_fixed_symbol(u32 offset, const char* name, u32 value); diff --git a/game/overlord/iso.cpp b/game/overlord/iso.cpp index 6380cc00e0..3f2be4ef52 100644 --- a/game/overlord/iso.cpp +++ b/game/overlord/iso.cpp @@ -649,7 +649,7 @@ u32 RunDGOStateMachine(IsoMessage* _cmd, IsoBufferHeader* buffer) { } } - printf("[DGO State Machine Complete] Out of things to read!\n"); + // printf("[DGO State Machine Complete] Out of things to read!\n"); cleanup_and_return: if (return_value == 0) { diff --git a/goal_src/engine/level/level.gc b/goal_src/engine/level/level.gc index 82acf4f3a1..a5ef0dfc9b 100644 --- a/goal_src/engine/level/level.gc +++ b/goal_src/engine/level/level.gc @@ -5,3 +5,8 @@ ;; name in dgo: level ;; dgos: GAME, ENGINE +(defun play () + (format #t "Play has been called!~%") + (format 0 "Play has been called!~%") + (kernel-shutdown) + ) \ No newline at end of file diff --git a/goal_src/engine/ui/progress-h.gc b/goal_src/engine/ui/progress-h.gc index 1f73571542..cb50150ab0 100644 --- a/goal_src/engine/ui/progress-h.gc +++ b/goal_src/engine/ui/progress-h.gc @@ -5,3 +5,23 @@ ;; name in dgo: progress-h ;; dgos: GAME, ENGINE +;; progress-h +(deftype count-info (structure) + ((money-count int32 :offset-assert 0) + (buzzer-count int32 :offset-assert 4) + ) + :pack-me + :method-count-assert 9 + :size-assert #x8 + :flag-assert #x900000008 + ) + +;; progress-h +(deftype game-count-info (basic) + ((length int32 :offset-assert 4) + (data count-info :inline :dynamic :offset-assert 8) + ) + :method-count-assert 9 + :size-assert #x8 + :flag-assert #x900000008 + ) \ No newline at end of file diff --git a/goal_src/engine/ui/progress/progress-static.gc b/goal_src/engine/ui/progress/progress-static.gc index f395b23899..5ab5534369 100644 --- a/goal_src/engine/ui/progress/progress-static.gc +++ b/goal_src/engine/ui/progress/progress-static.gc @@ -5,3 +5,4 @@ ;; name in dgo: progress-static ;; dgos: GAME, ENGINE +(define *game-counts* (the game-count-info '#f)) \ No newline at end of file diff --git a/goal_src/engine/ui/progress/progress.gc b/goal_src/engine/ui/progress/progress.gc index 4e275432cf..75ff34cc62 100644 --- a/goal_src/engine/ui/progress/progress.gc +++ b/goal_src/engine/ui/progress/progress.gc @@ -5,3 +5,7 @@ ;; name in dgo: progress ;; dgos: GAME, ENGINE +(defmethod relocate game-count-info ((this game-count-info) (offset int)) + "Load in the game-count-info. This is a bit of a hack." + (set! *game-counts* this) + ) \ No newline at end of file diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc index aee952fb94..b001badfa9 100644 --- a/goal_src/goal-lib.gc +++ b/goal_src/goal-lib.gc @@ -36,6 +36,7 @@ (defmacro build-data () `(begin (asm-data-file game-text "assets/game_text.txt") + (asm-data-file game-count "assets/game_count.txt") ) ) diff --git a/goal_src/kernel-defs.gc b/goal_src/kernel-defs.gc index 858a0aa97b..8156a3d0ae 100644 --- a/goal_src/kernel-defs.gc +++ b/goal_src/kernel-defs.gc @@ -138,7 +138,7 @@ ;; scf-get-timeout ;; scf-get-inactive-timeout ;; dma-to-iop -;; kernel-shutdown +(define-extern kernel-shutdown (function none)) ;; aybabtu ;; *stack-top* ;; *stack-base* diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index bd3d4471e6..2303e585f3 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -26,6 +26,7 @@ add_library(compiler compiler/Util.cpp data_compiler/game_text.cpp data_compiler/DataObjectGenerator.cpp + data_compiler/game_count.cpp debugger/Debugger.cpp debugger/DebugInfo.cpp logger/Logger.cpp @@ -38,7 +39,6 @@ add_library(compiler compiler/Compiler.cpp) add_executable(goalc main.cpp) -add_executable(data_compiler data_compiler.cpp) IF (WIN32) target_link_libraries(compiler goos type_system mman common_util spdlog cross_os_debug cross_sockets Zydis) @@ -46,5 +46,4 @@ ELSE () target_link_libraries(compiler goos type_system common_util spdlog cross_os_debug cross_sockets Zydis) ENDIF () -target_link_libraries(goalc goos compiler type_system) -target_link_libraries(data_compiler goos compiler type_system) \ No newline at end of file +target_link_libraries(goalc goos compiler type_system) \ No newline at end of file diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp index b3f43d8a15..60aba59b7e 100644 --- a/goalc/compiler/Util.cpp +++ b/goalc/compiler/Util.cpp @@ -1,36 +1,14 @@ #include "goalc/compiler/Compiler.h" #include "goalc/compiler/IR.h" +#include "common/goos/ParseHelpers.h" goos::Arguments Compiler::get_va(const goos::Object& form, const goos::Object& rest) { goos::Arguments args; - // loop over forms in list - goos::Object current = rest; - while (!current.is_empty_list()) { - auto arg = current.as_pair()->car; - // did we get a ":keyword" - if (arg.is_symbol() && arg.as_symbol()->name.at(0) == ':') { - auto key_name = arg.as_symbol()->name.substr(1); - - // check for multiple definition of key - if (args.named.find(key_name) != args.named.end()) { - throw_compile_error(form, "Key argument " + key_name + " multiply defined"); - } - - // check for well-formed :key value expression - current = current.as_pair()->cdr; - if (current.is_empty_list()) { - throw_compile_error(form, "Key argument didn't have a value"); - } - - args.named[key_name] = current.as_pair()->car; - } else { - // not a keyword. Add to unnamed or rest, depending on what we expect - args.unnamed.push_back(arg); - } - current = current.as_pair()->cdr; + std::string err; + if (!goos::get_va(rest, &err, &args)) { + throw_compile_error(form, err); } - return args; } @@ -40,44 +18,9 @@ void Compiler::va_check( const std::vector>& unnamed, const std::unordered_map>>& named) { - assert(args.rest.empty()); - if (unnamed.size() != args.unnamed.size()) { - throw_compile_error(form, "Got " + std::to_string(args.unnamed.size()) + - " arguments, but expected " + std::to_string(unnamed.size())); - } - - for (size_t i = 0; i < unnamed.size(); i++) { - if (unnamed[i].has_value() && unnamed[i] != args.unnamed[i].type) { - throw_compile_error(form, "Argument " + std::to_string(i) + " has type " + - object_type_to_string(args.unnamed[i].type) + " but " + - object_type_to_string(unnamed[i].value()) + " was expected"); - } - } - - for (const auto& kv : named) { - auto kv2 = args.named.find(kv.first); - if (kv2 == args.named.end()) { - // argument not given. - if (kv.second.first) { - // but was required - throw_compile_error(form, "Required named argument \"" + kv.first + "\" was not found"); - } - } else { - // argument given. - if (kv.second.second.has_value() && kv.second.second != kv2->second.type) { - // but is wrong type - throw_compile_error(form, "Argument \"" + kv.first + "\" has type " + - object_type_to_string(kv2->second.type) + " but " + - object_type_to_string(kv.second.second.value()) + - " was expected"); - } - } - } - - for (const auto& kv : args.named) { - if (named.find(kv.first) == named.end()) { - throw_compile_error(form, "Got unrecognized keyword argument \"" + kv.first + "\""); - } + std::string err; + if (!goos::va_check(args, unnamed, named, &err)) { + throw_compile_error(form, err); } } diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp index cb34a014da..1289cfc1dd 100644 --- a/goalc/compiler/compilation/CompilerControl.cpp +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -3,12 +3,14 @@ * Compiler implementation for forms which actually control the compiler. */ +#include #include "goalc/compiler/Compiler.h" #include "goalc/compiler/IR.h" #include "common/util/Timer.h" #include "common/util/DgoWriter.h" #include "common/util/FileUtil.h" #include "goalc/data_compiler/game_text.h" +#include "goalc/data_compiler/game_count.h" /*! * Exit the compiler. Disconnects the listener and tells the target to reset itself. @@ -57,6 +59,8 @@ Val* Compiler::compile_asm_data_file(const goos::Object& form, const goos::Objec auto kind = symbol_string(args.unnamed.at(0)); if (kind == "game-text") { compile_game_text(as_string(args.unnamed.at(1))); + } else if (kind == "game-count") { + compile_game_count(as_string(args.unnamed.at(1))); } else { throw_compile_error(form, "Unknown asm data file mode"); } @@ -290,8 +294,13 @@ Val* Compiler::compile_build_dgo(const goos::Object& form, const goos::Object& r DgoDescription::DgoEntry o; o.file_name = as_string(e_arg.unnamed.at(0)); o.name_in_dgo = as_string(e_arg.unnamed.at(1)); - if (o.file_name.substr(o.file_name.length() - 3) != ".go") { // kill v2's for now. + if (o.file_name.substr(o.file_name.length() - 3) != ".go") { desc.entries.push_back(o); + } else { + // allow data objects to be missing. + if (std::filesystem::exists(file_util::get_file_path({"out", "obj", o.file_name}))) { + desc.entries.push_back(o); + } } }); diff --git a/goalc/data_compiler.cpp b/goalc/data_compiler.cpp deleted file mode 100644 index ffd7632730..0000000000 --- a/goalc/data_compiler.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include "goalc/compiler/Compiler.h" -#include "common/versions.h" -#include "third-party/spdlog/include/spdlog/spdlog.h" -#include "third-party/spdlog/include/spdlog/sinks/basic_file_sink.h" -#include "third-party/spdlog/include/spdlog/sinks/stdout_color_sinks.h" - -void setup_logging(bool verbose) { - spdlog::set_level(spdlog::level::debug); - if (verbose) { - auto game_logger = spdlog::stdout_color_mt("GOAL Compiler: Data Mode"); - spdlog::set_default_logger(game_logger); - spdlog::flush_on(spdlog::level::info); - spdlog::set_pattern("%v"); - spdlog::info("Verbose logging enabled"); - } else { - auto game_logger = spdlog::basic_logger_mt("GOAL Compiler", "logs/data_compiler.log"); - spdlog::set_default_logger(game_logger); - spdlog::flush_on(spdlog::level::debug); - printf("OpenGOAL Compiler %d.%d: Data Mode\n", versions::GOAL_VERSION_MAJOR, - versions::GOAL_VERSION_MINOR); - } -} - -int main(int argc, char** argv) { - (void)argc; - (void)argv; - - bool verbose = false; - for (int i = 1; i < argc; i++) { - if (std::string("-v") == argv[i]) { - verbose = true; - break; - } - } - setup_logging(verbose); - - spdlog::info("OpenGOAL Compiler {}.{}: Data Mode", versions::GOAL_VERSION_MAJOR, - versions::GOAL_VERSION_MINOR); - - Compiler compiler; - compiler.run_front_end_on_string("(build-data)"); - - printf("Done!\n"); - return 0; -} diff --git a/goalc/data_compiler/DataObjectGenerator.cpp b/goalc/data_compiler/DataObjectGenerator.cpp index 3c1412a81f..2fa5f3f1d3 100644 --- a/goalc/data_compiler/DataObjectGenerator.cpp +++ b/goalc/data_compiler/DataObjectGenerator.cpp @@ -100,8 +100,6 @@ std::vector DataObjectGenerator::generate_v2() { // Generate the link table. std::vector link = generate_link_table(); - // add words - // header LinkHeaderV2 header; header.type_tag = 0xffffffff; @@ -124,6 +122,38 @@ std::vector DataObjectGenerator::generate_v2() { return result; } +std::vector DataObjectGenerator::generate_v4() { + // add string data at the end. + add_strings(); + + // Generate the link table. + std::vector link = generate_link_table(); + + // header (first) + LinkHeaderV4 first_header; + first_header.type_tag = 0xffffffff; + first_header.length = sizeof(LinkHeaderV2) + link.size(); + first_header.version = 4; + first_header.code_size = 4 * m_words.size(); + + LinkHeaderV2 second_header; + second_header.version = 2; + second_header.type_tag = 0xffffffff; + second_header.length = first_header.length; + + std::vector result; + add_data_to_vector(first_header, &result); + auto start = result.size(); + result.resize(result.size() + m_words.size() * 4); + memcpy(result.data() + start, m_words.data(), m_words.size() * 4); + while (result.size() % 16) { + result.push_back(0); + } + add_data_to_vector(second_header, &result); + result.insert(result.end(), link.begin(), link.end()); + return result; +} + std::vector DataObjectGenerator::generate_link_table() { std::vector link; diff --git a/goalc/data_compiler/DataObjectGenerator.h b/goalc/data_compiler/DataObjectGenerator.h index 74017ecb6a..3522b9a3b7 100644 --- a/goalc/data_compiler/DataObjectGenerator.h +++ b/goalc/data_compiler/DataObjectGenerator.h @@ -14,6 +14,7 @@ class DataObjectGenerator { int add_type_tag(const std::string& str); int add_symbol_link(const std::string& str); std::vector generate_v2(); + std::vector generate_v4(); void align(int alignment_words); int words() const; diff --git a/goalc/data_compiler/game_count.cpp b/goalc/data_compiler/game_count.cpp new file mode 100644 index 0000000000..c15a5b17ac --- /dev/null +++ b/goalc/data_compiler/game_count.cpp @@ -0,0 +1,78 @@ +#include +#include "DataObjectGenerator.h" +#include "common/goos/ParseHelpers.h" +#include "common/goos/Reader.h" +#include "common/util/FileUtil.h" +#include "game_count.h" + +void compile_game_count(const std::string& filename) { + printf("[Build Game Count] %s\n", filename.c_str()); + struct Count { + int buzzer; + int money; + }; + + std::vector counts; + int unknowns[2] = {0, 0}; + + goos::Reader reader; + auto code = reader.read_from_file({filename}); + int len = goos::list_length(code) - 1; + int i = 0; + std::string err; + + // parser + goos::for_each_in_list(code.as_pair()->cdr, [&](const goos::Object& obj) { + if (obj.is_pair()) { + if (i == len - 1) { + // last entry should be the unknowns. + goos::Arguments args; + if (!goos::get_va(obj, &err, &args)) { + throw std::runtime_error(err); + } + if (!goos::va_check(args, {}, + {{"unknown-1", {true, goos::ObjectType::INTEGER}}, + {"unknown-2", {true, goos::ObjectType::INTEGER}}}, + &err)) { + throw std::runtime_error(err); + } + unknowns[0] = args.get_named("unknown-1").as_int(); + unknowns[1] = args.get_named("unknown-2").as_int(); + } else { + goos::Arguments args; + if (!goos::get_va(obj, &err, &args)) { + throw std::runtime_error(err); + } + if (!goos::va_check(args, {}, + {{"buzzer", {true, goos::ObjectType::INTEGER}}, + {"money", {true, goos::ObjectType::INTEGER}}}, + &err)) { + throw std::runtime_error(err); + } + Count c; + c.buzzer = args.get_named("buzzer").as_int(); + c.money = args.get_named("money").as_int(); + counts.push_back(c); + } + } else { + throw std::runtime_error("Invalid game count file."); + } + i++; + }); + + // compiler + DataObjectGenerator gen; + gen.add_type_tag("game-count-info"); + gen.add_word(counts.size()); + for (auto& x : counts) { + gen.add_word(x.money); + gen.add_word(x.buzzer); + } + gen.add_word(unknowns[0]); + gen.add_word(unknowns[1]); + auto result = gen.generate_v4(); + + file_util::create_dir_if_needed(file_util::get_file_path({"out", "obj"})); + file_util::write_binary_file(file_util::get_file_path({"out", "obj", "game-cnt.go"}), + result.data(), result.size()); +} \ No newline at end of file diff --git a/goalc/data_compiler/game_count.h b/goalc/data_compiler/game_count.h new file mode 100644 index 0000000000..df6e39b372 --- /dev/null +++ b/goalc/data_compiler/game_count.h @@ -0,0 +1,4 @@ +#pragma once +#include + +void compile_game_count(const std::string& filename); \ No newline at end of file diff --git a/goalc/main.cpp b/goalc/main.cpp index 7dc63e30ac..afda7a3afe 100644 --- a/goalc/main.cpp +++ b/goalc/main.cpp @@ -25,12 +25,17 @@ int main(int argc, char** argv) { (void)argc; (void)argv; + std::string argument; bool verbose = false; for (int i = 1; i < argc; i++) { if (std::string("-v") == argv[i]) { verbose = true; break; } + + if (std::string("-cmd") == argv[i] && i < argc - 1) { + argument = argv[++i]; + } } setup_logging(verbose); @@ -38,7 +43,12 @@ int main(int argc, char** argv) { versions::GOAL_VERSION_MINOR); Compiler compiler; - compiler.execute_repl(); + + if (argument.empty()) { + compiler.execute_repl(); + } else { + compiler.run_front_end_on_string(argument); + } return 0; } diff --git a/offline_test_git_branch.sh b/offline_test_git_branch.sh index 829a536e4c..40b138ab61 100755 --- a/offline_test_git_branch.sh +++ b/offline_test_git_branch.sh @@ -45,9 +45,15 @@ echo " ================ Decompiling..." ../decomp.sh echo " ================ Building assets..." -./goalc/data_compiler +../gc.sh -cmd \(build-data\) echo " ================ Checking assets..." ../check.sh +echo " ================ Building game..." +../gc.sh -cmd \(build-game\) + +echo " ================ Booting game..." +../boot_game.sh + echo "Offline test has completed successfully!" diff --git a/out/hash.md5 b/out/hash.md5 index 1c6ddb6dd9..f4e9cb0e63 100644 --- a/out/hash.md5 +++ b/out/hash.md5 @@ -5,3 +5,4 @@ abcc25e5d7469dd6a572dc53dbb9671c iso/3COMMON.TXT 82eabdb7159f2059fbdbd18bb6fc06aa iso/4COMMON.TXT 5d62de2c78b4cf102b9a78f3aa96c8c9 iso/5COMMON.TXT 9495f80955e6782513fe12f6539fc8e7 iso/6COMMON.TXT +9765bdc3add08cb06fd3e87ebd5713aa obj/game-cnt.go diff --git a/test/goalc/source_templates/with_game/test-game-count.gc b/test/goalc/source_templates/with_game/test-game-count.gc new file mode 100644 index 0000000000..6f066c22f6 --- /dev/null +++ b/test/goalc/source_templates/with_game/test-game-count.gc @@ -0,0 +1,8 @@ +(start-test "game-count") + +(expect-true (= (-> *game-counts* data 0 money-count) 123)) +(expect-true (= (-> *game-counts* data 0 buzzer-count) 456)) +(expect-true (= (-> *game-counts* data 1 money-count) 789)) +(expect-true (= (-> *game-counts* data 1 buzzer-count) 221)) + +(finish-test) \ No newline at end of file diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index 8f66ec45cb..1d491b055b 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -293,6 +293,15 @@ TEST_F(WithGameTests, GameText) { get_test_pass_string("game-text", 5)); } +TEST_F(WithGameTests, GameCount) { + compiler.run_test_from_string( + "(asm-data-file game-count \"test/test_data/test_game_counts.txt\")"); + compiler.run_test_from_string("(build-dgos \"test/test_data/test_game_count_dgos.txt\")"); + compiler.run_test_from_string("(dgo-load \"game\" global #xf #x200000)"); + runner.run_static_test(env, testCategory, "test-game-count.gc", + get_test_pass_string("game-count", 4)); +} + TEST(TypeConsistency, TypeConsistency) { Compiler compiler; compiler.enable_throw_on_redefines(); diff --git a/test/test_data/test_game_count_dgos.txt b/test/test_data/test_game_count_dgos.txt new file mode 100644 index 0000000000..644cf0563f --- /dev/null +++ b/test/test_data/test_game_count_dgos.txt @@ -0,0 +1,6 @@ +("GAME.CGO" + ("types-h.o" "types-h") + ("vu1-macros.o" "vu1-macros") + ("game-cnt.go" "game-cnt") + ("math.o" "math") + ) \ No newline at end of file diff --git a/test/test_data/test_game_counts.txt b/test/test_data/test_game_counts.txt new file mode 100644 index 0000000000..124f0b7b43 --- /dev/null +++ b/test/test_data/test_game_counts.txt @@ -0,0 +1,3 @@ +(:money 123 :buzzer 456) +(:money 789 :buzzer 221) +(:unknown-1 123123 :unknown-2 234234)