/*! * @file runtime.cpp * Setup and launcher for the runtime. */ #include "common/common_types.h" #ifdef OS_POSIX #include #include #elif _WIN32 #include #include "third-party/mman/mman.h" #define NOMINMAX #define WIN32_LEAN_AND_MEAN #include #endif #include #include #include #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 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 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; }