mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-23 14:20:07 +00:00
478 lines
13 KiB
C++
478 lines
13 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/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;
|
|
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) {
|
|
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:
|
|
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;
|
|
}
|