jak-project/game/runtime.cpp

391 lines
11 KiB
C++
Raw Normal View History

2020-08-23 02:30:12 +00:00
/*!
* @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
2020-08-27 01:02:24 +00:00
#include <io.h>
#include "third-party/mman/mman.h"
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
2020-08-27 01:02:24 +00:00
#include <Windows.h>
#endif
2020-09-04 03:56:35 +00:00
2020-09-08 00:00:02 +00:00
#include <chrono>
#include <cstring>
2020-09-08 00:00:02 +00:00
#include <thread>
2020-08-23 02:30:12 +00:00
#include "runtime.h"
#include "common/cross_os_debug/xdbg.h"
#include "common/goal_constants.h"
#include "common/log/log.h"
#include "common/util/FileUtil.h"
#include "common/versions.h"
#include "game/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/klisten.h"
#include "game/kernel/jak1/kscheme.h"
#include "game/kernel/jak2/kboot.h"
#include "game/kernel/jak2/klisten.h"
#include "game/kernel/jak2/kscheme.h"
2020-08-23 02:30:12 +00:00
#include "game/overlord/dma.h"
#include "game/overlord/fake_iso.h"
#include "game/overlord/iso.h"
2020-08-23 02:30:12 +00:00
#include "game/overlord/iso_cd.h"
#include "game/overlord/iso_queue.h"
2020-08-23 02:30:12 +00:00
#include "game/overlord/overlord.h"
#include "game/overlord/ramdisk.h"
#include "game/overlord/sbank.h"
#include "game/overlord/srpc.h"
#include "game/overlord/ssound.h"
#include "game/overlord/stream.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"
#include "sce/sif_ee.h"
#include "system/SystemThread.h"
2020-08-23 02:30:12 +00:00
u8* g_ee_main_mem = nullptr;
std::thread::id g_main_thread_id = std::thread::id();
GameVersion g_game_version = GameVersion::Jak1;
2020-08-23 02:30:12 +00:00
namespace {
int g_argc = 0;
const char** g_argv = nullptr;
2020-08-23 02:30:12 +00:00
/*!
* SystemThread function for running the DECI2 communication with the GOAL compiler.
*/
2020-08-27 01:02:24 +00:00
2020-09-04 03:56:35 +00:00
void deci2_runner(SystemThreadInterface& iface) {
// callback function so the server knows when to give up and shutdown
2020-09-04 03:56:35 +00:00
std::function<bool()> shutdown_callback = [&]() { return iface.get_want_exit(); };
// create and register server
Deci2Server server(shutdown_callback, DECI2_PORT);
ee::LIBRARY_sceDeci2_register(&server);
// now its ok to continue with initialization
2020-09-04 03:56:35 +00:00
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.\n");
}
lg::debug("[DECI2] Waiting for listener...");
bool saw_listener = false;
2020-09-04 03:56:35 +00:00
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));
}
}
2020-08-23 02:30:12 +00:00
}
2020-08-27 01:02:24 +00:00
2020-08-23 02:30:12 +00:00
// EE System
/*!
* SystemThread Function for the EE (PS2 Main CPU)
*/
void ee_runner(SystemThreadInterface& iface) {
2020-08-23 02:30:12 +00:00
// 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
2020-08-23 02:30:12 +00:00
MAP_ANONYMOUS | MAP_32BIT | MAP_PRIVATE | MAP_POPULATE, 0, 0);
#endif
2020-08-23 02:30:12 +00:00
} 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();
2020-08-23 02:30:12 +00:00
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));
2020-08-23 02:30:12 +00:00
lg::debug("[EE] Initialization complete!");
iface.initialization_complete();
2020-08-23 02:30:12 +00:00
lg::debug("[EE] Run!");
2020-08-23 02:30:12 +00:00
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);
2020-08-23 02:30:12 +00:00
fileio_init_globals();
jak1::kboot_init_globals();
jak2::kboot_init_globals();
kboot_init_globals_common();
2020-08-23 02:30:12 +00:00
kdgo_init_globals();
kdsnetm_init_globals_common();
2020-08-23 02:30:12 +00:00
klink_init_globals();
kmachine_init_globals_common();
jak1::kscheme_init_globals();
jak2::kscheme_init_globals();
kscheme_init_globals_common();
kmalloc_init_globals_common();
2020-08-23 02:30:12 +00:00
klisten_init_globals();
jak1::klisten_init_globals();
jak2::klisten_init_globals();
2020-08-23 02:30:12 +00:00
kmemcard_init_globals();
kprint_init_globals_common();
2020-08-23 02:30:12 +00:00
// 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;
default:
ASSERT_MSG(false, "Unsupported game version");
}
lg::debug("[EE] Done!");
2020-08-23 02:30:12 +00:00
// // kill the IOP todo
iop::LIBRARY_kill();
// after main returns, trigger a shutdown.
iface.trigger_shutdown();
2020-08-23 02:30:12 +00:00
}
/*!
* SystemThread function for running the IOP (separate I/O Processor)
*/
void iop_runner(SystemThreadInterface& iface) {
2020-08-23 02:30:12 +00:00
IOP iop;
lg::debug("[IOP] Restart!");
2020-08-23 02:30:12 +00:00
iop.reset_allocator();
ee::LIBRARY_sceSif_register(&iop);
iop::LIBRARY_register(&iop);
Gfx::register_vsync_callback([&iop]() { iop.kernel.signal_vblank(); });
2020-08-23 02:30:12 +00:00
// todo!
dma_init_globals();
iso_init_globals();
fake_iso_init_globals();
// iso_api
iso_cd_init_globals();
iso_queue_init_globals();
// isocommon
// overlord
ramdisk_init_globals();
sbank_init_globals();
2020-08-23 02:30:12 +00:00
// soundcommon
srpc_init_globals();
// ssound
stream_init_globals();
2020-08-23 02:30:12 +00:00
iface.initialization_complete();
2020-08-23 02:30:12 +00:00
lg::debug("[IOP] Wait for OVERLORD to start...");
2020-08-23 02:30:12 +00:00
iop.wait_for_overlord_start_cmd();
if (iop.status == IOP_OVERLORD_INIT) {
lg::debug("[IOP] Run!");
2020-08-23 02:30:12 +00:00
} else {
lg::debug("[IOP] Shutdown!");
2020-08-23 02:30:12 +00:00
return;
}
iop.reset_allocator();
// init
bool complete = false;
start_overlord_wrapper(iop.overlord_argc, iop.overlord_argv, &complete); // todo!
while (complete == false) {
iop.wait_run_iop(iop.kernel.dispatch());
}
2020-08-23 02:30:12 +00:00
// unblock the EE, the overlord is set up!
iop.signal_overlord_init_finish();
// IOP Kernel loop
while (!iface.get_want_exit() && !iop.want_exit) {
// 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.
iop.wait_run_iop(iop.kernel.dispatch());
2020-08-23 02:30:12 +00:00
}
Gfx::clear_vsync_callback();
2020-08-23 02:30:12 +00:00
}
} // namespace
/*!
* SystemThread function for running NothingTM.
*/
void null_runner(SystemThreadInterface& iface) {
iface.initialization_complete();
return;
}
/*!
* 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();
return;
}
2020-08-23 02:30:12 +00:00
/*!
* Main function to launch the runtime.
* GOAL kernel arguments are currently ignored.
2020-08-23 02:30:12 +00:00
*/
RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const char** argv) {
g_argc = argc;
g_argv = argv;
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;
2020-08-23 02:30:12 +00:00
// set up discord stuff
gStartTime = time(nullptr);
init_discord_rpc();
// initialize graphics first - the EE code will upload textures during boot and we
// want the graphics system to catch them.
if (enable_display) {
Gfx::Init(g_game_version);
}
2020-08-23 02:30:12 +00:00
// step 1: sce library prep
iop::LIBRARY_INIT();
ee::LIBRARY_INIT_sceCd();
ee::LIBRARY_INIT_sceDeci2();
2020-08-23 02:30:12 +00:00
ee::LIBRARY_INIT_sceSif();
// step 2: system prep
VM::vm_prepare(); // our fake ps2 VM needs to be prepared
2020-08-23 02:30:12 +00:00
SystemThreadManager tm;
auto& deci_thread = tm.create_thread("DMP");
auto& iop_thread = tm.create_thread("IOP");
auto& ee_thread = tm.create_thread("EE");
auto& vm_dmac_thread = tm.create_thread("VM-DMAC");
2020-08-23 02:30:12 +00:00
// step 3: start the EE!
iop_thread.start(iop_runner);
deci_thread.start(deci2_runner);
ee_thread.start(ee_runner);
if (VM::use) {
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
if (enable_display) {
try {
Gfx::Loop([]() { return MasterExit == RuntimeExitStatus::RUNNING; });
} catch (std::exception& e) {
fmt::print("Exception thrown from graphics loop: {}\n", e.what());
fmt::print("Everything will crash now. good luck\n");
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
2020-08-23 02:30:12 +00:00
deci_thread.join();
// fully shut down EE first before stopping the other threads
ee_thread.join();
2020-08-23 02:30:12 +00:00
// 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;
}