[jak3] Set up ckernel (#3308)

This sets up the C Kernel for Jak 3, and makes it possible to build and
load code built with `goalc --jak3`.

There's not too much interesting here, other than they switched to a
system where symbol IDs (unique numbers less than 2^14) are generated at
compile time, and those get included in the object file itself.

This is kind of annoying, since it means all tools that produce a GOAL
object file need to work together to assign unique symbol IDs. And since
the symbol IDs can't conflict, and are only a number between 0 and 2^14,
you can't just hash and hope for no collisions.

We work around this by ignoring the IDs and re-assigning our own. I
think this is very similar to what the C Kernel did on early builds of
Jak 3 which supported loading old format level files, which didn't have
the IDs included.

As far as I can tell, this shouldn't cause any problems. It defeats all
of their fancy tricks to save memory by not storing the symbol string,
but we don't care.
This commit is contained in:
water111 2024-01-16 19:24:02 -05:00 committed by GitHub
parent ed6639ee60
commit 4f537d4a71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
88 changed files with 7364 additions and 653 deletions

View File

@ -191,7 +191,7 @@ bool can_node_be_inlined(const FormatterTreeNode& curr_node, int cursor_pos) {
}
std::vector<std::string> apply_formatting(const FormatterTreeNode& curr_node,
std::vector<std::string> output = {},
std::vector<std::string> /*output*/ = {},
int cursor_pos = 0) {
using namespace formatter_rules;
if (!curr_node.token && curr_node.refs.empty()) {

View File

@ -9,7 +9,7 @@ namespace config {
FormFormattingConfig new_flow_rule(int start_index) {
FormFormattingConfig cfg;
cfg.hang_forms = false;
cfg.inline_until_index = [start_index](const std::vector<std::string>& curr_lines) {
cfg.inline_until_index = [start_index](const std::vector<std::string>& /*curr_lines*/) {
return start_index;
};
return cfg;

View File

@ -62,15 +62,9 @@ constexpr int LEVEL_MAX = 6;
constexpr int LEVEL_TOTAL = LEVEL_MAX + 1;
} // namespace jak2
// TODO copypaste from jak 2 for now
namespace jak3 {
// for now, we don't have the ability to extend the size of the symbol table
constexpr s32 GOAL_MAX_SYMBOLS = 0x4000;
constexpr s32 SYM_TABLE_MEM_SIZE = 0x30000;
// from the "off-by-one" symbol pointer
constexpr int SYM_TO_STRING_OFFSET = 0xff37;
constexpr int SYM_TO_HASH_OFFSET = 0x1fe6f;
// amount of levels in level heap
constexpr int LEVEL_MAX = 10;
// total amount of levels, including ones outside level heap (default-level)
@ -84,7 +78,7 @@ constexpr s32 max_symbols(GameVersion version) {
case GameVersion::Jak2:
return jak2::GOAL_MAX_SYMBOLS;
case GameVersion::Jak3:
return jak2::GOAL_MAX_SYMBOLS;
return jak3::GOAL_MAX_SYMBOLS;
}
}

View File

@ -54,6 +54,21 @@ struct LinkHeaderV4 {
uint32_t code_size; // length of object data before link data starts
};
struct LinkHeaderV5Core {
uint32_t length_to_get_to_code; // 4 length.. of link data?
uint16_t version; // 8
uint16_t unknown; // 10
uint32_t length_to_get_to_link; // 12
uint32_t link_length; // 16
uint8_t n_segments; // 20
char name[59]; // 21 (really??)
};
struct LinkHeaderV5 {
uint32_t type_tag; // 0 always 0?
LinkHeaderV5Core core;
};
// when a u32/s32 symbol link contains this value, (s7 + <val>) should be a 4-byte aligned address,
// not including the 1 byte symbol offset. (no effect in jak 1).
constexpr u32 LINK_SYM_NO_OFFSET_FLAG = 0xbadbeef;

View File

@ -150,6 +150,180 @@ constexpr int FIX_FIXED_SYM_END_OFFSET = 0xec;
} // namespace jak2_symbols
namespace jak3_symbols {
constexpr int FIX_SYM_EMPTY_CAR = -0x8;
constexpr int S7_OFF_FIX_SYM_EMPTY_PAIR = -0x6 - 1;
constexpr int FIX_SYM_EMPTY_CDR = -0x4;
constexpr int FIX_SYM_FALSE = 0x0; // GOAL boolean #f (note that this is equal to the $s7 register)
constexpr int FIX_SYM_TRUE = 0x4; // GOAL boolean #t
constexpr int FIX_SYM_FUNCTION_TYPE = 0x8;
constexpr int FIX_SYM_BASIC = 0xc;
constexpr int FIX_SYM_STRING_TYPE = 0x10;
constexpr int FIX_SYM_SYMBOL_TYPE = 0x14;
constexpr int FIX_SYM_TYPE_TYPE = 0x18;
constexpr int FIX_SYM_OBJECT_TYPE = 0x1c;
constexpr int FIX_SYM_LINK_BLOCK = 0x20;
constexpr int FIX_SYM_INTEGER = 0x24;
constexpr int FIX_SYM_SINTEGER = 0x28;
constexpr int FIX_SYM_UINTEGER = 0x2c;
constexpr int FIX_SYM_BINTEGER = 0x30;
constexpr int FIX_SYM_INT8 = 0x34;
constexpr int FIX_SYM_INT16 = 0x38;
constexpr int FIX_SYM_INT32 = 0x3c;
constexpr int FIX_SYM_INT64 = 0x40;
constexpr int FIX_SYM_INT128 = 0x44;
constexpr int FIX_SYM_UINT8 = 0x48;
constexpr int FIX_SYM_UINT16 = 0x4c;
constexpr int FIX_SYM_UINT32 = 0x50;
constexpr int FIX_SYM_UINT64 = 0x54;
constexpr int FIX_SYM_UINT128 = 0x58;
constexpr int FIX_SYM_FLOAT = 0x5c;
constexpr int FIX_SYM_PROCESS_TREE = 0x60;
constexpr int FIX_SYM_PROCESS_TYPE = 0x64;
constexpr int FIX_SYM_THREAD = 0x68;
constexpr int FIX_SYM_STRUCTURE = 0x6c;
constexpr int FIX_SYM_PAIR_TYPE = 0x70;
constexpr int FIX_SYM_POINTER = 0x74;
constexpr int FIX_SYM_NUMBER = 0x78;
constexpr int FIX_SYM_ARRAY = 0x7c;
constexpr int FIX_SYM_VU_FUNCTION = 0x80;
constexpr int FIX_SYM_CONNECTABLE = 0x84;
constexpr int FIX_SYM_STACK_FRAME = 0x88;
constexpr int FIX_SYM_FILE_STREAM = 0x8c;
constexpr int FIX_SYM_HEAP = 0x90;
constexpr int FIX_SYM_NOTHING_FUNC = 0x94;
constexpr int FIX_SYM_DELETE_BASIC = 0x98;
constexpr int FIX_SYM_STATIC = 0x9c;
constexpr int FIX_SYM_GLOBAL_HEAP = 0xa0;
constexpr int FIX_SYM_DEBUG = 0xa4;
constexpr int FIX_SYM_LOADING_LEVEL = 0xa8;
constexpr int FIX_SYM_LOADING_PACKAGE = 0xac;
constexpr int FIX_SYM_PROCESS_LEVEL_HEAP = 0xb0;
constexpr int FIX_SYM_STACK = 0xb4;
constexpr int FIX_SYM_SCRATCH = 0xb8;
constexpr int FIX_SYM_SCRATCH_TOP = 0xbc;
constexpr int FIX_SYM_ZERO_FUNC = 0xc0;
constexpr int FIX_SYM_ASIZE_OF_BASIC_FUNC = 0xc4;
constexpr int FIX_SYM_COPY_BASIC_FUNC = 0xc8; // bugged name
constexpr int FIX_SYM_LEVEL = 0xcc;
constexpr int FIX_SYM_ART_GROUP = 0xd0;
constexpr int FIX_SYM_TEXTURE_PAGE_DIR = 0xd4;
constexpr int FIX_SYM_TEXTURE_PAGE = 0xd8;
constexpr int FIX_SYM_SOUND = 0xdc;
constexpr int FIX_SYM_DGO = 0xe0;
constexpr int FIX_SYM_TOP_LEVEL = 0xe4;
constexpr int FIX_SYM_QUOTE = 0xe8;
constexpr int FIX_SYM_LISTENER_LINK_BLOCK = 0xec;
constexpr int FIX_SYM_LISTENER_FUNCTION = 0xf0;
constexpr int FIX_SYM_STACK_TOP = 0xf4;
constexpr int FIX_SYM_STACK_BASE = 0xf8;
constexpr int FIX_SYM_STACK_SIZE = 0xfc;
constexpr int FIX_SYM_KERNEL_FUNCTION = 0x100;
constexpr int FIX_SYM_KERNEL_PACKAGES = 0x104;
constexpr int FIX_SYM_KERNEL_BOOT_MESSAGE = 0x108;
constexpr int FIX_SYM_KERNEL_BOOT_MODE = 0x10c;
constexpr int FIX_SYM_KERNEL_BOOT_LEVEL = 0x110;
constexpr int FIX_SYM_KERNEL_BOOT_ART_GROUP = 0x114;
constexpr int FIX_SYM_KERNEL_DEBUG = 0x118;
constexpr int FIX_SYM_KERNEL_VERSION = 0x11c;
constexpr int FIX_SYM_KERNEL_DISPATCHER = 0x120;
constexpr int FIX_SYM_SYNC_DISPATCHER = 0x124;
constexpr int FIX_SYM_PRINT_COLLUMN = 0x128;
constexpr int FIX_SYM_DEBUG_SEGMENT = 300;
constexpr int FIX_SYM_ENABLE_METHOD_SET = 0x130;
constexpr int FIX_SYM_SQL_RESULT = 0x134;
constexpr int FIX_SYM_COLLAPSE_QUOTE = 0x138;
constexpr int FIX_SYM_LEVEL_TYPE_LIST = 0x13C;
constexpr int FIX_SYM_DECI_COUNT = 0x140;
constexpr int FIX_SYM_USER = 0x144;
constexpr int FIX_SYM_VIDEO_MODE = 0x148;
constexpr int FIX_SYM_BOOT_VIDEO_MODE = 0x14C;
constexpr int FIX_SYM_BOOT = 0x150;
constexpr int FIX_SYM_DEMO = 0x154;
constexpr int FIX_SYM_DEMO_SHARED = 0x158;
constexpr int FIX_SYM_PREVIEW = 0x15C;
constexpr int FIX_SYM_KIOSK = 0x160;
constexpr int FIX_SYM_PLAY_BOOT = 0x164;
constexpr int FIX_SYM_SIN = 0x168;
constexpr int FIX_SYM_COS = 0x16C;
constexpr int FIX_SYM_PUT_DISPLAY_ENV = 0x170;
constexpr int FIX_SYM_SYNCV = 0x174;
constexpr int FIX_SYM_SYNC_PATH = 0x178;
constexpr int FIX_SYM_RESET_PATH = 0x17C;
constexpr int FIX_SYM_RESET_GRAPH = 0x180;
constexpr int FIX_SYM_DMA_SYNC = 0x184;
constexpr int FIX_SYM_GS_PUT_IMR = 0x188;
constexpr int FIX_SYM_GS_GET_IMR = 0x18C;
constexpr int FIX_SYM_GS_STORE_IMAGE = 400;
constexpr int FIX_SYM_FLUSH_CACHE = 0x194;
constexpr int FIX_SYM_CPAD_OPEN = 0x198;
constexpr int FIX_SYM_CPAD_GET_DATA = 0x19C;
constexpr int FIX_SYM_MOUSE_GET_DATA = 0x1A0;
constexpr int FIX_SYM_KEYBD_GET_DATA = 0x1A4;
constexpr int FIX_SYM_INSTALL_HANDLER = 0x1A8;
constexpr int FIX_SYM_INSTALL_DEBUG_HANDLER = 0x1AC;
constexpr int FIX_SYM_FILE_STREAM_OPEN = 0x1B0;
constexpr int FIX_SYM_FILE_STREAM_CLOSE = 0x1B4;
constexpr int FIX_SYM_FILE_STREAM_LENGTH = 0x1B8;
constexpr int FIX_SYM_FILE_STREAM_SEEK = 0x1BC;
constexpr int FIX_SYM_FILE_STREAM_READ = 0x1C0;
constexpr int FIX_SYM_FILE_STREAM_WRITE = 0x1C4;
constexpr int FIX_SYM_SCF_GET_LANGUAGE = 0x1C8;
constexpr int FIX_SYM_SCF_GET_TIME = 0x1CC;
constexpr int FIX_SYM_SCF_GET_ASPECT = 0x1D0;
constexpr int FIX_SYM_SCF_GET_VOLUME = 0x1D4;
constexpr int FIX_SYM_SCF_GET_TERRITORY = 0x1D8;
constexpr int FIX_SYM_SCF_GET_TIMEOUT = 0x1DC;
constexpr int FIX_SYM_SCF_GET_INACTIVE_TIMEOUT = 0x1E0;
constexpr int FIX_SYM_DMA_TO_IOP = 0x1E4;
constexpr int FIX_SYM_KERNEL_SHUTDOWN = 0x1E8;
constexpr int FIX_SYM_AYBABTU = 0x1EC;
constexpr int FIX_SYM_STRING_TO_SYMBOL = 0x1F0;
constexpr int FIX_SYM_SYMBOL_TO_STRING = 500;
constexpr int FIX_SYM_PRINT = 0x1F8;
constexpr int FIX_SYM_INSPECT = 0x1FC;
constexpr int FIX_SYM_LOAD = 0x200;
constexpr int FIX_SYM_LOADB = 0x204;
constexpr int FIX_SYM_LOADO = 0x208;
constexpr int FIX_SYM_UNLOAD = 0x20C;
constexpr int FIX_SYM_FORMAT = 0x210;
constexpr int FIX_SYM_MALLOC = 0x214;
constexpr int FIX_SYM_KMALLOC = 0x218;
constexpr int FIX_SYM_KMEMOPEN = 0x21C;
constexpr int FIX_SYM_KMEMCLOSE = 0x220;
constexpr int FIX_SYM_NEW_DYNAMIC_STRUCTURE = 0x224;
constexpr int FIX_SYM_METHOD_SET = 0x228;
constexpr int FIX_SYM_LINK = 0x22C;
constexpr int FIX_SYM_LINK_BUSY = 0x230;
constexpr int FIX_SYM_LINK_RESET = 0x234;
constexpr int FIX_SYM_LINK_BEGIN = 0x238;
constexpr int FIX_SYM_LINK_RESUME = 0x23C;
constexpr int FIX_SYM_DGO_LOAD = 0x240;
constexpr int FIX_SYM_SQL_QUERY = 0x244;
constexpr int FIX_SYM_MC_RUN = 0x248;
constexpr int FIX_SYM_MC_FORMAT = 0x24C;
constexpr int FIX_SYM_MC_UNFORMAT = 0x250;
constexpr int FIX_SYM_MC_CREATE_FILE = 0x254;
constexpr int FIX_SYM_MC_SAVE = 600;
constexpr int FIX_SYM_MC_LOAD = 0x25C;
constexpr int FIX_SYM_MC_CHECK_RESULT = 0x260;
constexpr int FIX_SYM_MC_GET_SLOT_INFO = 0x264;
constexpr int FIX_SYM_MC_MAKEFILE = 0x268;
constexpr int FIX_SYM_KSET_LANGUAGE = 0x26C;
constexpr int FIX_SYM_RPC_CALL = 0x270;
constexpr int FIX_SYM_RPC_BUSY = 0x274;
constexpr int FIX_SYM_TEST_LOAD_DGO_C = 0x278;
constexpr int FIX_SYM_SYMLINK2 = 0x27c;
constexpr int FIX_SYM_SYMLINK3 = 0x280;
constexpr int FIX_SYM_ULTIMATE_MEMCPY = 0x284;
constexpr int FIX_SYM_PLAY = 0x288;
constexpr int FIX_SYM_SYMBOL_STRING = 0x28c;
constexpr int FIX_SYM_KERNEL_SYMBOL_WARNINGS = 0x290;
constexpr int FIX_SYM_NETWORK_BOOTSTRAP = 0x294;
} // namespace jak3_symbols
constexpr int false_symbol_offset() {
return jak1_symbols::FIX_SYM_FALSE;
}

View File

@ -34,6 +34,7 @@ constexpr u32 TX_PAGE_VERSION = 8;
namespace jak3 {
constexpr u32 ART_FILE_VERSION = 8;
constexpr u32 LEVEL_FILE_VERSION = 36;
constexpr u32 DGO_FILE_VERSION = 1;
constexpr u32 TX_PAGE_VERSION = 8;
} // namespace jak3

View File

@ -472,7 +472,7 @@ static void link_v5(LinkedObjectFile& f,
for (int i = 0; i < n_segs; i++) {
segment_data_offsets[i] = header->length_to_get_to_code + seg_info_array[i].data;
segment_link_offsets[i] = header->length_to_get_to_link + seg_info_array[i].relocs;
ASSERT(seg_info_array[i].magic == 1);
ASSERT(seg_info_array[i].magic == 1); // if set, always use symlink2.
}
// check that the data region is filled

View File

@ -367,7 +367,7 @@ void ObjectFileDB::ir2_analyze_all_types(const fs::path& output_file,
for (const auto& [obj_name, type_names] : all_type_names) {
for (const auto& type_name : type_names) {
if (str_util::starts_with(sym_name, type_name) &&
type_name.length() > longest_match_length) {
(int)type_name.length() > longest_match_length) {
longest_match_length = type_name.length();
longest_match = type_name;
longest_match_object_name = obj_name;

View File

@ -1132,38 +1132,40 @@
(defenum process-mask
:type uint32
:bitfield #t
(execute 0)
(execute 0) ;; 1
(freeze 1)
(pause 2)
(menu 3)
(progress 4)
(actor-pause 5)
(sleep 6)
(sleep-code 7)
(process-tree 8)
(heap-shrunk 9)
(going 10)
(kernel-run 11)
(no-kill 12)
(movie 13)
(dark-effect 14)
(target 15)
(sidekick 16)
(crate 17)
(bit18 18) ;; unused?
(enemy 19)
(camera 20)
(platform 21)
(ambient 22)
(entity 23)
(projectile 24)
(bot 25)
(collectable 26)
(death 27)
(no-track 28)
(guard 29)
(vehicle 30)
(civilian 31))
(actor-pause 5) ;; 32
(sleep 6) ;; 64
(sleep-code 7) ;; 128
(process-tree 8) ;; 256
(heap-shrunk 9) ;; 512
(going 10) ;; 1024
(kernel-run 11) ;; 2048
(no-kill 12) ;; 4096
(movie 13) ;; 8192
(dark-effect 14) ;; 0x4000
(target 15) ;; 0x8000
(sidekick 16) ;; 0x1'0000
(crate 17) ;; 0x2'0000
(collectable 18) ;; 0x4'0000
(enemy 19) ;; 0x8'0000
(camera 20) ;; 0x10'0000
(platform 21) ;; 0x20'0000
(ambient 22) ;; 0x40'0000
(entity 23) ;; 0x80'0000
(projectile 24) ;; 0x100'0000
(bot 25) ;; 0x200'0000
(death 26) ;; 0x400'0000
(guard 27) ;; 0x800'0000
(vehicle 28) ;; 0x1000'0000
(civilian 29) ;; 0x2000'0000
(kg-robot 30) ;; 0x4000'0000
(metalhead 31) ;; 0x8000'0000
)
(deftype process-tree (basic)
((name string :offset-assert 4)
@ -1180,11 +1182,11 @@
:flag-assert #xe00000024
:no-runtime-type
(:methods
;; (new (symbol type string) _type_ 0)
(process-tree-method-9 () none) ;; 9 ;; (activate (_type_ process-tree basic pointer) process-tree 9)
(process-tree-method-10 () none) ;; 10 ;; (deactivate (_type_) none 10)
(process-tree-method-11 () none) ;; 11 ;; (init-from-entity! (_type_ entity-actor) none 11)
(process-tree-method-12 () none) ;; 12 ;; (run-logic? (_type_) symbol 12)
(new (symbol type string) _type_)
(activate (_type_ process-tree basic pointer) process-tree)
(deactivate (_type_) none)
(init-from-entity! (_type_ entity-actor) none)
(run-logic? (_type_) symbol)
(process-tree-method-13 () none) ;; 13 ;; (process-tree-method-13 () none 13)
)
)
@ -1210,7 +1212,7 @@
:method-count-assert 9
:size-assert #x40
:flag-assert #x900000040
;; field relocating-level uses ~A with a signed load.
;; field relocating-level uses ~A with a signed load.
)
|#
@ -1239,22 +1241,22 @@
:size-assert #x5c
:flag-assert #x180000005c
(:methods
;; (new (symbol type int) _type_ 0)
(clock-method-9 () none) ;; 9 ;; (update-rates! (_type_ float) float 9)
(new (symbol type int) _type_)
(update-rates! (_type_ float) float)
(clock-method-10 () none) ;; 10 ;; (advance-by! (_type_ float) clock 10)
(clock-method-11 () none) ;; 11 ;; (tick! (_type_) clock 11)
(clock-method-12 () none) ;; 12 ;; (save! (_type_ (pointer uint64)) int 12)
(clock-method-13 () none) ;; 13 ;; (load! (_type_ (pointer uint64)) int 13)
(clock-method-14 () none) ;; 14 ;; (reset! (_type_) none 14)
(clock-method-15 () none) ;; 15
(clock-method-16 () none) ;; 16
(clock-method-17 () none) ;; 17
(clock-method-18 () none) ;; 18
(clock-method-19 () none) ;; 19
(clock-method-20 () none) ;; 20
(clock-method-21 () none) ;; 21
(clock-method-22 () none) ;; 22
(clock-method-23 () none) ;; 23
(frame-mask-2 (_type_ int) symbol)
(frame-mask-4 (_type_ int) symbol)
(frame-mask-8 (_type_ int) symbol)
(frame-mask-16 (_type_ int) symbol)
(frame-mask-32 (_type_ int) symbol)
(frame-mask-64 (_type_ int) symbol)
(frame-mask-128 (_type_ int) symbol)
(frame-mask-256 (_type_ int) symbol)
)
)
@ -4067,7 +4069,7 @@
:method-count-assert 209
:size-assert #x4e0
:flag-assert #xd1046004e0
;; field gekko-flag is likely a value type.
;; field gekko-flag is likely a value type.
(:methods
(gekko-method-202 () none) ;; 202
(gekko-method-203 () none) ;; 203
@ -4793,7 +4795,7 @@
:method-count-assert 13
:size-assert #x30
:flag-assert #xd00000030
;; field on-enter uses ~A with a signed load. field on-inside uses ~A with a signed load. field on-exit uses ~A with a signed load.
;; field on-enter uses ~A with a signed load. field on-inside uses ~A with a signed load. field on-exit uses ~A with a signed load.
(:methods
;; (new (symbol type) _type_ 0)
(editable-region-method-9 () none) ;; 9 ;; (editable-region-method-9 (_type_ editable-array int int) symbol 9)
@ -5256,7 +5258,7 @@
:method-count-assert 9
:size-assert #x28
:flag-assert #x900000028
;; field textures uses ~A with a signed load.
;; field textures uses ~A with a signed load.
)
|#
@ -6665,7 +6667,7 @@
:method-count-assert 9
:size-assert #x20
:flag-assert #x900000020
;; field speech-type-flag is likely a value type.
;; field speech-type-flag is likely a value type.
)
|#
@ -6699,7 +6701,7 @@
:method-count-assert 15
:size-assert #x64
:flag-assert #xf00000064
;; field speech-channel-flag is likely a value type.
;; field speech-channel-flag is likely a value type.
(:methods
(speech-channel-method-9 () none) ;; 9 ;; (speech-channel-method-9 (_type_ process-drawable speech-type) none 9)
(speech-channel-method-10 () none) ;; 10 ;; (speech-channel-method-10 (_type_ handle) none 10)
@ -6905,7 +6907,7 @@
:method-count-assert 216
:size-assert #x404
:flag-assert #xd803900404
;; field citizen-flag is likely a value type.
;; field citizen-flag is likely a value type.
(:methods
(citizen-method-190 () none) ;; 190 ;; (citizen-method-190 (_type_ vector) none 190)
(citizen-method-191 () none) ;; 191 ;; (gen-clear-path (_type_) nav-segment 191)
@ -7322,7 +7324,7 @@
:method-count-assert 155
:size-assert #x228
:flag-assert #x9b01b00228
;; field enemy-flag is likely a value type. field on-notice uses ~A with a signed load. field on-active uses ~A with a signed load. field on-hostile uses ~A with a signed load. field on-death uses ~A with a signed load.
;; field enemy-flag is likely a value type. field on-notice uses ~A with a signed load. field on-active uses ~A with a signed load. field on-hostile uses ~A with a signed load. field on-death uses ~A with a signed load.
(:methods
(enemy-method-28 () none) ;; 28 ;; (dormant-aware () _type_ :state 28)
(enemy-method-29 () none) ;; 29 ;; (hit () _type_ :state 29)
@ -8202,7 +8204,7 @@
:method-count-assert 12
:size-assert #x28
:flag-assert #xc00000028
;; field out uses ~A with a signed load.
;; field out uses ~A with a signed load.
(:methods
;; (new (symbol type uint) _type_ 0)
(history-iterator-method-9 () none) ;; 9 ;; (frame-counter-delta (_type_ history-elt) time-frame 9)
@ -10303,7 +10305,7 @@
:method-count-assert 12
:size-assert #xa8
:flag-assert #xc000000a8
;; field handle is likely a value type.
;; field handle is likely a value type.
(:methods
(attack-info-method-9 () none) ;; 9 ;; (attack-info-method-9 (_type_ attack-info process-drawable process-drawable) none 9)
(attack-info-method-10 () none) ;; 10 ;; (compute-intersect-info (_type_ object process-drawable process touching-shapes-entry) attack-info 10)
@ -10647,7 +10649,7 @@
:method-count-assert 9
:size-assert #x20
:flag-assert #x900000020
;; field check-too-far uses ~A with a signed load.
;; field check-too-far uses ~A with a signed load.
)
|#
@ -10670,7 +10672,7 @@
:method-count-assert 9
:size-assert #x30
:flag-assert #x900000030
;; field default-check-too-far uses ~A with a signed load.
;; field default-check-too-far uses ~A with a signed load.
)
|#
@ -11359,7 +11361,7 @@
:method-count-assert 11
:size-assert #xa0
:flag-assert #xb000000a0
;; field extra-sound-bank uses ~A with a signed load.
;; field extra-sound-bank uses ~A with a signed load.
(:methods
(level-load-info-method-9 () none) ;; 9
(level-load-info-method-10 () none) ;; 10
@ -12400,7 +12402,7 @@
:method-count-assert 14
:size-assert #x20
:flag-assert #xe00000020
;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load.
;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load.
(:methods
(connection-method-9 () none) ;; 9 ;; (get-engine (connection) engine 9)
(connection-method-10 () none) ;; 10 ;; (get-process (connection) process 10)
@ -12468,7 +12470,7 @@
:method-count-assert 9
:size-assert #x20
:flag-assert #x900000020
;; field key uses ~A with a signed load.
;; field key uses ~A with a signed load.
)
|#
@ -12823,7 +12825,7 @@
:method-count-assert 9
:size-assert #x50
:flag-assert #x900000050
;; field key uses ~A with a signed load.
;; field key uses ~A with a signed load.
)
|#
@ -13152,7 +13154,7 @@
:method-count-assert 50
:size-assert #x70
:flag-assert #x3200000070
;; field nav-mesh-flag is likely a value type.
;; field nav-mesh-flag is likely a value type.
(:methods
(nav-mesh-method-9 () none) ;; 9 ;; (debug-draw (_type_) none 9)
(nav-mesh-method-10 () none) ;; 10 ;; (nav-mesh-method-10 (_type_ vector vector nav-poly) nav-poly 10)
@ -13488,7 +13490,7 @@
:method-count-assert 11
:size-assert #x30c
:flag-assert #xb0000030c
;; field cam-slave-options is likely a value type. field cam-master-options is likely a value type.
;; field cam-slave-options is likely a value type. field cam-master-options is likely a value type.
(:methods
(cam-setting-data-method-9 () none) ;; 9 ;; (cam-setting-data-method-9 (_type_ engine engine-pers engine) _type_ 9)
(cam-setting-data-method-10 () none) ;; 10 ;; (cam-setting-data-method-10 (_type_ object (pointer process) float int) _type_ 10)
@ -13643,7 +13645,7 @@
:method-count-assert 30
:size-assert #xa00
:flag-assert #x1e09800a00
;; field mode-param2 uses ~A with a 64-bit load. field mode-param3 uses ~A with a 64-bit load.
;; field mode-param2 uses ~A with a 64-bit load. field mode-param3 uses ~A with a 64-bit load.
(:methods
(target-method-28 () none) ;; 28 ;; (init-target (_type_ continue-point symbol) none 28)
(target-method-29 () none) ;; 29
@ -15664,7 +15666,7 @@
:method-count-assert 10
:size-assert #x20
:flag-assert #xa00000020
;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load.
;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load.
(:methods
;; (new (symbol type drawable) _type_ 0)
(cspace-method-9 () none) ;; 9 ;; (reset-and-assign-geo! (_type_ drawable) _type_ 9)
@ -15907,7 +15909,7 @@
:method-count-assert 17
:size-assert #x20
:flag-assert #x1100000020
;; field distance is a float printed as hex?
;; field distance is a float printed as hex?
)
|#
@ -16071,7 +16073,7 @@
:method-count-assert 52
:size-assert #x1a0
:flag-assert #x34012001a0
;; field on-activate uses ~A with a signed load. field on-deactivate uses ~A with a signed load. field on-up uses ~A with a signed load. field on-down uses ~A with a signed load. field on-running uses ~A with a signed load. field on-notice uses ~A with a signed load. field on-wait uses ~A with a signed load. field activate-test uses ~A with a signed load.
;; field on-activate uses ~A with a signed load. field on-deactivate uses ~A with a signed load. field on-up uses ~A with a signed load. field on-down uses ~A with a signed load. field on-running uses ~A with a signed load. field on-notice uses ~A with a signed load. field on-wait uses ~A with a signed load. field activate-test uses ~A with a signed load.
(:methods
(elevator-method-39 () none) ;; 39 ;; (calc-dist-between-points! (_type_ int int) none 39)
(elevator-method-41 () none) ;; 41 ;; (init-defaults! (_type_) none 41)
@ -16232,7 +16234,7 @@
:method-count-assert 10
:size-assert #x10
:flag-assert #xa00000010
;; field on-enter uses ~A with a signed load. field on-inside uses ~A with a signed load. field on-exit uses ~A with a signed load.
;; field on-enter uses ~A with a signed load. field on-inside uses ~A with a signed load. field on-exit uses ~A with a signed load.
(:methods
(region-method-9 () none) ;; 9 ;; (region-method-9 (_type_ vector) symbol 9)
)
@ -17081,7 +17083,7 @@
:method-count-assert 9
:size-assert #x10
:flag-assert #x900000010
;; field execute uses ~A with a signed load.
;; field execute uses ~A with a signed load.
)
|#
@ -17098,7 +17100,7 @@
:method-count-assert 9
:size-assert #x30
:flag-assert #x900000030
;; field resetter-flag is likely a value type.
;; field resetter-flag is likely a value type.
)
|#
@ -17125,7 +17127,7 @@
:method-count-assert 9
:size-assert #x3c
:flag-assert #x90000003c
;; field handle is likely a value type. field resolution-scene uses ~A with a signed load. field on-complete uses ~A with a signed load. field on-fail uses ~A with a signed load.
;; field handle is likely a value type. field resolution-scene uses ~A with a signed load. field on-complete uses ~A with a signed load. field on-fail uses ~A with a signed load.
)
|#
@ -17170,7 +17172,7 @@
:method-count-assert 14
:size-assert #x50
:flag-assert #xe00000050
;; field on-open uses ~A with a signed load. field on-close uses ~A with a signed load.
;; field on-open uses ~A with a signed load. field on-close uses ~A with a signed load.
(:methods
(game-task-node-info-method-9 () none) ;; 9 ;; (close! (_type_ symbol) int 9)
(game-task-node-info-method-10 () none) ;; 10 ;; (open! (_type_ symbol) int 10)
@ -17195,7 +17197,7 @@
:method-count-assert 10
:size-assert #x20
:flag-assert #xa00000020
;; field kiosk-play-continue uses ~A with a signed load.
;; field kiosk-play-continue uses ~A with a signed load.
(:methods
(game-task-info-method-9 () none) ;; 9
)
@ -18025,7 +18027,7 @@
:method-count-assert 14
:size-assert #x3c
:flag-assert #xe0000003c
;; field blend-shape-anim uses ~A with a signed load.
;; field blend-shape-anim uses ~A with a signed load.
)
|#
@ -18201,7 +18203,7 @@
:method-count-assert 16
:size-assert #xdc
:flag-assert #x10000000dc
;; field draw-control-status is likely a value type. field draw-control-global-effect is likely a value type.
;; field draw-control-status is likely a value type. field draw-control-global-effect is likely a value type.
(:methods
;; (new (symbol type process symbol) _type_ 0)
(draw-control-method-9 () none) ;; 9 ;; (get-skeleton-origin (_type_) vector 9)
@ -18255,7 +18257,7 @@
:method-count-assert 9
:size-assert #x1c
:flag-assert #x90000001c
;; field parm0 uses ~A with a signed load. field parm1 uses ~A with a signed load.
;; field parm0 uses ~A with a signed load. field parm1 uses ~A with a signed load.
)
|#
@ -18283,7 +18285,7 @@
:method-count-assert 9
:size-assert #x1c
:flag-assert #x90000001c
;; field track-val uses ~A with a signed load. field val-parm0 uses ~A with a signed load. field val-parm1 uses ~A with a signed load.
;; field track-val uses ~A with a signed load. field val-parm0 uses ~A with a signed load. field val-parm1 uses ~A with a signed load.
)
|#
@ -18788,7 +18790,7 @@
:method-count-assert 9
:size-assert #x80
:flag-assert #x900000080
;; field xyz-scale is a float printed as hex?
;; field xyz-scale is a float printed as hex?
)
|#
@ -19826,7 +19828,7 @@
:method-count-assert 12
:size-assert #x68
:flag-assert #xc00000068
;; field on-goto uses ~A with a signed load.
;; field on-goto uses ~A with a signed load.
(:methods
(continue-point-method-9 () none) ;; 9 ;; (debug-draw (_type_) int 9)
(continue-point-method-10 () none) ;; 10 ;; (continue-point-method-10 (_type_ load-state) continue-point 10)
@ -20181,7 +20183,7 @@
:method-count-assert 14
:size-assert #x30
:flag-assert #xe00000030
;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load.
;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load.
)
|#
@ -20470,7 +20472,7 @@
:method-count-assert 14
:size-assert #x20
:flag-assert #xe00000020
;; field on-close uses ~A with a signed load.
;; field on-close uses ~A with a signed load.
(:methods
(talker-speech-class-method-9 () none) ;; 9 ;; (talker-speech-class-method-9 (_type_) symbol 9)
(talker-speech-class-method-10 () none) ;; 10 ;; (play-communicator-speech! (_type_) none 10)
@ -21138,7 +21140,7 @@
:method-count-assert 13
:size-assert #x40
:flag-assert #xd00000040
;; field joint-control-status is likely a value type.
;; field joint-control-status is likely a value type.
(:methods
;; (new (symbol type int) _type_ 0)
(joint-control-method-9 () none) ;; 9 ;; (current-cycle-distance (_type_) float 9)
@ -21498,7 +21500,7 @@
:method-count-assert 14
:size-assert #xb0
:flag-assert #xe000000b0
;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load.
;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load.
)
|#
@ -21568,7 +21570,7 @@
:method-count-assert 22
:size-assert #x20
:flag-assert #x1600000020
;; field extra uses ~A with a signed load.
;; field extra uses ~A with a signed load.
(:methods
;; (new (symbol type int int) _type_ 0)
(res-lump-method-9 () none) ;; 9 ;; (get-property-data (_type_ symbol symbol float pointer (pointer res-tag) pointer) pointer 9)
@ -21680,7 +21682,7 @@
:method-count-assert 45
:size-assert #x15c
:flag-assert #x2d00e0015c
;; field on-activate uses ~A with a signed load.
;; field on-activate uses ~A with a signed load.
(:methods
(wstd-arena-plat-method-43 () none) ;; 43
(wstd-arena-plat-method-44 () none) ;; 44
@ -22226,7 +22228,7 @@
:method-count-assert 12
:size-assert #x28
:flag-assert #xc00000028
;; field actor-option is likely a value type.
;; field actor-option is likely a value type.
(:methods
;; (new (symbol type process pickup-type float) _type_ 0)
(fact-info-method-9 () none) ;; 9 ;; (drop-pickup (_type_ symbol process-tree fact-info int) (pointer process) 9)
@ -22287,7 +22289,7 @@
:method-count-assert 13
:size-assert #x138
:flag-assert #xd00000138
;; field actor-option is likely a value type.
;; field actor-option is likely a value type.
(:methods
;; (new (symbol type process-drawable pickup-type float) _type_ 0)
(fact-info-target-method-12 () none) ;; 12 ;; (get-gun-ammo (_type_) float 12)
@ -22318,7 +22320,7 @@
:method-count-assert 13
:size-assert #x53
:flag-assert #xd00000053
;; field actor-option is likely a value type.
;; field actor-option is likely a value type.
(:methods
;; (new (symbol type process (pointer float) pickup-type float) _type_ 0)
(fact-info-enemy-method-12 () none) ;; 12 ;; (clear-mask-bits (_type_ int) none 12)
@ -22338,7 +22340,7 @@
:method-count-assert 12
:size-assert #x2c
:flag-assert #xc0000002c
;; field actor-option is likely a value type.
;; field actor-option is likely a value type.
(:methods
;; (new (symbol type process pickup-type float) _type_ 0)
)
@ -22443,7 +22445,7 @@
:method-count-assert 12
:size-assert #xa0
:flag-assert #xc000000a0
;; field key uses ~A with a signed load. field expr uses ~A with a signed load.
;; field key uses ~A with a signed load. field expr uses ~A with a signed load.
(:methods
;; (new (symbol type object process vector) _type_ 0)
(script-context-method-9 () none) ;; 9 ;; (eval! (_type_ pair) object 9)
@ -22486,7 +22488,7 @@
:method-count-assert 9
:size-assert #x14
:flag-assert #x900000014
;; field info uses ~A with a signed load.
;; field info uses ~A with a signed load.
)
|#
@ -22735,7 +22737,7 @@
:method-count-assert 18
:size-assert #x84
:flag-assert #x1200000084
;; field on-running uses ~A with a signed load. field on-complete uses ~A with a signed load.
;; field on-running uses ~A with a signed load. field on-complete uses ~A with a signed load.
(:methods
(scene-method-16 () none) ;; 16 ;; (scene-method-16 (_type_) _type_ 16)
(scene-method-17 () none) ;; 17
@ -22778,7 +22780,7 @@
:method-count-assert 26
:size-assert #x198
:flag-assert #x1a01200198
;; field user-data uses ~A with a 64-bit load.
;; field user-data uses ~A with a 64-bit load.
(:methods
(scene-player-method-20 () none) ;; 20 ;; (wait (symbol) _type_ :state 20)
(scene-player-method-21 () none) ;; 21 ;; (release () _type_ :state 21)
@ -23545,7 +23547,7 @@
:method-count-assert 23
:size-assert #xe0
:flag-assert #x17006000e0
;; field open-test uses ~A with a signed load.
;; field open-test uses ~A with a signed load.
(:methods
(precur-door-a-method-22 () none) ;; 22
)
@ -24069,7 +24071,7 @@
:method-count-assert 16
:size-assert #xac
:flag-assert #x10000000ac
;; field track-mode is likely a value type.
;; field track-mode is likely a value type.
(:methods
;; (new (symbol type joint-mod-mode process-drawable int) _type_ 0)
(joint-mod-method-9 () none) ;; 9 ;; (mode-set! (_type_ joint-mod-mode) none 9)
@ -24875,7 +24877,7 @@
:method-count-assert 55
:size-assert #xc8
:flag-assert #x37000000c8
;; field penetrate is likely a value type. field penetrate is likely a value type.
;; field penetrate is likely a value type. field penetrate is likely a value type.
(:methods
;; (new (symbol type process-drawable collide-list-enum) _type_ 0)
(collide-shape-method-28 () none) ;; 28 ;; (move-by-vector! (_type_ vector) none 28)
@ -24938,7 +24940,7 @@
:method-count-assert 68
:size-assert #x1dc
:flag-assert #x44000001dc
;; field penetrate is likely a value type. field penetrate is likely a value type.
;; field penetrate is likely a value type. field penetrate is likely a value type.
(:methods
;; (new (symbol type process-drawable collide-list-enum) _type_ 0)
(collide-shape-moving-method-55 () none) ;; 55 ;; (find-ground (_type_ collide-query collide-spec float float float) symbol 55)
@ -25026,7 +25028,7 @@
:method-count-assert 18
:size-assert #x15c
:flag-assert #x1200e0015c
;; field userdata uses ~A with a 64-bit load.
;; field userdata uses ~A with a 64-bit load.
(:methods
(part-tracker-method-14 () none) ;; 14 ;; (active () _type_ :state 14)
(part-tracker-method-15 () none) ;; 15 ;; (notify-parent-of-death (_type_) none 15)
@ -25048,7 +25050,7 @@
:method-count-assert 9
:size-assert #x20
:flag-assert #x900000020
;; field userdata uses ~A with a 64-bit load. field mat-joint uses ~A with a signed load.
;; field userdata uses ~A with a 64-bit load. field mat-joint uses ~A with a signed load.
)
|#
@ -25065,7 +25067,7 @@
:method-count-assert 9
:size-assert #x24
:flag-assert #x900000024
;; field userdata uses ~A with a 64-bit load. field mat-joint uses ~A with a signed load.
;; field userdata uses ~A with a 64-bit load. field mat-joint uses ~A with a signed load.
)
|#
@ -25101,7 +25103,7 @@
:method-count-assert 17
:size-assert #x130
:flag-assert #x1100b00130
;; field userdata uses ~A with a 64-bit load.
;; field userdata uses ~A with a 64-bit load.
(:methods
(lightning-tracker-method-14 () none) ;; 14 ;; (active () _type_ :state 14)
(lightning-tracker-method-15 () none) ;; 15 ;; (notify-parent-of-death (_type_) none 15)
@ -29521,7 +29523,7 @@
:method-count-assert 9
:size-assert #x10
:flag-assert #x900000010
;; field object uses ~A with a signed load.
;; field object uses ~A with a signed load.
)
|#
@ -29934,7 +29936,7 @@
:method-count-assert 9
:size-assert #x8c
:flag-assert #x90000008c
;; field user-object uses ~A with a signed load. field key uses ~A with a signed load.
;; field user-object uses ~A with a signed load. field key uses ~A with a signed load.
)
|#
@ -32403,7 +32405,7 @@
:method-count-assert 56
:size-assert #x120
:flag-assert #x3800a00120
;; field rigid-body-object-flag is likely a value type.
;; field rigid-body-object-flag is likely a value type.
(:methods
(rigid-body-object-method-28 () none) ;; 28 ;; (active () _type_ :state 28)
(rigid-body-object-method-29 () none) ;; 29 ;; (rigid-body-object-method-29 (_type_ float) none 29)
@ -32815,7 +32817,7 @@
:method-count-assert 30
:size-assert #x1b4
:flag-assert #x1e014001b4
;; field open-test uses ~A with a signed load.
;; field open-test uses ~A with a signed load.
(:state-methods
open ;; 20
)
@ -34707,7 +34709,7 @@
:method-count-assert 9
:size-assert #x14
:flag-assert #x900000014
;; field param2 uses ~A with a signed load.
;; field param2 uses ~A with a signed load.
)
|#
@ -35025,7 +35027,7 @@
:method-count-assert 207
:size-assert #x2e0
:flag-assert #xcf026002e0
;; field mantis-flag is likely a value type.
;; field mantis-flag is likely a value type.
(:methods
(mantis-method-195 () none) ;; 195
(mantis-method-199 () none) ;; 199
@ -36189,7 +36191,7 @@
:method-count-assert 9
:size-assert #x130
:flag-assert #x900000130
;; field ra uses ~A with a signed load. field dummy0 uses ~A with a signed load. field dummy1 uses ~A with a signed load. field b-spfic uses ~A with a signed load. field l-spfic uses ~A with a signed load.
;; field ra uses ~A with a signed load. field dummy0 uses ~A with a signed load. field dummy1 uses ~A with a signed load. field b-spfic uses ~A with a signed load. field l-spfic uses ~A with a signed load.
)
|#
@ -37780,7 +37782,7 @@
:method-count-assert 9
:size-assert #x44
:flag-assert #x900000044
;; field squad-target-flag is likely a value type.
;; field squad-target-flag is likely a value type.
)
|#
@ -37803,7 +37805,7 @@
:method-count-assert 10
:size-assert #x120
:flag-assert #xa00000120
;; field squad-alert-flag is likely a value type.
;; field squad-alert-flag is likely a value type.
(:methods
(squad-alert-state-method-9 () none) ;; 9
)
@ -39263,7 +39265,7 @@
:method-count-assert 38
:size-assert #xac
:flag-assert #x26000000ac
;; field cloth-flag is likely a value type.
;; field cloth-flag is likely a value type.
(:methods
(cloth-system-method-16 () none) ;; 16
(cloth-system-method-17 () none) ;; 17
@ -39301,7 +39303,7 @@
:method-count-assert 38
:size-assert #x110
:flag-assert #x2600000110
;; field cloth-flag is likely a value type.
;; field cloth-flag is likely a value type.
)
|#
@ -40731,7 +40733,7 @@
:method-count-assert 17
:size-assert #xb0
:flag-assert #x11000000b0
;; field carry-mode is likely a value type.
;; field carry-mode is likely a value type.
(:methods
;; (new (symbol type process-drawable int vector vector float) _type_ 0)
(carry-info-method-9 () none) ;; 9 ;; (carry-info-method-9 (_type_) none 9)
@ -41211,7 +41213,7 @@
:method-count-assert 23
:size-assert #xe4
:flag-assert #x17007000e4
;; field open-test uses ~A with a signed load.
;; field open-test uses ~A with a signed load.
(:methods
(precura-door-a-method-22 () none) ;; 22
)
@ -43432,7 +43434,7 @@
:method-count-assert 26
:size-assert #x100
:flag-assert #x1a00800100
;; field on-notice uses ~A with a signed load. field on-activate uses ~A with a signed load. field on-close uses ~A with a signed load.
;; field on-notice uses ~A with a signed load. field on-activate uses ~A with a signed load. field on-close uses ~A with a signed load.
(:methods
(warp-gate-method-23 () none) ;; 23 ;; (init-skel-and-collide (_type_) none 23)
(warp-gate-method-24 () none) ;; 24 ;; (setup-fields (_type_) none 24)
@ -44792,7 +44794,7 @@
:method-count-assert 15
:size-assert #xa0
:flag-assert #xf002000a0
;; field activate-script uses ~A with a signed load. field enter-script uses ~A with a signed load. field exit-script uses ~A with a signed load.
;; field activate-script uses ~A with a signed load. field enter-script uses ~A with a signed load. field exit-script uses ~A with a signed load.
(:state-methods
active ;; 14
)
@ -45605,7 +45607,7 @@
:method-count-assert 30
:size-assert #x1b0
:flag-assert #x1e013001b0
;; field level-name uses ~A with a signed load. field open-test uses ~A with a signed load. field on-running uses ~A with a signed load.
;; field level-name uses ~A with a signed load. field open-test uses ~A with a signed load. field on-running uses ~A with a signed load.
(:methods
(com-airlock-method-22 () none) ;; 22 ;; (init-airlock! (_type_) _type_ 22)
(com-airlock-method-23 () none) ;; 23 ;; (want-cross-airlock? (_type_) symbol 23)
@ -46875,7 +46877,7 @@
:method-count-assert 53
:size-assert #x110
:flag-assert #x3500900110
;; field on-notice uses ~A with a signed load. field on-hostile uses ~A with a signed load. field on-beaten uses ~A with a signed load.
;; field on-notice uses ~A with a signed load. field on-hostile uses ~A with a signed load. field on-beaten uses ~A with a signed load.
(:methods
(battle-method-21 () none) ;; 21 ;; (battle-state-21 () _type_ :state 21)
(battle-method-25 () none) ;; 25 ;; (spawner-blocked? (_type_ battle-spawner) symbol 25)
@ -47086,7 +47088,7 @@
:method-count-assert 9
:size-assert #x1c
:flag-assert #x90000001c
;; field net-path-node-status is likely a value type.
;; field net-path-node-status is likely a value type.
)
|#
@ -49010,7 +49012,7 @@
:method-count-assert 59
:size-assert #x224
:flag-assert #x3b01b00224
;; field pause-proc uses ~A with a signed load.
;; field pause-proc uses ~A with a signed load.
(:methods
(target-turret-method-34 () none) ;; 34
(target-turret-method-35 () none) ;; 35
@ -49372,7 +49374,7 @@
:method-count-assert 22
:size-assert #x20
:flag-assert #x1600000020
;; field nav-node-flag is likely a value type.
;; field nav-node-flag is likely a value type.
(:methods
(nav-node-method-9 () none) ;; 9 ;; (debug-draw (_type_) none 9)
(nav-node-method-10 () none) ;; 10 ;; (debug-print (_type_ symbol string) none 10)
@ -49942,7 +49944,7 @@
:method-count-assert 11
:size-assert #x20
:flag-assert #xb00000020
;; field vis-cell-flag is likely a value type. field vis-cell-flag is likely a value type.
;; field vis-cell-flag is likely a value type. field vis-cell-flag is likely a value type.
(:methods
(vis-cell-method-9 () none) ;; 9 ;; (reset-segment-counts (_type_) none 9)
(vis-cell-method-10 () none) ;; 10 ;; (debug-draw (_type_) none 10)
@ -51284,7 +51286,7 @@
:method-count-assert 18
:size-assert #x60
:flag-assert #x1200000060
;; field turret-flag is likely a value type.
;; field turret-flag is likely a value type.
(:methods
(turret-control-method-9 () none) ;; 9 ;; (turret-control-method-9 (_type_ vehicle vector vector) none 9)
(turret-control-method-10 () none) ;; 10 ;; (turret-control-method-10 (_type_ vehicle) none 10)
@ -51369,7 +51371,7 @@
:method-count-assert 68
:size-assert #x1dc
:flag-assert #x44000001dc
;; field penetrate is likely a value type. field penetrate is likely a value type.
;; field penetrate is likely a value type. field penetrate is likely a value type.
)
|#
@ -55094,7 +55096,7 @@
:method-count-assert 152
:size-assert #x2b8
:flag-assert #x98024002b8
;; field vehicle-flag is likely a value type.
;; field vehicle-flag is likely a value type.
(:methods
(vehicle-method-56 () none) ;; 56 ;; (vehicle-state-56 () _type_ :state 56)
(vehicle-method-57 () none) ;; 57 ;; (player-control () _type_ :state 57)
@ -55855,7 +55857,7 @@
:method-count-assert 31
:size-assert #x200
:flag-assert #x1f01800200
;; field on-start uses ~A with a signed load. field on-stop uses ~A with a signed load. field on-shutdown uses ~A with a signed load. field on-trigger uses ~A with a signed load.
;; field on-start uses ~A with a signed load. field on-stop uses ~A with a signed load. field on-shutdown uses ~A with a signed load. field on-trigger uses ~A with a signed load.
(:methods
(elec-gate-method-24 () none) ;; 24 ;; (elec-gate-method-24 (_type_) none 24)
(elec-gate-method-25 () none) ;; 25 ;; (set-palette! (_type_) none 25)
@ -57117,7 +57119,7 @@
:method-count-assert 10
:size-assert #xaa
:flag-assert #xa000000aa
;; field borrow uses ~A with a signed load.
;; field borrow uses ~A with a signed load.
(:methods
(race-info-method-9 () none) ;; 9 ;; (initialize-mesh (_type_) none 9)
)
@ -58462,7 +58464,7 @@
:method-count-assert 9
:size-assert #x2c
:flag-assert #x90000002c
;; field vehicle-wheel-surface-flag is likely a value type.
;; field vehicle-wheel-surface-flag is likely a value type.
)
|#
@ -59361,7 +59363,7 @@
:method-count-assert 9
:size-assert #x38
:flag-assert #x900000038
;; field default-check-too-far uses ~A with a signed load.
;; field default-check-too-far uses ~A with a signed load.
)
|#
@ -66180,7 +66182,7 @@
:method-count-assert 9
:size-assert #x18
:flag-assert #x900000018
;; field param uses ~A with a signed load.
;; field param uses ~A with a signed load.
)
|#

View File

@ -126,6 +126,7 @@
// will be less picky about types related to pairs.
"pair_functions_by_name": [
"ref",
"ref&",
"(method 4 pair)",
"last",
"member",

View File

@ -1 +1,37 @@
{}
{
"(method 2 array)": [
[23, "gp", "(array int32)"],
[43, "gp", "(array uint32)"],
[63, "gp", "(array int64)"],
[83, "gp", "(array uint64)"],
[102, "gp", "(array int8)"],
[121, "gp", "(array uint8)"],
[141, "gp", "(array int16)"],
[161, "gp", "(array uint16)"],
[186, "gp", "(array uint128)"],
[204, "gp", "(array int32)"],
[223, "gp", "(array float)"],
[232, "gp", "(array float)"],
[249, "gp", "(array basic)"],
[258, "gp", "(array basic)"]
],
"(method 3 array)": [
[51, "gp", "(array int32)"],
[69, "gp", "(array uint32)"],
[87, "gp", "(array int64)"],
[105, "gp", "(array uint64)"],
[122, "gp", "(array int8)"],
[139, "gp", "(array int8)"],
[157, "gp", "(array int16)"],
[175, "gp", "(array uint16)"],
[198, "gp", "(array uint128)"],
[214, "gp", "(array int32)"],
[233, "gp", "(array float)"],
[250, "gp", "(array basic)"]
],
"(method 0 cpu-thread)": [[[0, 35], "v0", "cpu-thread"]],
"(method 0 process)": [
// [11, "a0", "int"],
// [[12, 45], "v0", "process"]
]
}

View File

@ -13,7 +13,9 @@ namespace decompiler {
bool allowable_base_type_for_symbol_to_string(const TypeSpec& ts);
constexpr PerGameVersion<int> SYMBOL_TO_STRING_MEM_OFFSET_DECOMP = {
8167 * 8, jak2::SYM_TO_STRING_OFFSET, jak3::SYM_TO_STRING_OFFSET};
8167 * 8, jak2::SYM_TO_STRING_OFFSET,
-99999, // not supported this way!
};
constexpr PerGameVersion<int> OFFSET_OF_NEXT_STATE_STORE = {72, 64, 68};
} // namespace decompiler

View File

@ -121,6 +121,19 @@ set(RUNTIME_SOURCE
kernel/jak2/kprint.cpp
kernel/jak2/kscheme.cpp
kernel/jak2/ksound.cpp
kernel/jak3/fileio.cpp
kernel/jak3/kboot.cpp
kernel/jak3/kdgo.cpp
kernel/jak3/kdsnetm.cpp
kernel/jak3/klink.cpp
kernel/jak3/klisten.cpp
kernel/jak3/kmachine.cpp
kernel/jak3/kmalloc.cpp
kernel/jak3/kmemcard.cpp
kernel/jak3/kprint.cpp
kernel/jak3/kscheme.cpp
kernel/jak3/ksocket.cpp
kernel/jak3/ksound.cpp
mips2c/jak1_functions/bones.cpp
mips2c/jak1_functions/collide_cache.cpp
mips2c/jak1_functions/collide_edge_grab.cpp
@ -234,8 +247,6 @@ set(RUNTIME_SOURCE
system/IOP_Kernel.cpp
system/iop_thread.cpp
system/SystemThread.cpp
system/vm/dmac.cpp
system/vm/vm.cpp
tools/filter_menu/filter_menu.cpp
tools/subtitle_editor/subtitle_editor_db.cpp
tools/subtitle_editor/subtitle_editor_repl_client.cpp

View File

@ -8,8 +8,7 @@
#include "common/common_types.h"
#include "common/versions/versions.h"
// TODO: jak 3 stub
constexpr PerGameVersion<int> DGO_RPC_ID(0xdeb4, 0xfab3, 0x0);
constexpr PerGameVersion<int> DGO_RPC_ID(0xdeb4, 0xfab3, 0xfab3);
constexpr int DGO_RPC_CHANNEL = 3;
constexpr int DGO_RPC_LOAD_FNO = 0;
constexpr int DGO_RPC_LOAD_NEXT_FNO = 1;
@ -28,3 +27,17 @@ struct RPC_Dgo_Cmd {
uint32_t buffer_heap_top;
char name[16];
};
namespace jak3 {
struct RPC_Dgo_Cmd {
uint16_t rsvd;
uint16_t result;
uint32_t buffer1;
uint32_t buffer2;
uint32_t buffer_heap_top;
char name[16];
uint16_t cgo_id;
uint8_t pad[30];
};
static_assert(sizeof(RPC_Dgo_Cmd) == 0x40);
} // namespace jak3

View File

@ -13,11 +13,11 @@ enum class Language {
Japanese = 5,
UK_English = 6,
// uk english?
Portuguese = 9
};
struct GameLaunchOptions {
GameVersion game_version = GameVersion::Jak1;
bool disable_display = false;
bool disable_debug_vm = true;
int server_port = DECI2_PORT;
};

View File

@ -7,6 +7,5 @@
#include "common/versions/versions.h"
// TODO: jak 3 stub
constexpr PerGameVersion<int> LOADER_RPC_ID(0xdeb2, 0xfab1, 0x0);
constexpr PerGameVersion<int> LOADER_RPC_ID(0xdeb2, 0xfab1, 0xfab1);
constexpr int LOADER_RPC_CHANNEL = 1;

View File

@ -7,6 +7,5 @@
*/
#include "common/versions/versions.h"
// TODO: jak 3 stub
constexpr PerGameVersion<int> PLAY_RPC_ID(0xdeb6, 0xfab5, 0x0);
constexpr PerGameVersion<int> PLAY_RPC_ID(0xdeb6, 0xfab5, 0xfab5);
constexpr int PLAY_RPC_CHANNEL = 5;

View File

@ -7,6 +7,5 @@
*/
#include "common/versions/versions.h"
// TODO: jak 3 stub
constexpr PerGameVersion<int> PLAYER_RPC_ID(0xdeb1, 0xfab0, 0x0);
constexpr PerGameVersion<int> PLAYER_RPC_ID(0xdeb1, 0xfab0, 0xfab0);
constexpr int PLAYER_RPC_CHANNEL = 0;

View File

@ -8,8 +8,7 @@
#include "common/common_types.h"
#include "common/versions/versions.h"
// TODO: jak 3 stub
constexpr PerGameVersion<int> RAMDISK_RPC_ID(0xdeb3, 0xfab2, 0x0);
constexpr PerGameVersion<int> RAMDISK_RPC_ID(0xdeb3, 0xfab2, 0xfab2);
constexpr int RAMDISK_RPC_CHANNEL = 2;
constexpr int RAMDISK_GET_DATA_FNO = 0;
constexpr int RAMDISK_RESET_AND_LOAD_FNO = 1;

View File

@ -5,8 +5,7 @@
#include "game/common/overlord_common.h"
// TODO: jak 3 stub
constexpr PerGameVersion<int> STR_RPC_ID(0xdeb5, 0xfab4, 0x0);
constexpr PerGameVersion<int> STR_RPC_ID(0xdeb5, 0xfab4, 0xfab4);
constexpr int STR_RPC_CHANNEL = 4;
/*

View File

@ -54,6 +54,7 @@ struct Ptr {
s32 operator-(Ptr<T> x) { return offset - x.offset; }
Ptr operator-(s32 diff) { return Ptr(offset - diff); }
bool operator==(const Ptr<T>& x) { return offset == x.offset; }
bool operator!=(const Ptr<T>& x) { return offset != x.offset; }
/*!
* Convert to a C pointer.

View File

@ -6,13 +6,13 @@
#include "game/kernel/common/Ptr.h"
/*!
* This type is bit strange. The Ptr<Symbol4> should have the same value as the original game's
* symbol address. The actual value in here is useless - the address of this thing is what matters.
*/
template <typename T>
struct Symbol4 {
u8 foo;
T& value() { return *reinterpret_cast<T*>(&foo - 1); }
const T& value() const { return *reinterpret_cast<T*>(&foo - 1); }
const char* name_cstr() const {
return (const char*)(g_ee_main_mem + 4 +
*reinterpret_cast<const u32*>(&foo + jak2::SYM_TO_STRING_OFFSET));
}
};

View File

@ -333,6 +333,7 @@ s32 FileSave(char* name, u8* data, s32 size) {
}
if (size != 0) {
// in jak 3, this became a loop over smaller writes for some reason.
s32 written = sceWrite(fd, data, size);
if (written != size) {
MsgErr("dkernel: can't write full file '%s'\n", name);

View File

@ -53,6 +53,7 @@ enum GoalFileType {
VAG_FILE_TYPE = 0x3e,
MISC_FILE_TYPE = 0x3f, // jak2 only
MAP_FILE_TYPE = 0x40, // jak2 only
CL_FILE_TYPE = 0x41, // jak 3 cloth animation
REFPLANT_FILE_TYPE = 0x301,
// added this, allows access directly to out/iso from fileio.
ISO_FILE_TYPE = 0x302

View File

@ -18,15 +18,11 @@ ee::sceSifClientData cd[6]; //! client data for each IOP Remove Procedure Call.
u32 sShowStallMsg; //! setting to show a "stalled on iop" message
u16 x[8]; //! stupid temporary for storing a message
u32 sMsgNum; //! Toggle for double buffered message sending.
RPC_Dgo_Cmd* sLastMsg; //! Last DGO command sent to IOP
RPC_Dgo_Cmd sMsg[2]; //! DGO message buffers
void kdgo_init_globals() {
memset(x, 0, sizeof(x));
memset(cd, 0, sizeof(cd));
sShowStallMsg = 1;
sLastMsg = nullptr;
memset(sMsg, 0, sizeof(sMsg));
}
/*!
@ -159,79 +155,6 @@ void StopIOP() {
printf("DMA shut down\n");
}
/*!
* Send message to IOP to start loading a new DGO file
* Uses a double-buffered message buffer
* @param name: the name of the DGO file
* @param buffer1 : one of the two file loading buffers
* @param buffer2 : the other of the two file loading buffers
* @param currentHeap : the current heap (for loading directly into the heap).
*
* DONE,
* MODIFIED : Added print statement to indicate when DGO load starts.
*/
void BeginLoadingDGO(const char* name, Ptr<u8> buffer1, Ptr<u8> buffer2, Ptr<u8> currentHeap) {
u8 msgID = sMsgNum;
RPC_Dgo_Cmd* mess = sMsg + sMsgNum;
sMsgNum = sMsgNum ^ 1; // toggle message buffer.
RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished
// put a dummy value here just to make sure the IOP overwrites it.
sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666
// inform IOP of buffers
sMsg[msgID].buffer1 = buffer1.offset;
sMsg[msgID].buffer2 = buffer2.offset;
// also give a heap pointer so it can load the last object file directly into the heap to save the
// precious time.
sMsg[msgID].buffer_heap_top = currentHeap.offset;
// file name
strcpy(sMsg[msgID].name, name);
lg::debug("[Begin Loading DGO RPC] {}, 0x{:x}, 0x{:x}, 0x{:x}", name, buffer1.offset,
buffer2.offset, currentHeap.offset);
// this RPC will return once we have loaded the first object file.
// but we call async, so we don't block here.
RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess,
sizeof(RPC_Dgo_Cmd));
sLastMsg = mess;
}
/*!
* Get the next object in the DGO. Will block until something is loaded.
* @param lastObjectFlag: will get set to 1 if this is the last object.
*
* DONE,
* MODIFIED : added exception if the sLastMessage isn't set (game just returns null as buffer)
*/
Ptr<u8> GetNextDGO(u32* lastObjectFlag) {
*lastObjectFlag = 1;
// Wait for RPC function to respond. This will happen once the first object file is loaded.
RpcSync(DGO_RPC_CHANNEL);
Ptr<u8> buffer(0);
if (sLastMsg) {
// if we got a good result, get pointer to object
if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) {
buffer.offset =
sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object.
}
// not the last one, so don't set the flag.
if (sLastMsg->result == DGO_RPC_RESULT_MORE) {
*lastObjectFlag = 0;
}
// no pending message.
sLastMsg = nullptr;
} else {
// I don't see how this case can happen unless there's a bug. The game does check for this and
// nothing in this case. (maybe from GOAL this can happen?)
printf("last message not set!\n");
}
return buffer;
}
/*!
* Load the TEST.DGO file.
* Presumably used for debugging DGO loads.
@ -242,6 +165,8 @@ Ptr<u8> GetNextDGO(u32* lastObjectFlag) {
* UNUSED
*/
void LoadDGOTest() {
ASSERT_NOT_REACHED(); // no longer supported.
/*
u32 lastObject = 0;
// backup show stall message and set it to false
@ -273,4 +198,5 @@ void LoadDGOTest() {
}
sShowStallMsg = lastShowStall;
*/
}

View File

@ -13,8 +13,6 @@ s32 RpcCall(s32 rpcChannel,
s32 sendSize,
void* recvBuff,
s32 recvSize);
void BeginLoadingDGO(const char* name, Ptr<u8> buffer1, Ptr<u8> buffer2, Ptr<u8> currentHeap);
Ptr<u8> GetNextDGO(u32* lastObjectFlag);
u64 RpcCall_wrapper(void* _args);
u32 RpcBusy(s32 channel);
void RpcSync(s32 channel);
@ -24,5 +22,3 @@ u32 InitRPC();
void StopIOP();
extern u32 sShowStallMsg;
extern RPC_Dgo_Cmd sMsg[2];
extern RPC_Dgo_Cmd* sLastMsg;

View File

@ -33,11 +33,11 @@ void klink_init_globals() {
* Initialize the link control.
* TODO: this hasn't been carefully checked for jak 2 differences.
*/
void link_control::begin(Ptr<uint8_t> object_file,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags) {
void link_control::jak1_jak2_begin(Ptr<uint8_t> object_file,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags) {
if (is_opengoal_object(object_file.c())) {
// save data from call to begin
m_object_data = object_file;

View File

@ -60,22 +60,41 @@ struct link_control {
bool m_opengoal;
bool m_busy; // only in jak2, but doesn't hurt to set it in jak 1.
void begin(Ptr<uint8_t> object_file,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags);
// jak 3 new stuff
bool m_on_global_heap = false;
LinkHeaderV5Core* m_link_hdr = nullptr;
bool m_moved_link_block = false;
void jak1_jak2_begin(Ptr<uint8_t> object_file,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags);
void jak3_begin(Ptr<uint8_t> object_file,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags);
// was originally "work"
uint32_t jak1_work();
uint32_t jak2_work();
uint32_t jak3_work();
uint32_t jak1_work_v3();
uint32_t jak1_work_v2();
uint32_t jak2_work_v3();
uint32_t jak2_work_v2();
uint32_t jak3_work_v5();
uint32_t jak3_work_opengoal();
void jak1_finish(bool jump_from_c_to_goal);
void jak2_finish(bool jump_from_c_to_goal);
void jak3_finish(bool jump_from_c_to_goal);
void reset() {
m_object_data.offset = 0;

View File

@ -22,7 +22,6 @@
#include "game/sce/libpad.h"
#include "game/sce/libscf.h"
#include "game/sce/sif_ee.h"
#include "game/system/vm/vm.h"
/*!
* Where does OVERLORD load its data from?
@ -973,10 +972,4 @@ void init_common_pc_port_functions(
// debugging tools
make_func_symbol_func("pc-filter-debug-string?", (void*)pc_filter_debug_string);
// init ps2 VM
if (VM::use) {
make_func_symbol_func("vm-ptr", (void*)VM::get_vm_ptr);
VM::vm_init();
}
}

View File

@ -12,11 +12,25 @@
// global and debug kernel heaps
Ptr<kheapinfo> kglobalheap;
Ptr<kheapinfo> kdebugheap;
// if we should count the number of strings and types allocated on the global heap.
bool kheaplogging = false;
enum MemItemsCategory {
STRING = 0,
TYPE = 1,
NUM_CATEGORIES = 2,
};
int MemItemsCount[NUM_CATEGORIES] = {0, 0};
int MemItemsSize[NUM_CATEGORIES] = {0, 0};
void kmalloc_init_globals_common() {
// _globalheap and _debugheap
kglobalheap.offset = GLOBAL_HEAP_INFO_ADDR;
kdebugheap.offset = DEBUG_HEAP_INFO_ADDR;
kheaplogging = false;
for (auto& x : MemItemsCount)
x = 0;
for (auto& x : MemItemsSize)
x = 0;
}
/*!
@ -75,6 +89,10 @@ Ptr<kheapinfo> kheapstatus(Ptr<kheapinfo> heap) {
Msg(6, "\t %d bytes before stack\n", GLOBAL_HEAP_END - heap->current.offset);
}
for (int i = 0; i < NUM_CATEGORIES; i++) {
printf(" %d: %d %d\n", i, MemItemsCount[i], MemItemsSize[i]);
}
// might not have returned heap in jak 1
return heap;
}
@ -171,6 +189,17 @@ Ptr<u8> kmalloc(Ptr<kheapinfo> heap, s32 size, u32 flags, char const* name) {
if (flags & KMALLOC_MEMSET)
std::memset(Ptr<u8>(memstart).c(), 0, (size_t)size);
// this logging was added in Jak 3, but we port it back to all games:
if ((heap == kglobalheap) && (kheaplogging != 0)) {
if (strcmp(name, "string") == 0) {
MemItemsCount[STRING]++;
MemItemsSize[STRING] += size;
} else if (strcmp(name, "type") == 0) {
MemItemsCount[TYPE]++;
MemItemsSize[TYPE] += size;
}
}
return Ptr<u8>(memstart);
}
}

View File

@ -17,6 +17,7 @@ struct kheapinfo {
// Kernel heaps
extern Ptr<kheapinfo> kglobalheap;
extern Ptr<kheapinfo> kdebugheap;
extern bool kheaplogging;
// flags for kmalloc/ksmalloc
constexpr u32 KMALLOC_TOP = 0x2000; //! Flag to allocate temporary memory from heap top

View File

@ -68,6 +68,9 @@ void init_output() {
case GameVersion::Jak2:
use_debug = MasterDebug && DebugSegment;
break;
case GameVersion::Jak3:
use_debug = MasterDebug || DebugSegment;
break;
default:
ASSERT(false);
}

View File

@ -28,4 +28,8 @@ constexpr u32 DEBUG_HEAP_START = 0x5000000;
namespace jak2 {
constexpr u32 DEBUG_HEAP_SIZE = 0x2f00000;
}
namespace jak3 {
constexpr u32 DEBUG_HEAP_SIZE = 0x2f00000;
}

View File

@ -12,6 +12,87 @@
namespace jak1 {
RPC_Dgo_Cmd* sLastMsg; //! Last DGO command sent to IOP
RPC_Dgo_Cmd sMsg[2]; //! DGO message buffers
void kdgo_init_globals() {
sLastMsg = nullptr;
memset(sMsg, 0, sizeof(sMsg));
}
/*!
* Send message to IOP to start loading a new DGO file
* Uses a double-buffered message buffer
* @param name: the name of the DGO file
* @param buffer1 : one of the two file loading buffers
* @param buffer2 : the other of the two file loading buffers
* @param currentHeap : the current heap (for loading directly into the heap).
*
* DONE,
* MODIFIED : Added print statement to indicate when DGO load starts.
*/
void BeginLoadingDGO(const char* name, Ptr<u8> buffer1, Ptr<u8> buffer2, Ptr<u8> currentHeap) {
u8 msgID = sMsgNum;
RPC_Dgo_Cmd* mess = sMsg + sMsgNum;
sMsgNum = sMsgNum ^ 1; // toggle message buffer.
RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished
// put a dummy value here just to make sure the IOP overwrites it.
sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666
// inform IOP of buffers
sMsg[msgID].buffer1 = buffer1.offset;
sMsg[msgID].buffer2 = buffer2.offset;
// also give a heap pointer so it can load the last object file directly into the heap to save the
// precious time.
sMsg[msgID].buffer_heap_top = currentHeap.offset;
// file name
strcpy(sMsg[msgID].name, name);
lg::debug("[Begin Loading DGO RPC] {}, 0x{:x}, 0x{:x}, 0x{:x}", name, buffer1.offset,
buffer2.offset, currentHeap.offset);
// this RPC will return once we have loaded the first object file.
// but we call async, so we don't block here.
RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess,
sizeof(RPC_Dgo_Cmd));
sLastMsg = mess;
}
/*!
* Get the next object in the DGO. Will block until something is loaded.
* @param lastObjectFlag: will get set to 1 if this is the last object.
*
* DONE,
* MODIFIED : added exception if the sLastMessage isn't set (game just returns null as buffer)
*/
Ptr<u8> GetNextDGO(u32* lastObjectFlag) {
*lastObjectFlag = 1;
// Wait for RPC function to respond. This will happen once the first object file is loaded.
RpcSync(DGO_RPC_CHANNEL);
Ptr<u8> buffer(0);
if (sLastMsg) {
// if we got a good result, get pointer to object
if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) {
buffer.offset =
sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object.
}
// not the last one, so don't set the flag.
if (sLastMsg->result == DGO_RPC_RESULT_MORE) {
*lastObjectFlag = 0;
}
// no pending message.
sLastMsg = nullptr;
} else {
// I don't see how this case can happen unless there's a bug. The game does check for this and
// nothing in this case. (maybe from GOAL this can happen?)
printf("last message not set!\n");
}
return buffer;
}
/*!
* Instruct the IOP to continue loading the next object.
* Only should be called once it is safe to overwrite the previous.

View File

@ -1,8 +1,10 @@
#pragma once
#include "common/common_types.h"
#include "game/common/dgo_rpc_types.h"
#include "game/kernel/common/Ptr.h"
#include "game/kernel/common/kmalloc.h"
namespace jak1 {
void load_and_link_dgo_from_c(const char* name,
Ptr<kheapinfo> heap,
@ -10,4 +12,7 @@ void load_and_link_dgo_from_c(const char* name,
s32 bufferSize,
bool jump_from_c_to_goal);
void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size);
void kdgo_init_globals();
extern RPC_Dgo_Cmd sMsg[2];
extern RPC_Dgo_Cmd* sLastMsg;
} // namespace jak1

View File

@ -582,7 +582,7 @@ Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
uint32_t flags,
bool jump_from_c_to_goal) {
link_control lc;
lc.begin(data, name, size, heap, flags);
lc.jak1_jak2_begin(data, name, size, heap, flags);
uint32_t done;
do {
done = lc.jak1_work();
@ -608,8 +608,8 @@ u64 link_and_exec_wrapper(u64* args) {
*/
uint64_t link_begin(u64* args) {
// object data, name size, heap flags
saved_link_control.begin(Ptr<u8>(args[0]), Ptr<char>(args[1]).c(), args[2],
Ptr<kheapinfo>(args[3]), args[4]);
saved_link_control.jak1_jak2_begin(Ptr<u8>(args[0]), Ptr<char>(args[1]).c(), args[2],
Ptr<kheapinfo>(args[3]), args[4]);
auto work_result = saved_link_control.jak1_work();
// if we managed to finish in one shot, take care of calling finish
if (work_result) {

View File

@ -43,7 +43,6 @@
#include "game/sce/libgraph.h"
#include "game/sce/sif_ee.h"
#include "game/sce/stubs.h"
#include "game/system/vm/vm.h"
using namespace ee;
@ -378,11 +377,6 @@ int ShutdownMachine() {
ShutdownSound();
ShutdownGoalProto();
// OpenGOAL only - kill ps2 VM
if (VM::use) {
VM::vm_kill();
}
Msg(6, "kernel: machine shutdown\n");
return 0;
}

View File

@ -161,7 +161,7 @@ char* MakeFileName(int type, const char* name, int new_string) {
// game CGO file (unused, all DGO/CGOs loaded through IOP)
sprintf(buf, "game/dgo%d/%s.cgo", DGO_FILE_VERSION, name);
} else if (type == CNT_FILE_TYPE) {
// game cnt file (continue point?)
// game cnt file. leftover from jak 1.
sprintf(buf, "%sfinal/res%d/game-cnt.go", prefix, 1);
} else if (type == RES_FILE_TYPE) {
// RES go file?

View File

@ -15,6 +15,87 @@
namespace jak2 {
RPC_Dgo_Cmd* sLastMsg; //! Last DGO command sent to IOP
RPC_Dgo_Cmd sMsg[2]; //! DGO message buffers
void kdgo_init_globals() {
sLastMsg = nullptr;
memset(sMsg, 0, sizeof(sMsg));
}
/*!
* Send message to IOP to start loading a new DGO file
* Uses a double-buffered message buffer
* @param name: the name of the DGO file
* @param buffer1 : one of the two file loading buffers
* @param buffer2 : the other of the two file loading buffers
* @param currentHeap : the current heap (for loading directly into the heap).
*
* DONE,
* MODIFIED : Added print statement to indicate when DGO load starts.
*/
void BeginLoadingDGO(const char* name, Ptr<u8> buffer1, Ptr<u8> buffer2, Ptr<u8> currentHeap) {
u8 msgID = sMsgNum;
RPC_Dgo_Cmd* mess = sMsg + sMsgNum;
sMsgNum = sMsgNum ^ 1; // toggle message buffer.
RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished
// put a dummy value here just to make sure the IOP overwrites it.
sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666
// inform IOP of buffers
sMsg[msgID].buffer1 = buffer1.offset;
sMsg[msgID].buffer2 = buffer2.offset;
// also give a heap pointer so it can load the last object file directly into the heap to save the
// precious time.
sMsg[msgID].buffer_heap_top = currentHeap.offset;
// file name
strcpy(sMsg[msgID].name, name);
lg::debug("[Begin Loading DGO RPC] {}, 0x{:x}, 0x{:x}, 0x{:x}", name, buffer1.offset,
buffer2.offset, currentHeap.offset);
// this RPC will return once we have loaded the first object file.
// but we call async, so we don't block here.
RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess,
sizeof(RPC_Dgo_Cmd));
sLastMsg = mess;
}
/*!
* Get the next object in the DGO. Will block until something is loaded.
* @param lastObjectFlag: will get set to 1 if this is the last object.
*
* DONE,
* MODIFIED : added exception if the sLastMessage isn't set (game just returns null as buffer)
*/
Ptr<u8> GetNextDGO(u32* lastObjectFlag) {
*lastObjectFlag = 1;
// Wait for RPC function to respond. This will happen once the first object file is loaded.
RpcSync(DGO_RPC_CHANNEL);
Ptr<u8> buffer(0);
if (sLastMsg) {
// if we got a good result, get pointer to object
if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) {
buffer.offset =
sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object.
}
// not the last one, so don't set the flag.
if (sLastMsg->result == DGO_RPC_RESULT_MORE) {
*lastObjectFlag = 0;
}
// no pending message.
sLastMsg = nullptr;
} else {
// I don't see how this case can happen unless there's a bug. The game does check for this and
// nothing in this case. (maybe from GOAL this can happen?)
printf("last message not set!\n");
}
return buffer;
}
/*!
* Instruct the IOP to continue loading the next object.
* Only should be called once it is safe to overwrite the previous.

View File

@ -1,8 +1,10 @@
#pragma once
#include "common/common_types.h"
#include "game/common/dgo_rpc_types.h"
#include "game/kernel/common/Ptr.h"
#include "game/kernel/common/kmalloc.h"
namespace jak2 {
void load_and_link_dgo_from_c(const char* name,
Ptr<kheapinfo> heap,
@ -14,4 +16,8 @@ void load_and_link_dgo_from_c_fast(const char* name,
Ptr<kheapinfo> heap,
u32 linkFlag,
s32 bufferSize);
void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size);
void kdgo_init_globals();
extern RPC_Dgo_Cmd sMsg[2];
extern RPC_Dgo_Cmd* sLastMsg;
} // namespace jak2

View File

@ -609,7 +609,7 @@ Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
// probably won't end well...
}
link_control lc;
lc.begin(data, name, size, heap, flags);
lc.jak1_jak2_begin(data, name, size, heap, flags);
uint32_t done;
do {
done = lc.jak2_work();
@ -635,8 +635,8 @@ u64 link_and_exec_wrapper(u64* args) {
*/
uint64_t link_begin(u64* args) {
// object data, name size, heap flags
saved_link_control.begin(Ptr<u8>(args[0]), Ptr<char>(args[1]).c(), args[2],
Ptr<kheapinfo>(args[3]), args[4]);
saved_link_control.jak1_jak2_begin(Ptr<u8>(args[0]), Ptr<char>(args[1]).c(), args[2],
Ptr<kheapinfo>(args[3]), args[4]);
auto work_result = saved_link_control.jak2_work();
// if we managed to finish in one shot, take care of calling finish
if (work_result) {

View File

@ -42,7 +42,6 @@
#include "game/sce/libgraph.h"
#include "game/sce/sif_ee.h"
#include "game/sce/stubs.h"
#include "game/system/vm/vm.h"
using namespace ee;
@ -434,10 +433,6 @@ int ShutdownMachine() {
ShutdownGoalProto();
// OpenGOAL only - kill ps2 VM
if (VM::use) {
VM::vm_kill();
}
return 0;
}
@ -514,10 +509,10 @@ u64 kopen(u64 fs, u64 name, u64 mode) {
char buffer[128];
// sprintf(buffer, "host:%s", Ptr<String>(name)->data());
sprintf(buffer, "%s", Ptr<String>(name)->data());
if (!strcmp(Ptr<Symbol4<u8>>(mode)->name_cstr(), "read")) {
if (!strcmp(symbol_name_cstr(*Ptr<Symbol4<u8>>(mode)), "read")) {
// 0x1
file_stream->file = sceOpen(buffer, SCE_RDONLY);
} else if (!strcmp(Ptr<Symbol4<u8>>(mode)->name_cstr(), "append")) {
} else if (!strcmp(symbol_name_cstr(*Ptr<Symbol4<u8>>(mode)), "append")) {
// new in jak 2!
// 0x202
file_stream->file = sceOpen(buffer, SCE_CREAT | SCE_WRONLY);

View File

@ -49,10 +49,15 @@ void kscheme_init_globals() {
u64 new_illegal(u32 allocation, u32 type) {
(void)allocation;
MsgErr("dkernel: illegal attempt to call new method of static object type %s\n",
Ptr<Type>(type)->symbol->name_cstr());
symbol_name_cstr(*Ptr<Type>(type)->symbol));
return s7.offset;
}
u32 u32_in_fixed_sym(u32 offset) {
return Ptr<Symbol4<u32>>(s7.offset + offset)->value();
}
namespace {
template <typename T>
Ptr<Ptr<String>> sym_to_string_ptr(Ptr<Symbol4<T>> in) {
return Ptr<Ptr<String>>(in.offset + SYM_TO_STRING_OFFSET);
@ -68,13 +73,10 @@ Ptr<u32> sym_to_hash(Ptr<Symbol4<T>> in) {
return Ptr<u32>(in.offset + SYM_TO_HASH_OFFSET);
}
u32 u32_in_fixed_sym(u32 offset) {
return Ptr<Symbol4<u32>>(s7.offset + offset)->value();
}
void fixed_sym_set(u32 offset, u32 value) {
Ptr<Symbol4<u32>>(s7.offset + offset)->value() = value;
}
} // namespace
u64 alloc_from_heap(u32 heap_symbol, u32 type, s32 size, u32 pp) {
using namespace jak2_symbols;
@ -1409,7 +1411,6 @@ u64 inspect_type(u32 obj) {
} else {
auto typ = Ptr<Type>(obj);
auto sym = typ->symbol;
;
cprintf("[%8x] type\n\tname: %s\n\tparent: ", obj, sym_to_string(sym)->data());
print_object(typ->parent.offset);
@ -1462,11 +1463,11 @@ u64 inspect_link_block(u32 ob) {
return ob;
}
namespace {
Ptr<Symbol4<Ptr<Type>>> get_fixed_type_symbol(u32 offset) {
return (s7 + offset).cast<Symbol4<Ptr<Type>>>();
}
namespace {
u64 pack_type_flag(u64 methods, u64 heap_base, u64 size) {
return (methods << 32) + (heap_base << 16) + (size);
}
@ -1799,7 +1800,6 @@ u64 loadc(const char* /*file_name*/, kheapinfo* /*heap*/, u32 /*flags*/) {
return 0;
}
// NOTE: copied from jak 1
u64 loado(u32 file_name_in, u32 heap_in) {
char loadName[272];
Ptr<String> file_name(file_name_in);
@ -1823,7 +1823,6 @@ u64 unload(u32 name) {
return 0;
}
// NOTE: copied from jak 1
s64 load_and_link(const char* filename, char* decode_name, kheapinfo* heap, u32 flags) {
(void)filename;
s32 sz;

View File

@ -63,4 +63,10 @@ u64 call_goal_function_by_name(const char* name);
u64 alloc_heap_memory(u32 heap, u32 size);
u64 alloc_heap_object(u32 heap, u32 type, u32 size, u32 pp);
u32 u32_in_fixed_sym(u32 offset);
template <typename T>
const char* symbol_name_cstr(const Symbol4<T>& sym) {
return (const char*)(g_ee_main_mem + 4 +
*reinterpret_cast<const u32*>(&sym.foo + jak2::SYM_TO_STRING_OFFSET));
}
} // namespace jak2

View File

@ -9,7 +9,6 @@ namespace jak2 {
* Set up some functions which are somewhat related to sound.
*/
void InitSoundScheme() {
make_function_symbol_from_c("rpc-call", (void*)RpcCall_wrapper);
make_function_symbol_from_c("rpc-busy?", (void*)RpcBusy);
make_function_symbol_from_c("test-load-dgo-c", (void*)LoadDGOTest);
make_stack_arg_function_symbol_from_c("rpc-call", (void*)RpcCall_wrapper);

254
game/kernel/jak3/fileio.cpp Normal file
View File

@ -0,0 +1,254 @@
#include "fileio.h"
#include <cstring>
#include "game/kernel/common/fileio.h"
namespace jak3 {
// This file naming system was used only in development, as it loads files from the development PC
// connected to the PS2 dev-kit.
// My theory is that the developers would use this to debug their level/art tools. They could use
// these file names to quickly load in new files and see if they worked correctly with the renderer,
// without needing to create/load entire new DGO files.
// They've been adding to this file over all 3 games, so I believe it is more than just a leftover
// from early jak 1.
/*!
* Convert a file-name like $CODE/thing to the appropriate file path on the development computer.
*/
char* DecodeFileName(const char* name) {
char* result;
if (name[0] == '$') {
if (!strncmp(name, "$TEXTURE/", 9)) {
result = MakeFileName(TX_PAGE_FILE_TYPE, name + 9, 0);
} else if (!strncmp(name, "$ART_GROUP/", 0xb)) {
result = MakeFileName(ART_GROUP_FILE_TYPE, name + 0xb, 0);
} else if (!strncmp(name, "$LEVEL/", 7)) {
int len = (int)strlen(name);
if (name[len - 4] == '.') {
result = MakeFileName(LEVEL_WITH_EXTENSION_FILE_TYPE, name + 7, 0);
} else {
// level files can omit a file type if desired
result = MakeFileName(LEVEL_FILE_TYPE, name + 7, 0);
}
} else if (!strncmp(name, "$FINAL/", 6)) { // in jak2, this is FINAL instead of DATA
result = MakeFileName(DATA_FILE_TYPE, name + 6, 0);
} else if (!strncmp(name, "$CODE/", 6)) {
result = MakeFileName(CODE_FILE_TYPE, name + 6, 0);
} else if (!strncmp(name, "$RES/", 5)) {
result = MakeFileName(RES_FILE_TYPE, name + 5, 0);
} else if (!strncmp(name, "$MISC/", 6)) {
result = MakeFileName(MISC_FILE_TYPE, name + 6, 0);
} else if (!strncmp(name, "$MAP/", 5)) {
result = MakeFileName(MAP_FILE_TYPE, name + 5, 0);
} else if (!strncmp(name, "$ISO/", 5)) {
result = MakeFileName(ISO_FILE_TYPE, name + 5, 0);
} else {
printf("[ERROR] DecodeFileName: UNKNOWN FILE NAME %s\n", name);
result = nullptr;
}
} else {
// no prefix. Treat this as a code file
return MakeFileName(CODE_FILE_TYPE, name, 0);
}
return result;
}
/*!
* Create a file name that looks in the appropriate folder in ND's development environment.
* This is a bit of dumping ground for all possible files they'd load.
*/
char* MakeFileName(int type, const char* name, int new_string) {
using namespace versions::jak3;
// start with network filesystem
// kstrcpy(buffer_633, "host:");
kstrcpy(buffer_633, "");
char* buf = strend(buffer_633);
// prefix to build directory
char prefix[64];
kstrcpy(prefix, FOLDER_PREFIX);
switch (type) {
// Unused files that could be used to exchange data between the dev PS2 and the GOAL compiler.
case LISTENER_TO_KERNEL_FILE_TYPE:
kstrcpy(buf, "kernel/LISTENERTOKERNEL");
break;
case KERNEL_TO_LISTENER_FILE_TYPE:
kstrcpy(buf, "kernel/KERNELTOLISTENER");
break;
// A GOAL object file containing code built from the GOAL compiler.
case CODE_FILE_TYPE:
sprintf(buf, "game/obj/%s.o", name);
break;
// Unused, opening the gamepad as a file.
case GAMEPAD_FILE_TYPE:
sprintf(buffer_633, "pad:0");
break;
// Locks for the unused kernel/listener interface. (funny that they added this after...)
case LISTENER_TO_KERNEL_LOCK_FILE_TYPE:
kstrcpy(buf, "kernel/LISTENERTOKERNEL_LOCK");
break;
case KERNEL_TO_LISTENER_LOCK_FILE_TYPE:
kstrcpy(buf, "kernel/KERNELTOLISTENER_LOCK");
break;
// Host0 IOP modules (stored on the linux SBC inside the dev ps2 itself!)
case IOP_MODULE_FILE_TYPE: // 8
sprintf(buffer_633, "host0:/usr/local/sce/iop/modules/%s.irx", name);
break;
// plain GOAL data object file
case DATA_FILE_TYPE: // 0x20
sprintf(buf, "%sfinal/%s.go", prefix, name);
break;
// texture page
case TX_PAGE_FILE_TYPE: // 0x21
sprintf(buf, "%sdata/texture-page%d/%s.go", prefix, TX_PAGE_VERSION, name);
break;
// joint animation
case JA_FILE_TYPE: // 0x22
sprintf(buf, "%sdb/artdata%d/%s-ja.go", prefix, ART_FILE_VERSION, name);
break;
// joint geo (skeleton)
case JG_FILE_TYPE: // 0x23
sprintf(buf, "%sdb/artdata%d/%s-jg.go", prefix, ART_FILE_VERSION, name);
break;
// mesh animation (unused)
case MA_FILE_TYPE: // 0x24
sprintf(buf, "%sdb/artdata%d/%s-ma.go", prefix, ART_FILE_VERSION, name);
break;
// likely art-mesh-geo, and unused. Maybe was used before MERC?
case MG_FILE_TYPE: // 0x25
sprintf(buf, "%sdb/artdata%d/%s-mg.go", prefix, ART_FILE_VERSION, name);
break;
// text group perhaps?
case TG_FILE_TYPE:
sprintf(buf, "%sdb/%s-tg.go", prefix, name);
break;
// level file
case LEVEL_FILE_TYPE: // 0x27
sprintf(buf, "%sdb/level%d/%s-bt.go", prefix, LEVEL_FILE_VERSION, name);
break;
// Everybody's favorite "art group" file. Container of different art.
case ART_GROUP_FILE_TYPE: // 0x30
sprintf(buf, "%sfinal/art-group%d/%s-ag.go", prefix, ART_FILE_VERSION, name);
break;
// GOAL data object file containing visibility data. This likely contained the visibility data
// that's included in the BSP file.
case VS_FILE_TYPE: // 0x31
sprintf(buf, "%sfinal/level%d/%s-vs.go", prefix, LEVEL_FILE_VERSION, name);
break;
// GOAL data object file containing text. Likely the same format as the .TXT in final ISOs.
case TX_FILE_TYPE: // 0x32
sprintf(buf, "%sfinal/res%d/%s-tx.go", prefix, 1, name);
break;
// Binary format visibility. Likely the format of Jak 1's .VIS files.
case VS_BIN_FILE_TYPE: // 0x33
sprintf(buf, "%sfinal/level%d/%s-vs.bin", prefix, LEVEL_FILE_VERSION, name);
break;
// DGO description files. These contain a list of files inside each DGO.
case DGO_TXT_FILE_TYPE: // 0x34
sprintf(buf, "%sfinal/dgo%d/%s.txt", prefix, DGO_FILE_VERSION, name);
break;
// Level file! but you have to provide the extension.
case LEVEL_WITH_EXTENSION_FILE_TYPE: // 0x35
sprintf(buf, "%sfinal/level%d/%s", prefix, LEVEL_FILE_VERSION, name);
break;
// DGO and CGO files. These can exist in either final/ or game/
case DATA_DGO_FILE_TYPE: // 0x36
sprintf(buf, "%sfinal/dgo%d/%s.dgo", prefix, DGO_FILE_VERSION, name);
break;
case GAME_DGO_FILE_TYPE: // 0x37
sprintf(buf, "game/dgo%d/%s.dgo", DGO_FILE_VERSION, name);
break;
case DATA_CGO_FILE_TYPE: // 0x38
sprintf(buf, "%sfinal/dgo%d/%s.cgo", prefix, DGO_FILE_VERSION, name);
break;
case GAME_CGO_FILE_TYPE: // 0x39
sprintf(buf, "game/dgo%d/%s.cgo", DGO_FILE_VERSION, name);
break;
// Jak 1 had a weird game-cnt.gco file containing the total number of orbs/cells.
case CNT_FILE_TYPE: // 0x3a
sprintf(buf, "%sfinal/res%d/game-cnt.go", prefix, 1);
break;
// Any res file with .go extension.
case RES_FILE_TYPE: // 0x3b
sprintf(buf, "%sfinal/res%d/%s.go", prefix, 1, name);
break;
// sound bank (sound effects)
case SND_BNK_FILE_TYPE: // 0x3c
sprintf(buf, "%sfinal/sound%d/%s.bnk", prefix, 1, name); // v1
break;
// music file
case MUSIC_BNK_FILE_TYPE: // 0x3d
sprintf(buf, "%sfinal/music%d/%s.bnk", prefix, 1, name); // v1
break;
// vag file, but it probably doesn't work due to the file extension.
case VAG_FILE_TYPE: // 0x3e
// interestingly, jak 2 used vagwad2, but jak 3 doesn't. But the memory bug is still there.
sprintf(buf, "%sfinal/vagwad/%s.%s", prefix, name, "<INVALID>"); // v1, memory bug here
break;
// whatever you want.
case MISC_FILE_TYPE: // 0x3f
sprintf(buf, "%sfinal/misc/%s", prefix, name);
break;
// possible minimap/bigmap data
case MAP_FILE_TYPE:
sprintf(buf, "%sfinal/map%d/%s", prefix, 1, name); // v1
break;
// jak 3 cloth animation file.
case CL_FILE_TYPE: // 0x41
sprintf(buf, "%sdb/artdata%d/%s-cl.go", prefix, ART_FILE_VERSION, name);
break;
// no idea
case REFPLANT_FILE_TYPE: // 0x301
sprintf(buf, "%sdb/refplant/%s", prefix, name);
break;
default:
printf("UNKNOWN FILE TYPE %d\n", type);
}
char* result;
if (!new_string) {
// return pointer to static filename buffer
result = buffer_633;
} else {
// or create a new string on the global heap.
int l = (int)strlen(buffer_633);
result = (char*)kmalloc(kglobalheap, l + 1, 0, "filename").c();
kstrcpy(result, buffer_633);
}
return result;
}
} // namespace jak3

View File

@ -0,0 +1,6 @@
#pragma once
namespace jak3 {
char* MakeFileName(int type, const char* name, int new_string);
char* DecodeFileName(const char* name);
} // namespace jak3

163
game/kernel/jak3/kboot.cpp Normal file
View File

@ -0,0 +1,163 @@
#include "kboot.h"
#include <cstring>
#include "common/repl/util.h"
#include "common/util/Timer.h"
#include "game/common/game_common_types.h"
#include "game/kernel/common/Ptr.h"
#include "game/kernel/common/Symbol4.h"
#include "game/kernel/common/kboot.h"
#include "game/kernel/common/klisten.h"
#include "game/kernel/common/kprint.h"
#include "game/kernel/common/kscheme.h"
#include "game/kernel/common/ksocket.h"
#include "game/kernel/jak3/klisten.h"
#include "game/kernel/jak3/kmachine.h"
#include "game/sce/libscf.h"
// KernelDispatch__3ndiPFv_x
// KernelCheckAndDispatch__3ndiv
// KernelShutdown__3ndii
// main
namespace jak3 {
void KernelCheckAndDispatch();
char DebugBootUser[64];
char DebugBootArtGroup[64];
void kboot_init_globals() {
memset(DebugBootUser, 0, sizeof(DebugBootUser));
memset(DebugBootArtGroup, 0, sizeof(DebugBootArtGroup));
// strcpy(DebugBootUser, "unknown");
// CHANGED : let's just try to find the username automatically by default!
// the default is still "unknown"
auto username = REPL::find_repl_username();
strcpy(DebugBootUser, username.c_str());
}
s32 goal_main(int argc, const char* const* argv) {
// only in PC port
InitParms(argc, argv);
masterConfig.aspect = ee::sceScfGetAspect();
auto sony_language = ee::sceScfGetLanguage();
masterConfig.inactive_timeout = 0;
masterConfig.volume = 100;
masterConfig.timeout = 0;
switch (sony_language) {
case SCE_JAPANESE_LANGUAGE:
masterConfig.language = 6; // ???
break;
case SCE_FRENCH_LANGUAGE: // 2 -> 1
masterConfig.language = (u16)Language::French;
break;
case SCE_SPANISH_LANGUAGE: // 3 -> 3
masterConfig.language = (u16)Language::Spanish;
break;
case SCE_GERMAN_LANGUAGE: // 4 -> 2
masterConfig.language = (u16)Language::German;
break;
case SCE_ITALIAN_LANGUAGE: // 5 -> 4
masterConfig.language = (u16)Language::Italian;
break;
case SCE_PORTUGUESE_LANGUAGE:
masterConfig.language = (u16)Language::Portuguese;
break;
default:
masterConfig.language = (u16)Language::English;
break;
}
// Set up aspect ratio override in demo
if (!strcmp(DebugBootMessage, "demo") || !strcmp(DebugBootMessage, "demo-shared")) {
masterConfig.aspect = SCE_ASPECT_FULL;
}
// removed in PC port
// DiskBoot = 1;
// MasterDebug = 0;
// DebugSegment = 0;
// Launch GOAL!
if (InitMachine() >= 0) { // init kernel
KernelCheckAndDispatch(); // run kernel
ShutdownMachine(); // kernel died, we should too.
// movie playback stuff removed.
} else {
fprintf(stderr, "InitMachine failed\n");
exit(1);
}
return 0;
}
void KernelDispatch(u32 dispatcher_func) {
// place our stack at the end of EE memory
u64 goal_stack = u64(g_ee_main_mem) + EE_MAIN_MEM_SIZE - 8;
// try to get a message from the listener, and process it if needed
Ptr<char> new_message = WaitForMessageAndAck();
if (new_message.offset) {
ProcessListenerMessage(new_message);
}
// remember the old listener
auto old_listener_function = ListenerFunction->value();
// run the kernel!
Timer dispatch_timer;
if (MasterUseKernel) {
call_goal_on_stack(Ptr<Function>(dispatcher_func), goal_stack, s7.offset, g_ee_main_mem);
} else {
// added, just calls the listener function
if (ListenerFunction->value() != s7.offset) {
auto result = call_goal_on_stack(Ptr<Function>(ListenerFunction->value()), goal_stack,
s7.offset, g_ee_main_mem);
#ifdef __linux__
cprintf("%ld\n", result);
#else
cprintf("%lld\n", result);
#endif
ListenerFunction->value() = s7.offset;
}
}
float time_ms = dispatch_timer.getMs();
if (time_ms > 50) {
printf("Kernel dispatch time: %.3f ms\n", time_ms);
}
// flush stdout
ClearPending();
// now run the extra "kernel function"
auto bonus_function = KernelFunction->value();
if (bonus_function != s7.offset) {
// clear the pending kernel function
KernelFunction->value() = s7.offset;
// and run
call_goal_on_stack(Ptr<Function>(bonus_function), goal_stack, s7.offset, g_ee_main_mem);
}
// send ack to indicate that the listener function has been processed and the result printed
if (MasterDebug && ListenerFunction->value() != old_listener_function) {
SendAck();
}
// prevent crazy spinning if we're not vsyncing (added)
if (time_ms < 4) {
std::this_thread::sleep_for(std::chrono::microseconds(1000));
}
}
void KernelShutdown(u32 reason) {
MasterExit = (RuntimeExitStatus)reason;
}
void KernelCheckAndDispatch() {
while (MasterExit == RuntimeExitStatus::RUNNING) {
KernelDispatch(kernel_dispatcher->value());
}
}
} // namespace jak3

11
game/kernel/jak3/kboot.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include "common/common_types.h"
namespace jak3 {
extern char DebugBootUser[64];
extern char DebugBootArtGroup[64];
void kboot_init_globals();
void KernelShutdown(u32 reason);
s32 goal_main(int argc, const char* const* argv);
} // namespace jak3

210
game/kernel/jak3/kdgo.cpp Normal file
View File

@ -0,0 +1,210 @@
#include "kdgo.h"
#include "common/global_profiler/GlobalProfiler.h"
#include "common/log/log.h"
#include "common/util/Timer.h"
#include "game/kernel/common/fileio.h"
#include "game/kernel/common/kdgo.h"
#include "game/kernel/common/kmalloc.h"
#include "game/kernel/jak3/klink.h"
namespace jak3 {
jak3::RPC_Dgo_Cmd* sLastMsg; //! Last DGO command sent to IOP
jak3::RPC_Dgo_Cmd sMsg[2]; //! DGO message buffers
uint16_t cgo_id = 10;
void kdgo_init_globals() {
sLastMsg = nullptr;
memset(sMsg, 0, sizeof(sMsg));
cgo_id = 10;
}
/*!
* Send message to IOP to start loading a new DGO file
* Uses a double-buffered message buffer
* @param name: the name of the DGO file
* @param buffer1 : one of the two file loading buffers
* @param buffer2 : the other of the two file loading buffers
* @param currentHeap : the current heap (for loading directly into the heap).
*
* DONE,
* MODIFIED : Added print statement to indicate when DGO load starts.
*/
void BeginLoadingDGO(const char* name, Ptr<u8> buffer1, Ptr<u8> buffer2, Ptr<u8> currentHeap) {
u8 msgID = sMsgNum;
RPC_Dgo_Cmd* mess = sMsg + sMsgNum;
sMsgNum = sMsgNum ^ 1; // toggle message buffer.
RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished
// put a dummy value here just to make sure the IOP overwrites it.
sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666
// inform IOP of buffers
sMsg[msgID].buffer1 = buffer1.offset;
sMsg[msgID].buffer2 = buffer2.offset;
// also give a heap pointer so it can load the last object file directly into the heap to save the
// precious time.
sMsg[msgID].buffer_heap_top = currentHeap.offset;
// new for Jak 3: a unique ID.
sMsg[msgID].cgo_id = cgo_id;
cgo_id++;
// file name
strcpy(sMsg[msgID].name, name);
lg::debug("[Begin Loading DGO RPC] {}, 0x{:x}, 0x{:x}, 0x{:x}", name, buffer1.offset,
buffer2.offset, currentHeap.offset);
// this RPC will return once we have loaded the first object file.
// but we call async, so we don't block here.
RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess,
sizeof(RPC_Dgo_Cmd));
sLastMsg = mess;
}
/*!
* Get the next object in the DGO. Will block until something is loaded.
* @param lastObjectFlag: will get set to 1 if this is the last object.
*
* DONE,
* MODIFIED : added exception if the sLastMessage isn't set (game just returns null as buffer)
*/
Ptr<u8> GetNextDGO(u32* lastObjectFlag) {
*lastObjectFlag = 1;
// Wait for RPC function to respond. This will happen once the first object file is loaded.
RpcSync(DGO_RPC_CHANNEL);
Ptr<u8> buffer(0);
if (sLastMsg) {
// if we got a good result, get pointer to object
if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) {
buffer.offset =
sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object.
}
// not the last one, so don't set the flag.
if (sLastMsg->result == DGO_RPC_RESULT_MORE) {
*lastObjectFlag = 0;
}
// no pending message.
sLastMsg = nullptr;
} else {
// I don't see how this case can happen unless there's a bug. The game does check for this and
// nothing in this case. (maybe from GOAL this can happen?)
printf("last message not set!\n");
}
return buffer;
}
/*!
* Instruct the IOP to continue loading the next object.
* Only should be called once it is safe to overwrite the previous.
* @param heapPtr : pointer to heap so the IOP could try to load directly into a heap if it wants.
* This should be updated after each object file load to make sure the IOP knows the exact location
* of the end of the GOAL heap data.
*
* Unlike jak 1, we update buffer1 and buffer2 here for borrow heap loads.
*/
void ContinueLoadingDGO(Ptr<u8> b1, Ptr<u8> b2, Ptr<u8> heapPtr) {
u32 msgID = sMsgNum;
jak3::RPC_Dgo_Cmd* sendBuff = sMsg + sMsgNum;
sMsgNum = sMsgNum ^ 1;
sendBuff->result = DGO_RPC_RESULT_INIT;
sMsg[msgID].buffer1 = b1.offset;
sMsg[msgID].buffer2 = b2.offset;
sMsg[msgID].buffer_heap_top = heapPtr.offset;
// the IOP will wait for this RpcCall to continue the DGO state machine.
RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_NEXT_FNO, true, sendBuff, sizeof(jak3::RPC_Dgo_Cmd),
sendBuff, sizeof(jak3::RPC_Dgo_Cmd));
// this async RPC call will complete when the next object is fully loaded.
sLastMsg = sendBuff;
}
/*!
* Load and link a DGO file.
* This does not use the mutli-threaded linker and will block until the entire file is done.
*/
void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size) {
auto name = Ptr<char>(name_gstr + 4).c();
auto heap = Ptr<kheapinfo>(heap_info);
load_and_link_dgo_from_c(name, heap, flag, buffer_size, false);
}
/*!
* Load and link a DGO file.
* This does not use the mutli-threaded linker and will block until the entire file is done.e
*/
void load_and_link_dgo_from_c(const char* name,
Ptr<kheapinfo> heap,
u32 linkFlag,
s32 bufferSize,
bool jump_from_c_to_goal) {
Timer timer;
lg::debug("[Load and Link DGO From C] {}", name);
u32 oldShowStall = sShowStallMsg;
// remember where the heap top point is so we can clear temporary allocations
auto oldHeapTop = heap->top;
// allocate temporary buffers from top of the given heap
// align 64 for IOP DMA
// note: both buffers named dgo-buffer-2
auto buffer2 = kmalloc(heap, bufferSize, KMALLOC_TOP | KMALLOC_ALIGN_64, "dgo-buffer-2");
auto buffer1 = kmalloc(heap, bufferSize, KMALLOC_TOP | KMALLOC_ALIGN_64, "dgo-buffer-2");
// build filename. If no extension is given, default to CGO.
char fileName[16];
kstrcpyup(fileName, name);
if (fileName[strlen(fileName) - 4] != '.') {
strcat(fileName, ".CGO");
}
// no stall messages, as this is a blocking load and when spending 100% CPU time on linking,
// the linker can beat the DVD drive.
sShowStallMsg = 0;
// start load on IOP.
BeginLoadingDGO(
fileName, buffer1, buffer2,
Ptr<u8>((heap->current + 0x3f).offset & 0xffffffc0)); // 64-byte aligned for IOP DMA
u32 lastObjectLoaded = 0;
while (!lastObjectLoaded) {
// check to see if next object is loaded (I believe it always is?)
auto dgoObj = GetNextDGO(&lastObjectLoaded);
if (!dgoObj.offset) {
continue;
}
// if we're on the last object, it is loaded at cheap->current. So we can safely reset the two
// dgo-buffer allocations. We do this _before_ we link! This way, the last file loaded has more
// heap available, which is important when we need to use the entire memory.
if (lastObjectLoaded) {
heap->top = oldHeapTop;
}
// determine the size and name of the object we got
auto obj = dgoObj + 0x40; // seek past dgo object header
u32 objSize = *(dgoObj.cast<u32>()); // size from object's link block
char objName[64];
strcpy(objName, (dgoObj + 4).cast<char>().c()); // name from dgo object header
lg::debug("[link and exec] {:18s} {} {:6d} heap-use {:8d} {:8d}: 0x{:x}", objName,
lastObjectLoaded, objSize, kheapused(kglobalheap),
kdebugheap.offset ? kheapused(kdebugheap) : 0, kglobalheap->current.offset);
{
auto p = scoped_prof(fmt::format("link-{}", objName).c_str());
link_and_exec(obj, objName, objSize, heap, linkFlag, jump_from_c_to_goal); // link now!
}
// inform IOP we are done
if (!lastObjectLoaded) {
ContinueLoadingDGO(buffer1, buffer2, Ptr<u8>((heap->current + 0x3f).offset & 0xffffffc0));
}
}
lg::info("load_and_link_dgo_from_c took {:.3f} s\n", timer.getSeconds());
sShowStallMsg = oldShowStall;
}
} // namespace jak3

20
game/kernel/jak3/kdgo.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include "common/common_types.h"
#include "game/kernel/common/Ptr.h"
#include "game/kernel/common/kmalloc.h"
namespace jak3 {
void load_and_link_dgo_from_c(const char* name,
Ptr<kheapinfo> heap,
u32 linkFlag,
s32 bufferSize,
bool jump_from_c_to_goal);
void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size);
void load_and_link_dgo_from_c_fast(const char* name,
Ptr<kheapinfo> heap,
u32 linkFlag,
s32 bufferSize);
void kdgo_init_globals();
} // namespace jak3

View File

@ -0,0 +1,3 @@
#include "kdsnetm.h"

View File

@ -0,0 +1,3 @@
#pragma once
namespace jak3 {}

656
game/kernel/jak3/klink.cpp Normal file
View File

@ -0,0 +1,656 @@
#include "klink.h"
#include "common/common_types.h"
#include "common/goal_constants.h"
#include "common/symbols.h"
#include "game/kernel/common/fileio.h"
#include "game/kernel/common/klink.h"
#include "game/kernel/common/kprint.h"
#include "game/kernel/jak3/kmalloc.h"
#include "game/kernel/jak3/kscheme.h"
#include "game/mips2c/mips2c_table.h"
#include "third-party/fmt/core.h"
namespace {
bool is_opengoal_object(void* data) {
u32 first_word;
memcpy(&first_word, data, 4);
return first_word != 0 && first_word != UINT32_MAX;
}
constexpr bool link_debug_printfs = false;
} // namespace
void link_control::jak3_begin(Ptr<uint8_t> object_file,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags) {
if (is_opengoal_object(object_file.c())) {
m_opengoal = true;
// save data from call to begin
m_object_data = object_file;
kstrcpy(m_object_name, name);
m_object_size = size;
m_heap = heap;
m_flags = flags;
// initialize link control
m_entry.offset = 0;
m_heap_top = m_heap->top;
m_keep_debug = false;
m_opengoal = true;
m_busy = true;
if (link_debug_printfs) {
char* goal_name = object_file.cast<char>().c();
printf("link %s\n", m_object_name);
printf("link_control::begin %c%c%c%c\n", goal_name[0], goal_name[1], goal_name[2],
goal_name[3]);
}
// points to the beginning of the linking data
m_link_block_ptr = object_file + BASIC_OFFSET;
m_code_size = 0;
m_code_start = object_file;
m_state = 0;
m_segment_process = 0;
ObjectFileHeader* ofh = m_link_block_ptr.cast<ObjectFileHeader>().c();
if (ofh->goal_version_major != versions::GOAL_VERSION_MAJOR) {
fprintf(
stderr,
"VERSION ERROR: C Kernel built from GOAL %d.%d, but object file %s is from GOAL %d.%d\n",
versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR, name, ofh->goal_version_major,
ofh->goal_version_minor);
ASSERT(false);
}
if (link_debug_printfs) {
printf("Object file header:\n");
printf(" GOAL ver %d.%d obj %d len %d\n", ofh->goal_version_major, ofh->goal_version_minor,
ofh->object_file_version, ofh->link_block_length);
printf(" segment count %d\n", ofh->segment_count);
for (int i = 0; i < N_SEG; i++) {
printf(" seg %d link 0x%04x, 0x%04x data 0x%04x, 0x%04x\n", i, ofh->link_infos[i].offset,
ofh->link_infos[i].size, ofh->code_infos[i].offset, ofh->code_infos[i].size);
}
}
m_version = ofh->object_file_version;
if (ofh->object_file_version < 4) {
// three segment file
// seek past the header
m_object_data.offset += ofh->link_block_length;
// todo, set m_code_size
if (m_link_block_ptr.offset < m_heap->base.offset ||
m_link_block_ptr.offset >= m_heap->top.offset) {
// the link block is outside our heap, or in the top of our heap. It's somebody else's
// problem.
if (link_debug_printfs) {
printf("Link block somebody else's problem\n");
}
if (m_heap->base.offset <= m_object_data.offset && // above heap base
m_object_data.offset < m_heap->top.offset && // less than heap top (not needed?)
m_object_data.offset < m_heap->current.offset) { // less than heap current
if (link_debug_printfs) {
printf("Code block in the heap, kicking it out for copy into heap\n");
}
m_heap->current = m_object_data;
}
} else {
// in our heap, we need to move it so we can free up its space later on
if (link_debug_printfs) {
printf("Link block needs to be moved!\n");
}
// allocate space for a new one
auto new_link_block = kmalloc(m_heap, ofh->link_block_length, KMALLOC_TOP, "link-block");
auto old_link_block = m_link_block_ptr - BASIC_OFFSET;
// copy it (was ultimate memcpy, but just use normal one to make it easier)
memmove(new_link_block.c(), old_link_block.c(), ofh->link_block_length);
m_link_block_ptr = new_link_block + BASIC_OFFSET;
// if we can save some memory here
if (old_link_block.offset < m_heap->current.offset) {
if (link_debug_printfs) {
printf("Kick out old link block\n");
}
m_heap->current = old_link_block;
}
}
} else {
ASSERT_MSG(false, "UNHANDLED OBJECT FILE VERSION");
}
if ((m_flags & LINK_FLAG_FORCE_DEBUG) && MasterDebug && !DiskBoot) {
m_keep_debug = true;
}
} else {
m_opengoal = false;
if (heap == kglobalheap) {
jak3::kmemopen_from_c(heap, name);
m_on_global_heap = true;
} else {
m_on_global_heap = false;
}
m_object_data = object_file;
kstrcpy(this->m_object_name, name);
m_object_size = size;
// l_hdr = (LinkHdrWithType*)this->m_object_data;
LinkHeaderV5* l_hdr = (LinkHeaderV5*)m_object_data.c();
m_flags = flags;
u16 version = l_hdr->core.version;
ASSERT(version == 5); // I think, since there's only a work v5.
m_heap_top = heap->top;
// this->unk_init1 = 1; TODO
m_busy = true;
m_heap = heap;
// this->m_unk_init0_0 = 0; TODO
m_keep_debug = false;
m_link_hdr = &l_hdr->core; // m_hdr_ptr
m_code_size = 0;
// this->m_ptr_2 = l_hdr; just used for cache flush, so skip it! not really the right thing??
// this->m_unk_init0_3 = 0; TODO
// this->m_unk_init0_4 = 0; TODO
// this->m_unk_init0_5 = 0; TODO
if (version == 4) {
ASSERT_NOT_REACHED();
} else {
m_object_data.offset = object_file.offset + l_hdr->core.length_to_get_to_code;
if (version == 5) {
static_assert(0x50 == sizeof(LinkHeaderV5));
size = (size - l_hdr->core.link_length) - sizeof(LinkHeaderV5);
} else {
ASSERT_NOT_REACHED();
}
m_code_size = size;
if ((u8*)m_link_hdr < m_heap->base.c() || (u8*)m_link_hdr >= m_heap->top.c()) {
// the link block is outside our heap, or in the allocated top part.
// so we ignore it, and leave it as somebody else's problem.
// let's try to move the code part:
if (m_heap->base.offset <= m_object_data.offset && // above heap base
m_object_data.offset < m_heap->top.offset && // less than heap top (not needed?)
m_object_data.offset < m_heap->current.offset) { // less than heap current
if (link_debug_printfs) {
printf("Code block in the heap, kicking it out for copy into heap\n");
}
m_heap->current = m_object_data;
}
} else {
m_moved_link_block = true;
if (m_link_hdr->version == 5) {
// the link block is inside our heap, but we'd like to avoid this.
// we'll copy the link block, and the header to the temporary part of our heap:
// where we loaded the link data:
auto offset_to_link_data = m_link_hdr->length_to_get_to_link;
// allocate memory for link data, and header
auto new_link_block_mem = kmalloc(m_heap, m_link_hdr->link_length + sizeof(LinkHeaderV5),
KMALLOC_TOP, "link-block");
// we'll place the header and link block back to back in the newly alloated block,
// so patch up the offset for this new layout before copying
m_link_hdr->length_to_get_to_link = sizeof(LinkHeaderV5);
// move header!
memmove(new_link_block_mem.c(), object_file.c(), sizeof(LinkHeaderV5));
// move link data!
auto old_link_block = object_file.c() + offset_to_link_data;
memmove(new_link_block_mem.c() + sizeof(LinkHeaderV5), old_link_block,
m_link_hdr->link_length);
// update our pointer to the link header core.
m_link_hdr = &((LinkHeaderV5*)new_link_block_mem.c())->core;
// scary: update the heap to kick out all the link data (and likely the actual data too).
// we'll be relying on the linking process to copy the data as needed.l
if (old_link_block < m_heap->current.c()) {
if (link_debug_printfs) {
printf("Kick out old link block\n");
}
m_heap->current.offset = old_link_block - g_ee_main_mem;
}
} else {
ASSERT_NOT_REACHED();
}
}
}
if ((m_flags & LINK_FLAG_FORCE_DEBUG) && MasterDebug && !DiskBoot) {
m_keep_debug = true;
}
}
}
uint32_t link_control::jak3_work() {
auto old_debug_segment = DebugSegment;
if (m_keep_debug) {
DebugSegment = s7.offset + true_symbol_offset(g_game_version);
}
// set type tag of link block
uint32_t rv;
if (m_version == 3) {
ASSERT(m_opengoal);
*((m_link_block_ptr - 4).cast<u32>()) =
*((s7 + jak3_symbols::FIX_SYM_LINK_BLOCK - 1).cast<u32>());
rv = jak3_work_opengoal();
} else if (m_version == 5) {
ASSERT(!m_opengoal);
*(u32*)(((u8*)m_link_hdr) - 4) = *((s7 + jak3_symbols::FIX_SYM_LINK_BLOCK - 1).cast<u32>());
rv = jak3_work_v5();
} else {
ASSERT_MSG(false, fmt::format("UNHANDLED OBJECT FILE VERSION {} IN WORK!", m_version));
return 0;
}
DebugSegment = old_debug_segment;
return rv;
}
uint32_t link_control::jak3_work_v5() {
ASSERT_NOT_REACHED(); // save this for another day...
// TODO: there are some missing vars in begin. I just commented them out for now.
}
namespace {
/*!
* Link a single relative offset (used for RIP)
*/
uint32_t cross_seg_dist_link_v3(Ptr<uint8_t> link,
ObjectFileHeader* ofh,
int current_seg,
int size) {
// target seg, dist into mine, dist into target, patch loc in mine
uint8_t target_seg = *link;
ASSERT(target_seg < ofh->segment_count);
uint32_t* link_data = (link + 1).cast<uint32_t>().c();
int32_t mine = link_data[0] + ofh->code_infos[current_seg].offset;
int32_t tgt = link_data[1] + ofh->code_infos[target_seg].offset;
int32_t diff = tgt - mine;
uint32_t offset_of_patch = link_data[2] + ofh->code_infos[current_seg].offset;
// second debug segment case added for jak 2.
if (!ofh->code_infos[target_seg].offset || (!DebugSegment && target_seg == DEBUG_SEGMENT)) {
// we want to address GOAL 0. In the case where this is a rip-relative load or store, this
// will crash, which is what we want. If it's an lea and just getting an address, this will get
// us a nullptr. If you do a method-set! with a null pointer it does nothing, so it's safe to
// method-set! to things that are in unloaded segments and it'll just keep the old method.
diff = -mine;
}
// printf("link object in seg %d diff %d at %d (%d + %d)\n", target_seg, diff, offset_of_patch,
// link_data[2], ofh->code_infos[current_seg].offset);
// both 32-bit and 64-bit pointer links are supported, though 64-bit ones are no longer in use.
// we still support it just in case we want to run ancient code.
if (size == 4) {
*Ptr<int32_t>(offset_of_patch).c() = diff;
} else if (size == 8) {
*Ptr<int64_t>(offset_of_patch).c() = diff;
} else {
ASSERT(false);
}
return 1 + 3 * 4;
}
uint32_t ptr_link_v3(Ptr<u8> link, ObjectFileHeader* ofh, int current_seg) {
auto* link_data = link.cast<u32>().c();
u32 patch_loc = link_data[0] + ofh->code_infos[current_seg].offset;
u32 patch_value = link_data[1] + ofh->code_infos[current_seg].offset;
*Ptr<u32>(patch_loc).c() = patch_value;
return 8;
}
/*!
* Link type pointers for a single type in "v3 equivalent" link data
* Returns a pointer to the link table data after the typelinking data.
*/
uint32_t typelink_v3(Ptr<uint8_t> link, Ptr<uint8_t> data) {
// get the name of the type
uint32_t seek = 0;
char sym_name[256];
while (link.c()[seek]) {
sym_name[seek] = link.c()[seek];
seek++;
ASSERT(seek < 256);
}
sym_name[seek] = 0;
seek++;
// determine the number of methods
uint32_t method_count = link.c()[seek++];
// jak2 special
method_count *= 4;
if (method_count) {
method_count += 3;
}
// intern the GOAL type, creating the vtable if it doesn't exist.
auto type_ptr = jak3::intern_type_from_c(-1, 0, sym_name, method_count);
// prepare to read the locations of the type pointers
Ptr<uint32_t> offsets = link.cast<uint32_t>() + seek;
uint32_t offset_count = *offsets;
offsets = offsets + 4;
seek += 4;
// write the type pointers into memory
for (uint32_t i = 0; i < offset_count; i++) {
*(data + offsets.c()[i]).cast<int32_t>() = type_ptr.offset;
seek += 4;
}
return seek;
}
/*!
* Link symbols (both offsets and pointers) in "v3 equivalent" link data.
* Returns a pointer to the link table data after the linking data for this symbol.
*/
uint32_t symlink_v3(Ptr<uint8_t> link, Ptr<uint8_t> data) {
// get the symbol name
uint32_t seek = 0;
char sym_name[256];
while (link.c()[seek]) {
sym_name[seek] = link.c()[seek];
seek++;
ASSERT(seek < 256);
}
sym_name[seek] = 0;
seek++;
// intern
auto sym = jak3::intern_from_c(-1, 0, sym_name);
int32_t sym_offset = sym.cast<u32>() - s7;
uint32_t sym_addr = sym.cast<u32>().offset;
// prepare to read locations of symbol links
Ptr<uint32_t> offsets = link.cast<uint32_t>() + seek;
uint32_t offset_count = *offsets;
offsets = offsets + 4;
seek += 4;
for (uint32_t i = 0; i < offset_count; i++) {
uint32_t offset = offsets.c()[i];
seek += 4;
auto data_ptr = (data + offset).cast<int32_t>();
if (*data_ptr == -1) {
// a "-1" indicates that we should store the address.
*(data + offset).cast<int32_t>() = sym_addr;
} else if (*(data_ptr.cast<u32>()) == LINK_SYM_NO_OFFSET_FLAG) {
*(data + offset).cast<int32_t>() = sym_offset - 1;
} else {
// otherwise store the offset to st.
*(data + offset).cast<int32_t>() = sym_offset;
}
}
return seek;
}
} // namespace
uint32_t link_control::jak3_work_opengoal() {
// note: I'm assuming that the allocation we used in jak2/jak1 will still work here. Once work_v5
// is done, we could revisit this.
ObjectFileHeader* ofh = m_link_block_ptr.cast<ObjectFileHeader>().c();
if (m_state == 0) {
// state 0 <- copying data.
// the actual game does all copying in one shot. I assume this is ok because v3 files are just
// code and always small. Large data which takes too long to copy should use v2.
// loop over segments
for (s32 seg_id = ofh->segment_count - 1; seg_id >= 0; seg_id--) {
// link the infos
ofh->link_infos[seg_id].offset += m_link_block_ptr.offset;
ofh->code_infos[seg_id].offset += m_object_data.offset;
if (seg_id == DEBUG_SEGMENT) {
if (!DebugSegment) {
// clear code info if we aren't going to copy the debug segment.
ofh->code_infos[seg_id].offset = 0;
ofh->code_infos[seg_id].size = 0;
} else {
if (ofh->code_infos[seg_id].size == 0) {
// not actually present
ofh->code_infos[seg_id].offset = 0;
} else {
Ptr<u8> src(ofh->code_infos[seg_id].offset);
ofh->code_infos[seg_id].offset =
kmalloc(kdebugheap, ofh->code_infos[seg_id].size, 0, "debug-segment").offset;
if (ofh->code_infos[seg_id].offset == 0) {
MsgErr("dkernel: unable to malloc %d bytes for debug-segment\n",
ofh->code_infos[seg_id].size);
return 1;
}
memmove(Ptr<u8>(ofh->code_infos[seg_id].offset).c(), src.c(),
ofh->code_infos[seg_id].size);
}
}
} else if (seg_id == MAIN_SEGMENT) {
if (ofh->code_infos[seg_id].size == 0) {
ofh->code_infos[seg_id].offset = 0;
} else {
Ptr<u8> src(ofh->code_infos[seg_id].offset);
ofh->code_infos[seg_id].offset =
kmalloc(m_heap, ofh->code_infos[seg_id].size, 0, "main-segment").offset;
if (ofh->code_infos[seg_id].offset == 0) {
MsgErr("dkernel: unable to malloc %d bytes for main-segment\n",
ofh->code_infos[seg_id].size);
return 1;
}
memmove(Ptr<u8>(ofh->code_infos[seg_id].offset).c(), src.c(),
ofh->code_infos[seg_id].size);
}
} else if (seg_id == TOP_LEVEL_SEGMENT) {
if (ofh->code_infos[seg_id].size == 0) {
ofh->code_infos[seg_id].offset = 0;
} else {
Ptr<u8> src(ofh->code_infos[seg_id].offset);
ofh->code_infos[seg_id].offset =
kmalloc(m_heap, ofh->code_infos[seg_id].size, KMALLOC_TOP, "top-level-segment")
.offset;
if (ofh->code_infos[seg_id].offset == 0) {
MsgErr("dkernel: unable to malloc %d bytes for top-level-segment\n",
ofh->code_infos[seg_id].size);
return 1;
}
memmove(Ptr<u8>(ofh->code_infos[seg_id].offset).c(), src.c(),
ofh->code_infos[seg_id].size);
}
} else {
printf("UNHANDLED SEG ID IN WORK V3 STATE 1\n");
}
}
m_state = 1;
m_segment_process = 0;
return 0;
} else if (m_state == 1) {
// state 1: linking. For now all links are done at once. This is probably going to be fine on a
// modern computer. But the game broke this into multiple steps.
if (m_segment_process < ofh->segment_count) {
if (ofh->code_infos[m_segment_process].offset) {
Ptr<u8> lp(ofh->link_infos[m_segment_process].offset);
while (*lp) {
switch (*lp) {
case LINK_TABLE_END:
break;
case LINK_SYMBOL_OFFSET:
lp = lp + 1;
lp = lp + symlink_v3(lp, Ptr<u8>(ofh->code_infos[m_segment_process].offset));
break;
case LINK_TYPE_PTR:
lp = lp + 1; // seek past id
lp = lp + typelink_v3(lp, Ptr<u8>(ofh->code_infos[m_segment_process].offset));
break;
case LINK_DISTANCE_TO_OTHER_SEG_64:
lp = lp + 1;
lp = lp + cross_seg_dist_link_v3(lp, ofh, m_segment_process, 8);
break;
case LINK_DISTANCE_TO_OTHER_SEG_32:
lp = lp + 1;
lp = lp + cross_seg_dist_link_v3(lp, ofh, m_segment_process, 4);
break;
case LINK_PTR:
lp = lp + 1;
lp = lp + ptr_link_v3(lp, ofh, m_segment_process);
break;
default:
ASSERT_MSG(false, fmt::format("unknown link table thing {}", *lp));
break;
}
}
}
m_segment_process++;
} else {
// all done, can set the entry point to the top-level.
m_entry = Ptr<u8>(ofh->code_infos[TOP_LEVEL_SEGMENT].offset) + 4;
return 1;
}
return 0;
}
else {
printf("WORK v3 INVALID STATE\n");
return 1;
}
}
void link_control::jak3_finish(bool jump_from_c_to_goal) {
// CacheFlush(this->m_ptr_2, this->m_code_size);
auto old_debug_segment = DebugSegment;
if (m_keep_debug) {
// note - this probably doesn't work because DebugSegment isn't *debug-segment*.
DebugSegment = s7.offset + jak3_symbols::FIX_SYM_TRUE;
}
if (m_flags & LINK_FLAG_FORCE_FAST_LINK) {
FastLink = 1;
}
*EnableMethodSet = *EnableMethodSet + m_keep_debug;
if (m_opengoal) {
// setup mips2c functions
const auto& it = Mips2C::gMips2CLinkCallbacks[GameVersion::Jak3].find(m_object_name);
if (it != Mips2C::gMips2CLinkCallbacks[GameVersion::Jak3].end()) {
for (auto& x : it->second) {
x();
}
}
// execute top level!
if (m_entry.offset && (m_flags & LINK_FLAG_EXECUTE)) {
if (jump_from_c_to_goal) {
u64 goal_stack = u64(g_ee_main_mem) + EE_MAIN_MEM_SIZE - 8;
call_goal_on_stack(m_entry.cast<Function>(), goal_stack, s7.offset, g_ee_main_mem);
} else {
call_goal(m_entry.cast<Function>(), 0, 0, 0, s7.offset, g_ee_main_mem);
}
}
// inform compiler that we loaded.
if (m_flags & LINK_FLAG_OUTPUT_LOAD) {
output_segment_load(m_object_name, m_link_block_ptr, m_flags);
}
} else {
ASSERT_NOT_REACHED();
}
*EnableMethodSet = *EnableMethodSet - this->m_keep_debug;
FastLink = 0;
m_heap->top = m_heap_top;
DebugSegment = old_debug_segment;
m_busy = false;
if (m_on_global_heap) {
jak3::kmemclose();
}
return;
}
namespace jak3 {
Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags,
bool jump_from_c_to_goal) {
if (link_busy()) {
printf("-------------> saved link is busy\n");
// probably won't end well...
}
link_control lc;
lc.jak3_begin(data, name, size, heap, flags);
uint32_t done;
do {
done = lc.jak3_work();
} while (!done);
lc.jak3_finish(jump_from_c_to_goal);
return lc.m_entry;
}
u64 link_and_exec_wrapper(u64* args) {
return link_and_exec(Ptr<u8>(args[0]), Ptr<char>(args[1]).c(), args[2], Ptr<kheapinfo>(args[3]),
args[4], false)
.offset;
}
u32 link_busy() {
return saved_link_control.m_busy;
}
void link_reset() {
saved_link_control.m_busy = 0;
}
uint64_t link_begin(u64* /*args*/) {
ASSERT_NOT_REACHED();
}
uint64_t link_resume() {
ASSERT_NOT_REACHED();
}
// Note: update_goal_fns changed to skip the hashtable lookup since symlink2/symlink3 are now fixed
// symbols.
/*!
* The ULTIMATE MEMORY COPY
* IT IS VERY FAST
* but it may use the scratchpad. It is implemented in GOAL, and falls back to normal C memcpy
* if GOAL isn't loaded, or if the alignment isn't good enough.
*/
void ultimate_memcpy(void* dst, void* src, uint32_t size) {
// only possible if alignment is good.
if (!(u64(dst) & 0xf) && !(u64(src) & 0xf) && !(u64(size) & 0xf) && size > 0xfff) {
if (!gfunc_774.offset) {
// GOAL function is unknown, lets see if its loaded:
auto sym_val = *((s7 + jak3_symbols::FIX_SYM_ULTIMATE_MEMCPY - 1).cast<u32>());
if (sym_val == 0) {
memmove(dst, src, size);
return;
}
gfunc_774.offset = sym_val;
}
Ptr<u8>(call_goal(gfunc_774, make_u8_ptr(dst).offset, make_u8_ptr(src).offset, size, s7.offset,
g_ee_main_mem))
.c();
} else {
memmove(dst, src, size);
}
}
} // namespace jak3

20
game/kernel/jak3/klink.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include "common/common_types.h"
#include "game/kernel/common/Ptr.h"
#include "game/kernel/common/kmalloc.h"
namespace jak3 {
Ptr<uint8_t> link_and_exec(Ptr<uint8_t> data,
const char* name,
int32_t size,
Ptr<kheapinfo> heap,
uint32_t flags,
bool jump_from_c_to_goal);
u64 link_and_exec_wrapper(u64* args);
u32 link_busy();
void link_reset();
uint64_t link_begin(u64* args);
uint64_t link_resume();
} // namespace jak3

View File

@ -0,0 +1,118 @@
#include "klisten.h"
#include "common/symbols.h"
#include "game/kernel/common/Ptr.h"
#include "game/kernel/common/Symbol4.h"
#include "game/kernel/common/kdsnetm.h"
#include "game/kernel/common/klink.h"
#include "game/kernel/common/klisten.h"
#include "game/kernel/common/kmalloc.h"
#include "game/kernel/common/kprint.h"
#include "game/kernel/common/kscheme.h"
#include "game/kernel/jak3/klink.h"
#include "game/kernel/jak3/kscheme.h"
namespace jak3 {
using namespace jak3_symbols;
Ptr<Symbol4<u32>> ListenerLinkBlock;
Ptr<Symbol4<u32>> ListenerFunction;
Ptr<Symbol4<u32>> KernelFunction; // new in jak2
Ptr<Symbol4<u32>> kernel_dispatcher;
Ptr<Symbol4<u32>> kernel_packages;
Ptr<Symbol4<u32>> sync_dispatcher;
void klisten_init_globals() {
ListenerLinkBlock.offset = 0;
ListenerFunction.offset = 0;
KernelFunction.offset = 0;
kernel_dispatcher.offset = 0;
kernel_packages.offset = 0;
sync_dispatcher.offset = 0;
}
/*!
* Initialize the Listener by setting up symbols shared between GOAL and C for the listener.
* Also adds "kernel" to the kernel_packages list.
* There was an "ACK" message sent here, but this is removed because we don't need it.
*/
void InitListener() {
ListenerLinkBlock = intern_from_c(-1, 0, "*listener-link-block*");
ListenerFunction = intern_from_c(-1, 0, "*listener-function*");
KernelFunction = intern_from_c(-1, 0, "*kernel-function*");
kernel_dispatcher = intern_from_c(-1, 0, "kernel-dispatcher");
sync_dispatcher = intern_from_c(-1, 0, "sync-dispatcher");
kernel_packages = intern_from_c(-1, 0, "*kernel-packages*");
print_column = intern_from_c(-1, 0, "*print-column*").cast<u32>(); // this is wrong
ListenerLinkBlock->value() = s7.offset;
ListenerFunction->value() = s7.offset;
KernelFunction->value() = s7.offset;
kernel_packages->value() =
new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE - 1).cast<u32>()),
make_string_from_c("kernel"), kernel_packages->value());
// if(MasterDebug) {
// SendFromBufferD(MSG_ACK, 0, AckBufArea + sizeof(ListenerMessageHeader), 0);
// }
}
/*!
* Handle an incoming listener message
*/
void ProcessListenerMessage(Ptr<char> msg) {
// flag that the listener is connected!
ListenerStatus = 1;
switch (protoBlock.msg_kind) {
case LTT_MSG_POKE:
// just flush any pending stuff.
ClearPending();
break;
case LTT_MSG_INSPECT:
inspect_object(atoi(msg.c()));
ClearPending();
break;
case LTT_MSG_PRINT:
print_object(atoi(msg.c()));
ClearPending();
break;
case LTT_MSG_PRINT_SYMBOLS:
printf("[ERROR] unsupported message kind LTT_MSG_PRINT_SYMBOLS (NYI)\n");
break;
case LTT_MSG_RESET:
MasterExit = RuntimeExitStatus::RESTART_RUNTIME;
break;
case LTT_MSG_SHUTDOWN:
MasterExit = RuntimeExitStatus::EXIT;
break;
case LTT_MSG_CODE: {
auto buffer = kmalloc(kdebugheap, MessCount, 0, "listener-link-block");
memcpy(buffer.c(), msg.c(), MessCount);
ListenerLinkBlock->value() = buffer.offset + 4;
// note - this will stash the linked code in the top level and free it.
// it will then be used-after-free, but this is OK because nobody else will allocate.
// the kernel dispatcher should immediately execute the listener function to avoid this
// getting squashed.
// this setup allows listener function execution to clean up after itself.
// we have added the LINK_FLAG_OUTPUT_LOAD
// jump from c to goal because this is called from the C++ stack.
ListenerFunction->value() = link_and_exec(buffer, "*listener*", 0, kdebugheap,
LINK_FLAG_FORCE_DEBUG | LINK_FLAG_OUTPUT_LOAD, true)
.offset;
return; // don't ack yet, this will happen after the function runs.
} break;
default:
MsgErr("dkernel: unknown message error: <%d> of %d bytes\n", protoBlock.msg_kind, MessCount);
break;
}
SendAck();
}
int sql_query_sync(Ptr<String> /*string_in*/) {
ASSERT_NOT_REACHED();
}
} // namespace jak3

View File

@ -0,0 +1,20 @@
#pragma once
#include "common/common_types.h"
#include "game/kernel/common/Ptr.h"
#include "game/kernel/common/Symbol4.h"
#include "game/kernel/common/kscheme.h"
namespace jak3 {
extern Ptr<Symbol4<u32>> ListenerLinkBlock;
extern Ptr<Symbol4<u32>> ListenerFunction;
extern Ptr<Symbol4<u32>> KernelFunction; // new in jak2
extern Ptr<Symbol4<u32>> kernel_dispatcher;
extern Ptr<Symbol4<u32>> sync_dispatcher; // new in jak2
extern Ptr<Symbol4<u32>> kernel_packages;
void InitListener();
void klisten_init_globals();
void ProcessListenerMessage(Ptr<char> msg);
int sql_query_sync(Ptr<String> string_in);
} // namespace jak3

View File

@ -0,0 +1,513 @@
#include "kmachine.h"
#include <cstring>
#include "common/symbols.h"
#include "game/graphics/gfx.h"
#include "game/graphics/sceGraphicsInterface.h"
#include "game/kernel/common/fileio.h"
#include "game/kernel/common/kdgo.h"
#include "game/kernel/common/kdsnetm.h"
#include "game/kernel/common/kernel_types.h"
#include "game/kernel/common/klink.h"
#include "game/kernel/common/kmachine.h"
#include "game/kernel/common/kmalloc.h"
#include "game/kernel/common/kprint.h"
#include "game/kernel/common/ksocket.h"
#include "game/kernel/common/ksound.h"
#include "game/kernel/common/memory_layout.h"
#include "game/kernel/jak3/kboot.h"
#include "game/kernel/jak3/kdgo.h"
#include "game/kernel/jak3/klisten.h"
#include "game/kernel/jak3/kmalloc.h"
#include "game/kernel/jak3/kscheme.h"
#include "game/kernel/jak3/ksound.h"
#include "game/sce/deci2.h"
#include "game/sce/libdma.h"
#include "game/sce/libgraph.h"
#include "game/sce/sif_ee.h"
#include "game/sce/stubs.h"
namespace jak3 {
using namespace ee;
/*!
* Initialize global variables based on command line parameters. Not called in retail versions,
* but it is present in the ELF.
* DONE
* Modified to use std::string, and removed call to fflush.
*/
void InitParms(int argc, const char* const* argv) {
// Modified default settings to boot up the game like normal if no arguments are present.
if (argc == 1) {
DiskBoot = 1;
isodrv = fakeiso;
modsrc = 0;
reboot_iop = 0;
DebugSegment = 0;
MasterDebug = 0;
DebugSymbols = true;
}
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
// DVD Settings
// ----------------------------
// the "cd" mode uses the DVD drive for everything. This is how the game runs in retail
if (arg == "-cd") {
Msg(6, "dkernel: cd mode\n");
isodrv = iso_cd; // use the actual DVD drive for data files
modsrc = 1; // use the DVD drive data for IOP modules
reboot_iop = 1; // Reboot the IOP (load new IOP runtime)
}
// the "cddata" uses the DVD drive for everything but IOP modules.
if (arg == "-cddata") {
Msg(6, "dkernel: cddata mode\n");
isodrv = iso_cd; // tell IOP to use actual DVD drive for data files
modsrc = 0; // don't use DVD drive for IOP modules
reboot_iop = 0; // no need to reboot the IOP
}
if (arg == "-demo") {
Msg(6, "dkernel: demo mode\n");
kstrcpy(DebugBootMessage, "demo");
}
// new for jak 2
if (arg == "-kiosk") {
Msg(6, "dkernel: kiosk mode\n");
kstrcpy(DebugBootMessage, "kiosk");
}
// new for jak 2
if (arg == "-preview") {
Msg(6, "dkernel: preview mode\n");
kstrcpy(DebugBootMessage, "preview");
}
// the "deviso" mode is one of two modes for testing without the need for DVDs
if (arg == "-deviso") {
Msg(6, "dkernel: deviso mode\n");
isodrv = deviso; // IOP deviso mode
modsrc = 2; // now 2 for Jak 2
reboot_iop = 0;
}
// the "fakeiso" mode is the other of two modes for testing without the need for DVDs
if (arg == "-fakeiso") {
Msg(6, "dkernel: fakeiso mode\n");
isodrv = fakeiso; // IOP fakeeiso mode
modsrc = 0; // no IOP module loading (there's no DVD to load from!)
reboot_iop = 0;
}
// the "boot" mode is used to set GOAL up for running the game in retail mode
if (arg == "-boot") {
Msg(6, "dkernel: boot mode\n");
MasterDebug = 0;
DiskBoot = 1;
DebugSegment = 0;
}
// new for jak 2
if (arg == "-debug-boot") {
Msg(6, "dkernel: debug-boot mode\n");
MasterDebug = 0;
DebugSegment = 1;
DiskBoot = 1;
}
// traditional debug mode
if (arg == "-debug") {
Msg(6, "dkernel: debug mode\n");
MasterDebug = 1;
DebugSegment = 1;
}
// the "debug-mem" mode is used to set up GOAL in debug mode, but not to load debug-segments
if (arg == "-debug-mem") {
Msg(6, "dkernel: debug-mem mode\n");
MasterDebug = 1;
DebugSegment = 0;
}
// TODO overlord 1 vs. 2 switch
if (arg == "-debug-symbols") {
Msg(6, "dkernel: debug-symbols on\n");
DebugSymbols = true;
}
if (arg == "-no-debug-symbols") {
Msg(6, "dkernel: debug-symbols off\n");
DebugSymbols = true;
}
// the "-level [level-name]" mode is used to inform the game to boot a specific level
// the default level is "#f".
if (arg == "-level") {
i++;
std::string levelName = argv[i];
Msg(6, "dkernel: level %s\n", levelName.c_str());
kstrcpy(DebugBootLevel, levelName.c_str());
ASSERT_NOT_REACHED(); // symbol ID junk
}
// new for jak 2
if (arg == "-user") {
i++;
std::string userName = argv[i];
Msg(6, "dkernel: user %s\n", userName.c_str());
kstrcpy(DebugBootUser, userName.c_str());
}
// new for jak 2
if (arg == "-art") {
i++;
std::string artGroupName = argv[i];
Msg(6, "dkernel: art-group %s\n", artGroupName.c_str());
kstrcpy(DebugBootArtGroup, artGroupName.c_str());
kstrcpy(DebugBootMessage, "art-group");
}
// an added mode to allow booting without a KERNEL.CGO for testing
if (arg == "-nokernel") {
Msg(6, "dkernel: no kernel mode\n");
MasterUseKernel = false;
}
// an added mode to allow booting without sound for testing
if (arg == "-nosound") {
Msg(6, "dkernel: no sound mode\n");
masterConfig.disable_sound = true;
}
}
}
/*!
* This is mostly copy-pasted from jak2 and very simplified until we have overlord 2.
*/
void InitIOP() {
Msg(6, "dkernel: boot:%d debug:%d mem:%d syms:%d dev:%d mod:%d\n", DiskBoot, MasterDebug,
DebugSegment, DebugSymbols, isodrv, modsrc);
sceSifInitRpc(0);
// init cd if we need it
if (((isodrv == iso_cd) || (modsrc == 1)) || (reboot_iop == 1)) {
InitCD();
}
if ((isodrv == iso_cd) || (modsrc == 1)) {
InitCD();
}
char overlord_boot_command[256];
char* cmd = overlord_boot_command;
kstrcpy(cmd, init_types[(int)isodrv]);
cmd = cmd + strlen(cmd) + 1;
if (!strncmp(DebugBootMessage, "demo", 4)) {
kstrcpy(cmd, "SCREEN1.DEM");
} else {
kstrcpy(cmd, "SCREEN1.USA");
}
cmd = cmd + strlen(cmd) + 1;
if (masterConfig.disable_sound) {
kstrcpy(cmd, "-nosound");
cmd = cmd + strlen(cmd) + 1;
}
int total_len = cmd - overlord_boot_command;
if (modsrc == 0) {
printf("Initializing CD library in FAKEISO mode\n");
if (sceSifLoadModule("host0:bin/overlord.irx", total_len, overlord_boot_command) < 0) {
MsgErr("loading overlord.irx <3> failed\n");
exit(0);
}
} else {
ASSERT_NOT_REACHED();
}
int rv = sceMcInit();
if (rv < 0) {
MsgErr("MC driver init failed %d\n", rv);
exit(0);
}
printf("InitIOP OK\n");
}
int InitMachine() {
// uVar2 = FUN_00116ec8(0x10);
// heap_start = malloc(0x10);
u32 global_heap_size = GLOBAL_HEAP_END - HEAP_START;
float size_mb = ((float)global_heap_size) / (float)(1 << 20);
lg::info("gkernel: global heap 0x{:08x} to 0x{:08x} (size {:.3f} MB)", HEAP_START,
GLOBAL_HEAP_END, size_mb);
kinitheap(kglobalheap, Ptr<u8>(HEAP_START), global_heap_size);
kmemopen_from_c(kglobalheap, "global");
kmemopen_from_c(kglobalheap, "scheme-globals");
if (!MasterDebug && !DebugSegment) {
// if no debug, we make the kheapinfo structure NULL so GOAL knows not to use it.
// note: either MasterDebug or DebugSegment is enough to give use the debug heap.
kdebugheap.offset = 0;
} else {
kinitheap(kdebugheap, Ptr<u8>(DEBUG_HEAP_START), jak3::DEBUG_HEAP_SIZE);
}
init_output();
InitIOP();
// sceGsResetPath();
InitVideo();
// FlushCache(0);
// FlushCache(2);
// sceGsSyncV(0);
// if (scePadInit(0) != 1) {
// MsgErr("dkernel: !init pad\n");
// }
if (MasterDebug != 0) {
InitGoalProto();
} else {
ee::sceDeci2Disable(); // added
}
printf("InitSound\n");
InitSound();
printf("InitRPC\n");
InitRPC();
reset_output();
clear_print();
auto status = InitHeapAndSymbol();
if (status >= 0) {
printf("InitListenerConnect\n");
InitListenerConnect();
printf("InitCheckListener\n");
InitCheckListener();
Msg(6, "kernel: machine started\n");
return 0;
}
return status;
}
int ShutdownMachine() {
Msg(6, "kernel: machine shutdown (reason %d)\n", MasterExit);
StopIOP();
ShutdownSound();
CloseListener();
ShutdownGoalProto();
return 0;
}
u32 KeybdGetData(u32 /*_mouse*/) {
ASSERT_NOT_REACHED();
}
u32 MouseGetData(u32 /*_mouse*/) {
ASSERT_NOT_REACHED();
}
/*!
* Open a file-stream. Name is a GOAL string. Mode is a GOAL symbol. Use 'read for readonly
* and anything else for write only.
*/
u64 kopen(u64 fs, u64 name, u64 mode) {
auto file_stream = Ptr<FileStream>(fs).c();
file_stream->mode = mode;
file_stream->name = name;
file_stream->flags = 0;
printf("****** CALL TO kopen() ******\n");
char buffer[128];
// sprintf(buffer, "host:%s", Ptr<String>(name)->data());
sprintf(buffer, "%s", Ptr<String>(name)->data());
if (!strcmp(sym_to_cstring(Ptr<Symbol4<u8>>(mode)), "read")) {
// 0x1
file_stream->file = ee::sceOpen(buffer, SCE_RDONLY);
} else if (!strcmp(sym_to_cstring(Ptr<Symbol4<u8>>(mode)), "append")) {
// new in jak 2!
// 0x202
file_stream->file = ee::sceOpen(buffer, SCE_CREAT | SCE_WRONLY);
} else {
// 0x602
file_stream->file = ee::sceOpen(buffer, SCE_TRUNC | SCE_CREAT | SCE_WRONLY);
}
return fs;
}
void PutDisplayEnv(u32 alp) {
// we can mostly ignore this, except for one value that sets the 'blackout' amount.
auto* renderer = Gfx::GetCurrentRenderer();
if (renderer) {
renderer->set_pmode_alp(alp / 255.f);
}
}
void aybabtu() {}
void pc_set_levels(u32 lev_list) {
if (!Gfx::GetCurrentRenderer()) {
return;
}
std::vector<std::string> levels;
for (int i = 0; i < LEVEL_MAX; i++) {
u32 lev = *Ptr<u32>(lev_list + i * 4);
std::string ls = Ptr<String>(lev).c()->data();
if (ls != "none" && ls != "#f" && ls != "") {
levels.push_back(ls);
}
}
Gfx::GetCurrentRenderer()->set_levels(levels);
}
void pc_set_active_levels(u32 lev_list) {
if (!Gfx::GetCurrentRenderer()) {
return;
}
std::vector<std::string> levels;
for (int i = 0; i < LEVEL_MAX; i++) {
u32 lev = *Ptr<u32>(lev_list + i * 4);
std::string ls = Ptr<String>(lev).c()->data();
if (ls != "none" && ls != "#f" && ls != "") {
levels.push_back(ls);
}
}
Gfx::GetCurrentRenderer()->set_active_levels(levels);
}
//// PC Stuff
void InitMachine_PCPort() {
// PC Port added functions
init_common_pc_port_functions(
make_function_symbol_from_c,
[](const char* name) {
const auto result = intern_from_c(-1, 0, name);
InternFromCInfo info{};
info.offset = result.offset;
return info;
},
make_string_from_c);
make_function_symbol_from_c("__pc-set-levels", (void*)pc_set_levels);
make_function_symbol_from_c("__pc-set-active-levels", (void*)pc_set_active_levels);
// make_function_symbol_from_c("__pc-get-tex-remap", (void*)lookup_jak2_texture_dest_offset);
// make_function_symbol_from_c("pc-init-autosplitter-struct", (void*)init_autosplit_struct);
// make_function_symbol_from_c("pc-encode-utf8-string", (void*)encode_utf8_string);
// discord rich presence
// make_function_symbol_from_c("pc-discord-rpc-update", (void*)update_discord_rpc);
// debugging tools
// make_function_symbol_from_c("alloc-vagdir-names", (void*)alloc_vagdir_names);
// external RPCs
/*
make_function_symbol_from_c("pc-fetch-external-speedrun-times",
(void*)pc_fetch_external_speedrun_times);
make_function_symbol_from_c("pc-fetch-external-race-times", (void*)pc_fetch_external_race_times);
make_function_symbol_from_c("pc-fetch-external-highscores", (void*)pc_fetch_external_highscores);
make_function_symbol_from_c("pc-get-external-speedrun-time",
(void*)pc_get_external_speedrun_time);
make_function_symbol_from_c("pc-get-external-race-time", (void*)pc_get_external_race_time);
make_function_symbol_from_c("pc-get-external-highscore", (void*)pc_get_external_highscore);
make_function_symbol_from_c("pc-get-num-external-speedrun-times",
(void*)pc_get_num_external_speedrun_times);
make_function_symbol_from_c("pc-get-num-external-race-times",
(void*)pc_get_num_external_race_times);
make_function_symbol_from_c("pc-get-num-external-highscores",
(void*)pc_get_num_external_highscores);
*/
// setup string constants
auto user_dir_path = file_util::get_user_config_dir();
intern_from_c(-1, 0, "*pc-user-dir-base-path*")->value() =
make_string_from_c(user_dir_path.string().c_str());
auto settings_path = file_util::get_user_settings_dir(g_game_version);
intern_from_c(-1, 0, "*pc-settings-folder*")->value() =
make_string_from_c(settings_path.string().c_str());
intern_from_c(-1, 0, "*pc-settings-built-sha*")->value() =
make_string_from_c(build_revision().c_str());
}
// End PC Stuff
void InitMachineScheme() {
make_function_symbol_from_c("put-display-env", (void*)PutDisplayEnv);
make_function_symbol_from_c("syncv", (void*)sceGsSyncV);
make_function_symbol_from_c("sync-path", (void*)sceGsSyncPath);
make_function_symbol_from_c("reset-path", (void*)sceGsResetPath);
make_function_symbol_from_c("reset-graph", (void*)sceGsResetGraph);
make_function_symbol_from_c("dma-sync", (void*)sceDmaSync);
make_function_symbol_from_c("gs-put-imr", (void*)sceGsPutIMR);
make_function_symbol_from_c("gs-get-imr", (void*)sceGsGetIMR);
make_function_symbol_from_c("gs-store-image", (void*)sceGsExecStoreImage);
make_function_symbol_from_c("flush-cache", (void*)FlushCache);
make_function_symbol_from_c("cpad-open", (void*)CPadOpen);
make_function_symbol_from_c("cpad-get-data", (void*)CPadGetData);
make_function_symbol_from_c("mouse-get-data", (void*)MouseGetData);
make_function_symbol_from_c("keybd-get-data", (void*)KeybdGetData);
make_function_symbol_from_c("install-handler", (void*)InstallHandler);
make_function_symbol_from_c("install-debug-handler", (void*)InstallDebugHandler);
make_function_symbol_from_c("file-stream-open", (void*)kopen);
make_function_symbol_from_c("file-stream-close", (void*)kclose);
make_function_symbol_from_c("file-stream-length", (void*)klength);
make_function_symbol_from_c("file-stream-seek", (void*)kseek);
make_function_symbol_from_c("file-stream-read", (void*)kread);
make_function_symbol_from_c("file-stream-write", (void*)kwrite);
make_function_symbol_from_c("scf-get-language", (void*)DecodeLanguage);
make_function_symbol_from_c("scf-get-time", (void*)DecodeTime);
make_function_symbol_from_c("scf-get-aspect", (void*)DecodeAspect);
make_function_symbol_from_c("scf-get-volume", (void*)DecodeVolume);
make_function_symbol_from_c("scf-get-territory", (void*)DecodeTerritory);
make_function_symbol_from_c("scf-get-timeout", (void*)DecodeTimeout);
make_function_symbol_from_c("scf-get-inactive-timeout", (void*)DecodeInactiveTimeout);
make_function_symbol_from_c("dma-to-iop", (void*)dma_to_iop);
make_function_symbol_from_c("kernel-shutdown", (void*)KernelShutdown);
make_function_symbol_from_c("aybabtu", (void*)aybabtu); // was nothing
InitMachine_PCPort();
InitSoundScheme();
intern_from_c(-1, 0, "*stack-top*")->value() = 0x7f00000;
intern_from_c(-1, 0, "*stack-base*")->value() = 0x7ffffff;
intern_from_c(-1, 0, "*stack-size*")->value() = 0x100000;
intern_from_c(-1, 0, "*kernel-boot-message*")->value() =
intern_from_c(-1, 0, DebugBootMessage).offset;
intern_from_c(-1, 0, "*user*")->value() = make_string_from_c(DebugBootUser);
if (DiskBoot) {
intern_from_c(-1, 0, "*kernel-boot-mode*")->value() = intern_from_c(-1, 0, "boot").offset;
}
if (strcmp(DebugBootLevel, "#f") == 0) {
intern_from_c(-1, 0, "*kernel-boot-level*")->value() = s7.offset;
} else {
ASSERT_NOT_REACHED();
}
intern_from_c(-1, 0, "*kernel-boot-art-group*")->value() = make_string_from_c(DebugBootArtGroup);
if (DiskBoot != 0) {
*EnableMethodSet = *EnableMethodSet + 1;
load_and_link_dgo_from_c("game", kglobalheap,
LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN,
0x400000, true);
*EnableMethodSet = *EnableMethodSet + -1;
using namespace jak3_symbols;
kernel_packages->value() =
new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE - 1).cast<u32>()),
make_string_from_c("engine"), kernel_packages->value());
kernel_packages->value() =
new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE - 1).cast<u32>()),
make_string_from_c("art"), kernel_packages->value());
kernel_packages->value() =
new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE - 1).cast<u32>()),
make_string_from_c("common"), kernel_packages->value());
printf("calling play-boot!\n");
call_goal_function_by_name("play-boot"); // new function for jak2!
}
}
} // namespace jak3

View File

@ -0,0 +1,8 @@
#pragma once
namespace jak3 {
void InitParms(int argc, const char* const* argv);
void InitMachineScheme();
int InitMachine();
int ShutdownMachine();
} // namespace jak3

View File

@ -0,0 +1,16 @@
#include "kmalloc.h"
namespace jak3 {
// these functions are all stubs in all known copies of the ELF.
void kmemopen_from_c(Ptr<kheapinfo> heap, const char* name) {
(void)heap;
(void)name;
}
void kmemopen(u32 heap, u32 name) {
(void)heap;
(void)name;
}
void kmemclose() {}
} // namespace jak3

View File

@ -0,0 +1,9 @@
#pragma once
#include "game/kernel/common/kmalloc.h"
namespace jak3 {
void kmemopen_from_c(Ptr<kheapinfo> heap, const char* name);
void kmemopen(u32 heap, u32 name);
void kmemclose();
} // namespace jak3

View File

@ -0,0 +1,3 @@
#include "kmemcard.h"

View File

@ -0,0 +1,3 @@
#pragma once
namespace jak3 {}

564
game/kernel/jak3/kprint.cpp Normal file
View File

@ -0,0 +1,564 @@
#include "kprint.h"
#include <cstdio>
#include <cstring>
#include "common/goal_constants.h"
#include "common/listener_common.h"
#include "common/symbols.h"
#include "game/kernel/common/fileio.h"
#include "game/kernel/common/kboot.h"
#include "game/kernel/common/klisten.h"
#include "game/kernel/common/kprint.h"
#include "game/kernel/jak3/kscheme.h"
#include "game/sce/sif_ee.h"
/*!
* The GOAL "format" function. The actual function is named "format". However, GOAL's calling
* convention differs from x86-64, so GOAL cannot directly call format. There is an assembly
* function in format_wrapper.nasm named format. It takes the GOAL argument registers, stores them
* in an array on the stack, and calls this function with a pointer to that array.
*
* This function is a disaster. For now, it's copied from jak1 and then jak 2, with the obvious
* fixes made, but it's probably worth another pass.
*/
s32 format_impl_jak3(uint64_t* args) {
using namespace jak3_symbols;
using namespace jak3;
// first two args are dest, format string
uint64_t* arg_regs = args + 2;
// data for arguments in a format command
format_struct argument_data[8];
u32 arg_reg_idx = 0;
// the gstring
char* format_gstring = Ptr<char>(args[1]).c();
u32 original_dest = args[0];
// set up print pending
char* print_temp = PrintPending.cast<char>().c();
if (!PrintPending.offset) {
print_temp = PrintBufArea.cast<char>().c() + sizeof(ListenerMessageHeader);
}
PrintPending = make_ptr(strend(print_temp)).cast<u8>();
// what we write to
char* output_ptr = PrintPending.cast<char>().c();
// convert gstring to cstring
char* format_cstring = format_gstring + 4;
// mysteries
char* PrintPendingLocal2 = PrintPending.cast<char>().c();
char* PrintPendingLocal3 = output_ptr;
// start by computing indentation
u32 indentation = 0;
// read goal binteger
if (print_column.offset) {
// added the if check so we can format even if the kernel didn't load right.
indentation = (*(print_column - 1)) >> 3;
}
// which arg we're on
u32 arg_idx = 0;
// if last char was newline and we have tabs, do tabs
if (indentation && output_ptr[-1] == '\n') {
for (u32 i = 0; i < indentation; i++) {
*output_ptr = ' ';
output_ptr++;
}
}
// input pointer
char* format_ptr = format_cstring;
// loop over the format string
while (*format_ptr) {
// got a command?
if (*format_ptr == '~') {
char* arg_start = format_ptr;
// get some arguments
arg_idx = 0;
u8 justify = 0;
for (auto& x : argument_data) {
x.reset();
}
// read arguments
while ((u8)(format_ptr[1] - '0') < 10 || // number 0 to 9
format_ptr[1] == ',' || // comma
format_ptr[1] == '\'' || // quote
format_ptr[1] == '`' || // backtick
(argument_data[arg_idx].data[0] == -1 &&
(format_ptr[1] == '-' || format_ptr[1] == '+') // flags1 == -1 && +/-
)) {
// here format_ptr[1] points to next unread character in argument
// format_ptr[0] is originally the ~
// should exit loop with format_ptr[1] == the command character
char arg_char = format_ptr[1]; // gVar1
if (arg_char == ',') {
// advance to next argument
arg_idx++; // increment which argument we're on
format_ptr++; // increment past comma, and try again
continue;
}
// character argument
if (arg_char == '\'') { // 0x27
argument_data[arg_idx].data[0] = format_ptr[2];
format_ptr += 2;
continue;
}
// string argument
if (arg_char == '`') { // 0x60
u32 i = 0;
format_ptr += 2;
// read string
while (*format_ptr != '`') {
argument_data[arg_idx].data[i] = *format_ptr;
i++;
format_ptr++;
}
// null terminate
argument_data[arg_idx].data[i] = 0;
continue;
}
if (arg_char == '-') { // 0x2d
// negative flag
argument_data[arg_idx].data[1] = 1;
format_ptr++;
continue;
}
if (arg_char == '+') { // 0x2b
// positive flag does nothing
format_ptr++;
continue;
}
// otherwise:
// null terminate if we got no args
if (argument_data[arg_idx].data[0] == -1) {
argument_data[arg_idx].data[0] = 0;
}
// otherwise it's a number
argument_data[arg_idx].data[0] = argument_data[arg_idx].data[0] * 10 + arg_char - '0';
format_ptr++;
} // end argument while
// switch on command
switch (format_ptr[1]) {
// offset of 0x25
case '%': // newline
*output_ptr = '\n';
output_ptr++;
// indent the next line if there is one
if (indentation && format_ptr[2]) {
for (u32 i = 0; i < indentation; i++) {
*output_ptr = ' ';
output_ptr++;
}
}
break;
case '~': // tilde escape
*output_ptr = '~';
output_ptr++;
break;
// pass through arguments
case 'H': // 23 -> 48, H
case 'J': // 25 -> 4A, J
case 'K': // 26 -> 4B, K
case 'L': // 27 -> 4C, L
case 'N': // 29 -> 4E, N
case 'V': // 31 -> 56, V
case 'W': // 32 -> 57, W
case 'Y': // 34 -> 59, Y
case 'Z': // 35 -> 5A, Z
case 'h':
case 'j':
case 'k':
case 'l':
case 'n':
case 'v':
case 'w':
case 'y':
case 'z':
while (arg_start < format_ptr + 1) {
*output_ptr = *arg_start;
arg_start++;
output_ptr++;
}
*output_ptr = format_ptr[1];
output_ptr++;
break;
case 'G': // like %s, prints a C string
case 'g': {
*output_ptr = 0;
u32 in = arg_regs[arg_reg_idx++];
kstrcat(output_ptr, Ptr<char>(in).c());
output_ptr = strend(output_ptr);
} break;
case 'O':
case 'o': {
*output_ptr = '~';
output_ptr++;
kitoa(output_ptr, arg_regs[arg_reg_idx++], 10, 0, ' ', 0);
output_ptr = strend(output_ptr);
*output_ptr = 'u';
output_ptr++;
} break;
case 'A': // print a boxed object
case 'a': // pad,padchar (like ) ~8,'0A
{
s8 arg0 = argument_data[0].data[0];
s32 desired_length = arg0;
*output_ptr = 0;
u32 in = arg_regs[arg_reg_idx++];
jak3::print_object(in);
if (desired_length != -1) {
s32 print_len = strlen(output_ptr);
if (desired_length < print_len) {
// too long!
if (desired_length > 1) { // mark with tilde that we will truncate
output_ptr[desired_length - 1] = '~';
}
output_ptr[desired_length] = 0; // and truncate
} else if (print_len < desired_length) {
// too short
if (justify == 0) {
char pad = ' ';
if (argument_data[1].data[0] != -1) {
pad = argument_data[1].data[0];
}
kstrinsert(output_ptr, pad, desired_length - print_len);
} else {
ASSERT(false);
// output_ptr = strend(output_ptr);
// while(0 < (desired_length - print_len)) {
// char pad = ' ';
// if(argument_data[0].data[1] != -1) {
// pad = argument_data[0].data[1];
// }
// output_ptr[0] = pad;
// output_ptr++;
//
// }
// *output_ptr = 0;
}
}
}
output_ptr = strend(output_ptr);
} break;
case 'S': // like A, but strings are printed without quotes
case 's': {
s8 arg0 = argument_data[0].data[0];
s32 desired_length = arg0;
*output_ptr = 0;
u32 in = arg_regs[arg_reg_idx++];
// if it's a string
if (((in & 0x7) == 0x4) && *Ptr<u32>(in - 4) == *(s7 + FIX_SYM_STRING_TYPE - 1)) {
cprintf("%s", Ptr<char>(in).c() + 4);
} else {
jak3::print_object(in);
}
if (desired_length != -1) {
s32 print_len = strlen(output_ptr);
if (desired_length < print_len) {
// too long!
if (desired_length > 1) { // mark with tilde that we will truncate
output_ptr[desired_length - 1] = '~';
}
output_ptr[desired_length] = 0; // and truncate
} else if (print_len < desired_length) {
// too short
if (justify == 0) {
char pad = ' ';
if (argument_data[1].data[0] != -1) {
pad = argument_data[1].data[0];
}
kstrinsert(output_ptr, pad, desired_length - print_len);
} else {
ASSERT(false);
// output_ptr = strend(output_ptr);
// u32 l140 = 0;
// while(l140 < (desired_length - print_len)) {
// char* l108 = output_ptr;
//
// char pad = ' ';
// if(argument_data[0].data[1] != -1) {
// pad = argument_data[0].data[1];
// }
// output_ptr[0] = pad;
// output_ptr++;
// }
// *output_ptr = 0;
}
}
}
output_ptr = strend(output_ptr);
} break;
case 'C': // character
case 'c':
*output_ptr = arg_regs[arg_reg_idx++];
output_ptr++;
break;
case 'P': // like ~A, but can specify type explicitly
case 'p': {
*output_ptr = 0;
s8 arg0 = argument_data[0].data[0];
u64 in = arg_regs[arg_reg_idx++];
if (arg0 == -1) {
jak3::print_object(in);
} else {
auto sym = jak3::find_symbol_from_c(-1, argument_data[0].data);
if (sym.offset) {
Ptr<Type> type(sym->value());
if (type.offset) {
call_method_of_type(in, type, GOAL_PRINT_METHOD);
}
} else {
ASSERT(false); // bad type.
}
}
output_ptr = strend(output_ptr);
} break;
case 'I': // like ~P, but calls inpsect
case 'i': {
*output_ptr = 0;
s8 arg0 = argument_data[0].data[0];
u64 in = arg_regs[arg_reg_idx++];
if (arg0 == -1) {
inspect_object(in);
} else {
auto sym = find_symbol_from_c(-1, argument_data[0].data);
if (sym.offset) {
Ptr<Type> type(sym->value());
if (type.offset) {
call_method_of_type(in, type, GOAL_INSPECT_METHOD);
}
} else {
ASSERT(false); // bad type
}
}
output_ptr = strend(output_ptr);
} break;
case 'Q': // not yet implemented. hopefully andy gavin finishes this one soon.
case 'q':
ASSERT(false);
break;
case 'X': // hex, 64 bit, pad padchar
case 'x': {
char pad = '0';
if (argument_data[1].data[0] != -1) {
pad = argument_data[1].data[0];
}
u64 in = arg_regs[arg_reg_idx++];
kitoa(output_ptr, in, 16, argument_data[0].data[0], pad, 0);
output_ptr = strend(output_ptr);
} break;
case 'D': // integer 64, pad padchar
case 'd': {
char pad = ' ';
if (argument_data[1].data[0] != -1) {
pad = argument_data[1].data[0];
}
u64 in = arg_regs[arg_reg_idx++];
kitoa(output_ptr, in, 10, argument_data[0].data[0], pad, 0);
output_ptr = strend(output_ptr);
} break;
case 'B': // integer 64, pad padchar
case 'b': {
char pad = '0';
if (argument_data[1].data[0] != -1) {
pad = argument_data[1].data[0];
}
u64 in = arg_regs[arg_reg_idx++];
kitoa(output_ptr, in, 2, argument_data[0].data[0], pad, 0);
output_ptr = strend(output_ptr);
} break;
case 'F': // float 12 pad, 4 precision
{
float in = *(float*)&arg_regs[arg_reg_idx++];
ftoa(output_ptr, in, 0xc, ' ', 4, 0);
output_ptr = strend(output_ptr);
} break;
case 'f': // float with args
{
float in = *(float*)&arg_regs[arg_reg_idx++];
s8 pad_length = argument_data[0].data[0];
s8 pad_char = argument_data[1].data[0];
if (pad_char == -1)
pad_char = ' ';
s8 precision = argument_data[2].data[0];
if (precision == -1)
precision = 4;
ftoa(output_ptr, in, pad_length, pad_char, precision, 0);
output_ptr = strend(output_ptr);
} break;
case 'R': // rotation degrees
case 'r': {
float in = *(float*)&arg_regs[arg_reg_idx++];
s8 pad_length = argument_data[0].data[0];
s8 pad_char = argument_data[1].data[0];
if (pad_char == -1)
pad_char = ' ';
s8 precision = argument_data[2].data[0];
if (precision == -1)
precision = 4;
ftoa(output_ptr, in * 360.f / 65536.f, pad_length, pad_char, precision, 0);
output_ptr = strend(output_ptr);
} break;
case 'M': // distance meters
case 'm': {
float in = *(float*)&arg_regs[arg_reg_idx++];
s8 pad_length = argument_data[0].data[0];
s8 pad_char = argument_data[1].data[0];
if (pad_char == -1)
pad_char = ' ';
s8 precision = argument_data[2].data[0];
if (precision == -1)
precision = 4;
ftoa(output_ptr, in / 4096.f, pad_length, pad_char, precision, 0);
output_ptr = strend(output_ptr);
} break;
case 'E': // time seconds
case 'e': {
s64 in = arg_regs[arg_reg_idx++];
s8 pad_length = argument_data[0].data[0];
s8 pad_char = argument_data[0].data[1];
if (pad_char == -1)
pad_char = ' ';
s8 precision = argument_data[0].data[2];
if (precision == -1)
precision = 4;
float value;
if (in < 0) {
ASSERT(false); // i don't get this one
} else {
value = in;
}
ftoa(output_ptr, value / 300.f, pad_length, pad_char, precision, 0);
output_ptr = strend(output_ptr);
} break;
case 'T':
case 't': {
sprintf(output_ptr, "\t");
output_ptr = strend(output_ptr);
} break;
default:
MsgErr("format: unknown code 0x%02x\n", format_ptr[1]);
MsgErr("input was %s\n", format_cstring);
// ASSERT(false);
goto copy_char_hack;
break;
}
format_ptr++;
} else {
// got normal char, just copy it
copy_char_hack: // we goto here if we get a bad code for ~, which sort of backtracks and falls
// back to regular character copying
*output_ptr = *format_ptr;
output_ptr++;
}
format_ptr++;
} // end format string while
// end
*output_ptr = 0;
output_ptr++;
if (original_dest == s7.offset + FIX_SYM_TRUE) {
// #t means to put it in the print buffer
// change for Jak 2: if we are disk-booting and do a (format #t, immediately flush to stdout.
// we'd get these eventually in ClearPending, but for some reason they flush these here.
// This is nicer because we may crash in between here and flushing the print buffer.
if (DiskBoot) {
// however, we are going to disable it anyway because it spams the console and is annoying
if (false) {
printf("%s", PrintPendingLocal3);
fflush(stdout);
}
PrintPending = make_ptr(PrintPendingLocal2).cast<u8>();
// if we don't comment this line, our output gets cleared
// *PrintPendingLocal3 = 0;
}
return 0;
} else if (original_dest == s7.offset + FIX_SYM_FALSE) {
// #f means print to new string
u32 string = make_string_from_c(PrintPendingLocal3);
PrintPending = make_ptr(PrintPendingLocal2).cast<u8>();
*PrintPendingLocal3 = 0;
return string;
} else if (original_dest == 0) {
printf("%s", PrintPendingLocal3);
fflush(stdout);
PrintPending = make_ptr(PrintPendingLocal2).cast<u8>();
*PrintPendingLocal3 = 0;
return 0;
} else {
if ((original_dest & OFFSET_MASK) == BASIC_OFFSET) {
Ptr<Type> type = *Ptr<Ptr<Type>>(original_dest - 4);
if (type == *Ptr<Ptr<Type>>(s7.offset + FIX_SYM_STRING_TYPE - 1)) {
u32 len = *Ptr<u32>(original_dest);
char* str = Ptr<char>(original_dest + 4).c();
kstrncat(str, PrintPendingLocal3, len);
PrintPending = make_ptr(PrintPendingLocal2).cast<u8>();
*PrintPendingLocal3 = 0;
return 0;
} else if (type == *Ptr<Ptr<Type>>(s7.offset + FIX_SYM_FILE_STREAM - 1)) {
size_t len = strlen(PrintPendingLocal3);
// sceWrite
ee::sceWrite(*Ptr<s32>(original_dest + 12), PrintPendingLocal3, len);
PrintPending = make_ptr(PrintPendingLocal2).cast<u8>();
*PrintPendingLocal3 = 0;
return 0;
}
}
ASSERT(false); // unknown destination
return 0;
}
ASSERT(false); // ??????
return 7;
}

12
game/kernel/jak3/kprint.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include "common/common_types.h"
namespace jak3 {
void output_sql_query(char* query_name);
}
// todo, do we actually have to do this, now that we aren't calling it from asm?
extern "C" {
s32 format_impl_jak3(uint64_t* args);
}

1986
game/kernel/jak3/kscheme.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,74 @@
#pragma once
#include "game/kernel/common/Ptr.h"
#include "game/kernel/common/Symbol4.h"
#include "game/kernel/common/kmalloc.h"
#include "game/kernel/common/kscheme.h"
namespace jak3 {
void kscheme_init_globals();
constexpr s32 SYMBOL_OFFSET = 1;
extern Ptr<u32> SymbolString;
extern bool DebugSymbols;
/*!
* GOAL Type
*/
struct Type {
Ptr<Symbol4<Ptr<Type>>> symbol; //! The type's symbol 0x0
Ptr<Type> parent; //! The type's parent 0x4
u16 allocated_size; //! The type's size in memory 0x8
u16 padded_size; //! The type's size, when padded? 0xa
u16 heap_base; //! relative location of heap 0xc
u16 num_methods; //! allocated-length field 0xe - 0xf
Ptr<Function> new_method; // 16 0
Ptr<Function> delete_method; // 20 1
Ptr<Function> print_method; // 24 2
Ptr<Function> inspect_method; // 28 3
Ptr<Function> length_method; // 32 4
Ptr<Function> asize_of_method; // 36 5
Ptr<Function> copy_method; // 40 6
Ptr<Function> relocate_method; // 44 7
Ptr<Function> memusage_method; // 48 8
Ptr<Function>& get_method(u32 i) {
Ptr<Function>* f = &new_method;
return f[i];
}
};
s64 load_and_link(const char* filename, char* decode_name, kheapinfo* heap, u32 flags);
Ptr<Symbol4<u32>> intern_from_c(int sym_id, int flags, const char* name);
u64 load(u32 /*file_name_in*/, u32 /*heap_in*/);
u64 loadb(u32 /*file_name_in*/, u32 /*heap_in*/, u32 /*param3*/);
u64 loadc(const char* /*file_name*/, kheapinfo* /*heap*/, u32 /*flags*/);
u64 loado(u32 file_name_in, u32 heap_in);
u64 unload(u32 name);
Ptr<Function> make_function_symbol_from_c(const char* name, void* f);
Ptr<Function> make_stack_arg_function_symbol_from_c(const char* name, void* f);
u64 print_object(u32 obj);
u64 inspect_object(u32 obj);
Ptr<Symbol4<u32>> find_symbol_from_c(uint16_t sym_id, const char* name);
u64 make_string_from_c(const char* c_str);
u64 call_method_of_type(u32 arg, Ptr<Type> type, u32 method_id);
u64 new_pair(u32 heap, u32 type, u32 car, u32 cdr);
u64 call_goal_function_by_name(const char* name);
Ptr<Type> intern_type_from_c(int a, int b, const char* name, u64 methods);
int InitHeapAndSymbol();
template <typename T>
Ptr<Ptr<String>> sym_to_string_ptr(Ptr<Symbol4<T>> in) {
return Ptr<Ptr<String>>(SymbolString.offset + in.offset - s7.offset);
}
template <typename T>
Ptr<String> sym_to_string(Ptr<Symbol4<T>> in) {
return *sym_to_string_ptr(in);
}
template <typename T>
const char* sym_to_cstring(Ptr<Symbol4<T>> in) {
return sym_to_string(in)->data();
}
} // namespace jak3

View File

@ -0,0 +1,3 @@
#include "ksocket.h"

View File

@ -0,0 +1,3 @@
#pragma once
namespace jak3 {}

View File

@ -0,0 +1,15 @@
#include "ksound.h"
#include "game/kernel/common/kdgo.h"
#include "game/kernel/jak3/kscheme.h"
namespace jak3 {
/*!
* Set up some functions which are somewhat related to sound.
*/
void InitSoundScheme() {
make_function_symbol_from_c("rpc-busy?", (void*)RpcBusy);
make_function_symbol_from_c("test-load-dgo-c", (void*)LoadDGOTest);
make_stack_arg_function_symbol_from_c("rpc-call", (void*)RpcCall_wrapper);
}
} // namespace jak3

View File

@ -0,0 +1,5 @@
#pragma once
namespace jak3 {
void InitSoundScheme();
}

View File

@ -1,29 +0,0 @@
kboot
---------
usleep in KernelCheckAndDispatch
kmachine
---------
rewrite InitParms to not use std::string
InitVideo
InitMachine
CPadGetData
InstallHandler
InstallDebugHandler
dma_to_iop
DecodeTime
PutDisplayEnv
kscheme
----------
remove the test function
add memory card stuff
read_clock_code
klink
-------
v2 support
kmemcard
---------
all of it, basically.

View File

@ -95,7 +95,6 @@ int main(int argc, char** argv) {
bool verbose_logging = false;
bool disable_avx2 = false;
bool disable_display = false;
bool enable_debug_vm = false;
bool enable_profiling = false;
std::string gpu_test = "";
std::string gpu_test_out_path = "";
@ -109,9 +108,8 @@ int main(int argc, char** argv) {
app.add_flag(
"--port", port_number,
"Specify port number for listener connection (default is 8112 for Jak 1 and 8113 for Jak 2)");
app.add_flag("--no-avx2", verbose_logging, "Disable AVX2 for testing");
app.add_flag("--no-avx2", disable_avx2, "Disable AVX2 for testing");
app.add_flag("--no-display", disable_display, "Disable video display");
app.add_flag("--vm", enable_debug_vm, "Enable debug PS2 VM (defaulted to off)");
app.add_flag("--profile", enable_profiling, "Enables profiling immediately from startup");
app.add_option("--gpu-test", gpu_test,
"Tests for minimum graphics requirements. Valid Options are: [opengl]");
@ -146,7 +144,6 @@ int main(int argc, char** argv) {
// Create struct with all non-kmachine handled args to pass to the runtime
GameLaunchOptions game_options;
game_options.disable_debug_vm = !enable_debug_vm;
game_options.disable_display = disable_display;
game_options.game_version = game_name_to_version(game_name);
game_options.server_port =

View File

@ -189,7 +189,7 @@ VagCmd* SmartAllocVagCmd(VagCmd* cmd) {
return selected;
}
void TerminateVAG(VagCmd* cmd, int param_2) {
void TerminateVAG(VagCmd* cmd, int /*param_2*/) {
VagStrListNode vag_node;
LfoListNode lfo_node;
// undefined4 auStack32 [2];
@ -386,7 +386,7 @@ void RestartVag(VagCmd* param_1, int param_2, int /*param_3*/) {
//}
}
void SetVAGVol(VagCmd* cmd, int param_2) {
void SetVAGVol(VagCmd* cmd, int /*param_2*/) {
VagCmd* stereo_cmd;
u32 lvol, rvol;

View File

@ -43,11 +43,17 @@
#include "game/kernel/common/kprint.h"
#include "game/kernel/common/kscheme.h"
#include "game/kernel/jak1/kboot.h"
#include "game/kernel/jak1/kdgo.h"
#include "game/kernel/jak1/klisten.h"
#include "game/kernel/jak1/kscheme.h"
#include "game/kernel/jak2/kboot.h"
#include "game/kernel/jak2/kdgo.h"
#include "game/kernel/jak2/klisten.h"
#include "game/kernel/jak2/kscheme.h"
#include "game/kernel/jak3/kboot.h"
#include "game/kernel/jak3/kdgo.h"
#include "game/kernel/jak3/klisten.h"
#include "game/kernel/jak3/kscheme.h"
#include "game/overlord/common/fake_iso.h"
#include "game/overlord/common/iso.h"
#include "game/overlord/common/sbank.h"
@ -74,8 +80,6 @@
#include "game/overlord/jak2/vag.h"
#include "game/system/Deci2Server.h"
#include "game/system/iop_thread.h"
#include "game/system/vm/dmac.h"
#include "game/system/vm/vm.h"
#include "sce/deci2.h"
#include "sce/iop.h"
#include "sce/libcdvd_ee.h"
@ -183,21 +187,28 @@ void ee_runner(SystemThreadInterface& iface) {
fileio_init_globals();
jak1::kboot_init_globals();
jak2::kboot_init_globals();
jak3::kboot_init_globals();
kboot_init_globals_common();
kdgo_init_globals();
jak1::kdgo_init_globals();
jak2::kdgo_init_globals();
jak3::kdgo_init_globals();
kdsnetm_init_globals_common();
klink_init_globals();
kmachine_init_globals_common();
jak1::kscheme_init_globals();
jak2::kscheme_init_globals();
jak3::kscheme_init_globals();
kscheme_init_globals_common();
kmalloc_init_globals_common();
klisten_init_globals();
jak1::klisten_init_globals();
jak2::klisten_init_globals();
jak3::klisten_init_globals();
jak2::vag_init_globals();
@ -216,6 +227,8 @@ void ee_runner(SystemThreadInterface& iface) {
case GameVersion::Jak2:
jak2::goal_main(g_argc, g_argv);
break;
case GameVersion::Jak3:
jak3::goal_main(g_argc, g_argv);
default:
ASSERT_MSG(false, "Unsupported game version");
}
@ -309,6 +322,7 @@ void iop_runner(SystemThreadInterface& iface, GameVersion version) {
jak1::start_overlord_wrapper(iop.overlord_argc, iop.overlord_argv, &complete);
break;
case GameVersion::Jak2:
case GameVersion::Jak3: // TODO: jak3 using jak2's overlord.
jak2::start_overlord_wrapper(iop.overlord_argc, iop.overlord_argv, &complete);
break;
default:
@ -349,32 +363,6 @@ void null_runner(SystemThreadInterface& iface) {
iface.initialization_complete();
}
/*!
* SystemThread function for running the PS2 DMA controller.
* This does not actually emulate the DMAC, it only fakes its existence enough that we can debug
* the DMA packets the original game sends. The port will replace all DMAC code.
*/
void dmac_runner(SystemThreadInterface& iface) {
VM::subscribe_component();
VM::dmac_init_globals();
iface.initialization_complete();
while (!iface.get_want_exit() && !VM::vm_want_exit()) {
// for (int i = 0; i < 10; ++i) {
// if (VM::dmac_ch[i]->chcr.str) {
// // lg::info("DMA detected on channel {}, clearing", i);
// VM::dmac_ch[i]->chcr.str = 0;
// }
// }
// avoid running the DMAC on full blast (this does not sync to its clockrate)
std::this_thread::sleep_for(std::chrono::microseconds(50000));
}
VM::unsubscribe_component();
}
/*!
* Main function to launch the runtime.
* GOAL kernel arguments are currently ignored.
@ -386,7 +374,6 @@ RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const c
g_main_thread_id = std::this_thread::get_id();
bool enable_display = !game_options.disable_display;
VM::use = !game_options.disable_debug_vm;
g_game_version = game_options.game_version;
g_server_port = game_options.server_port;
@ -417,7 +404,6 @@ RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const c
// step 2: system prep
prof().begin_event("startup::exec_runtime::system_prep");
VM::vm_prepare(); // our fake ps2 VM needs to be prepared
SystemThreadManager tm;
auto& deci_thread = tm.create_thread("DMP");
auto& iop_thread = tm.create_thread("IOP");
@ -425,7 +411,6 @@ RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const c
// a general worker thread to perform background operations from the EE thread (to not block the
// game)
auto& ee_worker_thread = tm.create_thread("EE-Worker");
auto& vm_dmac_thread = tm.create_thread("VM-DMAC");
prof().end_event();
// step 3: start the EE!
@ -445,10 +430,6 @@ RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const c
auto p = scoped_prof("startup::exec_runtime::ee-start");
ee_thread.start(ee_runner);
}
if (VM::use) {
auto p = scoped_prof("startup::exec_runtime::dmac-start");
vm_dmac_thread.start(dmac_runner);
}
// step 4: wait for EE to signal a shutdown. meanwhile, run video loop on main thread.
// TODO relegate this to its own function

View File

@ -27,7 +27,7 @@ void FileAttributes::Read(BinaryReader& data) {
num_chunks = data.read<u32>();
where.resize(num_chunks);
for (int i = 0; i < num_chunks; i++) {
for (size_t i = 0; i < num_chunks; i++) {
where[i].offset = data.read<u32>();
where[i].size = data.read<u32>();
}
@ -215,16 +215,16 @@ SFXBlock* SFXBlock::ReadBlock(nonstd::span<u8> bank_data, nonstd::span<u8> sampl
s16 NumSounds = data.read<s16>();
block->Sounds.resize(NumSounds);
s16 NumGrains = data.read<s16>();
s16 NumVAGs = data.read<s16>();
[[maybe_unused]] s16 NumGrains = data.read<s16>();
[[maybe_unused]] s16 NumVAGs = data.read<s16>();
u32 FirstSound = data.read<u32>();
u32 FirstGrain = data.read<u32>();
u32 VagsInSR = data.read<u32>();
u32 VagDataSize = data.read<u32>();
u32 SRAMAllocSize = data.read<u32>();
u32 NextBlock = data.read<u32>();
[[maybe_unused]] u32 VagsInSR = data.read<u32>();
[[maybe_unused]] u32 VagDataSize = data.read<u32>();
[[maybe_unused]] u32 SRAMAllocSize = data.read<u32>();
[[maybe_unused]] u32 NextBlock = data.read<u32>();
u32 GrainData = 0;
if (block->Version >= 2) {
GrainData = data.read<u32>();
@ -344,12 +344,12 @@ MusicBank* MusicBank::ReadBank(nonstd::span<u8> bank_data,
bank->Sounds.resize(NumSounds);
s16 NumProgs = data.read<s16>();
bank->Progs.resize(NumProgs);
s16 NumTones = data.read<s16>();
s16 NumVAGs = data.read<s16>();
[[maybe_unused]] s16 NumTones = data.read<s16>();
[[maybe_unused]] s16 NumVAGs = data.read<s16>();
u32 FirstSound = data.read<u32>();
u32 FirstProg = data.read<u32>();
u32 FirstTone = data.read<u32>();
[[maybe_unused]] u32 FirstTone = data.read<u32>();
// vagsinsr
data.read<u32>();

View File

@ -377,8 +377,8 @@ InputBindingInfo::InputBindingInfo(const InputBinding bind,
const bool _analog_button)
: sdl_idx(sdl_code),
pad_idx(bind.pad_data_index),
modifiers(bind.modifiers),
analog_button(_analog_button) {
analog_button(_analog_button),
modifiers(bind.modifiers) {
switch (device_type) {
case CONTROLLER:
if (analog_button) {

View File

@ -1,32 +0,0 @@
/*!
* @file dmac.cpp
* DMAC implementation for the "PS2 virtual machine".
* Not meant to work as a full DMAC emulator, just enough to inspect DMA packets.
*/
#include "dmac.h"
#include "vm.h"
#include "common/log/log.h"
#include "game/kernel/common/kmalloc.h"
#include "game/runtime.h"
namespace VM {
Ptr<DmaCommonRegisters> dmac;
Ptr<DmaChannelRegisters> dmac_ch[10];
void dmac_init_globals() {
dmac = kmalloc(kdebugheap, sizeof(DmaCommonRegisters), KMALLOC_ALIGN_16 | KMALLOC_MEMSET, "dmac")
.cast<DmaCommonRegisters>();
for (int i = 0; i < 10; ++i) {
dmac_ch[i] =
kmalloc(kdebugheap, sizeof(DmaChannelRegisters), KMALLOC_ALIGN_16 | KMALLOC_MEMSET, "dmach")
.cast<DmaChannelRegisters>();
}
}
} // namespace VM

View File

@ -1,73 +0,0 @@
#pragma once
/*!
* @file dmac.h
* DMAC implementation for the "PS2 virtual machine".
* Not meant to work as a full DMAC emulator, just enough to inspect DMA packets.
*/
#include "common/common_types.h"
#include "game/kernel/common/Ptr.h"
namespace VM {
/*!
* DMA channel CHCR register. The only one we care about right now.
*/
struct DmaChcr {
u32 dir : 1;
u32 _pad1 : 1;
u32 mod : 2;
u32 asp : 2;
u32 tte : 1;
u32 tie : 1;
u32 str : 1;
u32 _pad2 : 7;
u16 tag : 16;
};
/*!
* Layout of the DMA channel registers in EE memory. For simplicity's sake, all are included,
* however, each channel may not actually have some of these registers.
* They are 16-byte aligned.
*/
struct alignas(16) DmaChannelRegisters {
alignas(16) DmaChcr chcr;
alignas(16) u32 madr;
alignas(16) u32 qwc;
alignas(16) u32 tadr;
alignas(16) u32 asr0;
alignas(16) u32 asr1;
alignas(16) u128 _pad1;
alignas(16) u128 _pad2;
alignas(16) u32 sadr;
};
/*!
* Layout of the DMAC registers in EE memory.
* They are 16-byte aligned.
*/
struct alignas(16) DmaCommonRegisters {
alignas(16) u32 ctrl;
alignas(16) u32 stat;
alignas(16) u32 pcr;
alignas(16) u32 sqwc;
alignas(16) u32 rbsr;
alignas(16) u32 rbor;
alignas(16) u32 stadr;
};
// pointer to DMAC registers
extern Ptr<DmaCommonRegisters> dmac;
// array of pointers to DMAC channels (they are not stored contiguously)
extern Ptr<DmaChannelRegisters> dmac_ch[10];
// enum DmaChannel { VIF0, VIF1, GIF, fromIPU, toIPU, SIF0, SIF1, SIF2, fromSPR, toSPR };
static_assert(sizeof(DmaChannelRegisters) == 0x90, "DmaChannelRegisters wrong size");
static_assert(alignof(DmaChannelRegisters) == 0x10, "DmaChannelRegisters unaligned");
void dmac_init_globals();
} // namespace VM

View File

@ -1,140 +0,0 @@
/*!
* @file vm.cpp
* Base "PS2 virtual machine" code.
* Simulates the existence of select PS2 components, for inspection & debugging.
* Not an emulator!
*/
#include "vm.h"
#include <condition_variable>
#include <mutex>
#include "dmac.h"
#include "common/log/log.h"
namespace VM {
bool use = true; // enable VM by default, since we're debugging right now
namespace {
Status status;
std::condition_variable vm_init_cv;
std::condition_variable vm_dead_cv;
std::mutex status_mutex;
int components = 0;
} // namespace
static void vm_change_status(Status new_status) {
std::unique_lock<std::mutex> lk(status_mutex);
status = new_status;
}
void wait_vm_init() {
std::unique_lock<std::mutex> lk(status_mutex);
vm_init_cv.wait(lk, [&] { return status == Status::Inited; });
}
void wait_vm_dead() {
if (status != Status::Kill && status != Status::Dead) {
lg::warn("[VM] Dying without being killed! There are {} component(s) running", components);
}
std::unique_lock<std::mutex> lk(status_mutex);
vm_dead_cv.wait(lk, [&] { return components == 0; });
}
bool vm_want_exit() {
return status == Status::Kill || status == Status::Dead;
}
void vm_prepare() {
lg::debug("[VM] Preparing...");
vm_change_status(Status::Uninited);
lg::debug("[VM] Prepared");
}
void vm_init() {
if (status != Status::Uninited) {
lg::warn("[VM] unexpected status {}", fmt::underlying(status));
}
lg::debug("[VM] Inited");
vm_change_status(Status::Inited);
vm_init_cv.notify_all();
}
void vm_kill() {
lg::debug("[VM] Killing");
vm_change_status(Status::Kill);
// stall caller until VM is done dying
wait_vm_dead();
vm_change_status(Status::Dead);
}
void subscribe_component() {
if (status == Status::Dead) {
throw std::runtime_error("[VM] Cannot add new components when VM is dead!");
}
status_mutex.lock();
++components;
status_mutex.unlock();
// stall component until VM is ready
if (status == Status::Uninited) {
wait_vm_init();
}
}
void unsubscribe_component() {
status_mutex.lock();
--components;
status_mutex.unlock();
vm_dead_cv.notify_all();
}
/*!
* Return the GOAL pointer to a specified PS2 VM component based on the EE address.
*/
u64 get_vm_ptr(u32 ptr) {
// currently, only DMAC and DMA channel banks are implemented. add more as necessary.
if (ptr == 0x10008000) {
return VM::dmac_ch[0].offset;
} else if (ptr == 0x10009000) {
return VM::dmac_ch[1].offset;
} else if (ptr == 0x1000a000) {
return VM::dmac_ch[2].offset;
} else if (ptr == 0x1000b000) {
return VM::dmac_ch[3].offset;
} else if (ptr == 0x1000b400) {
return VM::dmac_ch[4].offset;
} else if (ptr == 0x1000c000) {
return VM::dmac_ch[5].offset;
} else if (ptr == 0x1000c400) {
return VM::dmac_ch[6].offset;
} else if (ptr == 0x1000c800) {
return VM::dmac_ch[7].offset;
} else if (ptr == 0x1000d000) {
return VM::dmac_ch[8].offset;
} else if (ptr == 0x1000d400) {
return VM::dmac_ch[9].offset;
} else if (ptr == 0x1000e000) {
return VM::dmac.offset;
} else {
// return zero, using this result will segfault GOAL!
// we could die immediately, but it might be worth it to keep going just on the off chance more
// errors are reported, and not just only this one.
lg::error("unknown EE register for VM at #x{:08x}", ptr);
return 0;
}
}
} // namespace VM

View File

@ -1,32 +0,0 @@
#pragma once
/*!
* @file vm.h
* Base "PS2 virtual machine" code.
* Simulates the existence of select PS2 components, for inspection & debugging.
* Not an emulator!
*/
#include "common/common_types.h"
namespace VM {
extern bool use;
enum class Status { Disabled, Uninited, Inited, Kill, Dead };
void wait_vm_init();
void wait_vm_dead();
bool vm_want_exit();
void vm_prepare();
void vm_init();
void vm_kill();
void subscribe_component();
void unsubscribe_component();
u64 get_vm_ptr(u32 ptr);
} // namespace VM

View File

@ -34,4 +34,4 @@
((basic? ,obj) (-> (the basic ,obj) type))
(else symbol)
)
)
)

View File

@ -127,6 +127,8 @@
(define-extern kernel-shutdown (function none))
(define-extern *boot-video-mode* "Defined in the kernel" int)
(define-extern *kernel-boot-message* symbol)
(define-extern *kernel-symbol-warnings* symbol)
(define-extern symbol->string (function symbol string))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; PC Port functions

File diff suppressed because it is too large Load Diff

View File

@ -7,3 +7,615 @@
;; DECOMP BEGINS
(defconstant *kernel-major-version* 2)
(defconstant *kernel-minor-version* 0)
(defconstant DPROCESS_STACK_SIZE (#if PC_PORT #x8000 #x3800))
(defconstant PROCESS_STACK_SIZE (#if PC_PORT #x4000 #x1c00))
;; the size of the shared heap used by dynamically sized processes
(#if PC_BIG_MEMORY
(defconstant PROCESS_HEAP_MULT 4) ;; 4x actors
(defconstant PROCESS_HEAP_MULT 1)
)
(defconstant PROCESS_HEAP_SIZE (* PROCESS_HEAP_MULT 1540 1024))
(defconstant PROCESS_HEAP_MAX (* PROCESS_HEAP_MULT 768))
(defconstant *tab-size* (the binteger 8))
(defconstant *gtype-basic-offset* 4)
;; if set, will attempt to detect memory corruption and stack overflow bugs
;; to some extent.
(defglobalconstant KERNEL_DEBUG #t)
(defconstant *scratch-memory-top* (the pointer #x70004000))
(defenum process-mask
:type uint32
:bitfield #t
(execute 0) ;; 1
(freeze 1)
(pause 2)
(menu 3)
(progress 4)
(actor-pause 5) ;; 32
(sleep 6) ;; 64
(sleep-code 7) ;; 128
(process-tree 8) ;; 256
(heap-shrunk 9) ;; 512
(going 10) ;; 1024
(kernel-run 11) ;; 2048
(no-kill 12) ;; 4096
(movie 13) ;; 8192
(dark-effect 14) ;; 0x4000
(target 15) ;; 0x8000
(sidekick 16) ;; 0x1'0000
(crate 17) ;; 0x2'0000
(collectable 18) ;; 0x4'0000
(enemy 19) ;; 0x8'0000
(camera 20) ;; 0x10'0000
(platform 21) ;; 0x20'0000
(ambient 22) ;; 0x40'0000
(entity 23) ;; 0x80'0000
(projectile 24) ;; 0x100'0000
(bot 25) ;; 0x200'0000
(death 26) ;; 0x400'0000
(guard 27) ;; 0x800'0000
(vehicle 28) ;; 0x1000'0000
(civilian 29) ;; 0x2000'0000
(kg-robot 30) ;; 0x4000'0000
(metalhead 31) ;; 0x8000'0000
)
;; forward declarations
(declare-type process-tree basic)
(declare-type process process-tree)
(declare-type res-lump basic)
(declare-type entity res-lump)
(declare-type entity-actor entity)
(declare-type dead-pool basic)
(declare-type level basic)
(declare-type state basic)
(declare-type event-message-block structure)
(declare-type stack-frame basic)
(declare-type thread basic)
(declare-type cpu-thread thread)
(deftype kernel-context (basic)
((prevent-from-run process-mask)
(require-for-run process-mask)
(allow-to-run process-mask)
(next-pid int32)
(fast-stack-top pointer)
(current-process process)
(relocating-process basic)
(relocating-min int32)
(relocating-max int32)
(relocating-offset int32)
(relocating-level level)
(low-memory-message symbol)
(login-object basic)
(login-art-group basic)
(login-level-index int32)
)
)
(deftype time-frame (int64)
()
)
;; times are stored in 300ths of a second.
;; this divides evenly into frames at both 50 and 60 fps.
;; typically these are stored as integers as more precision is not useful.
;; an unsigned 32-bit integer can store about 150 days
(defglobalconstant TICKS_PER_SECOND 300) ;; 5 t/frame @ 60fps, 6 t/frame @ 50fps
;; this was usec in GOAL
(defmacro seconds (x)
"Convert number to seconds unit.
Returns uint."
(cond
((integer? x)
(* TICKS_PER_SECOND x)
)
((float? x)
(* 1 (* 1.0 x TICKS_PER_SECOND))
)
(#t
`(the uint (* TICKS_PER_SECOND ,x))
)
)
)
(deftype clock (basic)
((index int16)
(ref-count uint16)
(mask process-mask)
(clock-ratio float)
(accum float)
(integral-accum float)
(frame-counter uint64)
(old-frame-counter uint64)
(integral-frame-counter uint64)
(old-integral-frame-counter uint64)
(sparticle-data vector :inline)
(seconds-per-frame float)
(frames-per-second float)
(time-adjust-ratio float)
)
(:methods
(new (symbol type int) _type_)
(update-rates! (_type_ float) float)
(clock-method-10 () none)
(clock-method-11 () none)
(clock-method-12 () none)
(clock-method-13 () none)
(clock-method-14 () none)
(clock-method-15 () none)
(frame-mask-2 (_type_ int) symbol)
(frame-mask-4 (_type_ int) symbol)
(frame-mask-8 (_type_ int) symbol)
(frame-mask-16 (_type_ int) symbol)
(frame-mask-32 (_type_ int) symbol)
(frame-mask-64 (_type_ int) symbol)
(frame-mask-128 (_type_ int) symbol)
(frame-mask-256 (_type_ int) symbol)
)
)
(defmethod frame-mask-2 ((this clock) (arg0 int))
(not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 1))
)
(defmethod frame-mask-4 ((this clock) (arg0 int))
(not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 3))
)
(defmethod frame-mask-8 ((this clock) (arg0 int))
(not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 7))
)
(defmethod frame-mask-16 ((this clock) (arg0 int))
(not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 15))
)
(defmethod frame-mask-32 ((this clock) (arg0 int))
(not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 31))
)
(defmethod frame-mask-64 ((this clock) (arg0 int))
(not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 63))
)
(defmethod frame-mask-128 ((this clock) (arg0 int))
(not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 127))
)
(defmethod frame-mask-256 ((this clock) (arg0 int))
(not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 255))
)
(defmethod new clock ((allocation symbol) (type-to-make type) (arg0 int))
(let ((gp-0 (object-new allocation type-to-make (the-as int (-> type-to-make size)))))
(set! (-> gp-0 index) arg0)
(set! (-> gp-0 frame-counter) (the-as uint #x493e0))
(set! (-> gp-0 integral-frame-counter) (the-as uint #x493e0))
(set! (-> gp-0 old-frame-counter) (+ (-> gp-0 frame-counter) -1))
(set! (-> gp-0 old-integral-frame-counter) (+ (-> gp-0 integral-frame-counter) -1))
(update-rates! gp-0 1.0)
gp-0
)
)
(deftype process-tree (basic)
((name string)
(mask process-mask)
(clock clock)
(parent (pointer process-tree))
(brother (pointer process-tree))
(child (pointer process-tree))
(ppointer (pointer process))
(self process-tree)
)
(:methods
(new (symbol type string) _type_)
(activate (_type_ process-tree basic pointer) process-tree)
(deactivate (_type_) none)
(init-from-entity! (_type_ entity-actor) none) ;; todo check
(run-logic? (_type_) symbol)
(process-tree-method-13 () none)
)
:no-runtime-type
)
(deftype thread (basic)
((name symbol)
(process process)
(previous thread)
(suspend-hook (function cpu-thread none))
(resume-hook (function cpu-thread none))
(pc pointer)
(sp pointer)
(stack-top pointer)
(stack-size int32)
)
(:methods
(thread-method-9 () none)
(thread-method-10 () none)
(thread-method-11 () none)
)
)
(deftype cpu-thread (thread)
((rreg uint64 7)
(freg float 8)
(stack uint8 :dynamic)
)
(:methods
(new (symbol type process symbol int pointer) _type_)
)
)
(deftype dead-pool (process-tree)
()
(:methods
(dead-pool-method-14 () none)
(dead-pool-method-15 () none)
)
)
(deftype dead-pool-heap-rec (structure)
((process process)
(prev dead-pool-heap-rec)
(next dead-pool-heap-rec)
)
:pack-me
)
(deftype dead-pool-heap (dead-pool)
((allocated-length int32)
(compact-time uint32)
(compact-count-targ uint32)
(compact-count uint32)
(fill-percent float)
(first-gap dead-pool-heap-rec)
(first-shrink dead-pool-heap-rec)
(heap kheap :inline)
(alive-list dead-pool-heap-rec :inline)
(last dead-pool-heap-rec :overlay-at (-> alive-list prev))
(dead-list dead-pool-heap-rec :inline)
(process-list dead-pool-heap-rec :dynamic)
)
(:methods
(dead-pool-heap-method-16 () none)
(dead-pool-heap-method-17 () none)
(dead-pool-heap-method-18 () none)
(dead-pool-heap-method-19 () none)
(dead-pool-heap-method-20 () none)
(dead-pool-heap-method-21 () none)
(dead-pool-heap-method-22 () none)
(dead-pool-heap-method-23 () none)
(dead-pool-heap-method-24 () none)
(dead-pool-heap-method-25 () none)
(dead-pool-heap-method-26 () none)
(dead-pool-heap-method-27 () none)
)
)
(deftype stack-frame (basic)
((name symbol)
(next stack-frame)
)
)
(deftype catch-frame (stack-frame)
((sp int32)
(ra int32)
(freg float 10)
(rreg uint128 7)
)
(:methods
(new (symbol type symbol function (pointer uint64)) object)
)
)
(deftype protect-frame (stack-frame)
((exit (function object))
)
)
(deftype handle (uint64)
((process (pointer process) :offset 0 :size 32)
(pid int32 :offset 32 :size 32)
(u64 uint64 :offset 0 :size 64)
)
)
(defmacro handle->process (handle)
"Convert a handle to a process. If the process no longer exists, returns #f."
`(let ((the-handle (the-as handle ,handle)))
(if (-> the-handle process) ;; if we don't point to a process, kernel sets this to #f
(let ((proc (-> (-> the-handle process))))
(if (= (-> the-handle pid) (-> proc pid)) ;; make sure it's the same process
proc
)
)
)
)
)
(defmethod inspect handle ((this handle))
(when (not this)
(return this)
)
(format #t "[~8x] ~A~%" this 'handle)
(format #t "~1Tprocess: #x~X~%" (-> this process))
(format #t "~1Tpid: ~D~%" (-> this pid))
this
)
; (defmethod print ((this handle))
; (if (nonzero? this)
; (format #t "#<handle :process ~A :pid ~D>" (handle->process this) (-> this pid))
; (format #t "#<handle :process 0 :pid 0>")
; )
; this
; )
(defmacro ppointer->process (ppointer)
"convert a (pointer process) to a process."
;; this uses the self field, which seems to always just get set to the object.
;; confirmed in Jak 1 that using self here is useless, not sure...
`(let ((the-pp ,ppointer))
(if the-pp (-> the-pp 0 self))
)
)
(defmacro process->ppointer (proc)
"safely get a (pointer process) from a process, returning #f if invalid."
`(let ((the-proc ,proc))
(if the-proc (-> the-proc ppointer))
)
)
(defmacro ppointer->handle (pproc)
"convert a ppointer to a handle. assumes the ppointer is valid."
`(let ((the-process (the-as (pointer process) ,pproc)))
(new 'static 'handle :process the-process :pid (if the-process (-> the-process 0 pid)))
)
)
(defmacro process->handle (proc)
"convert a process to a handle. if proc is #f, returns a #f handle."
`(ppointer->handle (process->ppointer (the-as process ,proc)))
)
(deftype state (protect-frame)
((parent basic)
(code function)
(trans (function object))
(post function)
(enter function)
(event (function process int symbol event-message-block object))
)
)
(deftype event-message-block (structure)
((to-handle uint64)
(to (pointer process) :overlay-at to-handle)
(from-handle uint64)
(from (pointer process) :overlay-at from-handle)
(param uint64 6)
(message symbol)
(num-params int32)
)
)
(deftype event-message-block-array (inline-array-class)
((data event-message-block :dynamic)
)
(:methods
(event-message-block-array-method-14 () none)
)
)
(set! (-> event-message-block-array heap-base) (the-as uint 80))
(deftype sql-result (array)
((result-data object :dynamic :offset 16)
)
)
; (defmethod new sql-result ((allocation symbol) (type-to-make type) (arg0 type) (arg1 int))
; (let ((v0-0 (object-new allocation type-to-make (the-as int (+ (-> type-to-make size) (* arg0 4))))))
; (set! (-> v0-0 allocated-length) (the-as int arg0))
; (set! (-> v0-0 content-type) (the-as type 'error))
; v0-0
; )
; )
(defmethod print ((this sql-result))
(format #t "#(~A" (-> this content-type))
(dotimes (s5-0 (-> this length))
(format #t " ~A" (-> this result-data s5-0))
)
(format #t ")")
this
)
(define *sql-result* (the-as sql-result #f))
;; failed to figure out what this is:
0
(defmacro defbehavior (name process-type bindings &rest body)
"define a new behavior. This is simply a function where self is bound to the process register,
which is assumed to have type process-type."
(if (and
(> (length body) 1) ;; more than one thing in function
(string? (first body)) ;; first thing is a string
)
;; then it's a docstring and we ignore it.
`(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@(cdr body)))
;; otherwise don't ignore it.
`(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@body))
)
)
(defmacro process-stack-used (proc)
"get how much stack the top thread of a process has used."
`(- (the int (-> ,proc top-thread stack-top))
(the int (-> ,proc top-thread sp))
)
)
(defmacro process-stack-size (proc)
"get how much stack the top thread of a process has"
`(-> ,proc top-thread stack-size)
)
(defmacro process-heap-used (proc)
"get how much heap a process has used."
`(- (-> ,proc allocated-length)
(- (the int (-> ,proc heap-top))
(the int (-> ,proc heap-cur))
)
)
)
(defmacro process-heap-size (proc)
"get how much heap a process has"
`(the int (-> ,proc allocated-length))
)
(defmacro break ()
"crash the game by dividing by 0."
`(/ 0 0)
)
(defmacro with-pp (&rest body)
"execute the body with pp bound to the current process register."
`(rlet ((pp :reg r13 :reset-here #t :type process))
,@body)
)
(defconstant PP (with-pp pp))
(defmacro process-mask? (mask enum-value)
"Are any of the given bits set in the process mask?"
`(!= 0 (logand ,mask (process-mask ,enum-value)))
)
(defmacro process-mask-set! (mask &rest enum-value)
"Set the given bits in the process mask"
`(logior! ,mask (process-mask ,@enum-value))
)
(defmacro process-mask-clear! (mask &rest enum-value)
"Clear the given bits in the process mask."
`(logclear! ,mask (process-mask ,@enum-value))
)
(defmacro suspend ()
"suspend the current process, to be resumed on the next frame."
`(rlet ((pp :reg r13 :reset-here #t))
;; debug check for stack overflow here, where we can easily print the process name.
(#when (or KERNEL_DEBUG)
(rlet ((sp :reg rsp :reset-here #t :type int)
(off :reg r15 :type uint))
(let* ((sp-goal (- sp off))
(stack-top-goal (-> (the process pp) top-thread stack-top))
(stack-used (&- stack-top-goal sp-goal))
(stack-size (-> (the process pp) top-thread stack-size))
)
(when (> stack-used stack-size)
(format 0 "ERROR: suspend called without enough stack in proc:~%~A~%Stack: ~D/~D~%" pp stack-used stack-size)
)
)
)
)
;; set to the current thread
(set! pp (-> (the process pp) top-thread))
;; call the suspend hook (put nothing as the argument)
((-> (the cpu-thread pp) suspend-hook) (the cpu-thread 0))
;; the kernel will set pp (possibly to a new value, if we've been relocated) on resume.
)
)
(defmacro process-deactivate ()
"deactivate (kill) the current process"
`(rlet ((pp :reg r13 :reset-here #t :type process))
(deactivate pp)
)
)
;; Some assembly functions in GOAL are ported to C++, then accessed from GOAL using these mips2c macros.
(defmacro def-mips2c (name type)
"Define a mips2c object (typically a function)."
`(begin
(define-extern ,name ,type)
(set! ,name (the-as ,type (__pc-get-mips2c ,(symbol->string name))))
)
)
(defmacro defmethod-mips2c (name method-id method-type)
"Define a mips2c method."
`(method-set! ,method-type ,method-id (__pc-get-mips2c ,name))
)
(defmacro kheap-alloc (heap size)
"allocate space for a kheap"
`(let ((heap ,heap) (size ,size))
(set! (-> heap base) (malloc 'global size))
(set! (-> heap current) (-> heap base))
(set! (-> heap top-base) (&+ (-> heap base) size))
(set! (-> heap top) (-> heap top-base))
)
)
(defmacro kheap-reset (heap)
"reset the kheap, so you can use its memory again"
`(let ((heap ,heap))
(set! (-> heap current) (-> heap base))
)
)
(defmacro scratchpad-object (type &key (offset 0))
"Access an object on the scratchpad."
`(the-as ,type (&+ *fake-scratchpad-data* ,offset))
)
(defmacro scratchpad-ptr (type &key (offset 0))
"Create a pointer to an object on the scratchpad."
`(the-as (pointer ,type) (&+ *fake-scratchpad-data* ,offset))
)
(defmacro current-time ()
`(-> PP clock frame-counter)
)
(defmacro seconds-per-frame ()
`(-> PP clock seconds-per-frame)
)
(defmacro seconds-per-frame-high-fps ()
"Macro for assuming a 16.6 ms frame time at higher frame rates."
`(if (= (get-video-mode) 'custom)
0.016666668
(-> PP clock seconds-per-frame)
)
)
(defmacro set-time! (time)
`(set! ,time (current-time))
)
(defmacro time-elapsed? (time duration)
`(>= (- (current-time) ,time) ,duration)
)

View File

@ -6,4 +6,25 @@
;; dgos: KERNEL
;; DECOMP BEGINS
(define *use-old-listener-print* #f)
(define *kernel-version* (the binteger (logior (ash *kernel-major-version* 16) *kernel-minor-version*)))
(defun kernel-dispatcher ()
"Temporary kernel dispatcher for now."
;; execute the listener function, if we got one.
(when *listener-function*
(+! *enable-method-set* 1) ;; allow out-of-order method definitions (slower)
;; (let ((result (reset-and-call (-> *listener-process* main-thread) *listener-function*))) ;; run function!
(let ((result (*listener-function*)))
;; print result.
(if *use-old-listener-print*
(format #t "~D~%" result result result)
(format #t "~D #x~X ~F ~A~%" result result result result)
)
)
;; clear pending function
(set! *listener-function* #f)
(+! *enable-method-set* -1)
)
)

View File

@ -493,10 +493,7 @@ void ObjectGenerator::emit_link_type_pointer(int seg, const TypeSystem* ts) {
out.push_back(ts->get_type_method_count(rec.first));
break;
case GameVersion::Jak2:
// the linker/intern_type functions do the +3.
out.push_back(ts->get_type_method_count(rec.first) / 4);
break;
case GameVersion::Jak3:
case GameVersion::Jak3: // jak3 opengoal uses same format as jak2 for code.
// the linker/intern_type functions do the +3.
out.push_back(ts->get_type_method_count(rec.first) / 4);
break;

View File

@ -17,7 +17,7 @@ void add_jak2_expected_type_mismatches(Compiler& c) {
c.add_ignored_type_definition("editable-plane");
}
void add_jak3_expected_type_mismatches(Compiler& c) {}
void add_jak3_expected_type_mismatches(Compiler& /*c*/) {}
TEST(Jak1TypeConsistency, MANUAL_TEST_TypeConsistencyWithBuildFirst) {
Compiler compiler(GameVersion::Jak1);