mirror of
https://github.com/open-goal/jak-project
synced 2026-05-23 15:02:01 -04:00
d3cc739e43
This attempts to get into master whatever work was done in this PR / it's earlier PR https://github.com/open-goal/jak-project/pull/3965 I don't want this work to be lost / floating around in massive PRs. However the changes are: - switch to ntsc_v1 instead of PAL as the development target, as we have done for all other games - remove most of the copied-from-jak2/3 changes as they need to be confirmed during the decompilation process not just assumed - avoids committing any changes to `game/kernel/common` as it was not clear to me if these were changes made in jak x's kernel that were not properly broken out into it's own functions. We don't want to accidentally introduce bugs into jak1-3's kernel code. - in other words, if the change in the kernel only happens in jak x...it should likely be specific to jak x's kernel, not common. --------- Co-authored-by: VodBox <dillon@vodbox.io> Co-authored-by: yodah <greenboyyodah@gmail.com>
485 lines
14 KiB
C++
485 lines
14 KiB
C++
/*!
|
|
* @file runtime.cpp
|
|
* Setup and launcher for the runtime.
|
|
*/
|
|
|
|
#include "common/common_types.h"
|
|
#ifdef OS_POSIX
|
|
#include <unistd.h>
|
|
|
|
#include <sys/mman.h>
|
|
#elif _WIN32
|
|
#include <io.h>
|
|
|
|
#include "third-party/mman/mman.h"
|
|
#define NOMINMAX
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <Windows.h>
|
|
#endif
|
|
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <thread>
|
|
|
|
#include "runtime.h"
|
|
|
|
#include "common/cross_os_debug/xdbg.h"
|
|
#include "common/global_profiler/GlobalProfiler.h"
|
|
#include "common/goal_constants.h"
|
|
#include "common/log/log.h"
|
|
#include "common/util/FileUtil.h"
|
|
#include "common/versions/versions.h"
|
|
|
|
#include "game/external/discord.h"
|
|
#include "game/graphics/gfx.h"
|
|
#include "game/kernel/common/fileio.h"
|
|
#include "game/kernel/common/kdgo.h"
|
|
#include "game/kernel/common/kdsnetm.h"
|
|
#include "game/kernel/common/klink.h"
|
|
#include "game/kernel/common/klisten.h"
|
|
#include "game/kernel/common/kmachine.h"
|
|
#include "game/kernel/common/kmalloc.h"
|
|
#include "game/kernel/common/kmemcard.h"
|
|
#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/kernel/jakx/kboot.h"
|
|
#include "game/kernel/jakx/kdgo.h"
|
|
#include "game/kernel/jakx/klisten.h"
|
|
#include "game/kernel/jakx/kscheme.h"
|
|
#include "game/overlord/common/fake_iso.h"
|
|
#include "game/overlord/common/iso.h"
|
|
#include "game/overlord/common/sbank.h"
|
|
#include "game/overlord/common/srpc.h"
|
|
#include "game/overlord/common/ssound.h"
|
|
#include "game/overlord/jak1/dma.h"
|
|
#include "game/overlord/jak1/fake_iso.h"
|
|
#include "game/overlord/jak1/iso.h"
|
|
#include "game/overlord/jak1/iso_queue.h"
|
|
#include "game/overlord/jak1/overlord.h"
|
|
#include "game/overlord/jak1/ramdisk.h"
|
|
#include "game/overlord/jak1/srpc.h"
|
|
#include "game/overlord/jak1/ssound.h"
|
|
#include "game/overlord/jak1/stream.h"
|
|
#include "game/overlord/jak2/dma.h"
|
|
#include "game/overlord/jak2/iso_cd.h"
|
|
#include "game/overlord/jak2/iso_queue.h"
|
|
#include "game/overlord/jak2/overlord.h"
|
|
#include "game/overlord/jak2/spustreams.h"
|
|
#include "game/overlord/jak2/srpc.h"
|
|
#include "game/overlord/jak2/ssound.h"
|
|
#include "game/overlord/jak2/stream.h"
|
|
#include "game/overlord/jak2/streamlist.h"
|
|
#include "game/overlord/jak2/vag.h"
|
|
#include "game/overlord/jak3/init.h"
|
|
#include "game/overlord/jak3/overlord.h"
|
|
#include "game/system/Deci2Server.h"
|
|
#include "game/system/iop_thread.h"
|
|
#include "sce/deci2.h"
|
|
#include "sce/iop.h"
|
|
#include "sce/libcdvd_ee.h"
|
|
#include "sce/sif_ee.h"
|
|
#include "system/SystemThread.h"
|
|
|
|
u8* g_ee_main_mem = nullptr;
|
|
std::thread::id g_main_thread_id = std::thread::id();
|
|
GameVersion g_game_version = GameVersion::Jak1;
|
|
BackgroundWorker g_background_worker;
|
|
int g_server_port = DECI2_PORT;
|
|
|
|
namespace {
|
|
|
|
int g_argc = 0;
|
|
const char** g_argv = nullptr;
|
|
|
|
/*!
|
|
* SystemThread function for running the DECI2 communication with the GOAL compiler.
|
|
*/
|
|
|
|
void deci2_runner(SystemThreadInterface& iface) {
|
|
// callback function so the server knows when to give up and shutdown
|
|
std::function<bool()> shutdown_callback = [&]() { return iface.get_want_exit(); };
|
|
|
|
// create and register server
|
|
Deci2Server server(shutdown_callback, DECI2_PORT - 1 + (int)g_game_version);
|
|
ee::LIBRARY_sceDeci2_register(&server);
|
|
|
|
// now its ok to continue with initialization
|
|
iface.initialization_complete();
|
|
|
|
// in our own thread, wait for the EE to register the first protocol driver
|
|
lg::debug("[DECI2] Waiting for EE to register protos");
|
|
if (!server.wait_for_protos_ready()) {
|
|
// requested shutdown before protos became ready.
|
|
return;
|
|
}
|
|
// then allow the server to accept connections
|
|
bool server_ok = server.init_server();
|
|
if (!server_ok) {
|
|
lg::error("[DECI2] failed to initialize, REPL will not work.");
|
|
}
|
|
|
|
lg::debug("[DECI2] Waiting for listener...");
|
|
bool saw_listener = false;
|
|
while (!iface.get_want_exit()) {
|
|
if (server_ok && server.is_client_connected()) {
|
|
if (!saw_listener) {
|
|
lg::debug("[DECI2] Connected!");
|
|
}
|
|
saw_listener = true;
|
|
// we have a listener, run!
|
|
server.read_data();
|
|
} else {
|
|
// no connection yet. Do a sleep so we don't spam checking the listener.
|
|
std::this_thread::sleep_for(std::chrono::microseconds(50000));
|
|
}
|
|
}
|
|
}
|
|
|
|
// EE System
|
|
|
|
/*!
|
|
* SystemThread Function for the EE (PS2 Main CPU)
|
|
*/
|
|
void ee_runner(SystemThreadInterface& iface) {
|
|
prof().root_event();
|
|
// Allocate Main RAM. Must have execute enabled.
|
|
if (EE_MEM_LOW_MAP) {
|
|
g_ee_main_mem =
|
|
(u8*)mmap((void*)0x10000000, EE_MAIN_MEM_SIZE, PROT_EXEC | PROT_READ | PROT_WRITE,
|
|
#ifdef __APPLE__
|
|
// has no map_populate
|
|
MAP_ANONYMOUS | MAP_32BIT | MAP_PRIVATE, 0, 0);
|
|
#else
|
|
MAP_ANONYMOUS | MAP_32BIT | MAP_PRIVATE | MAP_POPULATE, 0, 0);
|
|
#endif
|
|
} else {
|
|
g_ee_main_mem =
|
|
(u8*)mmap((void*)EE_MAIN_MEM_MAP, EE_MAIN_MEM_SIZE, PROT_EXEC | PROT_READ | PROT_WRITE,
|
|
MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
|
|
}
|
|
|
|
if (g_ee_main_mem == (u8*)(-1)) {
|
|
lg::debug("Failed to initialize main memory! {}", strerror(errno));
|
|
iface.initialization_complete();
|
|
return;
|
|
}
|
|
|
|
lg::debug("Main memory mapped at 0x{:016x}", (u64)(g_ee_main_mem));
|
|
lg::debug("Main memory size 0x{:x} bytes ({:.3f} MB)", EE_MAIN_MEM_SIZE,
|
|
(double)EE_MAIN_MEM_SIZE / (1 << 20));
|
|
|
|
lg::debug("[EE] Initialization complete!");
|
|
iface.initialization_complete();
|
|
|
|
lg::debug("[EE] Run!");
|
|
memset((void*)g_ee_main_mem, 0, EE_MAIN_MEM_SIZE);
|
|
|
|
// prevent access to the first 512 kB of memory.
|
|
// On the PS2 this is the kernel and can't be accessed either.
|
|
// this may not work well on systems with a page size > 1 MB.
|
|
mprotect((void*)g_ee_main_mem, EE_MAIN_MEM_LOW_PROTECT, PROT_NONE);
|
|
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();
|
|
|
|
jak2::init_globals_streamlist();
|
|
|
|
kmemcard_init_globals();
|
|
kprint_init_globals_common();
|
|
|
|
// Added for OpenGOAL's debugger
|
|
xdbg::allow_debugging();
|
|
|
|
switch (g_game_version) {
|
|
case GameVersion::Jak1:
|
|
jak1::goal_main(g_argc, g_argv);
|
|
break;
|
|
case GameVersion::Jak2:
|
|
jak2::goal_main(g_argc, g_argv);
|
|
break;
|
|
case GameVersion::Jak3:
|
|
jak3::goal_main(g_argc, g_argv);
|
|
break;
|
|
case GameVersion::JakX:
|
|
jakx::goal_main(g_argc, g_argv);
|
|
default:
|
|
ASSERT_MSG(false, "Unsupported game version");
|
|
}
|
|
lg::debug("[EE] Done!");
|
|
|
|
// // kill the IOP todo
|
|
iop::LIBRARY_kill();
|
|
|
|
// after main returns, trigger a shutdown.
|
|
iface.trigger_shutdown();
|
|
}
|
|
|
|
/*!
|
|
* SystemThread Function for the EE Worker Thread (general purpose background tasks from the EE to
|
|
* be non-blocking)
|
|
*/
|
|
void ee_worker_runner(SystemThreadInterface& iface) {
|
|
iface.initialization_complete();
|
|
while (!iface.get_want_exit()) {
|
|
const auto queues_weres_empty = !g_background_worker.process_queues();
|
|
if (queues_weres_empty) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* SystemThread function for running the IOP (separate I/O Processor)
|
|
*/
|
|
void iop_runner(SystemThreadInterface& iface, GameVersion version) {
|
|
prof().root_event();
|
|
prof().begin_event("iop-init");
|
|
IOP iop;
|
|
lg::debug("[IOP] Restart!");
|
|
iop.reset_allocator();
|
|
ee::LIBRARY_sceSif_register(&iop);
|
|
iop::LIBRARY_register(&iop);
|
|
Gfx::register_vsync_callback([&iop]() { iop.kernel.signal_vblank(); });
|
|
|
|
if (version != GameVersion::Jak3 && version != GameVersion::JakX) {
|
|
jak1::dma_init_globals();
|
|
jak2::dma_init_globals();
|
|
|
|
iso_init_globals();
|
|
jak1::iso_init_globals();
|
|
jak2::iso_init_globals();
|
|
|
|
fake_iso_init_globals();
|
|
jak1::fake_iso_init_globals();
|
|
jak2::iso_cd_init_globals();
|
|
|
|
jak1::iso_queue_init_globals();
|
|
jak2::iso_queue_init_globals();
|
|
|
|
jak2::spusstreams_init_globals();
|
|
jak1::ramdisk_init_globals();
|
|
sbank_init_globals();
|
|
|
|
// soundcommon
|
|
jak1::srpc_init_globals();
|
|
jak2::srpc_init_globals();
|
|
srpc_init_globals();
|
|
ssound_init_globals();
|
|
jak2::ssound_init_globals();
|
|
|
|
jak1::stream_init_globals();
|
|
jak2::stream_init_globals();
|
|
}
|
|
|
|
prof().end_event();
|
|
iface.initialization_complete();
|
|
|
|
lg::debug("[IOP] Wait for OVERLORD to start...");
|
|
{
|
|
auto p = scoped_prof("iop-wait-for-ee");
|
|
iop.wait_for_overlord_start_cmd();
|
|
}
|
|
if (iop.status == IOP_OVERLORD_INIT) {
|
|
lg::debug("[IOP] Run!");
|
|
} else {
|
|
lg::debug("[IOP] Shutdown!");
|
|
return;
|
|
}
|
|
|
|
iop.reset_allocator();
|
|
|
|
// init
|
|
|
|
bool complete = false;
|
|
{
|
|
auto p = scoped_prof("overlord-start");
|
|
switch (version) {
|
|
case GameVersion::Jak1:
|
|
jak1::start_overlord_wrapper(iop.overlord_argc, iop.overlord_argv, &complete);
|
|
break;
|
|
case GameVersion::Jak2:
|
|
jak2::start_overlord_wrapper(iop.overlord_argc, iop.overlord_argv, &complete);
|
|
break;
|
|
case GameVersion::Jak3:
|
|
case GameVersion::JakX:
|
|
jak3::start_overlord_wrapper(&complete);
|
|
break;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
{
|
|
auto p = scoped_prof("overlord-wait-for-init");
|
|
while (complete == false) {
|
|
iop.kernel.dispatch();
|
|
}
|
|
}
|
|
|
|
// unblock the EE, the overlord is set up!
|
|
iop.signal_overlord_init_finish();
|
|
|
|
// IOP Kernel loop
|
|
while (!iface.get_want_exit() && !iop.want_exit) {
|
|
// prof().root_event();
|
|
// The IOP scheduler informs us of how many microseconds are left until it has something to do.
|
|
// So we can wait for that long or until something else needs it to wake up.
|
|
auto wait_duration = iop.kernel.dispatch();
|
|
if (wait_duration) {
|
|
iop.wait_run_iop(*wait_duration);
|
|
}
|
|
}
|
|
|
|
Gfx::clear_vsync_callback();
|
|
}
|
|
} // namespace
|
|
|
|
/*!
|
|
* SystemThread function for running NothingTM.
|
|
*/
|
|
void null_runner(SystemThreadInterface& iface) {
|
|
iface.initialization_complete();
|
|
}
|
|
|
|
/*!
|
|
* Main function to launch the runtime.
|
|
* GOAL kernel arguments are currently ignored.
|
|
*/
|
|
RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const char** argv) {
|
|
prof().root_event();
|
|
g_argc = argc;
|
|
g_argv = argv;
|
|
g_main_thread_id = std::this_thread::get_id();
|
|
|
|
bool enable_display = !game_options.disable_display;
|
|
g_game_version = game_options.game_version;
|
|
g_server_port = game_options.server_port;
|
|
|
|
gStartTime = time(nullptr);
|
|
prof().instant_event("ROOT");
|
|
{
|
|
auto p = scoped_prof("startup::exec_runtime::init_discord_rpc");
|
|
init_discord_rpc();
|
|
}
|
|
|
|
// initialize graphics first - the EE code will upload textures during boot and we
|
|
// want the graphics system to catch them.
|
|
{
|
|
auto p = scoped_prof("startup::exec_runtime::init_gfx");
|
|
if (enable_display) {
|
|
Gfx::Init(g_game_version);
|
|
}
|
|
}
|
|
|
|
// step 1: sce library prep
|
|
{
|
|
auto p = scoped_prof("startup::exec_runtime::library_prep");
|
|
iop::LIBRARY_INIT();
|
|
ee::LIBRARY_INIT_sceCd();
|
|
ee::LIBRARY_INIT_sceDeci2();
|
|
ee::LIBRARY_INIT_sceSif();
|
|
}
|
|
|
|
// step 2: system prep
|
|
prof().begin_event("startup::exec_runtime::system_prep");
|
|
SystemThreadManager tm;
|
|
auto& deci_thread = tm.create_thread("DMP");
|
|
auto& iop_thread = tm.create_thread("IOP");
|
|
auto& ee_thread = tm.create_thread("EE");
|
|
// 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");
|
|
prof().end_event();
|
|
|
|
// step 3: start the EE!
|
|
{
|
|
auto p = scoped_prof("startup::exec_runtime::iop-start");
|
|
iop_thread.start([=](SystemThreadInterface& sti) { iop_runner(sti, g_game_version); });
|
|
}
|
|
{
|
|
auto p = scoped_prof("startup::exec_runtime::deci-start");
|
|
deci_thread.start(deci2_runner);
|
|
}
|
|
{
|
|
auto p = scoped_prof("startup::exec_runtime::ee-worker-start");
|
|
ee_worker_thread.start(ee_worker_runner);
|
|
}
|
|
{
|
|
auto p = scoped_prof("startup::exec_runtime::ee-start");
|
|
ee_thread.start(ee_runner);
|
|
}
|
|
|
|
// step 4: wait for EE to signal a shutdown. meanwhile, run video loop on main thread.
|
|
// TODO relegate this to its own function
|
|
if (enable_display) {
|
|
try {
|
|
Gfx::Loop([]() { return MasterExit == RuntimeExitStatus::RUNNING; });
|
|
} catch (std::exception& e) {
|
|
lg::error("Exception thrown from graphics loop: {}", e.what());
|
|
lg::error("Everything will crash now. good luck");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// hack to make the IOP die quicker if it's loading/unloading music
|
|
gMusicFade = 0;
|
|
|
|
// if we have no display, wait here for DECI to shutdown
|
|
deci_thread.join();
|
|
|
|
// fully shut down EE first before stopping the other threads
|
|
ee_thread.join();
|
|
|
|
// to be extra sure
|
|
tm.shutdown();
|
|
|
|
// join and exit
|
|
tm.join();
|
|
|
|
// kill renderer after all threads are stopped.
|
|
// this makes sure the std::shared_ptr<Display> is destroyed in the main thread.
|
|
if (enable_display) {
|
|
Gfx::Exit();
|
|
}
|
|
lg::info("GOAL Runtime Shutdown (code {})", fmt::underlying(MasterExit));
|
|
munmap(g_ee_main_mem, EE_MAIN_MEM_SIZE);
|
|
Discord_Shutdown();
|
|
return MasterExit;
|
|
}
|