mirror of
https://github.com/open-goal/jak-project
synced 2026-08-07 02:06:59 -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>
589 lines
16 KiB
C++
589 lines
16 KiB
C++
#include "dvd_driver.h"
|
|
|
|
#include <cstring>
|
|
#include <memory>
|
|
|
|
#include "common/log/log.h"
|
|
#include "common/util/Assert.h"
|
|
#include "common/util/FileUtil.h"
|
|
|
|
#include "game/overlord/jakx/isocommon.h"
|
|
#include "game/overlord/jakx/overlord.h"
|
|
#include "game/sce/iop.h"
|
|
|
|
namespace jakx {
|
|
using namespace iop;
|
|
std::unique_ptr<CDvdDriver> g_DvdDriver;
|
|
s32 g_nDvdDriverThread = -1;
|
|
|
|
void jakx_overlord_init_globals_dvd_driver() {
|
|
g_DvdDriver = std::make_unique<CDvdDriver>();
|
|
g_nDvdDriverThread = -1;
|
|
}
|
|
|
|
CDvdDriver* get_driver() {
|
|
return g_DvdDriver.get();
|
|
}
|
|
|
|
CMsg::CMsg(jakx::CMsg::MsgKind msg) : m_msg(msg) {
|
|
m_ret = 1;
|
|
m_thread = GetThreadId();
|
|
}
|
|
|
|
int CMsg::send() {
|
|
// note: changed from passing data
|
|
// and removed corresponding -4 to skip back past the vtable in DVD thread
|
|
// (what were they thinking?)
|
|
s32 ret = SendMbx(get_driver()->msgbox, this);
|
|
if (ret == 0) {
|
|
get_driver()->KickDvdThread();
|
|
SleepThread();
|
|
return m_ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// CMsgLock::CMsgLock() : CMsg(CMsg::MsgKind::LOCK) {}
|
|
//
|
|
// void CMsgLock::handler() {
|
|
// get_driver()->Lock();
|
|
// m_ret = 0;
|
|
// }
|
|
|
|
// CMsgReadRaw::CMsgReadRaw(jakx::BlockParams* params) : CMsg(CMsg::MsgKind::READ_RAW) {
|
|
// m_block_params = *params;
|
|
// }
|
|
//
|
|
// void CMsgReadRaw::handler() {
|
|
// m_ret = get_driver()->ReadDirect(&m_block_params);
|
|
// }
|
|
|
|
CMsgCancelRead::CMsgCancelRead(jakx::CDescriptor* desc) : CMsg(CMsg::MsgKind::CANCEL_READ) {
|
|
m_desc = desc;
|
|
}
|
|
|
|
void CMsgCancelRead::handler() {
|
|
get_driver()->CancelRead(m_desc);
|
|
m_ret = 0;
|
|
}
|
|
|
|
u32 DvdThread();
|
|
|
|
CDvdDriver::CDvdDriver() {
|
|
fifo_entry_sema = -1;
|
|
current_thread_priority = 0x13;
|
|
disk_type = 5;
|
|
tray_flag = 1;
|
|
// m_nLockCount = 0;
|
|
event_flag = -1;
|
|
fifo_access_sema = -1;
|
|
tray_flag2 = 1;
|
|
initialized = 0;
|
|
m_nNumFifoEntries = 0;
|
|
ring_head = 0;
|
|
ring_tail = 0;
|
|
read_in_progress = 0;
|
|
callback = nullptr;
|
|
// locked = false;
|
|
trayflag3 = 0;
|
|
m_nDvdThreadAccessSemaCount = 0;
|
|
memset(ring, 0, sizeof(Block) * 16);
|
|
}
|
|
|
|
void CDvdDriver::Initialize() {
|
|
if (!initialized) {
|
|
*this = {};
|
|
ThreadParam thread_param;
|
|
thread_param.attr = 0x2000000;
|
|
// mbox_param.option = gDvdDriverThreadOptions; // ???
|
|
thread_param.entry = DvdThread;
|
|
thread_param.stackSize = 0x800;
|
|
thread_param.initPriority = 0x13;
|
|
thread_param.option = 0;
|
|
strcpy(thread_param.name, "dvd");
|
|
// mbox_param.attr = (int)PTR_DvdThread_00015c98; // ???
|
|
g_nDvdDriverThread = CreateThread(&thread_param);
|
|
ASSERT(g_nDvdDriverThread >= 0);
|
|
|
|
SemaParam sema_param;
|
|
sema_param.attr = 0;
|
|
sema_param.init_count = 1;
|
|
sema_param.max_count = 1;
|
|
sema_param.option = 0;
|
|
fifo_access_sema = CreateSema(&sema_param);
|
|
ASSERT(fifo_access_sema >= 0);
|
|
sema_param.max_count = 0x10;
|
|
sema_param.attr = 0;
|
|
sema_param.init_count = 0x10;
|
|
sema_param.option = 0;
|
|
fifo_entry_sema = CreateSema(&sema_param);
|
|
ASSERT(fifo_entry_sema >= 0);
|
|
MbxParam mbox_param;
|
|
mbox_param.attr = 0;
|
|
mbox_param.option = 0;
|
|
msgbox = CreateMbx(&mbox_param);
|
|
ASSERT(msgbox >= 0);
|
|
|
|
EventFlagParam param;
|
|
param.attr = 0;
|
|
param.option = 0;
|
|
param.init_pattern = 0;
|
|
event_flag = CreateEventFlag(¶m);
|
|
ASSERT(event_flag >= 0);
|
|
StartThread(g_nDvdDriverThread, 0); // this...
|
|
}
|
|
initialized = 1;
|
|
}
|
|
|
|
void CDvdDriver::SetDriverCallback(std::function<void(int)> f) {
|
|
callback = f;
|
|
}
|
|
|
|
// GetDriveCallback
|
|
|
|
// Poll - would kick the thread...
|
|
|
|
// void CDvdDriver::Lock() {
|
|
// ASSERT_NOT_REACHED();
|
|
// if (GetThreadId() == g_nDvdDriverThread) {
|
|
// m_nLockCount++;
|
|
// locked = true;
|
|
// // needs break HACK
|
|
// needs_break = false;
|
|
// } else {
|
|
// CMsgLock lock;
|
|
// lock.send();
|
|
// }
|
|
// }
|
|
|
|
// Read
|
|
|
|
int CDvdDriver::ReadMultiple(CDescriptor* descriptor,
|
|
int* pages_read_out,
|
|
BlockParams* params,
|
|
int num_blocks,
|
|
bool block_if_queue_full) {
|
|
*pages_read_out = 0;
|
|
s32 ret = 1;
|
|
|
|
// check block parameters are reasonable
|
|
if (ValidateBlockParams(params, num_blocks) != 0) {
|
|
bool from_dvd_thread = GetThreadId() == g_nDvdDriverThread;
|
|
if (from_dvd_thread) {
|
|
// there is a setting to control if this function should block if there are too many
|
|
// queued reads. If the is called from the DVD thread, then this would deadlock.
|
|
// the original game ignored the block argument, but I'm asserting
|
|
block_if_queue_full = 0;
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
ovrld_log(LogCategory::DRIVER, "[driver] ReadMultiple (from our thread? {}) num_blocks {}",
|
|
from_dvd_thread, num_blocks);
|
|
|
|
ret = 0;
|
|
if (0 < num_blocks) {
|
|
// loop, until we've done all the requested reads.
|
|
do {
|
|
s32 acquired_slots = 0;
|
|
if (0 < num_blocks) {
|
|
// loop to try to get up to num_blocks slots in the fifo
|
|
// but, if we get less, we'll take that too
|
|
do {
|
|
if (PollSema(this->fifo_entry_sema) == -0x1a3)
|
|
break;
|
|
acquired_slots = acquired_slots + 1;
|
|
} while (acquired_slots < num_blocks);
|
|
}
|
|
ovrld_log(LogCategory::DRIVER, "[driver] ReadMultiple acquired {} slots in ring",
|
|
acquired_slots);
|
|
|
|
// if we are blocking, and we acquired no slots, then we'll wait here until we get one slot.
|
|
if ((block_if_queue_full != 0) && (acquired_slots < 1)) {
|
|
ovrld_log(LogCategory::DRIVER, "[driver] ring is full, blocking!");
|
|
acquired_slots = 1; // the one we'll get from the WaitSema below
|
|
do {
|
|
} while (WaitSema(fifo_entry_sema) != 0);
|
|
}
|
|
|
|
// lock, now that we've gotten the slots
|
|
AcquireFIFOSema(from_dvd_thread);
|
|
num_blocks = num_blocks - acquired_slots;
|
|
|
|
// if we didn't get any slots, bail.
|
|
if (acquired_slots < 1) {
|
|
ovrld_log(LogCategory::DRIVER, "[driver] ring is full, bailing!");
|
|
ReleaseFIFOSema(from_dvd_thread);
|
|
if (0 < *pages_read_out) {
|
|
KickDvdThread();
|
|
}
|
|
return 2;
|
|
}
|
|
|
|
// loop, updating the ring for each slot we aquired
|
|
do {
|
|
auto* slot = ring + ring_tail;
|
|
ovrld_log(LogCategory::DRIVER, "[driver] inserting in ring slot {}", ring_tail);
|
|
|
|
ring_tail++;
|
|
if (0xf < ring_tail) {
|
|
ring_tail = 0;
|
|
}
|
|
m_nNumFifoEntries++;
|
|
|
|
auto* tail = descriptor->m_pTail;
|
|
slot->descriptor = descriptor;
|
|
slot->params = params[*pages_read_out];
|
|
*pages_read_out = (*pages_read_out) + 1;
|
|
if (!tail) {
|
|
descriptor->m_pHead = slot;
|
|
} else {
|
|
tail->next = slot;
|
|
}
|
|
acquired_slots = acquired_slots + -1;
|
|
descriptor->m_pTail = slot;
|
|
slot->next = nullptr;
|
|
} while (acquired_slots != 0);
|
|
ReleaseFIFOSema(from_dvd_thread);
|
|
KickDvdThread();
|
|
ret = 0;
|
|
} while (0 < num_blocks);
|
|
}
|
|
} else {
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void CDvdDriver::CancelRead(jakx::CDescriptor* desc) {
|
|
if (GetThreadId() == g_nDvdDriverThread) {
|
|
AcquireFIFOSema(true);
|
|
if ((read_in_progress != 0) && (ring[ring_head].descriptor == desc)) {
|
|
// while (iVar1 = sceCdBreak(), iVar1 == 0) {
|
|
// DelayThread(8000);
|
|
// sceCdSync(0);
|
|
// }
|
|
// sceCdSync(0);
|
|
read_in_progress = 0;
|
|
}
|
|
|
|
Block* iter = desc->m_pHead;
|
|
Block* tail = desc->m_pTail;
|
|
while (iter) {
|
|
if (iter->descriptor) {
|
|
CompletionHandler(iter, 6);
|
|
}
|
|
iter->descriptor = nullptr;
|
|
iter->next = nullptr;
|
|
if (iter == tail)
|
|
break;
|
|
iter = desc->m_pHead;
|
|
}
|
|
|
|
if (desc->m_pTail == tail) {
|
|
desc->m_pTail = nullptr;
|
|
}
|
|
ReleaseFIFOSema(true);
|
|
} else {
|
|
CMsgCancelRead msg(desc);
|
|
msg.send();
|
|
}
|
|
}
|
|
|
|
s32 CDvdDriver::ValidateBlockParams(jakx::BlockParams* params, int num_params) {
|
|
if (!params) {
|
|
lg::die("Invalid BlockParams: nullptr");
|
|
return 0;
|
|
}
|
|
if (num_params < 1) {
|
|
lg::die("Invalid BlockParams: size == 0");
|
|
return 0;
|
|
}
|
|
|
|
for (int i = 0; i < num_params; i++) {
|
|
auto& p = params[i];
|
|
if (p.destination == nullptr) {
|
|
lg::die("Invalid BlockParams: {} had nullptr dest", i);
|
|
return 0;
|
|
}
|
|
int kMaxSector = 1000000;
|
|
if (p.sector_num > kMaxSector) {
|
|
// this is just a sanity check - if we ever have larger files this is okay to increase.
|
|
lg::die("Invalid BlockParams: {} had sector num {}", i, p.sector_num);
|
|
return 0;
|
|
}
|
|
if (p.num_sectors > 0x1d0) {
|
|
lg::die("Invalid BlockParams: {} had sector count {}", i, p.num_sectors);
|
|
return 0;
|
|
}
|
|
if (!p.file_def) {
|
|
lg::die("Invalid BlockParams: {} had no file", i);
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void CDvdDriver::KickDvdThread() {
|
|
while (SetEventFlag(event_flag, 1)) {
|
|
;
|
|
}
|
|
}
|
|
|
|
int CDvdDriver::AcquireFIFOSema(bool from_dvd_thread) {
|
|
int iVar1;
|
|
int iVar2;
|
|
|
|
if ((from_dvd_thread != 0) &&
|
|
(iVar2 = m_nDvdThreadAccessSemaCount, m_nDvdThreadAccessSemaCount = iVar2 + 1, 0 < iVar2)) {
|
|
return 0;
|
|
}
|
|
iVar1 = WaitSema(this->fifo_access_sema);
|
|
iVar2 = 0;
|
|
if ((iVar1 != 0) && (iVar2 = iVar1, from_dvd_thread != 0)) {
|
|
m_nDvdThreadAccessSemaCount = m_nDvdThreadAccessSemaCount + -1;
|
|
iVar2 = iVar1;
|
|
}
|
|
return iVar2;
|
|
}
|
|
|
|
int CDvdDriver::ReleaseFIFOSema(bool from_dvd_thread) {
|
|
int iVar1;
|
|
int iVar2;
|
|
|
|
if (from_dvd_thread != 0) {
|
|
iVar2 = m_nDvdThreadAccessSemaCount + -1;
|
|
if (m_nDvdThreadAccessSemaCount < 1) {
|
|
return -0x1a4;
|
|
}
|
|
m_nDvdThreadAccessSemaCount = iVar2;
|
|
if (0 < iVar2) {
|
|
return 0;
|
|
}
|
|
}
|
|
iVar1 = SignalSema(fifo_access_sema);
|
|
iVar2 = 0;
|
|
if ((iVar1 != 0) && (iVar2 = iVar1, from_dvd_thread != 0)) {
|
|
m_nDvdThreadAccessSemaCount = m_nDvdThreadAccessSemaCount + 1;
|
|
iVar2 = iVar1;
|
|
}
|
|
return iVar2;
|
|
}
|
|
|
|
/*!
|
|
* PC port added function to actually do a read of a block.
|
|
*/
|
|
void CDvdDriver::read_from_file(const jakx::Block* block) {
|
|
const auto* fd = block->params.file_def;
|
|
ASSERT(fd);
|
|
FileCacheEntry* selected_entry = nullptr;
|
|
|
|
// get a cache entry
|
|
for (auto& entry : m_file_cache) {
|
|
// if we already opened this file, use that
|
|
if (entry.def == fd) {
|
|
selected_entry = &entry;
|
|
break;
|
|
}
|
|
|
|
// otherwise pick the least recently used
|
|
if (!selected_entry) {
|
|
selected_entry = &entry;
|
|
} else {
|
|
if (selected_entry->last_use_count > entry.last_use_count) {
|
|
selected_entry = &entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
// open a new file if needed
|
|
if (selected_entry->def != fd) {
|
|
lg::debug("CDvdDriver swapping files {} - > {}",
|
|
selected_entry->def ? selected_entry->def->name.data : "NONE", fd->name.data);
|
|
if (selected_entry->def) {
|
|
fclose(selected_entry->fp);
|
|
selected_entry->size = 0;
|
|
}
|
|
|
|
selected_entry->def = fd;
|
|
selected_entry->fp = file_util::open_file(fd->full_path, "rb");
|
|
if (!selected_entry->fp) {
|
|
lg::die("Failed to open {} {}", fd->full_path, strerror(errno));
|
|
}
|
|
// get the size of the file we actually opened, rather than caching the size at startup to
|
|
// support changing the file length after the game has started.
|
|
fseek(selected_entry->fp, 0, SEEK_END);
|
|
selected_entry->size = ftell(selected_entry->fp);
|
|
fseek(selected_entry->fp, 0, SEEK_SET);
|
|
selected_entry->offset_in_file = 0;
|
|
}
|
|
|
|
// increment use counter
|
|
selected_entry->last_use_count = m_file_cache_counter++;
|
|
|
|
const u64 desired_offset = block->params.sector_num * 0x800;
|
|
|
|
// see if we're reading entirely past the end of the file
|
|
if (desired_offset >= selected_entry->size) {
|
|
return;
|
|
}
|
|
|
|
if (selected_entry->offset_in_file != desired_offset) {
|
|
lg::debug("CDvdDriver jumping in file {}: {} -> {}", fd->name.data,
|
|
selected_entry->offset_in_file, desired_offset);
|
|
if (fseek(selected_entry->fp, desired_offset, SEEK_SET)) {
|
|
ASSERT_NOT_REACHED_MSG("Failed to fseek");
|
|
}
|
|
selected_entry->offset_in_file = desired_offset;
|
|
}
|
|
|
|
// read
|
|
s64 read_length = block->params.num_sectors * 0x800;
|
|
s64 extra_length = read_length + desired_offset - selected_entry->size;
|
|
if (extra_length > 0) {
|
|
read_length -= extra_length;
|
|
}
|
|
auto ret = fread(block->params.destination, read_length, 1, selected_entry->fp);
|
|
if (ret != 1) {
|
|
lg::die("Failed to read {} {}, size {} of {} (ret {})", fd->full_path, strerror(errno),
|
|
read_length, selected_entry->size, ret);
|
|
}
|
|
selected_entry->offset_in_file += read_length;
|
|
}
|
|
|
|
u32 DvdThread() {
|
|
auto* driver = get_driver();
|
|
|
|
while (true) {
|
|
// Poll for messages
|
|
CMsg* msg = nullptr;
|
|
while (true) {
|
|
int poll_status = PollMbx((MsgPacket**)&msg, driver->msgbox);
|
|
if (poll_status || !msg) {
|
|
break;
|
|
}
|
|
// run message code
|
|
msg->handler();
|
|
// wake the waiting thread.
|
|
WakeupThread(msg->m_thread);
|
|
}
|
|
|
|
bool completed = false;
|
|
|
|
// if a read is in progress, wait for it to finish.
|
|
if (driver->read_in_progress) {
|
|
// TODO: if we switch to async reads, this is where we'd want to sync.
|
|
// sceCdSync(0);
|
|
completed = true;
|
|
// error checking
|
|
}
|
|
|
|
driver->AcquireFIFOSema(true);
|
|
// error handling
|
|
|
|
// handle ring book-keeping.
|
|
// note that these are somewhat double-buffered - we'll sync on read i-1, start read i, then
|
|
// run the completion handler for read i - 1.
|
|
// the completion is what actually removes stuff from the ring.
|
|
|
|
s32 fifo_slots_freed = completed ? 1 : 0;
|
|
s32 ring_entry = driver->ring_head + (completed ? 1 : 0);
|
|
if (ring_entry > 15) {
|
|
ring_entry = 0;
|
|
}
|
|
Block* block = driver->ring + ring_entry;
|
|
s32 fifo_entries = driver->m_nNumFifoEntries - fifo_slots_freed;
|
|
|
|
if (fifo_entries) {
|
|
// start a new read.
|
|
driver->read_in_progress = 1;
|
|
ovrld_log(LogCategory::DRIVER, "[driver thread] Reading for slot {}", block - driver->ring);
|
|
driver->read_from_file(block);
|
|
|
|
} else {
|
|
driver->read_in_progress = 0;
|
|
}
|
|
|
|
// run completion handler for the previous read - not the one we just started!
|
|
Block sblock;
|
|
if (completed) {
|
|
auto* last_block = &driver->ring[driver->ring_head];
|
|
ovrld_log(LogCategory::DRIVER, "[driver thread] Completion handler for {}",
|
|
driver->ring_head);
|
|
sblock = *last_block;
|
|
|
|
if (sblock.descriptor && sblock.descriptor->m_pHead) {
|
|
ASSERT(sblock.descriptor->m_pHead == last_block);
|
|
}
|
|
if (((sblock.descriptor)->m_pHead == last_block) &&
|
|
((sblock.descriptor)->m_pHead = &sblock, (sblock.descriptor)->m_pTail == last_block)) {
|
|
(sblock.descriptor)->m_pTail = &sblock;
|
|
}
|
|
}
|
|
driver->ring_head = ring_entry;
|
|
driver->m_nNumFifoEntries = fifo_entries;
|
|
while (0 < fifo_slots_freed) {
|
|
fifo_slots_freed = fifo_slots_freed + -1;
|
|
SignalSema(driver->fifo_entry_sema);
|
|
}
|
|
if (completed != 0) {
|
|
driver->CompletionHandler(&sblock, 0);
|
|
}
|
|
driver->ReleaseFIFOSema(true);
|
|
|
|
// this logic here was modified - we go to waiting if we have no more reads.
|
|
if (driver->m_nNumFifoEntries == 0) {
|
|
ovrld_log(LogCategory::DRIVER, "[driver thread] No work, waiting.");
|
|
WaitEventFlag(driver->event_flag, 1, 0x11);
|
|
ovrld_log(LogCategory::DRIVER, "[driver thread] Woken up!");
|
|
}
|
|
|
|
// s32 status;
|
|
// if ((((driver->locked != false)) ||
|
|
// ((driver->needs_break == 0 && (driver->m_nNumFifoEntries == 0)))) &&
|
|
// ((status = WaitEventFlag(driver->event_flag, 1, 0x11),
|
|
// status != 0 && (status != -0x1a2)))) {
|
|
// if (status == -0x1a9) {
|
|
// do {
|
|
// SleepThread();
|
|
// } while (true);
|
|
// }
|
|
// DelayThread(8000);
|
|
// }
|
|
}
|
|
}
|
|
|
|
void CDvdDriver::CompletionHandler(jakx::Block* block, int code) {
|
|
// there is some janky thread priority changes here,
|
|
// but they do not seem to be needed with the changes to the ISO thread.
|
|
|
|
// PushPri(this,local_20,0x35);
|
|
auto* desc = block->descriptor;
|
|
if (desc && desc->m_pHead) {
|
|
desc->m_status = code;
|
|
int thread = desc->m_ThreadID;
|
|
if (desc->m_Callback) {
|
|
(desc->m_Callback)(desc->m_File, block, code);
|
|
}
|
|
if (0 < thread) {
|
|
WakeupThread(thread);
|
|
}
|
|
|
|
Block* next;
|
|
if ((desc->m_pHead == block) && (next = block->next, desc->m_pHead = next, next == nullptr)) {
|
|
desc->m_pTail = nullptr;
|
|
}
|
|
}
|
|
block->next = nullptr;
|
|
block->descriptor = nullptr;
|
|
// PopPri(this, local_20[0]);
|
|
}
|
|
|
|
CDvdDriver::~CDvdDriver() {
|
|
for (auto& entry : m_file_cache) {
|
|
if (entry.fp) {
|
|
fclose(entry.fp);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace jakx
|