[wip] Jak 3 Overlord (#3567)
Some checks are pending
Build / 🖥️ Windows (push) Waiting to run
Build / 🐧 Linux (push) Waiting to run
Build / 🍎 MacOS (push) Waiting to run
Inform Pages Repo / Generate Documentation (push) Waiting to run
Lint / 📝 Required Checks (push) Waiting to run
Lint / 📝 Optional Checks (push) Waiting to run
Lint / 📝 Formatting (push) Waiting to run

This commit is contained in:
water111 2024-07-26 09:42:28 -04:00 committed by GitHub
parent 57772c59a0
commit e81431bd21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
80 changed files with 12658 additions and 132 deletions

View file

@ -29,8 +29,11 @@ class Wrapper {
Wrapper(const std::string& _username,
const Config& config,
const StartupFile& startup,
bool nrepl_alive)
: username(_username), repl_config(config), startup_file(startup), nrepl_alive(nrepl_alive) {}
bool _nrepl_alive)
: username(_username),
repl_config(config),
startup_file(startup),
nrepl_alive(_nrepl_alive) {}
replxx::Replxx& get_repl() { return repl; }
void init_settings();
void reload_startup_file();

View file

@ -27,11 +27,11 @@ Range<int> parse_json_optional_integer_range(const nlohmann::json& json);
}
template <typename T>
void json_get_optional(const nlohmann::json& json,
void json_get_optional(const nlohmann::json& j,
const std::string& key,
std::optional<T>& optionalValue) {
if (json.contains(key) && !json[key].is_null()) {
optionalValue = json[key].get<T>();
if (j.contains(key) && !j[key].is_null()) {
optionalValue = j[key].get<T>();
}
}

View file

@ -25,7 +25,7 @@ namespace decompiler {
*/
class LinkedObjectFile {
public:
LinkedObjectFile(GameVersion version) : version(version){};
LinkedObjectFile(GameVersion _version) : version(_version) {}
void set_segment_count(int n_segs);
void push_back_word_to_segment(uint32_t word, int segment);
int get_label_id_for(int seg, int offset);

View file

@ -138,7 +138,7 @@
[7, "(function none)"],
[3, "(function symbol :behavior process)"]
],
"scene": [[4, "(function none :behavior scene-player)"]],
"scene": [[4, "(function symbol :behavior scene-player)"]],
"pov-camera": [
[
7,

View file

@ -254,6 +254,29 @@ set(RUNTIME_SOURCE
overlord/jak2/streamlfo.cpp
overlord/jak2/streamlist.cpp
overlord/jak2/vag.cpp
overlord/jak3/overlord.cpp
overlord/jak3/pagemanager.cpp
overlord/jak3/iso_cd.cpp
overlord/jak3/dma.cpp
overlord/jak3/iso.cpp
overlord/jak3/iso_queue.cpp
overlord/jak3/srpc.cpp
overlord/jak3/vag.cpp
overlord/jak3/ssound.cpp
overlord/jak3/iso_api.cpp
overlord/jak3/spustreams.cpp
overlord/jak3/list.cpp
overlord/jak3/vblank_handler.cpp
overlord/jak3/dvd_driver.cpp
overlord/jak3/basefile.cpp
overlord/jak3/basefilesystem.cpp
overlord/jak3/ramdisk.cpp
overlord/jak3/isocommon.cpp
overlord/jak3/init.cpp
overlord/jak3/stream.cpp
overlord/jak3/sbank.cpp
overlord/jak3/soundcommon.cpp
overlord/jak3/streamlist.cpp
runtime.cpp
sce/deci2.cpp
sce/iop.cpp

View file

@ -31,19 +31,3 @@ struct RPC_Dgo_Cmd {
// a buffer.
uint8_t pad[32];
};
namespace jak3 {
struct RPC_Dgo_Cmd {
uint16_t rsvd;
uint16_t result;
uint32_t buffer1;
uint32_t buffer2;
uint32_t buffer_heap_top;
char name[16];
uint16_t cgo_id;
uint8_t pad[30];
};
static_assert(sizeof(RPC_Dgo_Cmd) == 0x40);
} // namespace jak3
static_assert(sizeof(RPC_Dgo_Cmd) == sizeof(jak3::RPC_Dgo_Cmd));

View file

@ -26,8 +26,8 @@ class EyeRenderer;
struct SharedRenderState {
explicit SharedRenderState(std::shared_ptr<TexturePool> _texture_pool,
std::shared_ptr<Loader> _loader,
GameVersion version)
: shaders(version), texture_pool(_texture_pool), loader(_loader) {}
GameVersion _version)
: shaders(_version), texture_pool(_texture_pool), loader(_loader), version(_version) {}
ShaderLibrary shaders;
std::shared_ptr<TexturePool> texture_pool;
std::shared_ptr<Loader> loader;

View file

@ -129,8 +129,8 @@ Hfrag::HfragLevel* Hfrag::get_hfrag_level(const std::string& name,
// first, see if this level is loaded. If not, there's nothing we can do.
auto lev_data = render_state->loader->get_tfrag3_level(name);
if (!lev_data) {
printf("[hfrag] can't display %s because it's not loaded.\n", name.c_str());
render_state->loader->debug_print_loaded_levels();
// printf("[hfrag] can't display %s because it's not loaded.\n", name.c_str());
// render_state->loader->debug_print_loaded_levels();
return nullptr;
}

View file

@ -125,7 +125,6 @@ struct link_control {
}
};
void klink_init_globals();
Ptr<u8> c_symlink2(Ptr<u8> objData, Ptr<u8> linkObj, Ptr<u8> relocTable);
extern link_control saved_link_control;

View file

@ -16,7 +16,6 @@ void load_and_link_dgo_from_c_fast(const char* name,
Ptr<kheapinfo> heap,
u32 linkFlag,
s32 bufferSize);
void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size);
void kdgo_init_globals();
extern RPC_Dgo_Cmd sMsg[2];
extern RPC_Dgo_Cmd* sLastMsg;

View file

@ -8,6 +8,7 @@
#include "game/kernel/common/kdgo.h"
#include "game/kernel/common/kmalloc.h"
#include "game/kernel/jak3/klink.h"
#include "game/overlord/jak3/rpc_interface.h"
namespace jak3 {
@ -39,7 +40,7 @@ void BeginLoadingDGO(const char* name, Ptr<u8> buffer1, Ptr<u8> buffer2, Ptr<u8>
RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished
// put a dummy value here just to make sure the IOP overwrites it.
sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666
sMsg[msgID].status = DGO_RPC_RESULT_INIT; // !! this is 666
// inform IOP of buffers
sMsg[msgID].buffer1 = buffer1.offset;
@ -78,13 +79,13 @@ Ptr<u8> GetNextDGO(u32* lastObjectFlag) {
Ptr<u8> buffer(0);
if (sLastMsg) {
// if we got a good result, get pointer to object
if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) {
if ((sLastMsg->status == DGO_RPC_RESULT_MORE) || (sLastMsg->status == DGO_RPC_RESULT_DONE)) {
buffer.offset =
sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object.
}
// not the last one, so don't set the flag.
if (sLastMsg->result == DGO_RPC_RESULT_MORE) {
if (sLastMsg->status == DGO_RPC_RESULT_MORE) {
*lastObjectFlag = 0;
}
@ -111,7 +112,7 @@ void ContinueLoadingDGO(Ptr<u8> b1, Ptr<u8> b2, Ptr<u8> heapPtr) {
u32 msgID = sMsgNum;
jak3::RPC_Dgo_Cmd* sendBuff = sMsg + sMsgNum;
sMsgNum = sMsgNum ^ 1;
sendBuff->result = DGO_RPC_RESULT_INIT;
sendBuff->status = DGO_RPC_RESULT_INIT;
sMsg[msgID].buffer1 = b1.offset;
sMsg[msgID].buffer2 = b2.offset;
sMsg[msgID].buffer_heap_top = heapPtr.offset;

View file

@ -30,7 +30,7 @@ void StopVAG(VagCmd* cmd, int /*param_2*/);
enum VolumeCategory {
DIALOGUE = 2, // VAG streams. Copied "dialogue" name from jak 1.
};
int MasterVolume[17];
int MasterVolume[32];
void vag_init_globals() {
memset(VagCmds, 0, sizeof(VagCmds));

View file

@ -113,7 +113,7 @@ extern int TrapSRAM[N_VAG_CMDS];
extern int StreamVoice[N_VAG_CMDS];
extern int ActiveVagStreams;
extern int MasterVolume[17];
extern int MasterVolume[32];
void vag_init_globals();

View file

@ -0,0 +1,333 @@
#include "basefile.h"
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "game/overlord/jak3/overlord.h"
#include "game/overlord/jak3/pagemanager.h"
#include "game/overlord/jak3/vag.h"
namespace jak3 {
void jak3_overlord_init_globals_basefile() {}
/*!
* Construct a CBaseFile in an unused state.
*/
CBaseFile::CBaseFile() {
m_Buffer.m_pCurrentData = nullptr;
m_Buffer.m_pCurrentPageStart = nullptr;
m_Buffer.m_nMinNumPages = 1;
m_Buffer.m_nMaxNumPages = kDefaultBufferPageCount;
m_Buffer.m_nDataLength = 0;
m_Buffer.m_pPageList = nullptr;
m_Buffer.m_pIsoCmd = nullptr;
m_Buffer.m_eBufferType = CBuffer::BufferType::EBT_FREE;
m_ProcessDataSemaphore = -1;
m_FileDef = nullptr;
m_FileKind = Kind::UNKNOWN;
m_Status = EIsoStatus::NONE_0;
m_ReadRate = 0;
m_LengthPages = 0;
m_PageOffset = 0;
m_nNumPages = kDefaultBufferPageCount;
}
/*!
* Construct a CBaseFile for a given file, but keep it in the "idle" state, with no buffer
* allocated.
*/
CBaseFile::CBaseFile(const jak3::ISOFileDef* file, int semaphore) {
m_Buffer.m_pCurrentData = nullptr;
m_Buffer.m_nMaxNumPages = kDefaultBufferPageCount;
m_Buffer.m_nDataLength = 0;
m_Buffer.m_nMinNumPages = 1;
m_Buffer.m_pPageList = nullptr;
m_Buffer.m_pIsoCmd = nullptr;
m_Buffer.m_pCurrentPageStart = nullptr;
m_Buffer.m_eBufferType = CBuffer::BufferType::EBT_FREE;
m_nNumPages = kDefaultBufferPageCount;
m_FileDef = file;
m_ProcessDataSemaphore = semaphore;
m_FileKind = Kind::UNKNOWN;
m_Status = EIsoStatus::IDLE_1;
m_ReadRate = 0;
m_LengthPages = 0;
m_PageOffset = 0;
}
/*!
* Update our buffer to handle crossing page boundaries, return pointer to next data.
*/
uint8_t* CBaseFile::CheckPageBoundary() {
// Can't check page boundary if the buffer is not allocated.
ASSERT(m_Buffer.m_eBufferType != CBuffer::BufferType::EBT_FREE);
// Can't check page boundary if there is no manager
ASSERT(m_Buffer.m_pPageList);
CPageList* page_list = m_Buffer.m_pPageList;
CPage* page = page_list->m_pCurrentActivePage;
// can only return data if there's an active page
if (!page || page_list->m_nNumActivePages <= 0) {
return nullptr;
}
// buffer doesn't know the current page, just reset it to the start of the active page.
if (m_Buffer.m_pCurrentPageStart == nullptr) {
m_Buffer.m_pCurrentData = page->m_pPageMemStart;
m_Buffer.m_pCurrentPageStart = page->m_pPageMemStart;
} else {
uint8_t* end_ptr = page->m_pPageMemEnd;
// check if our data pointer crossed the page boundary
if (end_ptr <= m_Buffer.m_pCurrentData) {
// it did!
uint8_t* past_boundary_ptr = m_Buffer.m_pCurrentData;
// get the next active page
CPage* next_page = page_list->StepActivePage();
if (!next_page) {
// no more active pages, no data to process
m_Buffer.m_pCurrentPageStart = nullptr;
m_Buffer.m_pCurrentData = nullptr;
ovrld_log(LogCategory::PAGING, "File {} ran out of pages in CheckPageBoundary",
m_FileDef->name.data);
} else {
// this is a little weird, but if we went past the end of the previous page, we actually
// start at an offset into the next page - perhaps the user could know that pages are
// consecutive in memory?
uint8_t* new_page_mem = next_page->m_pPageMemStart;
m_Buffer.m_pCurrentData = new_page_mem + (end_ptr + 1 - past_boundary_ptr);
m_Buffer.m_pCurrentPageStart = new_page_mem;
ovrld_log(LogCategory::PAGING, "File {} advanced to next page (wrapped {} bytes)",
m_FileDef->name.data, (end_ptr + 1 - past_boundary_ptr));
}
}
}
return m_Buffer.m_pCurrentData;
}
/*!
* Allocate pages and set up the CBuffer for the given ISO Msg and type.
*/
int CBaseFile::InitBuffer(CBuffer::BufferType type, jak3::ISO_Hdr* msg) {
ASSERT(msg);
m_Buffer.m_pCurrentData = nullptr;
m_Buffer.m_pPageList = nullptr;
m_Buffer.m_pIsoCmd = msg;
m_Buffer.m_pCurrentPageStart = nullptr;
m_Buffer.m_nDataLength = 0;
m_Buffer.m_eBufferType = CBuffer::BufferType::EBT_FREE;
m_Buffer.m_nMinNumPages = 1;
m_Buffer.m_nMaxNumPages = kDefaultBufferPageCount;
m_nNumPages = 4;
// adjust size based on the request buffer type
switch (type) {
case CBuffer::BufferType::REQUEST_NORMAL: { // 3
m_Buffer.m_pCurrentData = nullptr;
m_Buffer.m_nMinNumPages = 1;
m_Buffer.m_nMaxNumPages = 4;
m_nNumPages = 4;
m_Buffer.m_nDataLength = 0;
m_Buffer.m_pCurrentPageStart = nullptr;
m_Buffer.m_eBufferType = CBuffer::BufferType::NORMAL; // 1
// and the iso msg request.
switch (msg->msg_type) {
case ISO_Hdr::MsgType::LOAD_EE:
case ISO_Hdr::MsgType::LOAD_IOP:
case ISO_Hdr::MsgType::LOAD_EE_CHUNK:
m_Buffer.m_nMinNumPages = 1;
m_nNumPages = 4;
m_Buffer.m_nMaxNumPages = 4;
break;
case ISO_Hdr::MsgType::LOAD_SOUNDBANK:
m_Buffer.m_nMinNumPages = 1;
m_Buffer.m_nMaxNumPages = 2;
break;
default:
break;
}
} break;
case CBuffer::BufferType::REQUEST_VAG: {
m_Buffer.m_pCurrentData = nullptr;
m_Buffer.m_eBufferType = CBuffer::BufferType::VAG;
m_Buffer.m_nMinNumPages = 1;
m_nNumPages = 0x10;
m_Buffer.m_nDataLength = 0;
m_Buffer.m_pCurrentPageStart = nullptr;
m_Buffer.m_nMaxNumPages = 0x10;
} break;
default:
ASSERT_NOT_REACHED(); // bad buffer type
}
ovrld_log(LogCategory::PAGING, "File {} initializing buffer ({} pages {} min {} max)",
m_FileDef->name.data, m_nNumPages, m_Buffer.m_nMinNumPages, m_Buffer.m_nMaxNumPages);
// Actual allocation of page data
CPageList* page_list = AllocPages();
if (page_list) {
// set up the current pointers of the buffer.
m_Buffer.m_pCurrentPageStart = page_list->m_pFirstPage->m_pPageMemStart;
m_Buffer.m_pCurrentData = m_Buffer.m_pCurrentPageStart;
return 1;
} else {
ovrld_log(LogCategory::WARN, "File {} failed to allocate a page list.", m_FileDef->name.data);
// if it failed, terminate the buffer
TerminateBuffer();
return 0;
}
}
/*!
* Free page memory, clear event flags for cpages.
*/
void CBaseFile::TerminateBuffer() {
ovrld_log(LogCategory::PAGING, "File {} terminating buffer.", m_FileDef->name.data);
auto buffer_type = m_Buffer.m_eBufferType;
if (buffer_type != CBuffer::BufferType::EBT_FREE) {
// clean up our pages
auto* page_list = m_Buffer.m_pPageList;
if (page_list) {
// stop try to load
page_list->CancelActivePages();
}
switch (buffer_type) {
case CBuffer::BufferType::EBT_FREE:
case CBuffer::BufferType::NORMAL:
case CBuffer::BufferType::VAG:
case CBuffer::BufferType::REQUEST_NORMAL:
case CBuffer::BufferType::REQUEST_VAG:
// return memory
FreePages();
// reset our buffer
m_Buffer.m_nDataLength = 0;
m_Buffer.m_pCurrentPageStart = 0;
m_Buffer.m_eBufferType = CBuffer::BufferType::EBT_FREE;
// reset our command
if (buffer_type != CBuffer::BufferType::EBT_FREE) {
ASSERT(m_Buffer.m_pIsoCmd);
m_Buffer.m_pIsoCmd = nullptr;
}
break;
default:
ASSERT_NOT_REACHED(); // Invalid buffer type
}
} else {
ASSERT_NOT_REACHED(); // Buffer already terminated, shouldn't happen again.
}
}
/*!
* Allocate CPage and CPageList as needed to reach the target number of pages.
*/
CPageList* CBaseFile::AllocPages() {
// should have picked a buffer type
ASSERT(m_Buffer.m_eBufferType != CBuffer::BufferType::EBT_FREE);
if (m_Buffer.m_eBufferType == CBuffer::BufferType::EBT_FREE) {
return nullptr;
}
// We might have a PageList or not
auto* old_plist = m_Buffer.m_pPageList;
int old_unstepped_pages = 0;
bool have_plist = old_plist != nullptr;
CPageList* ret_plist = nullptr;
// if we do have a pagelist, we'll reuse it and any unstepped pages.
if (have_plist) {
old_unstepped_pages = old_plist->m_nNumUnsteppedPages;
ret_plist = old_plist;
}
int page_alloc_count = 0;
// to increase unstepped pages to the target m_nNumPages, we need to allocate this many more.
page_alloc_count = m_nNumPages - old_unstepped_pages;
// lg::warn("page counts in AllocPages: {} {}", m_nNumPages, old_unstepped_pages);
if (old_plist) {
// lg::warn(" {} {}", old_plist->m_nNumPages, old_plist->m_nNumActivePages);
}
if (old_unstepped_pages < (int)m_nNumPages) {
// alloc count will be positive.
// reduce allocation count to at most the number of free pages
if (get_page_manager()->m_CCache.m_nNumFreePages < page_alloc_count) {
page_alloc_count = get_page_manager()->m_CCache.m_nNumFreePages;
}
} else {
// alloc count would be negative, don't allocate
page_alloc_count = 0;
}
switch (m_Buffer.m_eBufferType) {
case CBuffer::BufferType::NORMAL:
case CBuffer::BufferType::REQUEST_NORMAL: {
// don't do anything special
} break;
case CBuffer::BufferType::VAG:
case CBuffer::BufferType::REQUEST_VAG: {
// for VAG commands, we don't bother buffering more than the DMA transfer size
int dma_xfer_size = ((ISO_VAGCommand*)m_Buffer.m_pIsoCmd)->xfer_size;
if (dma_xfer_size) {
ASSERT(dma_xfer_size > 0);
int limit = (dma_xfer_size + 0x7fff) >> 0xf;
if (page_alloc_count > limit) {
ovrld_log(LogCategory::WARN, "File {} wants {} pages, but VAG DMA sizes limit us to ",
m_FileDef->name.data, page_alloc_count, limit);
lg::info("page count dma limit {} -> {}\n", page_alloc_count, limit);
page_alloc_count = limit;
}
}
} break;
default:
ASSERT_NOT_REACHED(); // bad buffer type.
}
if (page_alloc_count == 0) {
return ret_plist;
}
ovrld_log(LogCategory::PAGING, "File {} wants {} more pages in AllocPages.", m_FileDef->name.data,
page_alloc_count);
if (have_plist) {
ret_plist = get_page_manager()->GrowPageList(m_Buffer.m_pPageList, page_alloc_count);
} else {
ret_plist = get_page_manager()->AllocPageList(page_alloc_count, 0);
}
if (ret_plist) {
m_Buffer.m_pPageList = ret_plist;
if (!have_plist) {
m_Buffer.m_pCurrentData = ret_plist->m_pFirstPage->m_pPageMemStart;
}
} else {
ASSERT_NOT_REACHED(); // might be ok.
}
return ret_plist;
}
/*!
* Free all pages and the page list.
*/
void CBaseFile::FreePages() {
ASSERT(m_Buffer.m_eBufferType != CBuffer::BufferType::EBT_FREE);
if (m_Buffer.m_pPageList) {
get_page_manager()->FreePageList(m_Buffer.m_pPageList);
}
m_Buffer.m_pPageList = nullptr;
}
} // namespace jak3

View file

@ -0,0 +1,76 @@
#pragma once
#include "common/common_types.h"
#include "game/overlord/jak3/isocommon.h"
#include "game/overlord/jak3/pagemanager.h"
namespace jak3 {
void jak3_overlord_init_globals_basefile();
struct CPageList;
struct ISOFileDef;
struct ISO_Hdr;
constexpr int kDefaultBufferPageCount = 4;
/*!
* Base class for a file that the ISO system is processing.
* This represents an "open" file, and contains references to the buffer holding this file's data
*/
struct CBaseFile {
CBaseFile();
CBaseFile(const ISOFileDef* file, int semaphore);
virtual ~CBaseFile() = default;
uint8_t* CheckPageBoundary();
int InitBuffer(CBuffer::BufferType type, ISO_Hdr* msg);
CPageList* AllocPages();
void TerminateBuffer();
void FreePages();
// buffer that stores some contents of this file
CBuffer m_Buffer;
// the number of pages that were allocated for reading this file.
u32 m_nNumPages;
// Metadata about the file
const ISOFileDef* m_FileDef;
// The compression format used on the file
enum class Kind {
UNKNOWN = 0,
NORMAL = 1,
LZO_COMPRESSED = 2,
} m_FileKind = Kind::UNKNOWN;
EIsoStatus m_Status = EIsoStatus::NONE_0;
// The expected read rate for streaming, used to prioritize CD reads. Can be 0 if unknown/not
// applicable.
int m_ReadRate = 0;
// Number of sectors that we should read in total, decided based on the file size and request from
// user when they opened this file.
int m_LengthPages = 0; // really, in pages...
// The current offset. (todo: is this for data we read, processed?)
int m_PageOffset = 0;
// Semaphore that we should wait on before handing new data to the process callback.
// Set to -1 if there is no semaphore.
// (this is a bit of hack, only used for VAG streaming).
int m_ProcessDataSemaphore = 0;
// virtual methods
virtual EIsoStatus BeginRead() = 0;
virtual EIsoStatus SyncRead() = 0;
virtual void Close() = 0;
virtual int RecoverPages(int num_pages) = 0;
virtual int GetSector() = 0;
// ??
// ??
// ??
};
} // namespace jak3

View file

@ -0,0 +1,26 @@
#include "basefilesystem.h"
#include "common/util/Assert.h"
#include "game/sce/iop.h"
using namespace iop;
namespace jak3 {
void jak3_overlord_init_globals_basefilesystem() {}
CBaseFileSystem::CBaseFileSystem() {
for (auto& sema : m_Sema) {
sema = -1;
SemaParam param;
param.max_count = 1;
param.attr = 0;
param.init_count = 1;
param.option = 0;
sema = CreateSema(&param);
if (sema < 0) {
ASSERT_NOT_REACHED();
}
}
}
} // namespace jak3

View file

@ -0,0 +1,33 @@
#pragma once
namespace jak3 {
void jak3_overlord_init_globals_basefilesystem();
struct ISOFileDef;
struct ISOName;
struct CBaseFile;
struct VagDirEntry;
constexpr int kMaxOpenFiles = 16;
/*!
* Base class for "FileSystem", which supports finding and opening files.
* The only implementation we have is CISOCDFileSystem
*/
struct CBaseFileSystem {
CBaseFileSystem();
virtual ~CBaseFileSystem() = default;
// semaphores for processing open files
int m_Sema[kMaxOpenFiles];
virtual int Init() = 0;
// polldrive
virtual ISOFileDef* Find(const char* name) = 0;
virtual ISOFileDef* FindIN(const ISOName* name) = 0;
virtual int GetLength(const ISOFileDef* file) = 0;
virtual CBaseFile* Open(const ISOFileDef* file_def, int sector_offset, int file_kind) = 0;
virtual CBaseFile* OpenWAD(const ISOFileDef* file_def, int page_offset) = 0;
virtual VagDirEntry* FindVAGFile(const char* name) = 0;
};
} // namespace jak3

526
game/overlord/jak3/dma.cpp Normal file
View file

@ -0,0 +1,526 @@
#include "dma.h"
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "game/overlord/jak3/basefile.h"
#include "game/overlord/jak3/overlord.h"
#include "game/overlord/jak3/vag.h"
#include "game/sce/iop.h"
#include "game/sound/sdshim.h"
#include "game/sound/sndshim.h"
#define VOICE_BIT(voice) (1 << ((voice) >> 1))
namespace jak3 {
using namespace iop;
namespace {
// most recent call to voice_trans_wrapper's arguments
u32 g_voiceTransMode = 0;
u32 g_voiceTransSize = 0;
s16 g_voiceTransChannel = 0;
const void* g_voiceTransAddr = nullptr;
u32 g_voiceTransSpuAddr = 0;
// if we've started a transfer recently
bool g_voiceTransRunning = false;
// when that transfer was started
u32 g_voiceTransTime = 0;
// despite the name, this is really an indicator that the SPU streaming system is waiting
// for a SPU interrupt on completion.
bool g_bSpuDmaBusy = false;
int g_nSpuDmaChannel = 0;
ISO_VAGCommand* g_pDmaVagCmd = nullptr;
ISO_VAGCommand* g_pDmaStereoVagCmd = nullptr;
int g_nSpuDmaChunks = 0;
std::array<DmaQueueEntry, 16> g_aSpuDmaQueue;
int g_nSpuDmaQueueHead = 0;
int g_nSpuDmaQueueTail = 0;
int g_nSpuDmaQueueCount = 0;
struct DmaInterruptHandlerHack {
s32 chan = 0;
sceSdTransIntrHandler cb = nullptr;
void* data;
int countdown = 0;
} g_DmaInterruptHack;
} // namespace
void jak3_overlord_init_globals_dma() {
g_voiceTransMode = 0;
g_voiceTransSize = 0;
g_voiceTransChannel = 0;
g_voiceTransAddr = nullptr;
g_voiceTransSpuAddr = 0;
g_voiceTransRunning = false;
g_voiceTransTime = 0;
g_bSpuDmaBusy = false;
g_nSpuDmaChannel = 0;
g_pDmaVagCmd = nullptr;
g_pDmaStereoVagCmd = nullptr;
g_nSpuDmaChunks = 0;
g_aSpuDmaQueue = {};
g_nSpuDmaQueueHead = 0;
g_nSpuDmaQueueCount = 0;
g_nSpuDmaQueueTail = 0;
g_DmaInterruptHack = {};
}
// The DMA callback hack below is used to defer dma completion "interrupts" until the next run
// of the ISO Thread. This avoids re-entry type problems where the original design would set off
// a dma transfer in the completion handler of the previous transfer, and expect a few instructions
// to run after.
void uninstall_dma_intr() {
g_DmaInterruptHack = {};
}
void set_dma_intr_handler_hack(s32 chan, sceSdTransIntrHandler cb, void* data) {
ASSERT(!g_DmaInterruptHack.cb);
g_DmaInterruptHack.chan = chan;
g_DmaInterruptHack.cb = cb;
g_DmaInterruptHack.data = data;
g_DmaInterruptHack.countdown = 10;
}
int SPUDmaIntr(int channel, void* userdata);
void dma_intr_hack() {
if (g_DmaInterruptHack.countdown) {
g_DmaInterruptHack.countdown--;
if (g_DmaInterruptHack.countdown == 0) {
int chan = g_DmaInterruptHack.chan;
void* data = g_DmaInterruptHack.data;
g_DmaInterruptHack = {};
SPUDmaIntr(chan, data);
}
}
}
/*!
* This function is used to set up a DMA transfer to SPU DMA.
*
* This wrapper was added very close to the end of Jak 3's development.
*
* I believe it basically checks for dma transfers that are somehow "dropped", and retries them.
* Since I don't think our IOP framework will ever do this, we have an assert if the dropped logic
* ever goes off.
*/
int voice_trans_wrapper(s16 chan, u32 mode, const void* iop_addr, u32 spu_addr, u32 size) {
// remember the transfer settings. If there's a transfer in progress, so we can't start here,
// we'll use these to start the transfer later.
g_voiceTransMode = mode;
g_voiceTransSize = size;
g_voiceTransChannel = chan;
g_voiceTransAddr = iop_addr;
g_voiceTransSpuAddr = spu_addr;
if (g_voiceTransRunning) {
// I claim this should never happen, and this is their workaround for a bug.
ASSERT_NOT_REACHED();
return -0xd2; // busy
} else {
g_voiceTransRunning = true;
g_voiceTransTime = GetSystemTimeLow();
return sceSdVoiceTrans(chan, mode, iop_addr, spu_addr, size);
}
}
u32 read_rate_calc(u32 pitch) {
u64 pitch1 = (pitch >> 3);
u64 mult_result = pitch1 * 0x2492'4925ull;
return mult_result >> 32;
}
/*!
* The worst function of all time - the SPU DMA completion interrupt.
*/
int SPUDmaIntr(int channel, void* userdata) {
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr enter! {} 0x{:x}", channel, (u64)userdata);
if (!g_bSpuDmaBusy) {
// we got an interrupt, but weren't expecting it, or no longer have the need for the data.
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - not busy");
return 0;
}
if (channel != g_nSpuDmaChannel) {
// interrupt was for the wrong channel, somehow.
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - not our channel ??");
return 0;
}
// since we're in the completion handler, we know that there is no voice trans (SPU DMA) running.
g_voiceTransRunning = false;
// This next block will handle updating the playback command that triggered this dma:
if (g_pDmaVagCmd) {
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDma for cmd {}", g_pDmaVagCmd->name);
if (!g_pDmaStereoVagCmd) {
// non-stereo audio
// set a flag to indicate even/odd number of chunks have been dma'd
if ((g_nSpuDmaChunks & 1) == 0) {
g_pDmaVagCmd->flags.dma_complete_even_chunk_count = 1;
} else {
g_pDmaVagCmd->flags.dma_complete_odd_chunk_count = 1;
}
} else {
// stereo audio. This requires two uploads, one for left/right audio. If we've finished the
// first, start the second one here:
if (g_pDmaStereoVagCmd->xfer_size) {
// parameters for second upload
int chan = g_pDmaVagCmd->dma_chan;
const u8* iop_addr = g_pDmaStereoVagCmd->dma_iop_mem_ptr;
int size = g_pDmaStereoVagCmd->xfer_size;
// SPU addr - toggle the buffer based on stereo side:
// TODO: better explanation of why this picks the correct buffer.
int spu_addr;
if ((g_nSpuDmaChunks & 1) == 0) {
spu_addr = g_pDmaStereoVagCmd->stream_sram;
} else {
spu_addr = g_pDmaStereoVagCmd->stream_sram + 0x2000;
}
// these lines reordered to possibly support immediate dma completion callback??
// clear flag so we know not to transfer the next part
g_pDmaStereoVagCmd->xfer_size = 0;
g_pDmaStereoVagCmd->dma_iop_mem_ptr = nullptr;
// start next transfer
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr starting stereo sibling transfer");
set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, userdata);
voice_trans_wrapper(chan, 0, iop_addr, spu_addr, size);
return 0;
}
// second stereo upload completed - update double-buffering flags
if ((g_nSpuDmaChunks & 1) == 0) {
g_pDmaVagCmd->flags.dma_complete_even_chunk_count = 1;
g_pDmaStereoVagCmd->flags.dma_complete_even_chunk_count = 1;
} else {
g_pDmaVagCmd->flags.dma_complete_odd_chunk_count = 1;
g_pDmaStereoVagCmd->flags.dma_complete_odd_chunk_count = 1;
}
}
// if this is the first chunk, we'll start the actual audio here:
// lg::warn("----------> interrupt with chunks {}\n", g_nSpuDmaChunks);
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks count {}", g_nSpuDmaChunks);
if (g_nSpuDmaChunks == 0) {
// compute pitch/playback rate
int pitch = CalculateVAGPitch(g_pDmaVagCmd->pitch1, g_pDmaVagCmd->pitch_cmd);
ASSERT(pitch == (pitch & 0xffff));
// inform the ISO system how fast we're reading
if (g_pDmaVagCmd->m_pBaseFile) {
// unlike actual playback, this is done with the pitch1 value from the file itself - so if
// we speed up/slow down stuff in debug, it won't change streaming modes
const int pitch_from_file =
CalculateVAGPitch(g_pDmaVagCmd->pitch1_file, g_pDmaVagCmd->pitch_cmd);
int rate = g_pDmaStereoVagCmd ? pitch_from_file * 0x2ee : pitch_from_file * 0x177;
g_pDmaVagCmd->m_pBaseFile->m_ReadRate = read_rate_calc(rate);
}
// start!
u32 voice_mask = 0;
if (!g_pDmaStereoVagCmd) {
// forget any previous spu address
g_pDmaVagCmd->current_spu_address = 0;
static_assert(SD_VA_SSA == 0x2040);
static_assert(SD_S_KOFF == 0x1600);
static_assert(SD_S_KON == 0x1500);
static_assert(SD_VP_ADSR1 == 0x300);
static_assert(SD_VP_ADSR2 == 0x400);
static_assert(SD_VP_PITCH == 0x200);
// before touching SPU2 hardware, wait for voice safety:
BlockUntilVoiceSafe(g_pDmaVagCmd->voice, 0x900);
// set address and ADSR settings
sceSdSetAddr(g_pDmaVagCmd->voice | SD_VA_SSA, g_pDmaVagCmd->stream_sram + 0x30);
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR1, 0xff);
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR2, 0x1fc0);
if (g_pDmaVagCmd->flags.paused) {
pitch = 0;
}
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, pitch);
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice);
} else {
// forget any previous spu address
g_pDmaVagCmd->current_spu_address = 0;
g_pDmaStereoVagCmd->current_spu_address = 0;
// wait for voices to be safe to adjust
BlockUntilVoiceSafe(g_pDmaVagCmd->voice, 0x900);
BlockUntilVoiceSafe(g_pDmaStereoVagCmd->voice, 0x900);
// set voice params
sceSdSetAddr(g_pDmaVagCmd->voice | SD_VA_SSA, g_pDmaVagCmd->stream_sram + 0x30);
sceSdSetAddr(g_pDmaStereoVagCmd->voice | SD_VA_SSA, g_pDmaStereoVagCmd->stream_sram + 0x30);
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR1, 0xff);
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_ADSR1, 0xff);
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR2, 0x1fc0);
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_ADSR2, 0x1fc0);
if (g_pDmaVagCmd->flags.paused) {
pitch = 0;
}
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, pitch);
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_PITCH, pitch);
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice) | VOICE_BIT(g_pDmaStereoVagCmd->voice);
}
// do key-on or key-off
if (g_pDmaVagCmd->flags.paused) {
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 0, key off");
BlockUntilAllVoicesSafe();
sceSdSetSwitch(SD_S_KOFF | (g_pDmaVagCmd->voice & 1), voice_mask);
} else {
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 0, key on");
BlockUntilAllVoicesSafe();
sceSdSetSwitch(SD_S_KON | (g_pDmaVagCmd->voice & 1), voice_mask);
}
// remember the time of the key-on/off. This is used to avoid sending voice commands
// quickly, which somehow confuses the sound hardware.
auto sys_time = GetSystemTimeLow();
MarkVoiceKeyedOnOff(g_pDmaVagCmd->voice, sys_time);
if (g_pDmaStereoVagCmd) {
MarkVoiceKeyedOnOff(g_pDmaStereoVagCmd->voice, sys_time);
}
} else if (g_nSpuDmaChunks == 1) {
g_pDmaVagCmd->flags.saw_chunks1 = 1;
if (g_pDmaStereoVagCmd) {
g_pDmaStereoVagCmd->flags.saw_chunks1 = 1;
}
if (g_pDmaVagCmd->flags.paused) {
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 1, pausing");
u32 voice_mask = 0;
if (!g_pDmaStereoVagCmd) {
// pause by setting pitches to 0
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, 0);
BlockUntilVoiceSafe(VOICE_BIT(g_pDmaVagCmd->voice), 0x900);
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice);
} else {
sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_PITCH, 0);
sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, 0);
BlockUntilVoiceSafe(VOICE_BIT(g_pDmaVagCmd->voice), 0x900);
BlockUntilVoiceSafe(VOICE_BIT(g_pDmaStereoVagCmd->voice), 0x900);
voice_mask = VOICE_BIT(g_pDmaVagCmd->voice) | VOICE_BIT(g_pDmaStereoVagCmd->voice);
}
// switch off
BlockUntilAllVoicesSafe();
sceSdSetSwitch(SD_S_KOFF | (g_pDmaVagCmd->voice & 1), voice_mask);
auto sys_time = GetSystemTimeLow();
MarkVoiceKeyedOnOff(g_pDmaVagCmd->voice, sys_time);
if (g_pDmaStereoVagCmd) {
MarkVoiceKeyedOnOff(g_pDmaStereoVagCmd->voice, sys_time);
}
} else {
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 1, unpausing by call to UnPauseVAG");
g_pDmaVagCmd->flags.paused = 1;
UnPauseVAG(g_pDmaVagCmd);
}
}
// now that we've processed the command from this interrupt, mark it as safe to modify
g_pDmaVagCmd->safe_to_modify_dma = 1;
if (g_pDmaStereoVagCmd) {
g_pDmaStereoVagCmd->safe_to_modify_dma = 1;
}
// and forget it!
g_pDmaVagCmd = nullptr;
g_pDmaStereoVagCmd = nullptr;
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr dma handling of VAG cmd is complete");
}
// release ref on this page. (interestingly, not a dma ref...)
if (userdata) {
CPage* page = (CPage*)userdata;
int ret = page->ReleaseRef();
ASSERT(ret >= 0);
}
// now - see if we have another queued dma transfer
ASSERT(g_nSpuDmaQueueCount >= 0);
if (g_nSpuDmaQueueCount == 0) {
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr dma queue is empty, disabling interrupt");
// we're done!
// set_dma_intr_handler_hack(channel, nullptr, nullptr);
uninstall_dma_intr();
// if (-1 < channel) {
// snd_FreeSPUDMA(channel);
// }
g_bSpuDmaBusy = false;
} else {
ovrld_log(LogCategory::SPU_DMA_STR,
"SPUDmaIntr dma queue is not empty, preparing to run {} ({} pending)",
g_nSpuDmaQueueHead, g_nSpuDmaQueueCount);
// nope, more dma to run
auto* next_xfer = &g_aSpuDmaQueue[g_nSpuDmaQueueHead];
// set up the next interrupt handler
set_dma_intr_handler_hack(channel, SPUDmaIntr, next_xfer->user_data);
// args for the dma transfer
int next_chan = channel;
int next_mode = 0;
const void* next_iop = next_xfer->iop_mem;
u32 next_spu = next_xfer->spu_addr;
u32 next_length = next_xfer->length;
// load up the commands to handle
g_pDmaVagCmd = next_xfer->command;
g_pDmaStereoVagCmd = nullptr;
if (g_pDmaVagCmd) {
g_pDmaStereoVagCmd = g_pDmaVagCmd->stereo_sibling;
}
g_nSpuDmaChunks = next_xfer->num_isobuffered_chunks;
// advance the queue!
g_nSpuDmaQueueCount = g_nSpuDmaQueueCount + -1;
g_nSpuDmaQueueHead = g_nSpuDmaQueueHead + 1;
if (0xf < g_nSpuDmaQueueHead) {
g_nSpuDmaQueueHead = 0;
}
// start the next one!
// set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, userdata);
voice_trans_wrapper(next_chan, next_mode, next_iop, next_spu, next_length);
}
ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - end of function");
return 0;
}
/*!
* Start DMA to EE.
*/
void DMA_SendToEE(void* ee_dest,
const void* iop_src,
u32 length,
void callback(void*),
void* callback_arg) {
ASSERT(iop_src);
ASSERT(ee_dest);
ASSERT(((uintptr_t)iop_src & 3) == 0);
ASSERT(((uintptr_t)ee_dest & 0xf) == 0);
ASSERT(length < 0xffff0);
sceSifDmaData cmd; // DMA settings
// setup command
cmd.mode = 0;
cmd.data = iop_src;
cmd.addr = ee_dest;
cmd.size = length;
// instant DMA
// ovrld_log(LogCategory::EE_DMA, "DMA_SendToEE: 0x{:x}, size {}", (u64)ee_dest, length);
sceSifSetDma(&cmd, 1);
// for now, we'll do the callback here, but I bet it will cause problems
if (callback) {
callback(callback_arg);
}
}
/*!
* Start DMA transfer to SPU. Despite the name, this does not actually "sync" - the transfer will
* be ongoing. If there is an ongoing transfer when this is called, the transfer will be queued.
*/
int DMA_SendToSPUAndSync(const u8* iop_mem,
int length,
int spu_addr,
ISO_VAGCommand* cmd,
void* user_data) {
// CpuSuspendIntr(local_28);
int ret = 1;
bool defer = false;
ovrld_log(LogCategory::SPU_DMA_STR,
"DMA to SPU requested for {}, {} bytes to 0x{:x}, currently busy? {}",
cmd ? cmd->name : "NO-CMD", length, spu_addr, g_bSpuDmaBusy);
if (g_bSpuDmaBusy == 0) {
// not busy, we can actually start dma now.
g_nSpuDmaChannel = snd_GetFreeSPUDMA();
if (g_nSpuDmaChannel == -1) {
return 0;
}
// set globals for DMA processing
if (cmd) {
g_nSpuDmaChunks = cmd->num_isobuffered_chunks;
g_pDmaStereoVagCmd = cmd->stereo_sibling;
g_pDmaVagCmd = cmd;
}
} else {
// busy, need to queue the dma
ASSERT(g_nSpuDmaQueueCount <= (int)g_aSpuDmaQueue.size());
// set values:
g_aSpuDmaQueue[g_nSpuDmaQueueTail].length = length;
g_aSpuDmaQueue[g_nSpuDmaQueueTail].spu_addr = spu_addr;
g_aSpuDmaQueue[g_nSpuDmaQueueTail].user_data = user_data;
g_aSpuDmaQueue[g_nSpuDmaQueueTail].num_isobuffered_chunks =
cmd ? cmd->num_isobuffered_chunks : 0;
g_aSpuDmaQueue[g_nSpuDmaQueueTail].command = cmd;
g_aSpuDmaQueue[g_nSpuDmaQueueTail].iop_mem = iop_mem;
g_nSpuDmaQueueCount = g_nSpuDmaQueueCount + 1;
g_nSpuDmaQueueTail = g_nSpuDmaQueueTail + 1;
if (0xf < g_nSpuDmaQueueTail) {
g_nSpuDmaQueueTail = 0;
}
defer = true;
}
// set up the stereo command
if (cmd) {
cmd->safe_to_modify_dma = 0;
auto* stereo = cmd->stereo_sibling;
if (stereo) {
stereo->num_isobuffered_chunks = cmd->num_isobuffered_chunks;
stereo->dma_iop_mem_ptr = iop_mem + length;
cmd->dma_chan = g_nSpuDmaChannel;
stereo->xfer_size = length;
}
}
// kick off dma, if we decided not to queue.
if (!defer) {
g_bSpuDmaBusy = true;
set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, user_data);
voice_trans_wrapper(g_nSpuDmaChannel, 0, iop_mem, spu_addr, length);
}
return ret;
}
/*!
* Run a dma transfer that was delayed or dropped.
*/
void RunDeferredVoiceTrans() {
// only if there's a currently happening transfer.
if (g_voiceTransRunning) {
if (GetSystemTimeLow() - g_voiceTransTime > 0x384000) {
ovrld_log(LogCategory::WARN, "DeferredVoiceTrans has detected hung dma... expect problems.");
// original game also check sceSdVoiceTransStatus here, we'll possibly need to mess with this
// if we delay dma completion interrupts...
g_voiceTransRunning = false;
voice_trans_wrapper(g_voiceTransChannel, g_voiceTransMode, g_voiceTransAddr,
g_voiceTransSpuAddr, g_voiceTransSize);
}
}
}
} // namespace jak3

32
game/overlord/jak3/dma.h Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#include "common/common_types.h"
namespace jak3 {
void jak3_overlord_init_globals_dma();
struct ISO_VAGCommand;
int voice_trans_wrapper(s16 chan, u32 mode, const void* iop_addr, u32 spu_addr, u32 size);
void DMA_SendToEE(void* ee_dest,
const void* iop_src,
u32 length,
void callback(void*),
void* callback_arg);
int DMA_SendToSPUAndSync(const u8* iop_mem,
int length,
int spu_addr,
ISO_VAGCommand* cmd,
void* user_data);
void RunDeferredVoiceTrans();
struct ISO_VAGCommand;
struct DmaQueueEntry {
ISO_VAGCommand* command = nullptr;
const void* iop_mem = nullptr;
u32 spu_addr = 0;
u32 length = 0;
void* user_data = nullptr;
u32 num_isobuffered_chunks = 0;
};
void dma_intr_hack();
} // namespace jak3

View file

@ -0,0 +1,582 @@
#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/jak3/isocommon.h"
#include "game/overlord/jak3/overlord.h"
#include "game/sce/iop.h"
namespace jak3 {
using namespace iop;
std::unique_ptr<CDvdDriver> g_DvdDriver;
s32 g_nDvdDriverThread = -1;
void jak3_overlord_init_globals_dvd_driver() {
g_DvdDriver = std::make_unique<CDvdDriver>();
g_nDvdDriverThread = -1;
}
CDvdDriver* get_driver() {
return g_DvdDriver.get();
}
CMsg::CMsg(jak3::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(jak3::BlockParams* params) : CMsg(CMsg::MsgKind::READ_RAW) {
// m_block_params = *params;
// }
//
// void CMsgReadRaw::handler() {
// m_ret = get_driver()->ReadDirect(&m_block_params);
// }
CMsgCancelRead::CMsgCancelRead(jak3::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(&param);
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(jak3::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(jak3::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 kMaxFileSize = 1024 * 1024 * 1024;
if (p.sector_num > kMaxFileSize / 0x800) {
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 jak3::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->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));
}
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 >= fd->length) {
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 - fd->length;
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, fd->length, 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(jak3::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 jak3

View file

@ -0,0 +1,144 @@
#pragma once
#include <functional>
#include "common/common_types.h"
#include "game/overlord/jak3/isocommon.h"
namespace jak3 {
void jak3_overlord_init_globals_dvd_driver();
/*!
* The DVD Driver is what actually called the Sony CD library functions.
* It also had many special cases for handling errors, open trays, retries, etc that are not
* recreated in this port.
*
* The original DVD driver thread didn't run the actual reads - the Sony CD library had its own
* threads for async reads. However, they would call sceCdSync() to wait until a read finished.
* It's not clear if sceCdSync would allow other threads to run while waiting on the read to finish.
* but if it did, this is a bit of a difference in the blocking behavior.
*
* This is something that could be revisited, as freads will now essentially block the entire
* overlord, including refilling sound RAM.
*/
struct CDescriptor;
struct BlockParams;
struct CPage;
struct ISOFileDef;
// The CMSG system is used to pass messages from external threads to the driver. It's mostly used
// internally by the driver, when functions are called on it from outside threads. In these cases
// the Cmsg will get send to the driver thread and handler will run on it here.
struct CMsg {
enum class MsgKind {
// LOCK = 0,
READ_RAW = 1,
CANCEL_READ = 2,
};
CMsg(MsgKind msg);
virtual int send();
virtual void handler() = 0;
u8 data[8];
MsgKind m_msg;
int m_thread;
int m_ret;
};
// struct CMsgLock : public CMsg {
// CMsgLock();
// void handler() override;
// };
// struct CMsgReadRaw : public CMsg {
// explicit CMsgReadRaw(BlockParams* params);
// void handler() override;
// BlockParams m_block_params;
// };
struct CMsgCancelRead : public CMsg {
explicit CMsgCancelRead(CDescriptor* desc);
void handler() override;
CDescriptor* m_desc;
};
struct CISOCDFile;
/*!
* Reference to an ongoing or requested read at the driver level, possibly made up of multiple
* blocks. In this case, the head/tail pointers point to Blocks stored inside the CDvdDriver's
* internal FIFO.
*/
struct CDescriptor {
CDescriptor() = default;
int m_unk0 = 0;
void (*m_Callback)(CISOCDFile*, Block*, s32) = nullptr;
int m_ThreadID = 0;
int m_status = 0;
CISOCDFile* m_File = nullptr;
Block* m_pHead = nullptr;
Block* m_pTail = nullptr;
};
class CDvdDriver {
public:
CDvdDriver();
~CDvdDriver();
void Initialize();
void CancelRead(CDescriptor* descriptor);
int ReadMultiple(CDescriptor* descriptor,
int* pages_read_out,
BlockParams* params,
int num_pages,
bool block_if_queue_full);
void SetDriverCallback(std::function<void(int)> f);
// int ReadDirect(BlockParams* params);
void KickDvdThread();
// void Lock();
int GetDiskType() const { return disk_type; }
s32 ValidateBlockParams(BlockParams* params, int num_params);
int ReleaseFIFOSema(bool from_dvd_thread);
int AcquireFIFOSema(bool from_dvd_thread);
void read_from_file(const Block* block);
void CompletionHandler(Block* block, int code);
u8 initialized = 0;
s32 event_flag = -1;
s32 fifo_access_sema = -1;
s32 fifo_entry_sema = -1;
s32 msgbox = -1;
s32 m_nNumFifoEntries = 0;
s32 ring_head = 0;
s32 ring_tail = 0;
Block ring[16];
u8 read_in_progress; // more likely: read in progress
std::function<void(int)> callback;
s32 current_thread_priority = 0;
// s32 m_nLockCount = 0;
// bool locked = false;
//
s32 disk_type;
u8 tray_flag2;
u8 trayflag3;
u8 tray_flag;
s32 m_nDvdThreadAccessSemaCount = 0;
private:
struct FileCacheEntry {
const ISOFileDef* def = nullptr;
FILE* fp = nullptr;
u32 last_use_count = 0;
u64 offset_in_file = 0;
};
u32 m_file_cache_counter = 0;
static constexpr int kNumFileCacheEntries = 6;
FileCacheEntry m_file_cache[kNumFileCacheEntries];
};
// replacement for g_DvdDriver
CDvdDriver* get_driver();
} // namespace jak3

View file

@ -0,0 +1,50 @@
#include "game/overlord/jak3/basefile.h"
#include "game/overlord/jak3/basefilesystem.h"
#include "game/overlord/jak3/dma.h"
#include "game/overlord/jak3/dvd_driver.h"
#include "game/overlord/jak3/iso.h"
#include "game/overlord/jak3/iso_api.h"
#include "game/overlord/jak3/iso_cd.h"
#include "game/overlord/jak3/iso_queue.h"
#include "game/overlord/jak3/isocommon.h"
#include "game/overlord/jak3/list.h"
#include "game/overlord/jak3/overlord.h"
#include "game/overlord/jak3/pagemanager.h"
#include "game/overlord/jak3/ramdisk.h"
#include "game/overlord/jak3/sbank.h"
#include "game/overlord/jak3/soundcommon.h"
#include "game/overlord/jak3/spustreams.h"
#include "game/overlord/jak3/srpc.h"
#include "game/overlord/jak3/ssound.h"
#include "game/overlord/jak3/stream.h"
#include "game/overlord/jak3/streamlist.h"
#include "game/overlord/jak3/vag.h"
#include "game/overlord/jak3/vblank_handler.h"
namespace jak3 {
void jak3_overlord_init_globals_all() {
jak3_overlord_init_globals_overlord();
jak3_overlord_init_globals_pagemanager();
jak3_overlord_init_globals_iso_cd();
jak3_overlord_init_globals_dma();
jak3_overlord_init_globals_iso();
jak3_overlord_init_globals_iso_queue();
jak3_overlord_init_globals_srpc();
jak3_overlord_init_globals_vag();
jak3_overlord_init_globals_ssound();
jak3_overlord_init_globals_iso_api();
jak3_overlord_init_globals_spustreams();
jak3_overlord_init_globals_list();
jak3_overlord_init_globals_vblank_handler();
jak3_overlord_init_globals_dvd_driver();
jak3_overlord_init_globals_basefile();
jak3_overlord_init_globals_basefilesystem();
jak3_overlord_init_globals_ramdisk();
jak3_overlord_init_globals_isocommon();
jak3_overlord_init_globals_stream();
jak3_overlord_init_globals_sbank();
jak3_overlord_init_globals_soundcommon();
jak3_overlord_init_globals_streamlist();
}
} // namespace jak3

View file

@ -0,0 +1,5 @@
#pragma once
namespace jak3 {
void jak3_overlord_init_globals_all();
}

1796
game/overlord/jak3/iso.cpp Normal file

File diff suppressed because it is too large Load diff

92
game/overlord/jak3/iso.h Normal file
View file

@ -0,0 +1,92 @@
#pragma once
#include "common/link_types.h"
#include "game/overlord/jak3/isocommon.h"
namespace jak3 {
struct ISOFileDef;
void jak3_overlord_init_globals_iso();
void InitISOFS();
const ISOFileDef* FindISOFile(const char*);
struct ISO_VAGCommand;
struct VagStreamData;
struct RPC_Dgo_Cmd;
struct ISO_DGOCommand : public ISO_Hdr {
u8* buffer1 = nullptr;
u8* buffer2 = nullptr;
u8* buffer_top = nullptr;
DgoHeader dgo_header; //
ObjectHeader obj_header; //
u8* ee_dest_buffer = nullptr; // 192
int bytes_processed = 0; // 196
int objects_loaded = 0; // 200
enum class State {
INIT = 0,
READ_DGO_HEADER = 1,
FINISH_OBJ = 2,
READ_LAST_OBJ = 3,
READ_OBJ_HEADER = 4,
READ_OBJ_DATA = 5,
FINISH_DGO = 6,
FINISH_OBJ_SINGLE_BUFFER = 7,
} state = State::INIT; // 204
int finished_first_object = 0; // 208
int buffer_toggle = 0; // 212
u8* selected_buffer = nullptr; // 216
int selected_id = 0; // 220
int last_id = 0; // 224
int acked_cancel_id = 0; // 228
u8 nosync_cancel_pending_flag = 0; // 232
int request_cancel_id = 0; // 236
u8 nosync_cancel_ack = 0; // 240
int sync_sent_count = 0; // 244
// the number of times we looked in the iso thread's sync mbox
// I think just used for debugging/asserts.
int sync_mbox_wait_count = 0; // 248
int sync_ret_count = 0; // 252
int want_abort = 0; // 256
};
enum class CopyKind {
EE = 0,
IOP = 1,
SBK = 2,
};
void set_active_a(ISO_Hdr* cmd, int val);
void set_active_b(ISO_Hdr* cmd, int val);
void set_active_c(ISO_Hdr* cmd, int val);
void IsoStopVagStream(ISO_VAGCommand* cmd);
void IsoPlayVagStream(ISO_VAGCommand* user_cmd);
EIsoStatus NullCallback(ISO_Hdr* msg);
EIsoStatus CopyDataToIOP(ISO_Hdr* msg);
EIsoStatus CopyDataSbkLoad(ISO_Hdr* msg);
EIsoStatus CopyDataToEE(ISO_Hdr* msg);
EIsoStatus RunDGOStateMachine(ISO_Hdr* msg);
void QueueVAGStream(VagStreamData* cmd);
void CopyDataDmaCallback(void*);
void* RPC_DGO(unsigned int fno, void* msg_ptr, int);
void LoadDGO(RPC_Dgo_Cmd* cmd);
void CancelDGO(RPC_Dgo_Cmd* cmd);
void LoadNextDGO(RPC_Dgo_Cmd* cmd);
EIsoStatus CopyData(ISO_LoadCommon* msg, CopyKind kind);
void CancelDGONoSync(int id);
void IsoPlayMusicStream(ISO_VAGCommand* user_cmd);
void IsoQueueVagStream(ISO_VAGCommand* user_cmd);
extern int g_nISOThreadID;
extern int g_nISOMbx;
extern bool g_bMusicPause;
extern int g_nMusicSemaphore;
extern char g_szTargetMusicName[0x30];
extern int g_nActiveMusicStreams;
extern bool g_bVagCmdsInitialized;
extern bool g_bMusicIsPaused;
extern bool g_bAnotherMusicPauseFlag;
extern int g_nMusicFade;
extern int g_nMusicTweak;
extern int g_nMusicFadeDir;
} // namespace jak3

View file

@ -0,0 +1,229 @@
#include "iso_api.h"
#include <cstring>
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "common/util/FileUtil.h"
#include "game/overlord/jak3/iso.h"
#include "game/overlord/jak3/iso_cd.h"
#include "game/overlord/jak3/iso_queue.h"
#include "game/overlord/jak3/srpc.h"
#include "game/overlord/jak3/vag.h"
#include "game/sce/iop.h"
namespace jak3 {
using namespace iop;
void jak3_overlord_init_globals_iso_api() {}
/*!
* This file is the "API" that implementations of RPCs or other code can use to submit things to the
* ISO thread. Note that not all RPCs use this API - for example the DGO RPC just manually submits
* messages. Generally, these functions will not return until the actual action is complete, like a
* file is loaded.
* - except for messages to pause/play audio, those functions will return immediately, but there may
* be a delay until they are actually processed.
*/
// PluginVagAndVagWad not ported.
u32 EEVagAndVagWad(ISO_VAGCommand* cmd, char* name) {
ISOName name_buff;
if (*name == '$' || strlen(name) < 9) {
if (*name == '$') {
name++;
}
MakeISOName(&name_buff, name);
} else {
file_util::ISONameFromAnimationName(name_buff.data, name);
}
cmd->vag_dir_entry = get_file_system()->FindVAGFile(name_buff.data);
if (cmd->vag_dir_entry) {
memcpy(name_buff.data, "VAGWAD ", 8);
strncpy(name_buff.data + 8, g_pszLanguage, 4);
auto* file_def = get_file_system()->FindIN(&name_buff);
// piVar1 = g_pFileSystem;
cmd->file_def = file_def;
strncpy(name_buff.data + 8, "INT", 4);
cmd->vag_file_def = get_file_system()->FindIN(&name_buff);
if (cmd->vag_dir_entry && cmd->file_def && cmd->vag_file_def) {
return 1;
}
}
cmd->vag_dir_entry = nullptr;
cmd->file_def = nullptr;
cmd->vag_file_def = nullptr;
return 0;
}
int LoadISOFileToIOP(const ISOFileDef* file_def, void* addr, int length) {
ISO_LoadSingle cmd;
cmd.msg_type = ISO_Hdr::MsgType::LOAD_IOP;
cmd.mbox_reply = 0;
cmd.thread_to_wake = GetThreadId();
cmd.file_def = file_def;
cmd.addr = (u8*)addr;
cmd.maxlen = length;
lg::warn("--------------- LoadISOFileToIOP START");
SendMbx(g_nISOMbx, &cmd);
SleepThread();
lg::warn("--------------- LoadISOFileToIOP END");
if (cmd.status == EIsoStatus::NONE_0) {
return cmd.length_to_copy;
} else {
return 0;
}
}
int LoadISOFileToEE(const ISOFileDef* file_def, u32 addr, int length) {
ISO_LoadSingle cmd;
cmd.msg_type = ISO_Hdr::MsgType::LOAD_EE;
cmd.mbox_reply = 0;
cmd.thread_to_wake = GetThreadId();
cmd.file_def = file_def;
cmd.addr = (u8*)(u64)addr;
cmd.maxlen = length;
lg::warn("--------------- LoadISOFileToEE START");
SendMbx(g_nISOMbx, &cmd);
SleepThread();
lg::warn("--------------- LoadISOFileToEE END");
if (cmd.status == EIsoStatus::NONE_0) {
return cmd.length_to_copy;
}
return 0;
}
int LoadISOFileChunkToEE(const ISOFileDef* file_def, u32 addr, int max_len, int sector_offset) {
ISO_LoadSingle cmd;
cmd.msg_type = ISO_Hdr::MsgType::LOAD_EE_CHUNK;
cmd.mbox_reply = 0;
cmd.thread_to_wake = GetThreadId();
cmd.file_def = file_def;
cmd.addr = (u8*)(u64)addr;
cmd.maxlen = max_len;
cmd.sector_offset = sector_offset;
lg::warn("--------------- LoadISOFileChunkToEE START");
SendMbx(g_nISOMbx, &cmd);
SleepThread();
lg::warn("--------------- LoadISOFileChunkToEE END");
if (cmd.status == EIsoStatus::NONE_0) {
return cmd.length_to_copy;
}
return 0;
}
u32 LoadSoundBankToIOP(const char* name, SoundBankInfo* bank, u32 mode) {
ISO_LoadSoundbank cmd;
cmd.msg_type = ISO_Hdr::MsgType::LOAD_SOUNDBANK;
cmd.mbox_reply = 0;
cmd.thread_to_wake = GetThreadId();
cmd.bank_info = bank;
cmd.name = name;
cmd.priority = mode;
lg::warn("--------------- LoadSoundBankToIOP START");
SendMbx(g_nISOMbx, &cmd);
SleepThread();
lg::warn("--------------- LoadSoundBankToIOP END");
return (u32)cmd.status;
}
void PlayMusicStream(VagStreamData* stream) {
int iVar1;
ISO_VAGCommand cmd;
cmd.msg_type = ISO_Hdr::MsgType::PLAY_MUSIC_STREAM;
cmd.mbox_reply = 0;
cmd.thread_to_wake = 0;
iVar1 = EEVagAndVagWad(&cmd, stream->name);
if (iVar1 == 0) {
// if (bWarn == 0) {
// bWarn = 1;
// }
} else {
cmd.play_volume = 0x400;
// bWarn = 0;
strncpy(cmd.name, stream->name, 0x30);
cmd.id = stream->id;
cmd.priority_pq = 9;
cmd.music_flag = 1;
cmd.maybe_sound_handler = 0;
cmd.plugin_id = 0;
cmd.art_flag = 0;
cmd.movie_flag = 0;
cmd.updated_trans = 0;
IsoPlayMusicStream(&cmd);
}
}
void QueueVAGStream(VagStreamData* stream) {
bool bVar1;
bool bVar2;
ISO_VAGCommand cmd;
cmd.msg_type = ISO_Hdr::MsgType::VAG_QUEUE;
cmd.mbox_reply = 0;
cmd.thread_to_wake = 0;
if (stream->sound_handler == 0) {
EEVagAndVagWad(&cmd, stream->name);
cmd.play_volume = 0x400;
cmd.play_group = 2;
} else {
ASSERT_NOT_REACHED();
// PluginVagAndVagWad(&cmd,stream);
// cmd.play_volume = stream->maybe_volume2;
// cmd.oog = stream->maybe_volume_3;
// cmd.play_group = stream->group;
}
strncpy(cmd.name, stream->name, 0x30);
cmd.id = stream->id;
cmd.plugin_id = stream->plugin_id;
cmd.priority_pq = stream->priority;
cmd.maybe_sound_handler = stream->sound_handler;
bVar1 = stream->movie_art_load != 0;
cmd.movie_flag = bVar1;
bVar2 = stream->art_load != 0;
cmd.art_flag = bVar2;
cmd.music_flag = 0;
if (bVar2) {
cmd.flags.art = 1;
}
if (bVar1) {
cmd.flags.movie = 1;
}
cmd.updated_trans = 0;
IsoQueueVagStream(&cmd);
}
void PauseVAGStreams() {
auto* cmd = GetVAGCommand();
cmd->msg_type = ISO_Hdr::MsgType::VAG_PAUSE;
cmd->mbox_reply = 0;
cmd->thread_to_wake = 0;
SendMbx(g_nISOMbx, cmd);
}
void UnpauseVAGStreams() {
auto* cmd = GetVAGCommand();
cmd->msg_type = ISO_Hdr::MsgType::VAG_UNPAUSE;
cmd->mbox_reply = 0;
cmd->thread_to_wake = 0;
SendMbx(g_nISOMbx, cmd);
}
void SetVAGStreamPitch(int id, int pitch) {
auto* cmd = GetVAGCommand();
cmd->msg_type = ISO_Hdr::MsgType::VAG_SET_PITCH_VOL;
cmd->id = id;
cmd->pitch_cmd = pitch;
cmd->mbox_reply = 0;
cmd->thread_to_wake = 0;
SendMbx(g_nISOMbx, cmd);
}
} // namespace jak3

View file

@ -0,0 +1,19 @@
#pragma once
#include "common/common_types.h"
namespace jak3 {
void jak3_overlord_init_globals_iso_api();
struct ISOFileDef;
struct VagStreamData;
struct SoundBankInfo;
int LoadISOFileToEE(const ISOFileDef* file_def, u32 addr, int length);
int LoadISOFileToIOP(const ISOFileDef* file_def, void* addr, int length);
void PlayMusicStream(VagStreamData* data);
int LoadISOFileChunkToEE(const ISOFileDef* file_def, u32 addr, int max_len, int sector_offset);
void SetVAGStreamPitch(s32 id, s32 pitch);
void UnpauseVAGStreams();
u32 LoadSoundBankToIOP(const char* name, SoundBankInfo* bank, u32 mode);
} // namespace jak3

View file

@ -0,0 +1,521 @@
#include "iso_cd.h"
#include <vector>
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "common/util/FileUtil.h"
#include "game/overlord/jak3/iso.h"
#include "game/overlord/jak3/isocommon.h"
#include "game/overlord/jak3/overlord.h"
#include "game/overlord/jak3/spustreams.h"
#include "game/sce/iop.h"
using namespace iop;
namespace jak3 {
VagDir g_VagDir;
MusicTweaks gMusicTweakInfo;
CISOCDFile g_CISOCDFiles[kMaxOpenFiles];
namespace {
CISOCDFile* g_pReadInfo = nullptr;
std::vector<ISOFileDef> g_FileDefs;
std::unique_ptr<CISOCDFileSystem> g_ISOCDFileSystem;
} // namespace
void jak3_overlord_init_globals_iso_cd() {
g_pReadInfo = nullptr;
for (auto& f : g_CISOCDFiles) {
f = CISOCDFile();
}
g_FileDefs.clear();
g_VagDir = {};
g_ISOCDFileSystem = std::make_unique<CISOCDFileSystem>();
gMusicTweakInfo = {};
}
CBaseFileSystem* get_file_system() {
return g_ISOCDFileSystem.get();
}
CISOCDFile::CISOCDFile() {
m_nSector = -1;
m_nLoaded = 0;
m_nLength = 0;
}
namespace {
void ReadPagesCallbackF(CISOCDFile* file, Block* block, s32 error) {
file->ReadPagesCallback(block, error);
}
} // namespace
CISOCDFile::CISOCDFile(const jak3::ISOFileDef* filedef, s32 process_data_semaphore)
: CBaseFile(filedef, process_data_semaphore) {
m_nSector = -1;
m_nLoaded = 0;
m_nLength = 0;
m_Descriptor.m_File = this;
m_Descriptor.m_Callback = ReadPagesCallbackF;
}
/*!
* Call ReadPages to read data from this file into its PageList. I believe that the read
* is finished after this function returns. Note that this returns COMPLETE enum in many cases, both
* if the read succeeds, or if the read is not attempted for some reasons.
*/
EIsoStatus CISOCDFile::BeginRead() {
ASSERT(m_Buffer.m_pPageList);
ASSERT(m_Buffer.m_eBufferType != CBuffer::BufferType::EBT_FREE);
if (m_Status == EIsoStatus::NONE_0) {
return EIsoStatus::ERROR_b;
}
if (m_Status == EIsoStatus::OK_2) {
return EIsoStatus::OK_2;
}
ASSERT(m_Status == EIsoStatus::IDLE_1);
auto* plist = m_Buffer.m_pPageList;
// how many pages are in our list, but not filled?
int num_pages_desired = plist->m_nNumPages - plist->m_nNumActivePages;
if (!num_pages_desired) {
// no space to read
return EIsoStatus::OK_2;
}
// convert pages to active, indicating that a read will attempt to fill them:
auto* first_page = plist->AddActivePages(num_pages_desired);
if (!first_page) {
lg::warn("Failed to add {} active pages", num_pages_desired);
}
// convert buffer type - ??
switch (m_Buffer.m_eBufferType) {
case CBuffer::BufferType::EBT_FREE:
ASSERT_NOT_REACHED();
case CBuffer::BufferType::NORMAL:
m_Buffer.m_eBufferType = CBuffer::BufferType::REQUEST_NORMAL;
break;
case CBuffer::BufferType::VAG:
m_Buffer.m_eBufferType = CBuffer::BufferType::REQUEST_VAG;
break;
case CBuffer::BufferType::REQUEST_NORMAL:
case CBuffer::BufferType::REQUEST_VAG:
break;
}
m_Status = EIsoStatus::OK_2;
// remember this as our currently reading file
g_pReadInfo = this;
if (m_FileKind != CBaseFile::Kind::LZO_COMPRESSED) {
if (m_nLength == 0 || m_nLoaded < m_nLength) {
ovrld_log(LogCategory::PAGING, "Calling ReadPages: sector {}, pages {}", m_nSector,
num_pages_desired);
ReadPages(m_nSector, first_page, num_pages_desired, nullptr, true);
int bytes = 0x8000 * num_pages_desired;
m_nLoaded += bytes;
m_Buffer.AddData(bytes);
m_nSector += bytes >> 0xb;
}
} else {
ASSERT_NOT_REACHED();
}
return EIsoStatus::OK_2;
}
/*!
* Called by ?? to indicate that the read is done.
*/
EIsoStatus CISOCDFile::SyncRead() {
if (m_Status != EIsoStatus::IDLE_1 && g_pReadInfo) {
if (m_Status == EIsoStatus::OK_2) {
m_Status = EIsoStatus::IDLE_1;
}
g_pReadInfo = nullptr;
return EIsoStatus::OK_2;
}
return EIsoStatus::ERROR_b;
}
/*!
* As soon as possible, stop ongoing reads and free buffers.
*/
void CISOCDFile::Close() {
// cancel ongoing reading in the driver
get_driver()->CancelRead(&m_Descriptor);
ASSERT(m_FileKind != CBaseFile::Kind::LZO_COMPRESSED); // unsupported in pc
if (this == g_pReadInfo) {
g_pReadInfo = nullptr;
}
if (m_Buffer.m_eBufferType != CBuffer::BufferType::EBT_FREE) {
TerminateBuffer();
}
// reset self
*this = CISOCDFile();
}
/*!
* In the case where we have run out of page memory, take pages that we have loaded, but aren't yet
* using, and discard them back to the pool.
*/
int CISOCDFile::RecoverPages(int num_pages_desired) {
// we only allow ourselves to recover pages for VAG streaming.
if (!m_Buffer.m_pIsoCmd || m_Buffer.m_pIsoCmd->callback != ProcessVAGData) {
return 0;
}
// lock semaphore for processing
ASSERT(m_ProcessDataSemaphore != -1);
WaitSema(m_ProcessDataSemaphore);
auto* plist = m_Buffer.m_pPageList;
int num_removed = 0;
if (plist) {
int pages_to_ask_for = plist->m_nNumUnsteppedPages;
if (pages_to_ask_for > 1) {
// don't ask for more than user asked for
if (pages_to_ask_for > num_pages_desired) {
pages_to_ask_for = num_pages_desired;
}
num_removed = plist->RemoveActivePages(pages_to_ask_for);
if (num_removed) {
m_nSector -= 0x10 * num_removed;
m_nLoaded -= 0x8000 * num_removed;
// suspend intr
ASSERT(m_Buffer.m_nDataLength >= 0);
if (m_Buffer.m_nDataLength > 0) {
m_Buffer.AddData(-num_removed * 0x8000);
}
// resume intr
ASSERT(m_nLoaded >= 0);
}
}
}
// move our reading pointer back.
m_PageOffset -= num_removed;
// unlock processing
SignalSema(m_ProcessDataSemaphore);
return num_removed;
}
/*!
* Get the next sector to read.
*/
int CISOCDFile::GetSector() {
return m_nSector;
}
// WaitForLZOPages - not ported.
void CISOCDFile::ReadPages(int sector,
jak3::CPage* destination_page,
int num_pages,
char* done_flag_ptr,
bool sleep_until_done) {
ASSERT(destination_page);
ASSERT(num_pages >= 1);
constexpr int kMaxPages = 24;
// I _think_ this might have been an alloca...
BlockParams params_array[kMaxPages];
ASSERT(num_pages <= kMaxPages);
// iVar2 = -((num_pages + -1) * 0x14 + 0x1bU & 0xfffffff8);
// params = (BlockParams*)((int)local_30 + iVar2);
BlockParams* params = params_array;
// increment our progress in the file - the distributed update of the various progress integers
// is somewhat confusing, especially since failures here don't seem to reset it properly.
m_PageOffset += num_pages;
// Set up block params for each page to read
CPage* page = destination_page;
int pg_remaining = num_pages;
do {
page->input_state = CPage::State::READING;
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask);
params->destination = page->m_pPageMemStart;
params->num_sectors = 0x10;
params->sector_num = sector;
params->file_def = m_FileDef;
params->page = page;
params->flag = nullptr;
if (pg_remaining == 1) { // on the last page...
params->flag = (char*)0xffffffff; // flag this
if (!sleep_until_done) {
params->flag = done_flag_ptr;
}
}
ovrld_log(LogCategory::PAGING, "[paging] building block for driver: 0x{:x}, {}, {}, flag: {}",
(u64)params->destination, params->sector_num, params->file_def->name.data,
(u64)params->flag);
page = page->m_pNextPage;
pg_remaining = pg_remaining + -1;
sector += 0x10;
params++;
} while (page && pg_remaining > 0);
if (pg_remaining == 0) {
// we set up block params for all the requested pages.
int pages_actually_read = -1;
ovrld_log(LogCategory::PAGING, "[paging] Submitting {} blocks to driver.\n", num_pages);
int status = get_driver()->ReadMultiple(&m_Descriptor, &pages_actually_read, params_array,
num_pages, true);
if ((status == 0) && pages_actually_read == num_pages) {
if (!sleep_until_done) {
return;
}
// put us to sleep...
ovrld_log(LogCategory::PAGING, "[paging] Sleeping, waiting for driver to read {}",
pages_actually_read);
SleepThread();
ovrld_log(LogCategory::PAGING, "[paging] Driver woke us up!");
return;
}
ASSERT_NOT_REACHED(); // unexpected failure to issue read
} else {
// ran out of pages....
ASSERT_NOT_REACHED(); // this was just a warning.. but I think it shouldn't happen.
if (destination_page) {
do {
destination_page->input_state = CPage::State::ACTIVE;
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag,
~destination_page->mask);
destination_page = destination_page->m_pNextPage;
} while (destination_page);
}
}
ASSERT_NOT_REACHED();
// only get here is we failed.... set the done flag.
if (sleep_until_done == 0 && done_flag_ptr) {
*done_flag_ptr = 1;
}
}
/*!
* Callback run by the dvd driver when a page is finished reading.
*/
void CISOCDFile::ReadPagesCallback(jak3::Block* block, int error) {
if (error == 0) {
ASSERT(block->params.page->m_nAllocState == 1);
// flag page as done
block->params.page->input_state = CPage::State::READ_DONE;
// set flag
SetEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, block->params.page->mask);
// if this block has a notification, process it:
if (block->params.flag == (char*)0xffffffff) { // indicates we should wake caller
// we assume the caller is the iso thread
WakeupThread(g_nISOThreadID);
} else if (block->params.flag) {
// in this case, it's a pointer.
*block->params.flag = 1;
}
}
}
// DecompressBlock - not ported
/*!
* Initialize the file system - find all files and set up their definitions.
*/
int CISOCDFileSystem::Init() {
// drive ready event flag - not ported
// get disc type - not ported
// this is mostly not needed... except for some vag pausing nonsense :(
get_driver()->SetDriverCallback([&](int a) { DvdDriverCallback(a); });
// skipped a bunch of lzo pages crap
ReadDirectory();
LoadMusicTweaks();
// skip load Disc ID
// should already be done in the constructor...
for (auto& f : g_CISOCDFiles) {
f.m_FileDef = nullptr;
}
return 0;
}
// PollDrive - not ported
/*!
* Find a file definition by name.
*/
ISOFileDef* CISOCDFileSystem::Find(const char* name) {
ISOName iname;
file_util::MakeISOName(iname.data, name);
return FindIN(&iname);
}
/*!
* Find a file definition by its "ISO Name", a 12-byte name.
*/
ISOFileDef* CISOCDFileSystem::FindIN(const jak3::ISOName* name) {
for (auto& def : g_FileDefs) {
if (def.name == *name) {
return &def;
}
}
return nullptr;
}
/*!
* Get the length of a file, in bytes.
*/
int CISOCDFileSystem::GetLength(const jak3::ISOFileDef* file) {
return file->length;
}
/*!
* Open a file for reading.
*/
CBaseFile* CISOCDFileSystem::Open(const jak3::ISOFileDef* file_def,
int sector_offset,
int file_kind) {
auto* file = AllocateFile(file_def);
ASSERT(file);
// file kind must be known to be non-compressed (1). (TODO: remove arg)
ASSERT(file_kind == 1);
file->m_FileKind = CBaseFile::Kind::NORMAL;
file->m_nLength = file_def->length;
file->m_LengthPages = (0x7fff + file->m_nLength) >> 0xf;
if (file->m_LengthPages < 1) {
ASSERT_NOT_REACHED();
}
file->m_nLoaded = 0;
file->m_PageOffset = 0;
// in the original game, this was the sector of the start of the file
// we just make it 0, since we don't build up a whole iso file.
file->m_nSector = 0;
if (sector_offset != -1) {
file->m_nSector += sector_offset;
}
return file;
}
/*!
* Open a WAD file for reading, given the offset of the data to read, in pages.
*/
CBaseFile* CISOCDFileSystem::OpenWAD(const jak3::ISOFileDef* file_def, int page_offset) {
auto* file = AllocateFile(file_def);
ASSERT(file);
file->m_LengthPages = 1; // this is not really true..
file->m_FileKind = CBaseFile::Kind::NORMAL;
file->m_PageOffset = 0;
file->m_nSector = page_offset * 0x10;
file->m_nLoaded = 0;
file->m_nLength = 0;
return file;
}
/*!
* Locate the entry for a VAG file in the VAG directory.
*/
VagDirEntry* CISOCDFileSystem::FindVAGFile(const char* name) {
u32 packed_name[2];
PackVAGFileName(packed_name, name);
for (int i = 0; i < g_VagDir.num_entries; i++) {
auto& entry = g_VagDir.entries[i];
if (packed_name[0] == entry.words[0] && packed_name[1] == (entry.words[1] & 0x3ff)) {
return &entry;
}
}
return nullptr;
}
/*!
* Get a CISOCDFile* for a newly opened file.
*/
CISOCDFile* CISOCDFileSystem::AllocateFile(const jak3::ISOFileDef* file_def) {
for (int i = 0; i < kMaxOpenFiles; i++) {
auto* file = &g_CISOCDFiles[i];
if (!file->m_FileDef) {
*file = CISOCDFile(file_def, m_Sema[i]);
return file;
}
}
ASSERT_NOT_REACHED();
}
/*!
* Callback from the DVD driver itself into the filesystem. This was originally used for notifying
* when the tray is opened or closed.
*/
void CISOCDFileSystem::DvdDriverCallback(int) {
// the only callbacks that do anything are tray open/close, which we don't care about
ASSERT_NOT_REACHED();
}
// CheckDiscID - not ported
// LoadDiscID - not ported
// ReadSectorsNow - not ported
/*!
* Find all the files on the disc and set up their information. This is modified for the PC port to
* just search for files in the appropriate out folder.
*/
void CISOCDFileSystem::ReadDirectory() {
for (const auto& f :
fs::directory_iterator(file_util::get_jak_project_dir() / "out" / "jak3" / "iso")) {
if (f.is_regular_file()) {
auto& e = g_FileDefs.emplace_back();
std::string file_name = f.path().filename().string();
ASSERT(file_name.length() < 16); // should be 8.3.
MakeISOName(&e.name, file_name.c_str());
e.full_path =
fmt::format("{}/out/jak3/iso/{}", file_util::get_jak_project_dir().string(), file_name);
auto* fp = file_util::open_file(e.full_path, "rb");
ASSERT(fp);
fseek(fp, 0, SEEK_END);
e.length = ftell(fp);
fclose(fp);
}
}
}
/*!
* Load the "Music Tweaks" file, which contains a volume setting per music track.
*/
void CISOCDFileSystem::LoadMusicTweaks() {
ISOName tweakname;
MakeISOName(&tweakname, "TWEAKVAL.MUS");
auto file = g_ISOCDFileSystem->FindIN(&tweakname);
if (file) {
auto fp = file_util::open_file(file->full_path, "rb");
ASSERT(fp);
ASSERT(file->length <= sizeof(gMusicTweakInfo));
auto ret = fread(&gMusicTweakInfo, file->length, 1, fp);
ASSERT(ret == 1);
fclose(fp);
} else {
lg::warn("Failed to open music tweak file.");
gMusicTweakInfo.TweakCount = 0;
}
}
// Crc32 - not ported
// ReadU32 - not ported
} // namespace jak3

View file

@ -0,0 +1,58 @@
#pragma once
#include <string>
#include "game/overlord/jak3/basefile.h"
#include "game/overlord/jak3/basefilesystem.h"
#include "game/overlord/jak3/dvd_driver.h"
#include "game/overlord/jak3/isocommon.h"
namespace jak3 {
void jak3_overlord_init_globals_iso_cd();
CBaseFileSystem* get_file_system();
extern VagDir g_VagDir;
extern MusicTweaks gMusicTweakInfo;
struct CISOCDFile : public CBaseFile {
CISOCDFile();
CISOCDFile(const ISOFileDef* filedef, s32 process_data_semaphore);
int m_nLoaded = 0; // bytes loaded so far
int m_nSector = 0; // next sector to read.
int m_nLength = 0; // bytes that we want to load. 0 for the whole file
CDescriptor m_Descriptor;
EIsoStatus BeginRead() override;
void ReadPages(int sector,
CPage* destination_page,
int num_pages,
char* done_flag_ptr,
bool flag);
EIsoStatus SyncRead() override;
void Close() override;
int RecoverPages(int num_pages) override;
int GetSector() override;
void ReadPagesCallback(Block* block, int error);
};
struct CISOCDFileSystem : public CBaseFileSystem {
CISOCDFileSystem() = default;
int Init() override;
ISOFileDef* Find(const char* name) override;
ISOFileDef* FindIN(const ISOName* name) override;
int GetLength(const ISOFileDef* file) override;
CBaseFile* Open(const ISOFileDef* file_def, int sector_offset, int file_kind) override;
CBaseFile* OpenWAD(const ISOFileDef* file_def, int page_offset) override;
VagDirEntry* FindVAGFile(const char* name) override;
void DvdDriverCallback(int a);
void ReadDirectory();
void LoadMusicTweaks();
CISOCDFile* AllocateFile(const ISOFileDef* file);
// int m_drive_ready_event_flag = -1;
};
} // namespace jak3

View file

@ -0,0 +1,732 @@
#include "iso_queue.h"
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "game/overlord/jak3/basefile.h"
#include "game/overlord/jak3/dma.h"
#include "game/overlord/jak3/iso.h"
#include "game/overlord/jak3/isocommon.h"
#include "game/overlord/jak3/pagemanager.h"
#include "game/overlord/jak3/spustreams.h"
#include "game/overlord/jak3/vag.h"
#include "game/sce/iop.h"
#include "game/sound/sndshim.h"
using namespace iop;
namespace jak3 {
s32 g_nPriQueueSema = 0;
s32 g_VagCmdSema = 0;
u32 g_auStrmSRAM[6];
u32 g_auTrapSRAM[6];
PriStackEntry gPriStack[2];
extern u32 time_of_last_unknown_rate_drive_op;
u32 g_cmds_with_speed_total = 0;
bool unk_time_mode_flag = false;
ISO_Hdr* g_selected_cmd = nullptr;
bool unk_time_mflag = 0;
s32 unk_sector = 0;
u32 vag_cmd_cnt = 0;
u32 vag_cmd_used = 0;
u32 max_vag_cmd_cnt = 0;
ISO_VAGCommand vag_cmds[16];
static constexpr s32 LOOP_END = 1;
static constexpr s32 LOOP_REPEAT = 2;
static constexpr s32 LOOP_START = 4;
// Empty ADPCM block with loop flags
// clang-format off
u8 VAG_SilentLoop[0x60] = {
0x0, LOOP_START | LOOP_REPEAT, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, LOOP_REPEAT, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, LOOP_END | LOOP_REPEAT, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
};
// clang-format on
void jak3_overlord_init_globals_iso_queue() {
g_nPriQueueSema = 0;
g_VagCmdSema = 0;
for (auto& x : gPriStack) {
x = {};
}
g_cmds_with_speed_total = 0;
unk_time_mode_flag = false;
g_selected_cmd = nullptr;
unk_time_mflag = 0;
unk_sector = 0;
vag_cmd_cnt = 0;
vag_cmd_used = 0;
for (auto& x : vag_cmds) {
x = {};
}
}
/*!
* Added function to check if there is a pending DGO load command.
* On PC, DOG loads are really the only loading time that users see. If there is a pending
* DGO load, we can modify logic to take advantage of PCs being dramatically faster than PS2 and
* get much better load times.
*/
bool DgoCmdWaiting() {
for (auto& level : gPriStack) {
for (int i = 0; i < level.count; i++) {
auto* cmd = level.cmds[i];
if (cmd && cmd->msg_type == ISO_Hdr::MsgType::DGO_LOAD) {
if (cmd->m_pBaseFile) {
auto* file = cmd->m_pBaseFile;
if (file->m_Buffer.m_nDataLength) {
return true;
}
}
}
}
}
return false;
}
void InitBuffers() {
SemaParam sema_param;
sema_param.max_count = 1;
sema_param.init_count = 1;
sema_param.attr = 0;
sema_param.option = 0;
g_nPriQueueSema = CreateSema(&sema_param);
ASSERT(g_nPriQueueSema >= 0);
get_page_manager()->Initialize();
g_auStrmSRAM[0] = 0x5040;
g_auTrapSRAM[0] = 0x9040;
snd_SRAMMarkUsed(0x5040, 0x4040);
g_auStrmSRAM[1] = 0x9080;
g_auTrapSRAM[1] = 0xd080;
snd_SRAMMarkUsed(0x9080, 0x4040);
g_auStrmSRAM[2] = 0xd0c0;
g_auTrapSRAM[2] = 0x110c0;
snd_SRAMMarkUsed(0xd0c0, 0x4040);
g_auStrmSRAM[3] = 0x11100;
g_auTrapSRAM[3] = 0x15100;
snd_SRAMMarkUsed(0x11100, 0x4040);
g_auStrmSRAM[4] = 0x15140; // 86384 - 48
g_auTrapSRAM[4] = 0x19140;
snd_SRAMMarkUsed(0x15140, 0x4040);
g_auStrmSRAM[5] = 0x019180;
g_auTrapSRAM[5] = 0x001d180;
snd_SRAMMarkUsed(0x19180, 0x4040);
for (int i = 0; i < 6; i++) {
if (!DMA_SendToSPUAndSync(VAG_SilentLoop, 0x30, g_auTrapSRAM[i], nullptr, nullptr)) {
DelayThread(1000);
ASSERT_NOT_REACHED();
break;
}
}
sema_param.max_count = 1;
sema_param.attr = 1;
sema_param.init_count = 1;
sema_param.option = 0;
g_VagCmdSema = CreateSema(&sema_param);
ASSERT(g_VagCmdSema >= 0);
}
int QueueMessage(ISO_Hdr* msg, int pri) {
msg->status = EIsoStatus::OK_2;
msg->priority = pri;
WaitSema(g_nPriQueueSema);
int queue_idx = (pri == 5) ? 1 : 0;
bool ok = gPriStack[queue_idx].count != 8;
if (ok) {
gPriStack[queue_idx].cmds[gPriStack[queue_idx].count] = msg;
gPriStack[queue_idx].count++;
SignalSema(g_nPriQueueSema);
} else {
msg->status = EIsoStatus::FAILED_TO_QUEUE_4;
SignalSema(g_nPriQueueSema);
ReturnMessage(msg);
ASSERT_NOT_REACHED();
}
return ok;
}
int UnqueueMessage(ISO_Hdr* msg) {
WaitSema(g_nPriQueueSema);
int iVar5 = 0;
PriStackEntry* stack = gPriStack;
do {
int iVar4 = 0;
ISO_Hdr** cmd = stack->cmds;
if (0 < stack->count) {
do {
if (*cmd == msg)
break;
iVar4 = iVar4 + 1;
cmd++;
} while (iVar4 < stack->count);
}
iVar5 = iVar5 + 1;
if (iVar4 < stack->count) {
stack->count = stack->count + -1;
if (iVar4 < stack->count) {
ISO_Hdr** ppIVar3 = stack->cmds + iVar4;
do {
iVar4 = iVar4 + 1;
*ppIVar3 = ppIVar3[1];
ppIVar3 = ppIVar3 + 1;
} while (iVar4 < stack->count);
}
return SignalSema(g_nPriQueueSema);
}
stack = stack + 1;
if (1 < iVar5) {
return SignalSema(g_nPriQueueSema);
}
} while (true);
}
/*!
* Select which command to read for next
* This function considers things like seeking time, reading rates of streamed files,
* and buffer sizing. To be entirely honest, I don't understand it almost at all, and it's not
* clear that it works as expected. It seems to work good enough, and no commands get entirely
* starved of data while there are multiple streams.
*/
ISO_Hdr* GetMessage() {
// bool been_a_while;
// bool bVar2;
// int now;
// int iVar3;
// int iVar4;
// CISOCDFile *file;
// CISOCDFile *pCVar5;
// int iVar6;
// CPageList *plist;
// uint uVar7;
// int iVar8;
// int iVar9;
// PriStack *local_t2_216;
// PriStack *pri_level;
// CISOCDFile *tfile4;
// code *pcVar10;
// CISOCDFile *tfile2;
// CISOCDFile *tfile3;
// uint uVar11;
// ISO_VAGCommand *cmd;
// int idx_on_level;
// ISO_VAGCommand **ppIVar12;
// int iVar13;
// CBaseFile *tfile;
// int cmds_total;
// int iVar14;
// uint uVar15;
ISO_Hdr* cmds_array[16];
u32 read_rates_array[16];
int num_pages_array[16];
int unstepped_pages_array[16];
int remaining_pages_array[16];
// uint its_been_a_while;
// int pages_total;
// int read_rate_total;
// int min_nospeed_pages_total;
// int max_pages_total;
// int min_speed_pages_total;
// ISO_VAGCommand *selected_cmd;
// int cmds_with_speed_total;
// uint cmd2_read_rate;
// int selected_cmd_rem_sectors;
// int pri_level_idx;
// simple logic to select which command to use next.
u32 now = GetSystemTimeLow();
bool been_a_while = false;
if (unk_time_mode_flag == 0) {
been_a_while = 0x384000 < (now - time_of_last_unknown_rate_drive_op);
} else {
unk_time_mode_flag = 0;
time_of_last_unknown_rate_drive_op = now;
}
s32 cmds_total = 0;
get_page_manager()->GarbageCollect();
s32 pages_total = 0;
s32 read_rate_total = 0;
s32 min_nospeed_pages_total = 0;
s32 max_pages_total = 0;
s32 min_speed_pages_total = 0;
LAB_000080e4:
s32 iVar9 = g_cmds_with_speed_total * 400 + 0x2ee;
s32 iVar13 = 0x7fffffff;
ISO_Hdr* selected_cmd = nullptr;
s32 cmds_with_speed_total = 0;
s32 cmd2_read_rate = 0;
s32 selected_cmd_rem_sectors = -1;
s32 pri_level_idx = 1;
PriStackEntry* pri_level = gPriStack + 1;
s32 iVar8 = iVar13;
// loop over priority levels
do {
s32 idx_on_level = pri_level->count + -1;
// if any exist on this level
if (-1 < idx_on_level) {
ISO_Hdr** ppIVar12 = pri_level->cmds + idx_on_level;
// iVar14 = cmds_total << 2;
// loop over commands on this level
do {
ISO_Hdr* cmd = *ppIVar12;
CBaseFile* file = nullptr;
// iVar4 = iVar14;
// basic check if this command is even valid:
if (cmd && (file = cmd->m_pBaseFile, file) && cmd->status == EIsoStatus::OK_2 &&
cmd->active_a != 0) {
u32 read_rate = file->m_ReadRate;
read_rate_total = read_rate_total + read_rate; // maybe this is an inverse rate...
// build up arrays of info for each command
read_rates_array[cmds_total] = read_rate;
cmds_array[cmds_total] = cmd;
if ((int)read_rate < 1) {
min_nospeed_pages_total = min_nospeed_pages_total + file->m_Buffer.m_nMinNumPages;
max_pages_total = max_pages_total + file->m_Buffer.m_nMaxNumPages;
} else {
min_speed_pages_total = min_speed_pages_total + file->m_Buffer.m_nMinNumPages;
cmds_with_speed_total = cmds_with_speed_total + 1;
}
CPageList* plist = file->m_Buffer.m_pPageList;
s32 npages = 0;
if (plist != (CPageList*)0x0) {
npages = plist->m_nNumPages;
}
pages_total = pages_total + npages;
num_pages_array[cmds_total] = npages;
s32 n_untepped_pages = 0;
if (plist != (CPageList*)0x0) {
n_untepped_pages = plist->m_nNumUnsteppedPages;
}
unstepped_pages_array[cmds_total] = n_untepped_pages;
s32 n_remaining_pages = 4;
if (cmd->callback != RunDGOStateMachine) {
n_remaining_pages = n_untepped_pages + file->m_LengthPages - file->m_PageOffset;
// lg::warn("remaining pages is {} = {} + {} - {}", n_remaining_pages, n_untepped_pages,
// file->m_LengthPages, file->m_PageOffset);
}
remaining_pages_array[cmds_total] = n_remaining_pages;
if (remaining_pages_array[cmds_total] < 1) {
remaining_pages_array[cmds_total] = 1;
}
// careful, this increments in a weird spot.
// I use cmds_total - 1 below...
int old_cmds_total = cmds_total;
cmds_total = cmds_total + 1;
// iVar4 = iVar14 + 4;
// next, we'll determine a desired page cutoff and discard commands where
// we have enough pages.
if (read_rate && plist) {
// 3/4 * mNumPages, this is if our allocated buffer is 75% full.
s32 desired_pages = (int)(file->m_nNumPages * 0x30) >> 6;
// but, we want at least min pages
if (desired_pages < file->m_Buffer.m_nMinNumPages) {
desired_pages = file->m_Buffer.m_nMinNumPages;
}
// and we want at least 2
if (desired_pages < 2) {
desired_pages = 2;
}
// but, we never want more pages than we plan to eventually read.
if (remaining_pages_array[old_cmds_total] < desired_pages) {
desired_pages = remaining_pages_array[old_cmds_total];
}
// if we have that many, we can just forget the command - no point in filling it now.
if (desired_pages <= unstepped_pages_array[old_cmds_total])
goto LAB_00008420;
}
s32 iVar14 = iVar9;
if ((0 < (int)read_rate) &&
(iVar14 = (int)(file->m_Buffer.m_nDataLength * 1000) / (int)read_rate,
read_rate == 0)) {
ASSERT_NOT_REACHED();
}
s32 pri = cmd->priority;
// really not sure what this is...
if (been_a_while) {
if ((read_rate == 0) || (iVar14 < 0x2ee)) {
been_a_while = false;
goto LAB_000080e4;
}
// if this isn't the command we said last time.
if (g_selected_cmd != cmd) {
s32 current_sector = file->GetSector();
current_sector = current_sector - unk_sector;
bool sector_ok = false;
if (current_sector < 0) {
current_sector = -current_sector;
if (unk_time_mflag != 0) {
LAB_000083ac:
current_sector = current_sector + 10000000;
}
sector_ok = current_sector < iVar13;
} else {
sector_ok = current_sector < iVar13;
if ((0 < current_sector) && (unk_time_mflag == 0))
goto LAB_000083ac;
}
if (sector_ok) {
iVar13 = current_sector;
iVar8 = iVar14;
selected_cmd = cmd;
cmd2_read_rate = read_rate;
}
}
} else {
// normal selection logic???
if ((((iVar14 == iVar8) && (selected_cmd_rem_sectors < pri)) || (iVar14 < iVar8)) &&
(iVar14 <= iVar9)) {
iVar8 = iVar14;
selected_cmd = cmd;
cmd2_read_rate = read_rate;
selected_cmd_rem_sectors = pri;
}
}
}
LAB_00008420:
idx_on_level = idx_on_level + -1;
/* WARNING: ptrarith problems */
ppIVar12 = ppIVar12 + -1;
// iVar14 = iVar4;
} while (-1 < idx_on_level);
}
pri_level--;
pri_level_idx = pri_level_idx + -1;
} while (-1 < pri_level_idx);
if (selected_cmd) {
if (cmd2_read_rate == 0) {
unk_time_mode_flag = 1;
time_of_last_unknown_rate_drive_op = now;
}
iVar13 = unk_sector;
if (selected_cmd->m_pBaseFile) {
iVar13 = selected_cmd->m_pBaseFile->GetSector();
}
if (unk_sector < iVar13) {
unk_time_mflag = 1;
unk_sector = iVar13;
} else {
been_a_while = iVar13 != unk_sector;
unk_sector = iVar13;
if (been_a_while) {
unk_time_mflag = 0;
unk_sector = iVar13;
}
}
}
min_speed_pages_total = min_speed_pages_total + min_nospeed_pages_total;
g_cmds_with_speed_total = cmds_with_speed_total;
g_selected_cmd = selected_cmd;
pages_total = get_page_manager()->m_CCache.m_nNumFreePages + pages_total;
if ((0 < min_speed_pages_total) && (0 < pages_total)) {
if (min_speed_pages_total == 0) {
// trap(0x1c00);
ASSERT_NOT_REACHED();
}
min_speed_pages_total = (min_nospeed_pages_total * pages_total) / min_speed_pages_total;
if (min_speed_pages_total < min_nospeed_pages_total) {
min_speed_pages_total = min_nospeed_pages_total;
}
if (max_pages_total < min_speed_pages_total) {
min_speed_pages_total = max_pages_total;
}
pages_total = pages_total - min_speed_pages_total;
iVar9 = 0;
iVar13 = min_speed_pages_total;
iVar8 = pages_total;
if (0 < cmds_total) {
// iVar14 = 0;
iVar13 = min_speed_pages_total;
iVar8 = pages_total;
do {
CBaseFile* tfile = cmds_array[iVar9]->m_pBaseFile;
if (read_rates_array[iVar9] < 1) {
s32 uVar11 = (tfile->m_Buffer).m_nMaxNumPages;
if (max_pages_total == 0) {
ASSERT_NOT_REACHED();
}
s32 uVar15 = (tfile->m_Buffer).m_nMinNumPages;
s32 uVar7 = (int)(uVar11 * min_speed_pages_total) / max_pages_total;
if ((int)uVar7 < (int)uVar15) {
uVar7 = uVar15;
}
tfile->m_nNumPages = uVar7;
if ((int)uVar11 < (int)uVar7) {
uVar7 = uVar11;
}
tfile->m_nNumPages = uVar7;
if (remaining_pages_array[iVar9] < (int)uVar7) {
uVar7 = remaining_pages_array[iVar9];
}
tfile->m_nNumPages = uVar7;
// lg::warn("num pages mod {}", uVar7);
iVar13 = iVar13 - uVar7;
} else {
s32 uVar11 = (tfile->m_Buffer).m_nMinNumPages;
s32 uVar7 = (tfile->m_Buffer).m_nMaxNumPages;
// lg::warn("taking else case: {} {} {}", uVar11, uVar7, tfile->m_nNumPages);
s32 uVar15 = -1;
if (read_rate_total == 0) {
LAB_000085e4:
// lg::warn("going to min! (rrt is {})", read_rate_total);
uVar15 = uVar11;
} else {
if (read_rate_total == 0) {
ASSERT_NOT_REACHED();
}
uVar15 = (read_rates_array[iVar9] * pages_total) / read_rate_total;
// lg::warn("read rate math is {}", uVar15);
if ((int)uVar15 < (int)uVar11)
goto LAB_000085e4;
}
// lg::warn("vals {} {}", uVar7, uVar15);
if ((int)uVar7 < (int)uVar15) {
uVar15 = uVar7;
}
// lg::warn("vals 2 {} {}", remaining_pages_array[iVar9], uVar15);
if (remaining_pages_array[iVar9] < (int)uVar15) {
uVar15 = remaining_pages_array[iVar9];
}
iVar8 = iVar8 - uVar15;
tfile->m_nNumPages = uVar15;
// lg::warn("num pages mod 2 {}", uVar15);
}
iVar9 = iVar9 + 1;
// iVar14 = iVar9 * 4;
} while (iVar9 < cmds_total);
}
while (0 < iVar13) {
iVar9 = 0;
s32 iVar14 = iVar13;
if (0 < cmds_total) {
s32 iVar4 = 0;
iVar14 = iVar13;
while (0 < iVar14) {
CBaseFile* tfile2 = cmds_array[iVar4 / 4]->m_pBaseFile;
iVar9 = iVar9 + 1;
s32 uVar11;
if (read_rates_array[iVar4 / 4] == 0 &&
(uVar11 = tfile2->m_nNumPages, (int)uVar11 < remaining_pages_array[iVar4 / 4]) &&
((int)uVar11 < tfile2->m_Buffer.m_nMaxNumPages)) {
tfile2->m_nNumPages = uVar11 + 1;
iVar14 = iVar14 + -1;
}
if (cmds_total <= iVar9)
break;
iVar4 = iVar9 * 4;
}
}
been_a_while = iVar13 == iVar14;
iVar13 = iVar14;
if (been_a_while) {
iVar8 = iVar8 + iVar14;
iVar13 = 0;
}
}
while (0 < iVar8) {
iVar13 = 0;
iVar9 = iVar8;
if (0 < cmds_total) {
s32 iVar14 = 0;
iVar9 = iVar8;
while (0 < iVar9) {
CBaseFile* tfile4 = cmds_array[iVar14 / 4]->m_pBaseFile;
iVar13 = iVar13 + 1;
s32 uVar11;
if (((0 < read_rates_array[iVar14 / 4]) &&
(uVar11 = tfile4->m_nNumPages, (int)uVar11 < remaining_pages_array[iVar14 / 4])) &&
((int)uVar11 < tfile4->m_Buffer.m_nMaxNumPages)) {
tfile4->m_nNumPages = uVar11 + 1;
iVar9 = iVar9 + -1;
}
if (cmds_total <= iVar13)
break;
iVar14 = iVar13 * 4;
}
}
been_a_while = iVar8 == iVar9;
iVar8 = iVar9;
if (been_a_while) {
iVar8 = 0;
}
}
iVar13 = 0;
if (0 < cmds_total) {
iVar8 = 0;
do {
CBaseFile* tfile3 = cmds_array[iVar8 / 4]->m_pBaseFile;
iVar13 = iVar13 + 1;
s32 uVar11 = tfile3->m_nNumPages;
// lg::warn("mystery values: {} - {} = {} (from {})", num_pages_array[iVar8 / 4],
// uVar11,
// num_pages_array[iVar8 / 4] - uVar11,
// cmds_array[iVar8 / 4]->m_pBaseFile->m_FileDef->name.data);
if (((int)uVar11 < num_pages_array[iVar8 / 4]) &&
(iVar8 = tfile3->RecoverPages(num_pages_array[iVar8 / 4] - uVar11), 0 < iVar8)) {
get_page_manager()->GarbageCollectPageList(tfile3->m_Buffer.m_pPageList);
}
iVar8 = iVar13 * 4;
} while (iVar13 < cmds_total);
}
}
return selected_cmd;
}
int ProcessMessageData(ISO_Hdr* tgt_msg) {
EIsoStatus stat;
s32 ret = 1;
LAB_000088a4:
s32 num_remaining = gPriStack[0].count + -1;
if (num_remaining < 0) {
return ret;
}
auto* cmd_array = gPriStack[0].cmds + num_remaining;
ISO_Hdr* cmd = nullptr;
do {
EIsoStatus (*cb)(ISO_Hdr*) = nullptr;
cmd = *cmd_array;
if (cmd && cmd->active_a) {
stat = cmd->status;
if (stat == EIsoStatus::OK_2) {
auto* file = cmd->m_pBaseFile;
auto* buffer = &file->m_Buffer;
if (!file) {
buffer = nullptr;
}
if ((buffer && (buffer->m_eBufferType != CBuffer::BufferType::EBT_FREE)) &&
(cb = cmd->callback, cb != ProcessVAGData)) {
stat = (cb)(cmd);
}
}
if (stat != EIsoStatus::OK_2)
break;
cmd->status = EIsoStatus::OK_2;
}
num_remaining = num_remaining + -1;
cmd_array = cmd_array + -1;
if (num_remaining < 0) {
return ret;
}
} while (true);
if (cmd == tgt_msg) {
ret = 0;
}
ReleaseMessage(cmd);
if (stat != EIsoStatus::IDLE_1) {
cmd->status = stat;
ReturnMessage(cmd);
}
goto LAB_000088a4;
}
void ReturnMessage(ISO_Hdr* msg) {
if (msg->mbox_reply == 0) {
if (msg->thread_to_wake == 0) {
FreeVAGCommand((ISO_VAGCommand*)msg);
} else {
WakeupThread(msg->thread_to_wake);
}
} else {
SendMbx(msg->mbox_reply, msg);
}
}
void ReleaseMessage(ISO_Hdr* msg) {
if (GetThreadId() == g_nISOThreadID) {
auto* file = msg->m_pBaseFile;
if (file && file->m_Status != EIsoStatus::NONE_0) {
file->Close();
}
UnqueueMessage(msg);
} else {
if (msg->msg_type != ISO_Hdr::MsgType::ABADBABE) {
set_active_a(msg, 0);
set_active_c(msg, 0);
msg->msg_type = ISO_Hdr::MsgType::ABADBABE;
SendMbx(g_nISOMbx, msg);
}
}
}
ISO_VAGCommand* GetVAGCommand() {
int iVar1;
u32 uVar2;
u32 uVar3;
do {
while (vag_cmd_cnt == 0x1f) {
DelayThread(1000);
}
do {
iVar1 = WaitSema(g_VagCmdSema);
uVar2 = 0;
} while (iVar1 != 0);
do {
uVar3 = uVar2 + 1;
if (((vag_cmd_used >> (uVar2 & 0x1f) ^ 1U) & 1) != 0) {
vag_cmd_cnt = vag_cmd_cnt + 1;
vag_cmd_used = vag_cmd_used | 1 << (uVar2 & 0x1f);
if (max_vag_cmd_cnt < vag_cmd_cnt) {
max_vag_cmd_cnt = vag_cmd_cnt;
}
SignalSema(g_VagCmdSema);
return vag_cmds + uVar2;
}
uVar2 = uVar3;
} while ((int)uVar3 < 0x1f);
SignalSema(g_VagCmdSema);
} while (true);
}
void FreeVAGCommand(ISO_VAGCommand* param_1) {
u32 uVar1;
int iVar2;
uVar1 = param_1 - vag_cmds;
ASSERT(uVar1 < 16);
if ((uVar1 < 0x1f) && (((vag_cmd_used >> (uVar1 & 0x1f) ^ 1U) & 1) == 0)) {
do {
iVar2 = WaitSema(g_VagCmdSema);
} while (iVar2 != 0);
vag_cmd_cnt = vag_cmd_cnt - 1;
vag_cmd_used = vag_cmd_used & ~(1 << (uVar1 & 0x1f));
SignalSema(g_VagCmdSema);
}
}
} // namespace jak3

View file

@ -0,0 +1,30 @@
#pragma once
#include "common/common_types.h"
namespace jak3 {
void jak3_overlord_init_globals_iso_queue();
struct ISO_Hdr;
struct ISO_VAGCommand;
void ReleaseMessage(ISO_Hdr* msg);
void FreeVagCmd(ISO_VAGCommand* msg);
int QueueMessage(ISO_Hdr* msg, int pri);
int UnqueueMessage(ISO_Hdr* msg);
void ReturnMessage(ISO_Hdr* msg);
void InitBuffers();
bool DgoCmdWaiting();
ISO_Hdr* GetMessage();
int ProcessMessageData(ISO_Hdr* msg);
void FreeVAGCommand(ISO_VAGCommand* msg);
ISO_VAGCommand* GetVAGCommand();
struct PriStackEntry {
ISO_Hdr* cmds[8];
int count = 0;
};
extern u32 g_auTrapSRAM[6];
extern u32 g_auStrmSRAM[6];
extern s32 g_nPriQueueSema;
extern PriStackEntry gPriStack[2];
} // namespace jak3

View file

@ -0,0 +1,118 @@
#include "isocommon.h"
#include "common/util/Assert.h"
namespace jak3 {
void jak3_overlord_init_globals_isocommon() {}
/*!
* Convert file name to "ISO Name"
* ISO names are upper case and 12 bytes long.
* xxxxxxxxyyy0
*
* x - uppercase letter of file name, or space
* y - uppercase letter of file extension, or space
* 0 - null terminator (\0, not the character zero)
*/
void MakeISOName(ISOName* dst, const char* src) {
int i = 0;
const char* src_ptr = src;
char* dst_ptr = dst->data;
// copy name and upper case
while ((i < 8) && (*src_ptr) && (*src_ptr != '.')) {
char c = *src_ptr;
src_ptr++;
if (('`' < c) && (c < '{')) { // lower case
c -= 0x20;
}
*dst_ptr = c;
dst_ptr++;
i++;
}
// pad out name with spaces
while (i < 8) {
*dst_ptr = ' ';
dst_ptr++;
i++;
}
// increment past period
if (*src_ptr == '.')
src_ptr++;
// same for extension
while (i < 11 && (*src_ptr)) {
char c = *src_ptr;
src_ptr++;
if (('`' < c) && (c < '{')) { // lower case
c -= 0x20;
}
*dst_ptr = c;
dst_ptr++;
i++;
}
while (i < 11) {
*dst_ptr = ' ';
dst_ptr++;
i++;
}
*dst_ptr = 0;
}
// UnmakeISOName
/*!
* Pack an 8-character (64-bits) file name into a packed vag file name (32 + 10 = 42 bit).
*/
int PackVAGFileName(u32* out, const char* name) {
if (!out || !name) {
return 0;
}
int ret = 1;
// accumulator of up to 4 packed characters
u32 acc = 0;
u32 first_four = 0;
for (int i = 0; i < 8; i++) {
// start the second word:
if (i == 4) {
first_four = acc;
acc = 0;
}
// read character from input
u32 name_char = *((const u8*)name);
name++;
u32 remapped_char;
if (name_char - 'A' < 26) { // capital letter
remapped_char = name_char - 'A' + 1; // so A becomes 1.
} else if (name_char - 'a' < 26) { // lowercase letter
remapped_char = name_char - 'a' + 1; // so a becomes 1.
} else if (name_char - '0' < 10) { // digit
remapped_char = name_char - '0' + 27; // so 0 becomes 27
} else if (name_char == '-') {
remapped_char = 37;
} else if (name_char == ' ' || name_char == '\0') {
remapped_char = 0;
} else {
ASSERT_NOT_REACHED(); // invalid char in input.
}
ASSERT((remapped_char & 0xff) == remapped_char);
acc = acc * 38 + remapped_char; // (null + alphabet + 10 + dash)
}
out[0] = (first_four << 0x15) | acc;
out[1] = first_four >> 0xb;
return ret;
}
// UnpackVAGFileName - nobody uses it...
} // namespace jak3

View file

@ -0,0 +1,152 @@
#pragma once
#include <string>
#include "common/common_types.h"
namespace jak3 {
void jak3_overlord_init_globals_isocommon();
struct CBaseFile;
struct ISOFileDef;
enum class EIsoStatus {
NONE_0 = 0,
IDLE_1 = 0x1,
OK_2 = 0x2, // already reading, or no need to read, or no mem to read.
FAILED_TO_QUEUE_4 = 0x4,
ERROR_OPENING_FILE_8 = 0x8,
NULL_CALLBACK = 9,
ERROR_NO_FILE = 0xa,
ERROR_b = 0xb, // tried to do BeginRead on a file that's not allocated.
ERROR_NO_SOUND = 0xc,
};
struct SoundBankInfo;
struct ISO_Hdr {
int unka;
int unkb;
EIsoStatus status;
int8_t active_a;
int8_t active_b;
int8_t active_c;
int8_t pad;
enum class MsgType : u32 {
MSG_0 = 0,
LOAD_EE = 0x100,
LOAD_IOP = 0x101,
LOAD_EE_CHUNK = 0x102,
LOAD_SOUNDBANK = 0x103,
DGO_LOAD = 0x200,
VAG_QUEUE = 0x400,
VAG_STOP = 0x402,
VAG_PAUSE = 0x403,
VAG_UNPAUSE = 0x404,
VAG_SET_PITCH_VOL = 0x406,
PLAY_MUSIC_STREAM = 0x408,
ABADBABE = 0xabadbabe,
ADEADBEE = 0xadeadbee,
} msg_type = MsgType::MSG_0;
int mbox_reply;
int thread_to_wake;
EIsoStatus (*callback)(ISO_Hdr*) = nullptr;
CBaseFile* m_pBaseFile = nullptr;
int priority = 0;
const ISOFileDef* file_def = nullptr;
};
struct ISO_LoadCommon : public ISO_Hdr {
u8* addr = 0; // 44
int maxlen = 0;
int length_to_copy = 0; // 52
u8* dest_ptr = 0; // 68 (not truly common??) what are they doing here.
int progress_bytes = 0; // 72
};
struct ISO_LoadSingle : public ISO_LoadCommon {
// addr
// maxlen
// maybe size
int sector_offset = 0; // 56
// 60
// 64
// dest_ptr
// maybe ofset
};
struct ISO_LoadSoundbank : public ISO_LoadCommon {
const char* name = nullptr; // 60
int priority; // 64
SoundBankInfo* bank_info = nullptr;
};
struct CPage;
struct BlockParams {
void* destination;
int num_sectors;
int sector_num;
// ADDED
const ISOFileDef* file_def;
CPage* page;
char* flag;
};
struct CDescriptor;
struct Block {
BlockParams params;
CDescriptor* descriptor;
Block* next;
};
struct ISOName {
char data[12];
bool operator==(const ISOName& other) {
for (int i = 0; i < 12; i++) {
if (data[i] != other.data[i]) {
return false;
}
}
return true;
}
};
struct ISOFileDef {
ISOName name;
std::string full_path; // pc
u32 length;
};
struct VagDirEntry {
u32 words[2];
};
struct VagDir {
int vag_magic_1 = 0;
int vag_magic_2 = 0;
int vag_version = 0;
int num_entries = 0;
VagDirEntry entries[4096];
};
static_assert(sizeof(VagDir) == 0x8010);
constexpr int MUSIC_TWEAK_COUNT = 0x40;
struct MusicTweaks {
u32 TweakCount = 0;
struct {
char MusicName[12] = "\0";
s32 VolumeAdjust = 0;
} MusicTweak[MUSIC_TWEAK_COUNT];
};
int PackVAGFileName(u32* out, const char* name);
void MakeISOName(ISOName* dst, const char* src);
} // namespace jak3

View file

@ -0,0 +1,54 @@
#include "list.h"
#include "common/util/Assert.h"
#include "game/overlord/jak3/vag.h"
#include "game/sce/iop.h"
namespace jak3 {
using namespace iop;
void jak3_overlord_init_globals_list() {}
void InitList(List* list, int count, int element_size) {
VagStreamData* iter;
int iVar1;
SemaParam sema_params;
list->count = count;
iter = (VagStreamData*)AllocSysMemory(0, count * element_size, 0);
ASSERT(iter);
ASSERT(element_size == sizeof(VagStreamData));
list->buffer = (u8*)iter;
if (iter != (VagStreamData*)0x0) {
list->next = iter;
iVar1 = 0;
if (0 < count) {
do {
iter->in_use = 0;
if (iVar1 < count + -1) {
iter->next = (VagStreamData*)((u8*)iter + element_size);
} else {
iter->next = nullptr;
}
if (iVar1 == 0) {
iter->prev = nullptr;
} else {
iter->prev = (VagStreamData*)((u8*)iter - element_size);
}
iVar1 = iVar1 + 1;
iter = (VagStreamData*)((u8*)iter + element_size);
} while (iVar1 < count);
}
list->unk_flag = 0;
list->pending_data = 0;
sema_params.max_count = 1;
sema_params.attr = 1;
sema_params.init_count = 1;
sema_params.option = 0;
iVar1 = CreateSema(&sema_params);
list->sema = iVar1;
ASSERT(list->sema >= 0);
}
}
} // namespace jak3

27
game/overlord/jak3/list.h Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include "common/common_types.h"
namespace jak3 {
void jak3_overlord_init_globals_list();
struct VagStreamData;
/*!
* The List system is a linked list used to track requested and playing streams.
* Originally, it supported multiple element types.
* One of those types is related to plugin streams which are not supported in PC.
* So, this is just hard-coded to use VagStreamData as the element type.
*/
struct List {
char name[8];
int sema = 0;
int unk_flag = 0; // set when there's a free node??
int count = 0;
int pending_data = 0;
VagStreamData* next = nullptr;
u8* buffer;
};
void InitList(List* list, int num_elements, int element_size);
} // namespace jak3

View file

@ -0,0 +1,151 @@
#include "overlord.h"
#include "common/log/log.h"
#include "game/overlord/jak3/dvd_driver.h"
#include "game/overlord/jak3/init.h"
#include "game/overlord/jak3/iso.h"
#include "game/overlord/jak3/ramdisk.h"
#include "game/overlord/jak3/sbank.h"
#include "game/overlord/jak3/srpc.h"
#include "game/overlord/jak3/ssound.h"
#include "game/overlord/jak3/vblank_handler.h"
#include "game/sce/iop.h"
namespace jak3 {
using namespace iop;
int g_nServerThreadID = 0;
int g_nPlayerThreadID = 0;
int g_nLoaderThreadID = 0;
void jak3_overlord_init_globals_overlord() {
g_nServerThreadID = 0;
g_nPlayerThreadID = 0;
g_nLoaderThreadID = 0;
}
namespace {
/*!
* Start up overlord threads. After this returns, the overlord is initialized and
* ready to run. (this might have been in start.cpp in the original, but this is close enough)
*/
int start_overlord() {
// Initialize SIF to communicate with the EE. Does nothing in port
if (!sceSifCheckInit()) {
sceSifInit();
}
// Initialize RPC to EE
sceSifInitRpc(0);
lg::info("======== overlrd2.irx startup ========");
// Removed some prints related to IOP memory size
// C++ ctors ran here. In the port, we've added these "init_globals" function that
// take care of resetting all the global/static variables and constructing C++ objects.
// do_global_ctors();
jak3_overlord_init_globals_all();
InitBanks();
InitSound();
VBlank_Initialize();
// RPC thread to load data from game files to the game memory.
ThreadParam thread_param;
thread_param.initPriority = 0x3b;
thread_param.stackSize = 0x800;
thread_param.entry = LoadToEE_RPC_Thread;
thread_param.attr = TH_C;
thread_param.option = 0;
strcpy(thread_param.name, "load_to_ee");
g_nServerThreadID = CreateThread(&thread_param);
if (g_nServerThreadID <= 0) {
return 1;
}
// thread to respond to sound play RPC's
thread_param.entry = Thread_Player;
thread_param.initPriority = 0x36;
thread_param.stackSize = 0xb00;
thread_param.attr = TH_C;
thread_param.option = 0;
strcpy(thread_param.name, "player");
g_nPlayerThreadID = CreateThread(&thread_param);
if (g_nPlayerThreadID <= 0) {
return 1;
}
// thread to respond to sound load RPC's
thread_param.attr = TH_C;
thread_param.entry = Thread_Loader;
thread_param.initPriority = 0x3a;
thread_param.stackSize = 0x900;
thread_param.option = 0;
strcpy(thread_param.name, "loader");
g_nLoaderThreadID = CreateThread(&thread_param);
if (g_nLoaderThreadID <= 0) {
return 1;
}
// Initialize the dvd driver that will be used to implement the "ISO FS" system
get_driver()->Initialize();
// then, initialize ISO FS itself
InitISOFS();
// start up RPC threads!
StartThread(g_nServerThreadID, 0);
StartThread(g_nPlayerThreadID, 0);
StartThread(g_nLoaderThreadID, 0);
lg::info("[overlord2] Threads started");
return 0;
}
bool* init_complete;
/*!
* Wrapper of start_overlord that can be run as an IOP thread. Sets init_complete flag to true.
* This is a bit of hack to transition from the normal and sane game code to the world of IOP
* threading.
*/
u32 call_start() {
start_overlord();
*init_complete = true;
// TODO: figure out how to quit this thread.
while (true) {
SleepThread();
}
return 0;
}
} // namespace
int start_overlord_wrapper(bool* signal) {
ThreadParam param = {};
init_complete = signal;
param.attr = TH_C;
param.initPriority = 0;
param.stackSize = 0x800;
param.option = 0;
strcpy(param.name, "start"); // added for debug
param.entry = call_start;
auto start_thread = CreateThread(&param);
StartThread(start_thread, 0);
return 0;
}
/*!
* Copy null-terminated string to destination buffer with the given size.
* If the string doesn't fit, it will be truncated and null terminated.
*/
char* strncpyz(char* dst, const char* src, size_t n) {
if (n) {
strncpy(dst, src, n);
dst[n - 1] = 0;
}
return dst;
}
} // namespace jak3

View file

@ -0,0 +1,61 @@
#pragma once
#include <cstdlib>
#include "common/common_types.h"
#include "common/log/log.h"
namespace jak3 {
/*!
* External entry point for the game to start Overlord. This assumes that the IOP Kernel
* is at least initialized, then sets up all overlord threads/RPCs. Once this returns,
* it's safe to call overlord functions.
*/
int start_overlord_wrapper(bool* signal);
void jak3_overlord_init_globals_overlord();
char* strncpyz(char* dst, const char* src, size_t n);
extern int g_nServerThreadID;
extern int g_nPlayerThreadID;
extern int g_nLoaderThreadID;
enum class LogCategory {
PAGING,
FILESYSTEM,
WARN,
SPU_DMA_STR,
EE_DMA,
ISO_QUEUE,
VAG_SETUP,
DGO,
RPC,
STR_RPC,
PLAYER_RPC,
DRIVER,
NUM_CATETORIES
};
constexpr bool g_OverlordLogEnable[(int)LogCategory::NUM_CATETORIES] = {
false, // paging: cpage's, page manager, page crossing, etc
true, // filesystem: opening/finding files
true, // warning: something weird
false, // spu dma streaming: vag streaming, clocks, spu, dma
true, // ee dma: sending stuff to the ee (dgo, etc)
true, // iso queue: message queuing
true, // vag setup: creation of vag commands (lists, etc)
false, // dgo
true, // rpc in general
true, // str rpc
false, // PLAYER
false, // driver
};
template <typename... Args>
void ovrld_log(LogCategory category, const std::string& format, Args&&... args) {
if (g_OverlordLogEnable[(int)category]) {
lg::info(format, std::forward<Args>(args)...);
}
}
} // namespace jak3

View file

@ -0,0 +1,852 @@
#include "pagemanager.h"
#include <cstdlib>
#include <cstring>
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "game/overlord/jak3/overlord.h"
#include "game/sce/iop.h"
using namespace iop;
namespace jak3 {
namespace {
std::unique_ptr<CPageManager> g_manager;
}
void jak3_overlord_init_globals_pagemanager() {
g_manager = std::make_unique<CPageManager>();
}
CPageManager* get_page_manager() {
return g_manager.get();
}
/*!
* Add the given number of pages to the active list, returning the first in the active list.
* This should be called before writing to the pages.
* This grows the active list from the end and adds a reference count.
*/
CPage* CPageList::AddActivePages(int num_pages) {
ASSERT(m_nAllocState == AllocState::EPLAS_ALLOCATED);
ASSERT(num_pages > 0); // game warned on this, might be ok to just return null
if (m_nNumPages < num_pages + m_nNumActivePages) {
// the original game just gave you no pages, but that seems sus, so abort for now.
ASSERT_NOT_REACHED();
}
// pick the page to convert from non-active to active
CPage* first_to_convert;
if (m_pLastActivePage) {
// if we have active pages, go to the page after that
first_to_convert = m_pLastActivePage->m_pNextPage;
} else {
// otherwise, start at the beginning of the page list.
first_to_convert = m_pFirstPage;
}
if (first_to_convert) {
// the first active page should stay the same if we have one
CPage* new_first_active = m_pFirstActivePage;
if (!new_first_active) {
// but if we don't, it'll be the first we convert.
new_first_active = first_to_convert;
}
int page_idx = 0; // how many we've done
CPage* last_converted = nullptr; // last one that was finished
CPage* to_convert = first_to_convert; // next one to convert
if (0 < num_pages) {
// loop over pages and convert them!!
do {
ASSERT(to_convert->input_state == CPage::State::UNMAKRED);
last_converted = to_convert;
last_converted->input_state = CPage::State::ACTIVE;
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~last_converted->mask);
int status = last_converted->AddRef();
ASSERT(status >= 1);
page_idx = page_idx + 1;
if (!last_converted->m_pNextPage)
break;
to_convert = last_converted->m_pNextPage;
} while (page_idx < num_pages);
}
if (page_idx == num_pages) {
// success! all converted.
// CpuSuspendIntr(local_28);
// update the active range and count
m_pFirstActivePage = new_first_active;
m_pLastActivePage = last_converted;
m_nNumActivePages = m_nNumActivePages + page_idx;
m_nNumUnsteppedPages = m_nNumUnsteppedPages + page_idx;
if (!m_pCurrentActivePage) {
m_pCurrentActivePage = first_to_convert;
}
// CpuResumeIntr(local_28[0]);
return first_to_convert;
} else {
// darn, didn't have enough. undo what we did.
// Not really sure that we need to clear the even flag again.
while (0 < page_idx) {
new_first_active->input_state = CPage::State::UNMAKRED;
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag,
~new_first_active->mask);
new_first_active->ReleaseRef();
}
return nullptr;
}
}
return nullptr;
}
/*!
* Try to remove count pages from the active list, starting at the end and working back.
* This will remove buffered that the user has not yet read. The data will have to be read from the
* DVD again, so this should only be used when more free pages are absolutely needed.
*
* This may remove fewer than requested. It will not remove the current active page, and always
* leaves at least 1 active page to avoid removing a page that's currently being used.
*/
int CPageList::RemoveActivePages(int count) {
// lg::error("Remove Active Pages {}", count);
int num_removed = 0;
ASSERT(m_nAllocState == AllocState::EPLAS_ALLOCATED);
// CpuSuspendIntr(local_28);
num_removed = 0;
// only attempt if we have more than 1 active
if (count > 0 && m_nNumActivePages > 1) {
CPage* last_active = m_pLastActivePage;
CPage* current_active = m_pCurrentActivePage;
// I'm not sure what the last->next = current check is actually doing.
// this check is added - I have no idea how this could happen, or why the game avoids removing
// pages in this case. Hopefully we don't hit it.
if (last_active) {
ASSERT(last_active->m_pNextPage != current_active);
}
if (current_active && last_active && last_active->m_pNextPage != current_active) {
// assume we remove them all except 1.
num_removed = m_nNumActivePages + -1;
// but if that's more than we asked for, reduce.
if (count < num_removed) {
num_removed = count;
}
// iterate backward to find the first to remove, stopping if we reach current_active, or we
// exceed the count to remove.
int num_pages_back = 0;
CPage* iter = last_active;
if (last_active != current_active && 0 < num_removed) {
do {
iter = iter->m_pPrevPage;
num_pages_back = num_pages_back + 1;
if (!iter) {
ASSERT_NOT_REACHED();
}
} while (iter != current_active && num_pages_back < num_removed);
if (0 < num_pages_back) {
// now, iterate forward and convert to inactive.
// go one forward, since the last loop terminated on the page after the last one we want.
CPage* fwd_iter = iter->m_pNextPage;
// the last one that stays active is one after that
m_pLastActivePage = fwd_iter->m_pPrevPage;
m_nNumActivePages = m_nNumActivePages - num_pages_back;
m_nNumUnsteppedPages = m_nNumUnsteppedPages - num_pages_back;
// the end of the conversion loop
CPage* end_iter = last_active->m_pNextPage;
num_removed = num_pages_back;
while (fwd_iter && fwd_iter != end_iter) {
fwd_iter->input_state = CPage::State::UNMAKRED;
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~fwd_iter->mask);
fwd_iter->ReleaseRef();
fwd_iter = fwd_iter->m_pNextPage;
}
} else {
// in this case, I think we return the wrong number of removed pages.
ASSERT_NOT_REACHED();
}
}
}
}
// CpuResumeIntr(local_28[0]);
return num_removed;
}
/*!
* Remove all active pages.
*/
void CPageList::CancelActivePages() {
ASSERT(m_nAllocState == AllocState::EPLAS_ALLOCATED);
// CpuSuspendIntr(local_18);
CPage* last_active = m_pLastActivePage;
CPage* iter = m_pCurrentActivePage;
// note: keep the last active page pointing at the right point in the ring to allocate.
m_pLastActivePage = m_pCurrentActivePage;
m_pFirstActivePage = nullptr;
m_pCurrentActivePage = nullptr;
m_nNumActivePages = 0;
m_nNumUnsteppedPages = 0;
if (iter && last_active && last_active->m_pNextPage != iter) {
CPage* end = last_active->m_pNextPage;
do {
if (iter == end)
break;
iter->input_state = CPage::State::UNMAKRED;
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~iter->mask);
iter->ReleaseRef();
iter = iter->m_pNextPage;
} while (iter);
}
// CpuResumeIntr(local_18[0]);
}
/*!
* Step the current active page forward. This will release the reference count added when the page
* became active. If no other references were added, this page may be Garbage Collected at any time.
*/
CPage* CPageList::StepActivePage() {
ASSERT(m_nAllocState == AllocState::EPLAS_ALLOCATED);
CPage* new_current_active = nullptr;
// CpuSuspendIntr(local_18);
auto* current_active = m_pCurrentActivePage;
if (current_active && m_pLastActivePage && m_pLastActivePage->m_pNextPage != current_active) {
ASSERT(m_nNumActivePages > 0);
m_nNumUnsteppedPages = m_nNumUnsteppedPages + -1;
current_active->ReleaseRef();
current_active->input_state = CPage::State::UNMAKRED;
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~current_active->mask);
new_current_active = current_active->m_pNextPage;
ASSERT(new_current_active != m_pCurrentActivePage);
m_pCurrentActivePage = new_current_active;
} else {
ASSERT_NOT_REACHED(); // step past end of active warning, seems bad.
}
// CpuResumeIntr(local_18[0]);
return new_current_active;
}
/*!
* Remove pages that are no longer needed. They will be sent back to the Page Manager.
*/
void CPageList::GarbageCollect() {
ovrld_log(LogCategory::PAGING, "[paging] Garbage collecting, currently have {} pages, {} active",
m_nNumPages, m_nNumActivePages);
// for (auto* p = m_pFirstPage; p; p = p->m_pNextPage) {
// ovrld_log(LogCategory::PAGING,
// "page 0x{:x}, first active? {} last active? {} current active? {} last? {}",
// p->m_nPageIdx, p == m_pFirstActivePage, p == m_pLastActivePage,
// p == m_pCurrentActivePage, p == m_pLastPage);
// }
// trim pages at the front. Anything unreferenced before the current active page is ok to clean.
CPage* page = m_pFirstPage;
if (page && page != m_pCurrentActivePage) {
ASSERT(page->m_nAllocState == 1); // pages in CPageLists should always be allocated.
while (page->m_nPageRefCount == 0 && page->m_nDmaRefCount == 0) { // only unref'd pages.
ASSERT(page->m_nAllocState == 1); // prior to active.
CPage* next_page = page->m_pNextPage;
// CpuSuspendIntr(&local_18);
m_nNumPages = m_nNumPages + -1;
// pop page from our normal list
m_pFirstPage = next_page;
if (m_pLastPage == page) {
m_pLastPage = nullptr;
// sanity check - we just killed our last page from the list, count should be 0
ASSERT(m_nNumPages == 0);
}
// maintain active list too
if (m_pFirstActivePage == page) {
m_nNumActivePages = m_nNumActivePages + -1;
if (page == m_pLastActivePage) {
// since we're getting rid of this page from our list, don't keep a ref to it.
m_pFirstActivePage = nullptr;
m_pLastActivePage = nullptr;
ASSERT(m_nNumActivePages == 0); // sanity check count.
} else {
m_pFirstActivePage = next_page;
}
}
if (m_pLastActivePage == page) {
m_pLastActivePage = nullptr;
}
if (next_page) {
next_page->m_pPrevPage = nullptr;
}
// CpuResumeIntr(local_18);
// now clean the page itself
ovrld_log(LogCategory::PAGING, "[paging] GC took page 0x{:x} (fwd)", page->m_nPageIdx);
page->m_pPageList = nullptr;
page->m_pPrevPage = nullptr;
page->m_pNextPage = nullptr;
page->m_nAllocState = 0;
page->input_state = CPage::State::UNMAKRED;
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask);
int page_idx = page - get_page_manager()->m_CCache.m_Pages;
if (page_idx < 0x1d) {
get_page_manager()->m_CCache.m_nAllocatedMask &= ~(1 << (page_idx & 0x1f));
} else {
ASSERT_NOT_REACHED(); // idk
}
get_page_manager()->m_CCache.m_nNumFreePages++;
if (!next_page) {
break;
}
page = next_page;
if (page == m_pCurrentActivePage) {
break;
}
}
}
// now, start at the end and work backward. We'll stop once we reach the last active page, since
// we expect everything before that to have a nonzero ref count.
page = m_pLastPage;
if (page && page != m_pLastActivePage && page != m_pCurrentActivePage) {
ASSERT(page->m_nAllocState == 1);
while ((page->m_nPageRefCount == 0 && (page->m_nDmaRefCount == 0))) {
ovrld_log(LogCategory::PAGING, "[paging] GC took page 0x{:x} (bwd)", page->m_nPageIdx);
ASSERT(page->m_nAllocState == 1);
CPage* prev = page->m_pPrevPage;
// CpuSuspendIntr(&local_14);
m_nNumPages = m_nNumPages + -1;
m_pLastPage = prev;
if (m_pFirstPage == page) {
m_pFirstPage = nullptr;
ASSERT(m_nNumPages == 0);
}
if (prev != (CPage*)0x0) {
prev->m_pNextPage = (CPage*)0x0;
}
// CpuResumeIntr(local_14);
page->m_pPageList = nullptr;
page->m_pPrevPage = nullptr;
page->m_pNextPage = nullptr;
page->m_nAllocState = 0;
page->input_state = CPage::State::UNMAKRED;
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask);
int page_idx = page - get_page_manager()->m_CCache.m_Pages;
if (page_idx < 0x1d) {
get_page_manager()->m_CCache.m_nAllocatedMask &= ~(1 << (page_idx & 0x1f));
} else {
ASSERT_NOT_REACHED();
}
get_page_manager()->m_CCache.m_nNumFreePages++;
if (!prev) {
return;
}
if (prev == m_pLastActivePage) {
return;
}
page = prev;
if (prev == m_pCurrentActivePage) {
return;
}
}
}
ovrld_log(LogCategory::PAGING,
"[paging] Done Garbage collecting, currently have {} pages, {} active in 0x{:x}",
m_nNumPages, m_nNumActivePages, (u64)this);
}
/*!
* Wait on the pages indicated by the mask.
*/
void CPageManager::WaitForPagesFilled(u32 mask) {
if ((mask & m_CCache.m_PendingMask) != 0) {
WaitEventFlag(m_CCache.m_PagesFilledEventFlag, mask, 0);
} else {
// waiting for something that's not pending... might be ok. was a warning.
ASSERT_NOT_REACHED();
}
}
CPage::CPage(uint8_t* page_mem_start, uint8_t* page_mem_end, int page_idx) {
m_pNextPage = nullptr;
mask = 1 << (page_idx & 0x1f);
m_pPrevPage = nullptr;
m_pPageMemStart = page_mem_start;
m_pPageList = nullptr;
m_pPageMemEnd = page_mem_end;
m_nPageRefCount = 0;
m_nPageIdx = page_idx;
m_nDmaRefCount = 0;
m_nAllocState = 0;
input_state = State::UNMAKRED;
}
/*!
* Add one to this CPage's reference count, preventing it from being garbage collected
*/
int CPage::AddRef() {
// CpuSuspendIntr(local_18);
auto* page_list = m_pPageList;
int ret = -1;
ASSERT(page_list);
ASSERT(m_nAllocState == 1);
if (m_nAllocState == 1 && page_list) {
page_list->m_nPageRefCnt = page_list->m_nPageRefCnt + 1;
m_nPageRefCount = m_nPageRefCount + 1;
ret = m_nPageRefCount;
}
// CpuResumeIntr(local_18[0]);
return ret;
}
/*!
* Subtract one from this CPage's reference count.
*/
int CPage::ReleaseRef() {
// CpuSuspendIntr(local_18);
auto* page_list = m_pPageList;
int ret = -1;
ASSERT(page_list);
ASSERT(m_nAllocState == 1);
if (m_nAllocState == 1 && page_list) {
page_list->m_nPageRefCnt = page_list->m_nPageRefCnt - 1;
m_nPageRefCount = m_nPageRefCount - 1;
ret = m_nPageRefCount;
ASSERT(ret >= 0);
}
// CpuResumeIntr(local_18[0]);
return ret;
}
/*!
* Add one to the DMA reference count of this page
*/
int CPage::AddDmaRef() {
// CpuSuspendIntr(local_18);
auto* page_list = m_pPageList;
int ret = -1;
ASSERT(page_list);
ASSERT(m_nAllocState == 1);
if (m_nAllocState == 1 && page_list) {
page_list->m_nDmaRefCnt = page_list->m_nDmaRefCnt + 1;
m_nDmaRefCount = m_nDmaRefCount + 1;
ret = m_nPageRefCount;
}
// CpuResumeIntr(local_18[0]);
return ret;
}
int CPage::ReleaseDmaRef() {
// CpuSuspendIntr(local_18);
auto* page_list = m_pPageList;
int ret = -1;
ASSERT(page_list);
ASSERT(m_nAllocState == 1);
if (m_nAllocState == 1 && page_list) {
page_list->m_nDmaRefCnt = page_list->m_nDmaRefCnt - 1;
m_nDmaRefCount = m_nDmaRefCount - 1;
ret = m_nPageRefCount;
ASSERT(ret >= 0);
}
// CpuResumeIntr(local_18[0]);
return ret;
}
/*!
* Copy data from this page to destination. This works with sizes that are greater than the page
* size, and will look at future pages. However, it does not actually advance progress in the page.
*/
void CPage::FromPagesCopy(uint8_t* in, uint8_t* dest, s32 size) {
ASSERT(in);
ASSERT(dest);
ASSERT(size >= 0);
CPage* page = this;
if (0 < size) {
while (true) {
s32 input_page_left = (page->m_pPageMemEnd - in) + 1;
ASSERT(input_page_left >= 0);
if (size < input_page_left)
break;
size -= input_page_left;
memcpy(dest, in, input_page_left);
dest += input_page_left;
if (size == 0) {
return;
}
ASSERT(size > 0);
ASSERT(page->m_pNextPage);
page = page->m_pNextPage;
in = page->m_pPageMemStart;
}
memcpy(dest, in, size);
}
}
CCache::CCache() {
m_PendingMask = kAllPagesMask;
m_PagesFilledEventFlag = -1;
m_nNumFreePages = 0;
m_nAllocatedMask = 0;
m_nPagelistAllocatedMask = 0;
}
void CCache::Initialize() {
static_assert(0xe81d0 == kPageStride * kNumPages);
m_paCache = AllocSysMemory(0, kPageStride * kNumPages, nullptr);
ASSERT(m_paCache);
m_nNumFreePages = kNumPages;
// initialize pageslists
for (auto& page_list : m_PageLists) {
page_list.m_pFirstActivePage = nullptr;
page_list.m_pLastActivePage = nullptr;
page_list.m_pCurrentActivePage = nullptr;
page_list.m_pFirstPage = nullptr;
page_list.m_pLastPage = nullptr;
page_list.m_nNumActivePages = 0;
page_list.m_nNumUnsteppedPages = 0;
page_list.m_nPageRefCnt = 0;
page_list.m_nDmaRefCnt = 0;
page_list.m_nAllocState = CPageList::AllocState::EPLAS_FREE;
}
u8* mem = (u8*)m_paCache;
for (int i = 0; i < kNumPages; i++) {
m_Pages[i] = CPage(mem, mem + kPageSize - 1, i);
// interestingly, the stride is a bit longer.
mem += kPageStride;
}
m_nAllocatedMask = 0;
m_nPagelistAllocatedMask = 0;
EventFlagParam param;
param.attr = 2;
param.option = 0;
param.init_pattern = 0;
m_PagesFilledEventFlag = CreateEventFlag(&param); // TODO args here
ASSERT(m_PagesFilledEventFlag >= 0);
}
/*!
* Increase the length by the given amount. This is used for the DVD reading side to inform the
* consumer that there is more data available.
*/
void CBuffer::AddData(int len) {
// suspend interrupts
m_nDataLength += len;
// resume interrupts
}
/*!
* Advance the current point in the buffer. This is used by the consume to mark forward progress.
*/
void CBuffer::AdvanceCurrentData(int len) {
// suspend interrupts
m_nDataLength -= len;
m_pCurrentData += len;
// resume interrupts
}
/*!
* Set up pages.
*/
void CPageManager::Initialize() {
m_CCache.Initialize();
}
CPageList* CPageManager::AllocPageListBytes(int bytes, bool flag) {
return AllocPageList((bytes + kPageSize - 1) / kPageSize, flag);
}
s32 alloc_bitmask(u32* mask, u32 length, u32 start = 0) {
for (u32 i = start; i < length; i++) {
if ((*mask & (1 << i)) == 0) {
// it's free!
(*mask) |= (1 << i);
return i;
}
}
return -1;
}
/*!
* Allocate a PageList with the given number of pages.
*/
CPageList* CPageManager::AllocPageList(int count, bool consecutive_pages) {
ASSERT(count > 0);
ASSERT(count <= CCache::kNumPages);
if (count > m_CCache.m_nNumFreePages) {
// if we're out of pages, use RecoverPages to discard pages that we've already read, but
// nobody is using yet. We'll be able to read them from the DVD again.
lg::warn("Recovering pages - {} requested in AllocPageList, but only {} available", count,
m_CCache.m_nNumFreePages);
RecoverPages(count);
ASSERT(m_CCache.m_nNumFreePages >= count);
}
// next, find a pagelist. the original game had some fancy bit magic here, but this is simpler
int plist_idx = alloc_bitmask(&m_CCache.m_nPagelistAllocatedMask, CCache::kNumPageLists);
ASSERT(plist_idx >= 0);
CPageList* plist = &m_CCache.m_PageLists[plist_idx];
// Fill this array with allocated pages
CPage* pages[CCache::kNumPages];
int pages_allocated = 0;
int last_page_allocated = -1;
int next_page_to_check = 0;
while (pages_allocated < count) {
if (next_page_to_check >= CCache::kNumPages) {
break;
}
// grab the next page
int page_idx = alloc_bitmask(&m_CCache.m_nAllocatedMask, CCache::kNumPages, next_page_to_check);
ASSERT(page_idx >= 0);
// start after this page on the next search
next_page_to_check = page_idx + 1;
pages[pages_allocated] = &m_CCache.m_Pages[page_idx];
pages_allocated++;
// if we asked for consecutive pages, but didn't get them, we need to rewind our progress.
// but, we shouldn't rewind next_page_to_check!
if (consecutive_pages && last_page_allocated != -1 && last_page_allocated + 1 != page_idx) {
page_idx = -1;
while (pages_allocated) {
pages_allocated--;
u32 i = pages[pages_allocated] - m_CCache.m_Pages;
ASSERT(i >= 0 && i < CCache::kNumPages);
m_CCache.m_nAllocatedMask &= ~(1 << i);
}
}
last_page_allocated = page_idx;
}
if (pages_allocated != count) {
// allocation failed
ASSERT_NOT_REACHED();
}
m_CCache.m_nNumFreePages -= count;
ASSERT(m_CCache.m_nNumFreePages >= 0);
// zero everything
plist->m_pFirstPage = nullptr;
plist->m_pLastPage = nullptr;
plist->m_pFirstActivePage = nullptr;
plist->m_pLastActivePage = nullptr;
plist->m_pCurrentActivePage = nullptr;
plist->m_nNumPages = 0;
plist->m_nNumActivePages = 0;
plist->m_nNumUnsteppedPages = 0;
ASSERT(plist->m_nPageRefCnt == 0);
ASSERT(plist->m_nDmaRefCnt == 0);
plist->m_nAllocState = CPageList::AllocState::EPLAS_ALLOCATED;
// set up the pages
CPage* prev = nullptr;
CPage* page = nullptr;
for (int i = 0; i < pages_allocated; i++) {
page = pages[i];
page->m_pPageList = plist;
page->m_pPrevPage = prev;
if (prev) {
prev->m_pNextPage = page;
}
page->m_nAllocState = 1;
page->input_state = CPage::State::UNMAKRED;
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask);
prev = page;
}
page->m_pNextPage = nullptr;
plist->m_nNumPages = count;
plist->m_pLastPage = page;
plist->m_pFirstPage = pages[0];
return plist;
}
/*!
* Allocate page_count more pages, add them to the end of the list. This can be used to get more
* pages for the DVD driver to write to.
*/
CPageList* CPageManager::GrowPageList(jak3::CPageList* in, int page_count) {
ASSERT(in);
ASSERT(in->m_nAllocState == CPageList::AllocState::EPLAS_ALLOCATED);
ASSERT(page_count >= 0);
if (page_count > m_CCache.m_nNumFreePages) {
// if we're out of pages, use RecoverPages to discard pages that we've already read, but
// nobody is using yet. We'll be able to read them from the DVD again.
lg::warn("Recovering pages - {} requested in AllocPageList, but only {} available", page_count,
m_CCache.m_nNumFreePages);
RecoverPages(page_count);
ASSERT(m_CCache.m_nNumFreePages >= page_count);
}
CPage* pages[CCache::kNumPages];
int next_page_to_check = 0;
int pages_allocated = 0;
while (pages_allocated != page_count) {
if (next_page_to_check >= CCache::kNumPages) {
break;
}
int page_idx = alloc_bitmask(&m_CCache.m_nAllocatedMask, CCache::kNumPages, next_page_to_check);
ASSERT(page_idx >= 0); // otherwise need to handle this better
next_page_to_check = page_idx + 1;
pages[pages_allocated] = &m_CCache.m_Pages[page_idx];
pages_allocated++;
}
if (pages_allocated != page_count) {
ASSERT_NOT_REACHED();
}
m_CCache.m_nNumFreePages -= pages_allocated;
// set up the pages
CPage* prev = nullptr;
CPage* page = nullptr;
for (int i = 0; i < pages_allocated; i++) {
page = pages[i];
ASSERT(page);
ASSERT(page->m_nAllocState == 0);
page->m_pPageList = in;
page->m_pPrevPage = prev;
if (prev) {
prev->m_pNextPage = page;
}
page->m_nAllocState = 1;
page->input_state = CPage::State::UNMAKRED;
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask);
prev = page;
}
page->m_pNextPage = nullptr;
// suspendintr
if (in->m_nNumPages == 0) {
ASSERT(!in->m_pFirstPage);
ASSERT(!in->m_pLastPage);
ASSERT(!in->m_pFirstActivePage);
ASSERT(!in->m_pLastActivePage);
ASSERT(!in->m_pCurrentActivePage);
in->m_pFirstPage = pages[0];
} else {
auto* old_end = in->m_pLastPage;
ASSERT(!old_end->m_pNextPage);
old_end->m_pNextPage = pages[0];
pages[0]->m_pPrevPage = old_end;
}
in->m_pLastPage = page;
in->m_nNumPages += pages_allocated;
// resume intr
return in;
}
/*!
* Return a PageList and its pages to the CCache. If the PageList is still referenced, the freeing
* will be deferred until GC.
*/
void CPageManager::FreePageList(jak3::CPageList* list) {
ASSERT(list);
ASSERT(list->m_nAllocState != CPageList::AllocState::EPLAS_FREE);
// suspend itr
list->m_nAllocState = CPageList::AllocState::FREE_PENDING;
// resume intr
if (list->m_nDmaRefCnt || list->m_nPageRefCnt) {
lg::warn("Skipping free since list is referenced!");
return;
}
// loop over pages, clearing them.
CPage* page = list->m_pFirstPage;
int pages_count = 0;
int pages[CCache::kNumPages];
while (page) {
u32 page_slot = page - m_CCache.m_Pages;
ASSERT(page_slot < CCache::kNumPages);
pages[pages_count] = page_slot;
pages_count++;
auto* next = page->m_pNextPage;
page->m_pPageList = nullptr;
page->m_pPrevPage = nullptr;
page->m_pNextPage = nullptr;
page->m_nAllocState = 0;
page->input_state = CPage::State::UNMAKRED;
ASSERT(!page->m_nPageRefCount);
ASSERT(!page->m_nDmaRefCount);
ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask);
page = next;
}
// clear list
if (pages_count != list->m_nNumPages) {
lg::die("Paging error: found {} pages out of {} in FreePageList", pages_count,
list->m_nNumPages);
}
ASSERT(pages_count == list->m_nNumPages);
list->m_pFirstActivePage = nullptr;
list->m_nNumPages = 0;
list->m_pLastActivePage = nullptr;
list->m_pCurrentActivePage = nullptr;
list->m_nNumActivePages = 0;
list->m_nNumUnsteppedPages = 0;
list->m_nAllocState = CPageList::AllocState::EPLAS_FREE;
m_CCache.m_nNumFreePages += pages_count;
ASSERT(m_CCache.m_nNumFreePages <= CCache::kNumPages);
list->m_pFirstPage = nullptr;
list->m_pLastPage = nullptr;
// unset bits in allocated mask
while (0 < pages_count) {
pages_count--;
m_CCache.m_nAllocatedMask &= ~(1 << pages[pages_count]);
}
// unset bit for the pagelist itself
u32 plist_idx = list - m_CCache.m_PageLists;
ASSERT(plist_idx >= 0 && plist_idx < CCache::kNumPageLists);
m_CCache.m_nPagelistAllocatedMask &= ~(1 << plist_idx);
}
int CPageManager::RecoverPages(int) {
ASSERT_NOT_REACHED(); // TODO, this looks at pristack
}
/*!
* Call GarbageCollect on all allocate CPageLists.
*/
void CPageManager::GarbageCollect() {
for (u32 i = 0; i < CCache::kNumPageLists; i++) {
if (m_CCache.m_nPagelistAllocatedMask & (1 << i)) {
GarbageCollectPageList(&m_CCache.m_PageLists[i]);
}
}
}
/*!
* Do garbage collection of pages and page lists.
*/
void CPageManager::GarbageCollectPageList(jak3::CPageList* list) {
ASSERT(list);
list->GarbageCollect();
// if we tried to free the list in the past, but failed, try to do it again now.
if (list->m_nAllocState == CPageList::AllocState::FREE_PENDING) {
FreePageList(list);
}
}
} // namespace jak3

View file

@ -0,0 +1,181 @@
#pragma once
#include "common/common_types.h"
namespace jak3 {
void jak3_overlord_init_globals_pagemanager();
/*!
* CPages Overview
*
* Data is read from the DVD driver into CPages. The CPages are then given to consumers.
* Each file that's opened has an associated CPageList.
*
* The CPageList is a linked list of pages. Within this linked list, there is a section of "active
* pages" which have been filled by the DVD driver. There is also the "current active page", which
* is the page that the user will read from next. Note that pages before the current active page
* and still be referenced by the user, but they should keep a nonzero reference count so the CPage
* is not Garbage Collected.
*
* By default, each active page will have a ref count of 1.
*
* The CBuffer object owned by CBaseFile uses memory managed by this CPage system.
*
* The pages are preallocated by the CPageManager, which gives out pages as needed.
*/
struct CPage;
struct CPageList;
struct ISO_Hdr;
constexpr int kPageSize = 0x8000;
constexpr int kPageStride = 0x8010;
/*!
* A CBuffer stores file data in pages, and tracks the progress through the page data as it is fed
* into the ISO data handling system.
*/
struct CBuffer {
// The pages in this buffer are managed by a PageList
CPageList* m_pPageList = nullptr;
// The ISO command that requested us to load this data
ISO_Hdr* m_pIsoCmd = nullptr;
// Current progress through the data (todo: load or process?)
uint8_t* m_pCurrentData = nullptr;
// First address in the current page
uint8_t* m_pCurrentPageStart = nullptr;
// todo
int m_nDataLength = 0;
enum class BufferType {
// this is a really confusing enum...
EBT_FREE = 0, // there is no buffer allocate
NORMAL = 1, // a buffer sized for non-VAG stream operations (several possible sizes)
VAG = 2, // a buffer size for VAG streams (larger than normal sizes)
REQUEST_NORMAL = 3, // argument passed to InitBuffer to get a normal buffer
REQUEST_VAG = 4, // argument passed to InitBuffer to get a VAG size buffer
} m_eBufferType = BufferType::EBT_FREE;
// Try to have at least this many pages filled
int m_nMinNumPages = 0;
// Don't fill more than this number of pages
int m_nMaxNumPages = 0;
void AddData(int len);
void AdvanceCurrentData(int len);
};
/*!
* List of pages for a file.
*/
struct CPageList {
CPageList() = default; // ???
// list of all pages
CPage* m_pFirstPage = nullptr;
CPage* m_pLastPage = nullptr;
// list of active pages. This is part of the all page list
CPage* m_pFirstActivePage = nullptr;
CPage* m_pLastActivePage = nullptr;
// page that the application is currently reading from
CPage* m_pCurrentActivePage = nullptr;
// total number of CPage, including both active/inactive
int m_nNumPages = 0;
// number of cpages in the active page list
int m_nNumActivePages = 0;
// number of CPages remaining for the user, including the current active page
int m_nNumUnsteppedPages = 0;
// Reference counters to know if this data is still needed or not.
int m_nPageRefCnt = 0;
int m_nDmaRefCnt = 0;
enum class AllocState {
EPLAS_FREE = 0,
EPLAS_ALLOCATED = 1,
FREE_PENDING = 2, // FreePageList called, but no
} m_nAllocState = AllocState::EPLAS_FREE;
CPage* StepActivePage();
CPage* AddActivePages(int num_pages);
int RemoveActivePages(int num_pages);
void CancelActivePages();
void GarbageCollect();
};
/*!
* A single page, pointing to a contiguous buffer of file data.
*/
struct CPage {
CPage(uint8_t* page_mem_start, uint8_t* page_mem_end, int page_idx);
CPage() = default; // ???
CPage* m_pNextPage = nullptr;
CPage* m_pPrevPage = nullptr;
CPageList* m_pPageList = nullptr;
int m_nPageRefCount = 0;
int m_nDmaRefCount = 0;
int m_nAllocState = 0;
enum class State {
UNMAKRED = 0,
ACTIVE = 1,
READING = 2,
READ_DONE = 3
} input_state = State::UNMAKRED;
uint8_t* m_pPageMemStart = nullptr;
uint8_t* m_pPageMemEnd = nullptr;
u32 m_nPageIdx = 0;
u32 mask = 0;
int AddRef();
int ReleaseRef();
int AddDmaRef();
int ReleaseDmaRef();
void FromPagesCopy(uint8_t* in, uint8_t* dest, s32 size);
};
/*!
* CCache contains the actual CPage/CPageList objects to be given out to files.
*/
struct CCache {
static constexpr int kNumPages = 29;
static constexpr int kNumPageLists = 29;
static constexpr int kAllPagesMask = (1 << kNumPages) - 1;
CCache();
void Initialize();
void* m_paCache = nullptr;
CPageList m_PageLists[kNumPageLists];
CPage m_Pages[kNumPages];
int m_nNumFreePages = 0;
u32 m_nAllocatedMask;
u32 m_nPagelistAllocatedMask;
int m_PagesFilledEventFlag;
int m_PendingMask;
};
struct CPageManager {
CCache m_CCache;
CPageList* GrowPageList(CPageList* in, int page_count);
CPageList* AllocPageList(int count, bool flag);
CPageList* AllocPageListBytes(int bytes, bool flag);
void FreePageList(CPageList* list);
void WaitForPagesFilled(u32 mask);
void Initialize();
int RecoverPages(int k);
void GarbageCollect();
void GarbageCollectPageList(CPageList* list);
};
CPageManager* get_page_manager();
} // namespace jak3

View file

@ -0,0 +1,50 @@
#include "ramdisk.h"
#include "common/util/Assert.h"
#include "game/overlord/jak3/iso.h"
#include "game/overlord/jak3/iso_api.h"
#include "game/overlord/jak3/rpc_interface.h"
#include "game/sce/iop.h"
namespace jak3 {
constexpr int kRamdiskBufSize = 512;
u8 gRamdiskRpcBuf[kRamdiskBufSize];
void jak3_overlord_init_globals_ramdisk() {}
/*!
* RPC Handler for "load to ee" rpc.
*/
void* RPC_LoadToEE(unsigned int fno, void* msg_ptr, int) {
switch (fno) {
case LoadToEEFno::LOAD_FILE: {
RpcLoadToEEMsg* msg = (RpcLoadToEEMsg*)(msg_ptr);
auto f = FindISOFile(msg->name);
ASSERT(f);
LoadISOFileToEE(f, msg->addr, msg->length);
} break;
default:
ASSERT_NOT_REACHED();
}
return nullptr;
}
u32 LoadToEE_RPC_Thread() {
using namespace iop;
sceSifQueueData dq;
sceSifServeData serve;
// set up RPC
CpuDisableIntr();
sceSifInitRpc(0);
sceSifSetRpcQueue(&dq, GetThreadId());
sceSifRegisterRpc(&serve, RpcId::LoadToEE, RPC_LoadToEE, gRamdiskRpcBuf, kRamdiskBufSize, nullptr,
nullptr, &dq);
CpuEnableIntr();
sceSifRpcLoop(&dq);
return 0;
}
} // namespace jak3

View file

@ -0,0 +1,10 @@
#pragma once
#include "common/common_types.h"
namespace jak3 {
void jak3_overlord_init_globals_ramdisk();
u32 LoadToEE_RPC_Thread();
} // namespace jak3

View file

@ -0,0 +1,280 @@
#pragma once
#include "common/common_types.h"
/*!
* This file has structs that are shared between GOAL and Overlord.
* The memory layout of these structs should not be changed.
*/
namespace jak3 {
struct SoundStreamName {
char chars[48];
};
// vblank message
struct SoundIOPInfo {
u32 frame; // 0
s32 strpos; // 4
u32 str_id; // 8
u32 freemem; // 12
u8 chinfo[48]; // 16
u32 freemem2; // 64
u32 nocd; // 68
u32 dirtycd; // 72
u32 diskspeed[2]; // 76
u32 lastspeed; // 84
s32 dupseg; // 88
s32 times[41]; // 92
u32 times_seq; // 256
u32 iop_ticks; // 260
u32 pad0[2];
u32 stream_position[4]; // 272
u32 stream_status[4]; // 288
SoundStreamName stream_name[4];
u32 stream_id[4];
u8 music_register[17];
// s8 music_excite;
char ramdisk_name[16];
u32 pad[11];
char sound_bank0[16];
char sound_bank1[16];
char sound_bank2[16];
char sound_bank3[16];
char sound_bank4[16];
char sound_bank5[16];
char sound_bank6[16];
char sound_bank7[16];
};
// static_assert(offsetof(SoundIOPInfo, stream_name) == 304);
// static_assert(offsetof(SoundIOPInfo, stream_id) == 496);
// static_assert(offsetof(SoundIOPInfo, music_register) == 512);
// static_assert(offsetof(SoundIOPInfo, ramdisk_name) == 529);
// static_assert(offsetof(SoundIOPInfo, sound_bank0) == 592);
static_assert(sizeof(SoundIOPInfo) == 0x2d0);
// static_assert(sizeof(SoundIOPInfo) == 288);
// Common
enum RpcId {
Player = 0xfab0, // sound effects playback
Loader = 0xfab1, // sound effects loading.
LoadToEE = 0xfab2, // was ramdisk, now just a simple way to load a file to EE memory.
DGO = 0xfab3, // level/engine .DGO loading
STR = 0xfab4, // loading .str files of animations or other streamed data
PLAY = 0xfab5, // playing and queueing vag streams
};
// RAMDISK RPC (renamed to LoadToEE for jak 3, kinda)
struct RpcLoadToEEMsg {
u32 unk;
u32 addr;
u32 unk2;
u32 length;
char name[16];
};
static_assert(sizeof(RpcLoadToEEMsg) == 32);
enum LoadToEEFno {
LOAD_FILE = 4,
};
// DGO RPC
struct RPC_Dgo_Cmd {
uint16_t rsvd;
uint16_t status;
uint32_t buffer1;
uint32_t buffer2;
uint32_t buffer_heap_top;
char name[16];
int32_t cgo_id;
uint8_t pad[28];
};
static_assert(sizeof(RPC_Dgo_Cmd) == 0x40);
enum DgoFno {
LOAD = 0,
LOAD_NEXT = 1,
CANCEL = 2,
};
// STR RPC
struct RPC_Str_Cmd {
u16 rsvd;
u16 result; // 2
u32 address;
s32 section; // 8
u32 maxlen;
u32 dummy[4];
char basename[48]; // 32
};
// PLAYER/LOADER RPCS
struct RPC_Play_Cmd {
u16 rsvd;
u16 result;
u32 address;
u32 section;
u32 maxlen;
u32 id[4];
SoundStreamName names[4];
u32 pad[8];
};
struct SoundName {
char data[16];
};
enum class SoundCommand : u16 {
// IOP_STORE = 0,
// IOP_FREE = 1,
LOAD_BANK = 2,
// LOAD_BANK_FROM_IOP = 3,
// LOAD_BANK_FROM_EE = 4,
LOAD_MUSIC = 5,
UNLOAD_BANK = 6,
PLAY = 7,
PAUSE_SOUND = 8,
STOP_SOUND = 9,
CONTINUE_SOUND = 10,
SET_PARAM = 11,
SET_MASTER_VOLUME = 12,
PAUSE_GROUP = 13,
STOP_GROUP = 14,
CONTINUE_GROUP = 15,
GET_IRX_VERSION = 16,
// SET_FALLOFF_CURVE = 17,
// SET_SOUND_FALLOFF = 18,
// RELOAD_INFO = 19,
SET_LANGUAGE = 20,
// SET_FLAVA = 21,
SET_MIDI_REG = 22,
SET_REVERB = 23,
SET_EAR_TRANS = 24,
SHUTDOWN = 25,
// LIST_SOUNDS = 26,
UNLOAD_MUSIC = 27,
SET_FPS = 28,
// BOOT_LOAD = 29,
// GAME_LOAD = 30,
// NUM_TESTS = 31,
// NUM_TESTRUNS = 32,
// NUM_SECTORS = 33,
// NUM_STREAMSECTORS = 34,
// NUM_STREMBANKS = 35,
// TRACK_PITCH = 36,
// LINVEL_NOM = 37,
CANCEL_DGO = 49,
SET_STEREO_MODE = 50,
};
struct SoundPlayParams {
u16 mask;
s16 pitch_mod;
s16 bend;
s16 fo_min;
s16 fo_max;
s8 fo_curve;
s8 priority;
s32 volume;
s32 trans[3];
u8 group;
u8 reg[3];
};
struct Rpc_Player_Base_Cmd {
u16 rsvd1 = 0;
SoundCommand command;
};
static_assert(sizeof(Rpc_Player_Base_Cmd) == 4);
struct Rpc_Player_Sound_Cmd : public Rpc_Player_Base_Cmd {
s32 sound_id = 0;
};
static_assert(sizeof(Rpc_Player_Sound_Cmd) == 8);
struct Rpc_Player_Group_Cmd : public Rpc_Player_Base_Cmd {
u32 group = 0;
};
static_assert(sizeof(Rpc_Player_Group_Cmd) == 8);
struct Rpc_Player_Play_Cmd : public Rpc_Player_Sound_Cmd {
s32 pad[2];
SoundName name;
SoundPlayParams params;
};
static_assert(sizeof(Rpc_Player_Play_Cmd) == 0x40);
struct Rpc_Player_Set_Param_Cmd : public Rpc_Player_Sound_Cmd {
SoundPlayParams params;
s32 auto_time;
s32 auto_from;
};
static_assert(sizeof(Rpc_Player_Set_Param_Cmd) == 0x30);
struct Rpc_Player_Set_Master_Volume_Cmd : public Rpc_Player_Group_Cmd {
s32 volume;
};
static_assert(sizeof(Rpc_Player_Set_Master_Volume_Cmd) == 12);
struct Rpc_Player_Set_Ear_Trans_Cmd : public Rpc_Player_Base_Cmd {
s32 ear_trans1[3];
s32 ear_trans0[3];
s32 ear_trans[3];
s32 cam_forward[3];
s32 cam_left[3];
s32 cam_scale;
s32 cam_inverted;
};
static_assert(sizeof(Rpc_Player_Set_Ear_Trans_Cmd) == 0x48);
struct Rpc_Player_Set_Fps_Cmd : public Rpc_Player_Base_Cmd {
u8 fps;
u8 pad;
};
static_assert(sizeof(Rpc_Player_Set_Fps_Cmd) == 5 + 1);
struct Rpc_Player_Cancel_Dgo_Cmd : public Rpc_Player_Group_Cmd {
u32 id;
};
static_assert(sizeof(Rpc_Player_Cancel_Dgo_Cmd) == 12);
struct Rpc_Loader_Bank_Cmd : public Rpc_Player_Base_Cmd {
u32 pad[3];
SoundName bank_name;
};
static_assert(sizeof(Rpc_Loader_Bank_Cmd) == 32);
struct Rpc_Loader_Load_Bank_Cmd : public Rpc_Loader_Bank_Cmd {
u32 ee_addr;
u32 mode;
u32 priority;
};
static_assert(sizeof(Rpc_Loader_Load_Bank_Cmd) == 0x2c);
struct Rpc_Loader_Get_Irx_Version : public Rpc_Player_Base_Cmd {
u32 major;
u32 minor;
u32 ee_addr;
};
static_assert(sizeof(Rpc_Loader_Get_Irx_Version) == 16);
struct Rpc_Loader_Set_Language : public Rpc_Player_Base_Cmd {
u32 lang;
};
static_assert(sizeof(Rpc_Loader_Set_Language) == 8);
struct Rpc_Loader_Set_Stereo_Mode : public Rpc_Player_Base_Cmd {
s32 mode;
};
static_assert(sizeof(Rpc_Loader_Set_Stereo_Mode) == 8);
constexpr int kPlayerCommandStride = 0x50;
constexpr int kLoaderCommandStride = 0x50;
} // namespace jak3

View file

@ -0,0 +1,167 @@
#include "sbank.h"
#include "common/util/Assert.h"
#include "game/overlord/jak3/overlord.h"
namespace jak3 {
constexpr int kNumBanks = 8;
SoundBankInfo* gBanks[kNumBanks];
SoundBankInfo gCommonBank;
SoundBankInfo gModeBank;
SoundBankInfo gLevel0Bank, gLevel0hBank;
SoundBankInfo gLevel1Bank, gLevel1hBank;
SoundBankInfo gLevel2Bank, gLevel2hBank;
void jak3_overlord_init_globals_sbank() {
gBanks[0] = &gCommonBank;
gBanks[1] = &gModeBank;
gBanks[2] = &gLevel0Bank;
gBanks[3] = &gLevel0hBank;
gBanks[4] = &gLevel1Bank;
gBanks[5] = &gLevel1hBank;
gBanks[6] = &gLevel2Bank;
gBanks[7] = &gLevel2hBank;
}
void InitBanks() {
for (int i = 0; i < kNumBanks; i++) {
auto* bank = gBanks[i];
bank->in_use = 0;
bank->snd_handle = nullptr;
bank->loaded = false;
bank->idx = i;
bank->unk0 = 0;
}
strncpyz(gBanks[0]->m_name2, "common", 0x10);
gBanks[0]->m_nSpuMemSize = 0xbbe40;
gBanks[0]->m_nSpuMemLoc = 0x1d1c0;
strncpyz(gBanks[1]->m_name2, "mode", 0x10);
gBanks[1]->m_nSpuMemSize = 0x25400;
gBanks[1]->m_nSpuMemLoc = 0xe0000;
strncpyz(gBanks[2]->m_name2, "level0", 0x10);
gBanks[2]->m_nSpuMemLoc = 0x105400;
gBanks[2]->m_nSpuMemSize = 0x51400;
strncpyz(gBanks[3]->m_name2, "level0h", 0x10);
gBanks[3]->m_nSpuMemLoc = 0x12de00;
gBanks[3]->m_nSpuMemSize = 0x28a00;
strncpyz(gBanks[4]->m_name2, "level1", 0x10);
gBanks[4]->m_nSpuMemSize = 0x51400;
gBanks[4]->m_nSpuMemLoc = 0x156800;
strncpyz(gBanks[5]->m_name2, "level1h", 0x10);
gBanks[5]->m_nSpuMemSize = 0x28a00;
gBanks[5]->m_nSpuMemLoc = 0x17f200;
strncpyz(gBanks[6]->m_name2, "level2", 0x10);
gBanks[6]->m_nSpuMemSize = 0x51400;
gBanks[6]->m_nSpuMemLoc = 0x1a7c00;
strncpyz(gBanks[7]->m_name2, "level2h", 0x10);
gBanks[7]->m_nSpuMemSize = 0x28a00;
gBanks[7]->m_nSpuMemLoc = 0x1d0600;
}
SoundBankInfo* AllocateBankName(const char* name, u32 mode) {
int iVar1;
int mem_sz;
SoundBankInfo** ppSVar2;
int iVar3;
int iVar4;
SoundBankInfo* pSVar5;
int iVar6;
int iVar6_d4 = 2;
SoundBankInfo* bank = nullptr;
// handle common case
if (memcmp(name, "common", 7) == 0 || memcmp(name, "commonj", 8) == 0) {
if (!gBanks[0]->in_use) {
bank = gBanks[0];
}
} else if (memcmp(name, "mode", 4) == 0) {
if (!gBanks[1]->in_use) {
bank = gBanks[1];
}
}
if (mode == 4) {
for (int bank_idx = 2; bank_idx < kNumBanks; bank_idx += 2) {
if (!gBanks[bank_idx]->in_use && !gBanks[bank_idx + 1]->in_use) {
bank = gBanks[bank_idx];
bank->m_nSpuMemSize = 0x51400;
break;
}
}
} else if (mode > 3 && (mode - 6u < 3)) { // wtf
iVar1 = 2;
// iVar6 = 8;
iVar6_d4 = 2;
while (gBanks[iVar6_d4]->in_use == 0 || gBanks[iVar6_d4]->mode != mode) {
auto* sbi = gBanks[iVar6_d4 + 1];
iVar6 = iVar6 + 8;
if (((sbi->in_use != 0) && (iVar4 = iVar1, sbi->mode == mode)) ||
(iVar1 = iVar1 + 2, iVar4 = -1, 7 < iVar1))
goto LAB_0000c2fc;
}
iVar4 = iVar1 + 1;
LAB_0000c2fc:
if (iVar4 < 0) {
iVar1 = 2;
ppSVar2 = gBanks;
LAB_0000c36c:
ppSVar2 = ppSVar2 + 2;
pSVar5 = *ppSVar2;
iVar1 = iVar1 + 2;
if ((pSVar5->in_use != 0) || (ppSVar2[1]->in_use != 0))
goto LAB_0000c39c;
mem_sz = 0x28a00;
pSVar5->m_nSpuMemSize = mem_sz;
bank = pSVar5;
goto LAB_0000c3a4;
}
pSVar5 = gBanks[iVar4];
if (pSVar5->in_use == 0) {
gBanks[iVar1]->m_nSpuMemSize = 0x28a00;
bank = pSVar5;
}
}
LAB_0000c3a4:
if (bank) {
bank->mode = mode;
bank->snd_handle = nullptr;
bank->unk0 = 0;
}
return bank;
LAB_0000c39c:
if (7 < iVar1)
goto LAB_0000c3a4;
goto LAB_0000c36c;
}
SoundBankInfo* LookupBank(const char* name) {
for (int i = kNumBanks; i-- > 0;) {
if (memcmp(name, gBanks[i]->m_name1, 16) == 0) {
return gBanks[i];
}
}
return nullptr;
}
int GetFalloffCurve(int x) {
if (x < 0) {
return 1;
}
if (x == 0 || 0x28 < x) {
x = 2;
}
return x;
}
} // namespace jak3

View file

@ -0,0 +1,29 @@
#pragma once
#include "common/common_types.h"
#include "game/sound/sndshim.h"
namespace jak3 {
struct SoundBankInfo {
// int m_name1[4];
char m_name1[16];
char m_name2[16];
int m_nSpuMemLoc = 0;
int m_nSpuMemSize = 0;
// s32 snd_handle = 0;
snd::BankHandle snd_handle = nullptr;
u8 in_use = 0;
u8 loaded = 0;
u8 mode = 0;
u8 idx = 0;
int unk0 = 0;
};
void jak3_overlord_init_globals_sbank();
void InitBanks();
SoundBankInfo* LookupBank(const char* name);
SoundBankInfo* AllocateBankName(const char* name, u32 mode);
extern SoundBankInfo* gBanks[8];
} // namespace jak3

View file

@ -0,0 +1,19 @@
#include "soundcommon.h"
#include <algorithm>
#include <cstring>
#include <string>
namespace jak3 {
void jak3_overlord_init_globals_soundcommon() {}
// Only for use with 16 character sound names!
void strcpy_toupper(char* dest, const char* source) {
// clear the dest string
memset(dest, 0, 16);
std::string string(source);
std::transform(string.begin(), string.end(), string.begin(), ::toupper);
std::replace(string.begin(), string.end(), '-', '_');
string.copy(dest, 16);
}
} // namespace jak3

View file

@ -0,0 +1,7 @@
#pragma once
namespace jak3 {
void jak3_overlord_init_globals_soundcommon();
void strcpy_toupper(char* dest, const char* src);
} // namespace jak3

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,14 @@
#pragma once
#include "common/common_types.h"
#include "game/overlord/jak3/isocommon.h"
namespace jak3 {
struct ISO_Hdr;
struct ISO_VAGCommand;
void jak3_overlord_init_globals_spustreams();
EIsoStatus ProcessVAGData(ISO_Hdr* msg);
void StopVagStream(ISO_VAGCommand* cmd);
u32 GetSpuRamAddress(ISO_VAGCommand* cmd);
} // namespace jak3

604
game/overlord/jak3/srpc.cpp Normal file
View file

@ -0,0 +1,604 @@
#include "srpc.h"
#include "common/util/Assert.h"
#include "game/overlord/jak3/iso.h"
#include "game/overlord/jak3/iso_api.h"
#include "game/overlord/jak3/overlord.h"
#include "game/overlord/jak3/rpc_interface.h"
#include "game/overlord/jak3/sbank.h"
#include "game/overlord/jak3/soundcommon.h"
#include "game/overlord/jak3/spustreams.h"
#include "game/overlord/jak3/ssound.h"
#include "game/overlord/jak3/vag.h"
#include "game/overlord/jak3/vblank_handler.h"
#include "game/sce/iop.h"
#include "game/sound/sndshim.h"
namespace jak3 {
using namespace iop;
// This file has two RPCs: PLAYER and LOADER
// Generally, PLAYER receives commands to play/pause sound effects or streams, which complete
// quickly.
// LOADER will load soundbanks, and can take some time to complete - likely why it is moved
// into its own RPC, to avoid having soundbank loads block playback of other sounds.
constexpr int kPlayerBufSize = 0x50 * 128;
static uint8_t s_anRPC_PlayerBuf[kPlayerBufSize];
constexpr int kLoaderBufSize = 0x50;
static uint8_t s_anRPC_LoaderBuf[kLoaderBufSize];
constexpr u32 kNumLanguages = 12;
static const char* languages[kNumLanguages] = {"ENG", "FRE", "GER", "SPA", "ITA", "COM",
"JAP", "KOR", "RUS", "POR", "DUT", "UKE"};
const char* g_pszLanguage = languages[0];
u8 g_nFPS = 60;
SoundBankInfo* g_LoadingSoundBank = nullptr;
void jak3_overlord_init_globals_srpc() {
g_nFPS = 60;
g_LoadingSoundBank = nullptr;
g_pszLanguage = languages[0];
}
u32 Thread_Player() {
sceSifQueueData dq;
sceSifServeData serve;
CpuDisableIntr();
sceSifInitRpc(0);
sceSifSetRpcQueue(&dq, GetThreadId());
sceSifRegisterRpc(&serve, RpcId::Player, RPC_Player, &s_anRPC_PlayerBuf, kPlayerBufSize, nullptr,
nullptr, &dq);
CpuEnableIntr();
sceSifRpcLoop(&dq);
return 0;
}
u32 Thread_Loader() {
sceSifQueueData dq;
sceSifServeData serve;
CpuDisableIntr();
sceSifInitRpc(0);
sceSifSetRpcQueue(&dq, GetThreadId());
sceSifRegisterRpc(&serve, RpcId::Loader, RPC_Loader, &s_anRPC_LoaderBuf, kLoaderBufSize, nullptr,
nullptr, &dq);
CpuEnableIntr();
sceSifRpcLoop(&dq);
return 0;
}
void* RPC_Player(unsigned int, void* msg, int size) {
if (!g_bSoundEnable) {
return nullptr;
}
// const auto* cmd = (RPC_Player_Cmd*)msg;
ovrld_log(LogCategory::PLAYER_RPC, "Got Player RPC with {} cmds", size / kPlayerCommandStride);
const u8* m_ptr = (const u8*)msg;
const u8* end = m_ptr + size;
for (; m_ptr < end; m_ptr += kPlayerCommandStride) {
switch (((const Rpc_Player_Base_Cmd*)m_ptr)->command) {
case SoundCommand::PLAY: {
const auto* cmd = (const Rpc_Player_Play_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[Player RPC] command PLAY {} id {}", cmd->name.data,
cmd->sound_id);
s32 id = cmd->sound_id;
if (id) {
auto* sound = LookupSound(id);
if (!sound) {
ovrld_log(LogCategory::PLAYER_RPC, "[Player RPC] allocating a new one");
sound = AllocateSound();
if (sound) {
SFXUserData user_val;
strcpy_toupper(sound->name.data, cmd->name.data);
sound->params = cmd->params;
sound->auto_time = 0;
s32 get_status =
snd_GetSoundUserData(nullptr, nullptr, -1, sound->name.data, &user_val);
s32 mask = sound->params.mask;
if ((mask & 8) == 0) {
sound->params.group = 0;
}
if ((mask & 0x40) == 0) {
if (get_status == 0 || user_val.data[0] == 0) {
sound->params.fo_min = 5;
} else {
sound->params.fo_min = (int16_t)user_val.data[0];
}
}
if ((mask & 0x80) == 0) {
if (get_status == 0 || user_val.data[1] == 0) {
sound->params.fo_max = 0x1e;
} else {
sound->params.fo_max = (int16_t)user_val.data[1];
}
}
if ((mask & 0x100) == 0) {
u32 fo_curve = 0;
if (get_status != 0) {
fo_curve = user_val.data[2];
}
(sound->params).fo_curve = fo_curve;
}
(sound->params).fo_curve = GetFalloffCurve(sound->params.fo_curve);
auto handle = snd_PlaySoundByNameVolPanPMPB(
0, 0, sound->name.data, GetVolume(sound), GetPan(sound),
(int)(sound->params).pitch_mod, (int)(sound->params).bend);
sound->id = id;
sound->sound_handle = handle;
if (handle != 0) {
if ((sound->params.mask & 0x800) != 0) {
snd_SetSoundReg(sound->sound_handle, 0, sound->params.reg[0]);
}
if ((sound->params.mask & 0x1000) != 0) {
snd_SetSoundReg(sound->sound_handle, 1, sound->params.reg[1]);
}
if ((sound->params.mask & 0x2000) != 0) {
snd_SetSoundReg(sound->sound_handle, 2, sound->params.reg[2]);
}
}
}
} else {
SFXUserData user_val;
sound->params = cmd->params;
s32 get_status =
snd_GetSoundUserData(nullptr, nullptr, -1, sound->name.data, &user_val);
s32 mask = (sound->params).mask;
if ((mask & 8) == 0) {
(sound->params).group = 0;
}
if ((mask & 0x40) == 0) {
if (get_status == 0 || user_val.data[0] == 0) {
(sound->params).fo_min = 5;
} else {
(sound->params).fo_min = user_val.data[0];
}
}
if ((mask & 0x80) == 0) {
if (get_status == 0 || user_val.data[1] == 0) {
(sound->params).fo_max = 0x1e;
} else {
(sound->params).fo_max = user_val.data[1];
}
}
if ((mask & 0x100) == 0) {
s8 fo_curve = 0;
if (get_status != 0) {
fo_curve = user_val.data[2];
}
(sound->params).fo_curve = fo_curve;
}
(sound->params).fo_curve = GetFalloffCurve(sound->params.fo_curve);
UpdateVolume(sound);
snd_SetSoundPitchModifier(sound->sound_handle, (int)(sound->params).pitch_mod);
if (((sound->params).mask & 4) != 0) {
snd_SetSoundPitchBend(sound->sound_handle, (int)(sound->params).bend);
}
if (((sound->params).mask & 0x800) != 0) {
snd_SetSoundReg(sound->sound_handle, 0, sound->params.reg[0]);
}
if (((sound->params).mask & 0x1000) != 0) {
snd_SetSoundReg(sound->sound_handle, 1, (int)(char)(sound->params).reg[1]);
}
if (((sound->params).mask & 0x2000) != 0) {
snd_SetSoundReg(sound->sound_handle, 2, sound->params.reg[2]);
}
}
}
} break;
case SoundCommand::PAUSE_SOUND: {
const auto* cmd = (const Rpc_Player_Sound_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Pause Sound ID {}", cmd->sound_id);
if (cmd->sound_id) {
auto* sound = LookupSound(cmd->sound_id);
if (sound) {
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching Sound {} to pause",
sound->name.data);
snd_PauseSound(sound->sound_handle);
} else {
auto* vag = FindVagStreamId(cmd->sound_id);
if (vag) {
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching VAG {} to pause",
vag->name);
PauseVAG(vag);
}
}
}
} break;
case SoundCommand::STOP_SOUND: {
const auto* cmd = (const Rpc_Player_Sound_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] stop Sound ID {}", cmd->sound_id);
if (cmd->sound_id) {
auto* sound = LookupSound(cmd->sound_id);
if (sound) {
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching Sound {} to stop",
sound->name.data);
snd_StopSound(sound->sound_handle);
} else {
auto* vag = FindVagStreamId(cmd->sound_id);
if (vag) {
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching VAG {} to stop",
vag->name);
StopVagStream(vag);
}
}
}
} break;
case SoundCommand::CONTINUE_SOUND: {
const auto* cmd = (const Rpc_Player_Sound_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] continue Sound ID {}", cmd->sound_id);
if (cmd->sound_id) {
auto* sound = LookupSound(cmd->sound_id);
if (sound) {
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching Sound {} to continue",
sound->name.data);
snd_ContinueSound(sound->sound_handle);
} else {
auto* vag = FindVagStreamId(cmd->sound_id);
if (vag) {
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching VAG {} to continue",
vag->name);
UnPauseVAG(vag);
}
}
}
} break;
case SoundCommand::SET_PARAM: {
const auto* cmd = (const Rpc_Player_Set_Param_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] SET_PARAM Sound ID {}", cmd->sound_id);
if (cmd->sound_id) {
auto* sound = LookupSound(cmd->sound_id);
if (sound) {
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching Sound {} to SET_PARAM",
sound->name.data);
auto& params = cmd->params;
u16 mask = cmd->params.mask;
s32 atime = cmd->auto_time;
s32 afrom = cmd->auto_from;
if ((mask & 1) != 0) {
if ((mask & 0x10) == 0) {
sound->params.volume = params.volume;
} else {
sound->auto_time = atime;
sound->new_volume = params.volume;
}
}
if ((mask & 0x20) != 0) {
sound->params.trans[0] = params.trans[0];
sound->params.trans[1] = params.trans[1];
sound->params.trans[2] = params.trans[2];
}
if ((mask & 0x21) != 0) {
UpdateVolume(sound);
}
if ((mask & 2) != 0) {
auto pitch_mod = params.pitch_mod;
sound->params.pitch_mod = pitch_mod;
if ((mask & 0x10) == 0) {
snd_SetSoundPitchModifier(sound->sound_handle, params.pitch_mod);
} else {
snd_AutoPitch(sound->sound_handle, pitch_mod, atime, afrom);
}
}
if ((mask & 4) != 0) {
auto bend = params.bend;
sound->params.bend = bend;
if ((mask & 0x10) == 0) {
snd_SetSoundPitchBend(sound->sound_handle, params.bend);
} else {
snd_AutoPitchBend(sound->sound_handle, (int)bend, atime, afrom);
}
}
if ((mask & 0x400) != 0) {
sound->params.priority = params.priority;
}
if ((mask & 8) != 0) {
sound->params.group = params.group;
}
if ((mask & 0x40) != 0) {
sound->params.fo_min = params.fo_min;
}
if ((mask & 0x80) != 0) {
sound->params.fo_max = params.fo_max;
}
if ((mask & 0x100) != 0) {
sound->params.fo_curve = GetFalloffCurve(params.fo_curve);
}
if ((mask & 0x800) != 0) {
sound->params.reg[0] = params.reg[0];
snd_SetSoundReg(sound->sound_handle, 0, params.reg[0]);
}
if ((mask & 0x1000) != 0) {
sound->params.reg[1] = params.reg[1];
snd_SetSoundReg(sound->sound_handle, 1, params.reg[1]);
}
if ((mask & 0x2000) != 0) {
sound->params.reg[2] = params.reg[2];
snd_SetSoundReg(sound->sound_handle, 2, params.reg[2]);
}
} else {
auto* vag = FindVagStreamId(cmd->sound_id);
if (vag) {
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching VAG {} to SET_PARAM",
vag->name);
auto& params = cmd->params;
auto mask = params.mask;
if ((mask & 2) != 0) {
SetVAGStreamPitch(cmd->sound_id, params.pitch_mod);
}
if ((mask & 0x20) != 0) {
vag->trans[0] = params.trans[0];
vag->trans[1] = params.trans[1];
vag->trans[2] = params.trans[2];
vag->updated_trans = 1;
}
if ((mask & 8) != 0) {
vag->play_group = params.group;
}
if ((mask & 0x40) != 0) {
vag->fo_min = (int)params.fo_min;
}
if ((mask & 0x80) != 0) {
vag->fo_max = (int)params.fo_max;
}
if ((mask & 0x100) != 0) {
vag->fo_curve = GetFalloffCurve(params.fo_curve);
}
if ((mask & 1) != 0) {
vag->play_volume = params.volume;
}
}
}
}
} break;
case SoundCommand::SET_MASTER_VOLUME: {
const auto* cmd = (const Rpc_Player_Set_Master_Volume_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Set Master Volume to {}", cmd->volume);
for (int i = 0; i < 17; i++) {
if (cmd->group & (1 << i)) {
g_anMasterVolume[i] = cmd->volume;
snd_SetMasterVolume(i, cmd->volume);
SetAllVagsVol(i);
}
}
} break;
case SoundCommand::PAUSE_GROUP: {
const auto* cmd = (const Rpc_Player_Group_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Pause groups 0b{:b}", cmd->group);
snd_PauseAllSoundsInGroup(cmd->group);
if (cmd->group & 4) {
PauseVAGStreams();
}
if (cmd->group & 2) {
g_bMusicPause = true;
}
} break;
case SoundCommand::STOP_GROUP: {
const auto* cmd = (const Rpc_Player_Group_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Stop groups 0b{:b}", cmd->group);
KillSoundsInGroup(cmd->group);
if (cmd->group & 4) {
ISO_VAGCommand vag_cmd;
vag_cmd.msg_type = ISO_Hdr::MsgType::VAG_STOP; // seems unsupported by iso thread.
vag_cmd.mbox_reply = 0;
vag_cmd.thread_to_wake = 0;
vag_cmd.vag_dir_entry = nullptr;
vag_cmd.name[0] = 0;
vag_cmd.maybe_sound_handler = 0;
vag_cmd.id = 0;
vag_cmd.priority_pq = 0;
StopVagStream(&vag_cmd);
}
} break;
case SoundCommand::CONTINUE_GROUP: {
const auto* cmd = (const Rpc_Player_Group_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Continue groups 0b{:b}", cmd->group);
snd_ContinueAllSoundsInGroup(cmd->group);
if (cmd->group & 4) {
UnpauseVAGStreams();
}
if (cmd->group & 2) {
g_bMusicPause = false;
}
} break;
case SoundCommand::SET_REVERB: {
ovrld_log(LogCategory::WARN, "[RPC Player] Unimplemented set reverb.");
} break;
case SoundCommand::SET_EAR_TRANS: {
const auto* cmd = (const Rpc_Player_Set_Ear_Trans_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] set ear trans");
SetEarTrans(cmd->ear_trans0, cmd->ear_trans1, cmd->ear_trans, cmd->cam_forward,
cmd->cam_left, cmd->cam_scale, (cmd->cam_inverted != 0));
} break;
case SoundCommand::SHUTDOWN: {
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Shutdown!");
WaitSema(g_n989Semaphore);
if (g_bSoundEnable) {
g_bSoundEnable = false;
snd_StopSoundSystem();
}
SignalSema(g_n989Semaphore);
} break;
case SoundCommand::SET_FPS: {
const auto* cmd = (const Rpc_Player_Set_Fps_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] set fps {}", (int)cmd->fps);
g_nFPS = cmd->fps;
} break;
case SoundCommand::CANCEL_DGO: {
const auto* cmd = (const Rpc_Player_Cancel_Dgo_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] cancel dgo {}", cmd->id);
CancelDGONoSync(cmd->id);
} break;
case SoundCommand::SET_MIDI_REG:
// this is what the real overlord does - just ignore it!
break;
default:
ovrld_log(LogCategory::WARN, "[RPC Player] Unsupported Player {}",
(int)((const Rpc_Player_Base_Cmd*)m_ptr)->command);
ASSERT_NOT_REACHED();
}
}
return nullptr;
}
void* RPC_Loader(unsigned int, void* msg, int size) {
if (!g_bSoundEnable) {
return nullptr;
}
// const auto* cmd = (RPC_Player_Cmd*)msg;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got Loader RPC with {} cmds",
size / kLoaderCommandStride);
u8* m_ptr = (u8*)msg;
const u8* end = m_ptr + size;
for (; m_ptr < end; m_ptr += kLoaderCommandStride) {
switch (((const Rpc_Player_Base_Cmd*)m_ptr)->command) {
case SoundCommand::LOAD_BANK: {
auto* cmd = (const Rpc_Loader_Load_Bank_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got sound bank load command: {}",
cmd->bank_name.data);
// src = &cmd->bank_name;
if (!LookupBank(cmd->bank_name.data)) {
auto* info = AllocateBankName(cmd->bank_name.data, cmd->mode);
if (info) {
strncpyz(info->m_name1, cmd->bank_name.data, 0x10);
info->in_use = 1;
info->unk0 = 0;
g_LoadingSoundBank = info;
if (LoadSoundBankToIOP(cmd->bank_name.data, info, cmd->priority) == 0) {
info->loaded = 1;
} else {
info->loaded = 0;
info->in_use = 0;
}
g_LoadingSoundBank = nullptr;
}
}
} break;
case SoundCommand::LOAD_MUSIC: {
auto* cmd = (const Rpc_Loader_Bank_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got music load command: {}",
cmd->bank_name.data);
// lock
u32 wait_status = 1;
while (wait_status) {
wait_status = WaitSema(g_nMusicSemaphore);
}
// set music name
if ((cmd->bank_name).data[0] == 0) {
g_szTargetMusicName[0] = 0;
} else {
strcpy(g_szTargetMusicName, cmd->bank_name.data);
}
// release
SignalSema(g_nMusicSemaphore);
} break;
case SoundCommand::UNLOAD_BANK: {
auto* cmd = (const Rpc_Loader_Bank_Cmd*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got bank load unload command: {}",
cmd->bank_name.data);
SoundBankInfo* ifno = LookupBank(cmd->bank_name.data);
if (ifno) {
auto snd_handle = ifno->snd_handle;
ifno->snd_handle = nullptr;
if (ifno->unk0 == 0) {
ifno->in_use = 0;
}
ifno->mode = 0;
ifno->loaded = 0;
snd_UnloadBank(snd_handle);
snd_ResolveBankXREFS();
}
} break;
case SoundCommand::GET_IRX_VERSION: {
auto* cmd = (Rpc_Loader_Get_Irx_Version*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got IRX version command");
g_nInfoEE = cmd->ee_addr;
cmd->major = 4;
cmd->minor = 0;
return cmd;
} break;
case SoundCommand::SET_LANGUAGE: {
auto* cmd = (Rpc_Loader_Set_Language*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got set language command {}", cmd->lang);
ASSERT(cmd->lang < kNumLanguages);
g_pszLanguage = languages[cmd->lang];
} break;
case SoundCommand::UNLOAD_MUSIC: {
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got unload music command");
// lock
u32 wait_status = 1;
while (wait_status) {
wait_status = WaitSema(g_nMusicSemaphore);
}
// set music name
g_szTargetMusicName[0] = 0;
// release
SignalSema(g_nMusicSemaphore);
} break;
case SoundCommand::SET_STEREO_MODE: {
auto* cmd = (Rpc_Loader_Set_Stereo_Mode*)m_ptr;
ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got set stereo command {}", cmd->mode);
switch (cmd->mode) {
case 0:
SetPlaybackMode(1);
break;
case 1:
SetPlaybackMode(2);
break;
case 2:
SetPlaybackMode(0);
break;
default:
ASSERT_NOT_REACHED();
}
} break;
default:
ovrld_log(LogCategory::WARN, "[RPC Loader] Unsupported Loader {}",
(int)((const Rpc_Player_Base_Cmd*)m_ptr)->command);
ASSERT_NOT_REACHED();
break;
}
}
return nullptr;
}
void SetVagStreamName(ISO_VAGCommand* cmd, int len) {
ASSERT(cmd);
if (!cmd->music_flag && cmd->info_idx < 4) {
if (!len) {
g_SRPCSoundIOPInfo.stream_name[cmd->info_idx].chars[0] = 0;
} else {
strncpy(g_SRPCSoundIOPInfo.stream_name[cmd->info_idx].chars, cmd->name, 0x30);
}
} else {
// ASSERT_NOT_REACHED();
}
}
} // namespace jak3

16
game/overlord/jak3/srpc.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include "common/common_types.h"
namespace jak3 {
void jak3_overlord_init_globals_srpc();
u32 Thread_Player();
u32 Thread_Loader();
struct ISO_VAGCommand;
void SetVagStreamName(ISO_VAGCommand* cmd, int len);
void* RPC_Player(unsigned int fno, void* msg, int size);
void* RPC_Loader(unsigned int fno, void* msg, int size);
extern const char* g_pszLanguage;
extern u8 g_nFPS;
} // namespace jak3

View file

@ -0,0 +1,995 @@
#include "ssound.h"
#include <array>
#include "common/util/Assert.h"
#include "game/overlord/jak3/iso.h"
#include "game/overlord/jak3/overlord.h"
#include "game/overlord/jak3/spustreams.h"
#include "game/overlord/jak3/streamlist.h"
#include "game/overlord/jak3/vag.h"
#include "game/sce/iop.h"
#include "game/sound/sdshim.h"
#include "game/sound/sndshim.h"
namespace jak3 {
struct Curve {
s32 a, b, c, d;
};
constexpr int kNumSounds = 0x40;
using namespace iop;
s32 g_n989Semaphore = -1;
s32 g_EarTransSema = -1;
bool g_bSoundEnable = true;
u32 g_anStreamVoice[6];
VolumePair g_aPanTable[361];
SoundInfo gSounds[kNumSounds];
s32 gEarTrans[6];
s32 gCamTrans[3];
s32 gCamForward[3];
s32 gCamLeft[3];
s32 gCamScale;
Curve gCurves[0x29];
std::array<u8, 1024> unktable;
bool g_CameraInvert = false;
u32 gLastTick = 0;
static s32 sqrt_table[256] = {
0, 4096, 5793, 7094, 8192, 9159, 10033, 10837, 11585, 12288, 12953, 13585, 14189,
14768, 15326, 15864, 16384, 16888, 17378, 17854, 18318, 18770, 19212, 19644, 20066, 20480,
20886, 21283, 21674, 22058, 22435, 22806, 23170, 23530, 23884, 24232, 24576, 24915, 25249,
25580, 25905, 26227, 26545, 26859, 27170, 27477, 27780, 28081, 28378, 28672, 28963, 29251,
29537, 29819, 30099, 30377, 30652, 30924, 31194, 31462, 31727, 31991, 32252, 32511, 32768,
33023, 33276, 33527, 33776, 34024, 34270, 34514, 34756, 34996, 35235, 35472, 35708, 35942,
36175, 36406, 36636, 36864, 37091, 37316, 37540, 37763, 37985, 38205, 38424, 38642, 38858,
39073, 39287, 39500, 39712, 39923, 40132, 40341, 40548, 40755, 40960, 41164, 41368, 41570,
41771, 41972, 42171, 42369, 42567, 42763, 42959, 43154, 43348, 43541, 43733, 43925, 44115,
44305, 44494, 44682, 44869, 45056, 45242, 45427, 45611, 45795, 45977, 46160, 46341, 46522,
46702, 46881, 47059, 47237, 47415, 47591, 47767, 47942, 48117, 48291, 48465, 48637, 48809,
48981, 49152, 49322, 49492, 49661, 49830, 49998, 50166, 50332, 50499, 50665, 50830, 50995,
51159, 51323, 51486, 51649, 51811, 51972, 52134, 52294, 52454, 52614, 52773, 52932, 53090,
53248, 53405, 53562, 53719, 53874, 54030, 54185, 54340, 54494, 54647, 54801, 54954, 55106,
55258, 55410, 55561, 55712, 55862, 56012, 56162, 56311, 56459, 56608, 56756, 56903, 57051,
57198, 57344, 57490, 57636, 57781, 57926, 58071, 58215, 58359, 58503, 58646, 58789, 58931,
59073, 59215, 59357, 59498, 59639, 59779, 59919, 60059, 60199, 60338, 60477, 60615, 60753,
60891, 61029, 61166, 61303, 61440, 61576, 61712, 61848, 61984, 62119, 62254, 62388, 62523,
62657, 62790, 62924, 63057, 63190, 63323, 63455, 63587, 63719, 63850, 63982, 64113, 64243,
64374, 64504, 64634, 64763, 64893, 65022, 65151, 65279, 65408,
};
void jak3_overlord_init_globals_ssound() {
g_bSoundEnable = true;
g_n989Semaphore = -1;
g_EarTransSema = -1;
for (auto& x : g_anStreamVoice) {
x = 0;
}
for (auto& x : gSounds) {
x = {};
}
unktable.fill(0);
g_CameraInvert = false;
gLastTick = 0;
}
void InitSound() {
for (auto& sound : gSounds) {
sound.id = 0;
}
int j = 0;
do {
unktable[j] = 0;
unktable[j + 0x2c] = 0;
unktable[j + 0x58] = 0;
unktable[j + 0x84] = 0;
unktable[j + 0xb0] = 0;
j = j + 1;
} while (j < 0x29);
SetCurve(0, 0, 0, 1, 0, 0, 0, 0);
SetCurve(1, 0, 0, 0, 0, 0, 1, 0);
SetCurve(2, 0, 0, 1, 0, 0, 0, 0);
SetCurve(3, 0x1000, 0, 1, 0, 0, 0, 0);
SetCurve(4, 0, 0x1000, 1, 0, 0, 0, 0);
SetCurve(5, 0x800, 0, 1, 0, 0, 0, 0);
SetCurve(6, 0x800, 0x800, 1, 0, 0, 0, 0);
SetCurve(7, 0xfffff000, 0, 1, 0, 0, 0, 0);
SetCurve(8, 0xfffff800, 0, 1, 0, 0, 0, 0);
SetCurve(9, 0, 0, 1, 0, 0, 0, 0);
SetCurve(10, 0, 0, 0, 0, 0, 0, 0);
SetCurve(0xb, 0, 0, 1, 0, 0, 0, 0);
SetCurve(0xc, 0, 0, 1, 0, 1, 0, 0);
SetCurve(0xd, 0x1000, 0, 1, 0, 1, 0, 0);
SetCurve(0xe, 0, 0x1000, 1, 0, 1, 0, 0);
SetCurve(0xf, 0x800, 0, 1, 0, 1, 0, 0);
SetCurve(0x10, 0x800, 0x800, 1, 0, 1, 0, 0);
SetCurve(0x11, 0xfffff000, 0, 1, 0, 1, 0, 0);
SetCurve(0x12, 0xfffff800, 0, 1, 0, 1, 0, 0);
SetCurve(0x13, 0, 0, 0, 0, 1, 0, 0);
SetCurve(0x14, 0, 0, 0, 0, 0, 1, 1);
SetCurve(0x15, 0, 0, 1, 0, 0, 0, 1);
SetCurve(0x16, 0x1000, 0, 1, 0, 0, 0, 1);
SetCurve(0x17, 0, 0x1000, 1, 0, 0, 0, 1);
SetCurve(0x18, 0x800, 0, 1, 0, 0, 0, 1);
SetCurve(0x19, 0x800, 0x800, 1, 0, 0, 0, 1);
SetCurve(0x1a, 0xfffff000, 0, 1, 0, 0, 0, 1);
SetCurve(0x1b, 0xfffff800, 0, 1, 0, 0, 0, 1);
SetCurve(0x1c, 0, 0, 1, 0, 0, 0, 1);
SetCurve(0x1d, 0, 0, 0, 0, 0, 0, 1);
SetCurve(0x1e, 0, 0, 1, 0, 0, 0, 1);
SetCurve(0x1f, 0, 0, 1, 0, 1, 0, 1);
SetCurve(0x20, 0x1000, 0, 1, 0, 1, 0, 1);
SetCurve(0x21, 0, 0x1000, 1, 0, 1, 0, 1);
SetCurve(0x22, 0x800, 0, 1, 0, 1, 0, 1);
SetCurve(0x23, 0x800, 0x800, 1, 0, 1, 0, 1);
SetCurve(0x24, 0xfffff000, 0, 1, 0, 1, 0, 1);
SetCurve(0x25, 0xfffff800, 0, 1, 0, 1, 0, 1);
SetCurve(0x26, 0, 0, 0, 0, 1, 0, 1);
SetCurve(0x27, 0, 0, 1, 1, 0, 1, 0);
SetCurve(0x28, 0, 0, 1, 1, 0, 1, 1);
// changed
// snd_StartSoundSystemEx(2);
snd_StartSoundSystem();
// iVar4 = 5;
// snd_RegisterIOPMemAllocator(FUN_0000dc7c,FUN_0000de84);
// snd_LockVoiceAllocatorEx(1,0x12345678);
// piVar1 = g_anStreamVoice;
// do {
// iVar2 = snd_ExternVoiceAlloc(2,0x7f);
// iVar4 = iVar4 + -1;
// *piVar1 = iVar2 * 2 + ((iVar2 / 6 + (iVar2 >> 0x1f) >> 2) - (iVar2 >> 0x1f)) * -0x2f;
// piVar1 = piVar1 + 1;
// } while (-1 < iVar4);
g_anStreamVoice[0] = SD_VOICE(0, 0);
g_anStreamVoice[1] = SD_VOICE(0, 1);
g_anStreamVoice[2] = SD_VOICE(0, 2);
g_anStreamVoice[3] = SD_VOICE(0, 3);
g_anStreamVoice[4] = SD_VOICE(0, 4);
g_anStreamVoice[5] = SD_VOICE(0, 5);
// snd_UnlockVoiceAllocator();
// snd_SetMixerMode(0,0);
// iVar4 = 0;
// do {
// iVar2 = iVar4 + 1;
// snd_SetGroupVoiceRange(iVar4,6,0x2f);
// iVar4 = iVar2;
// } while (iVar2 < 0xe);
// snd_SetGroupVoiceRange(2,0,5);
// what is this even doing.
// sceSdGetAddr(0x1c00);
// sceSdGetAddr(0x1d00);
// sceSdGetAddr(0x1c01);
// sceSdGetAddr(0x1d01);
// CpuSuspendIntr(local_18);
// sceSdSetAddr(0,0);
// sceSdSetAddr(1,0);
// sceSdSetAddr(0,0xff);
// sceSdSetAddr(1,0xff);
// CpuResumeIntr(local_18[0]);
// uVar3 = sceSdGetAddr(0x1c01);
// snd_SRAMMarkUsed(uVar3,0x7000);
// uVar3 = sceSdGetAddr(0x1c00);
// snd_SRAMMarkUsed(uVar3,0x7000);
// local_3c = 0x104;
// local_36 = 0xa7b;
// local_38 = 0xa7b;
// g_nCore1ReverbMode = 4;
// g_nCore0ReverbMode = 4;
// local_34 = 0;
// local_30 = 0;
// local_40 = 0;
// sceSdSetEffectAttr(0, &local_40);
// local_40 = 1;
// sceSdSetEffectAttr(1, &local_40);
// maybe_sceSdSetCoreAttr(2, 1);
// maybe_sceSdSetCoreAttr(3, 1);
// TODO: this is possibly very wrong:
// g_aPanTable = snd_GetPanTable();
for (int i = 0; i < 91; i++) {
s16 opposing_front = static_cast<s16>(((i * 0x33ff) / 90) + 0xc00);
s16 rear_right = static_cast<s16>(((i * -0x2800) / 90) + 0x3400);
s16 rear_left = static_cast<s16>(((i * -0xbff) / 90) + 0x3fff);
g_aPanTable[90 - i].left = 0x3FFF;
g_aPanTable[180 - i].left = opposing_front;
g_aPanTable[270 - i].left = rear_right;
g_aPanTable[360 - i].left = rear_left;
g_aPanTable[i].right = opposing_front;
g_aPanTable[90 + i].right = 0x3FFF;
g_aPanTable[180 + i].right = rear_left;
g_aPanTable[270 + i].right = rear_right;
}
SetPlaybackMode(2);
SemaParam param;
param.attr = 1;
param.init_count = 1;
param.max_count = 1;
param.option = 0;
g_nMusicSemaphore = CreateSema(&param);
ASSERT(g_nMusicSemaphore >= 0);
param.attr = 1;
param.init_count = 1;
param.max_count = 1;
param.option = 0;
g_n989Semaphore = CreateSema(&param);
ASSERT(g_n989Semaphore >= 0);
param.max_count = 1;
param.attr = 1;
param.init_count = 1;
param.option = 0;
g_EarTransSema = CreateSema(&param);
ASSERT(g_EarTransSema >= 0);
// Init989Plugins();
// InitStreamLfoHandler();
// InitVagStreamList((List*)&g_PluginStreamsList, 4, s_plugin_00015918);
InitVagStreamList(&g_EEStreamsList, 4, "ee");
InitVagStreamList(&g_EEPlayList, 8, "play");
InitVagStreamList(&g_RequestedStreamsList, 8, "streams");
InitVagStreamList(&g_NewStreamsList, 4, "new");
}
SoundInfo* LookupSound(int id) {
if (id == 0) {
return nullptr;
}
for (auto& sound : gSounds) {
if (sound.id == id) {
s32 handle = snd_SoundIsStillPlaying(sound.sound_handle);
sound.sound_handle = handle;
if (handle) {
return &sound;
} else {
sound.id = 0;
return nullptr;
}
}
}
return nullptr;
}
void CleanSounds() {
for (auto& sound : gSounds) {
if (sound.id) {
s32 handle = snd_SoundIsStillPlaying(sound.sound_handle);
sound.sound_handle = handle;
if (handle == 0) {
sound.id = 0;
}
}
}
}
void KillSoundsInGroup(u32 group) {
for (auto& sound : gSounds) {
if (sound.id) {
s32 handle = snd_SoundIsStillPlaying(sound.sound_handle);
sound.sound_handle = handle;
if (handle) {
if (sound.params.group & group) {
snd_StopSound(handle);
sound.id = 0;
}
} else {
sound.id = 0;
}
}
}
}
void KillLeastUsefulSound() {
int unique_sounds = 0;
struct Entry {
u32 id;
u32 count;
SoundInfo* info;
};
Entry entries[kNumSounds];
Entry* best_entry = nullptr;
for (auto& sound : gSounds) {
if (sound.id) {
Entry* existing_entry = nullptr;
u32 uid = snd_GetSoundID(sound.sound_handle);
// look for entry:
for (int i = 0; i < unique_sounds; i++) {
if (entries[i].id == uid) {
existing_entry = &entries[i];
break;
}
}
// if none found, create
if (!existing_entry) {
existing_entry = &entries[unique_sounds];
unique_sounds++;
existing_entry->id = uid;
existing_entry->count = 0;
existing_entry->info = &sound;
}
// update
existing_entry->count++;
// se if we're best
if (!best_entry) {
best_entry = existing_entry;
} else {
if (best_entry->count < existing_entry->count) {
best_entry = existing_entry;
}
}
// update entry:
// update best:
}
}
if (best_entry) {
snd_StopSound(best_entry->info->sound_handle);
best_entry->info->id = 0;
}
}
SoundInfo* AllocateSound() {
for (auto& sound : gSounds) {
if (!sound.id) {
return &sound;
}
}
CleanSounds();
for (auto& sound : gSounds) {
if (!sound.id) {
return &sound;
}
}
KillLeastUsefulSound();
for (auto& sound : gSounds) {
if (!sound.id) {
return &sound;
}
}
ASSERT_NOT_REACHED();
return nullptr;
}
u32 CalculateFalloffVolume(s32* trans,
u32 vol,
u32 fo_curve,
u32 fo_min,
u32 fo_max,
u32* outa,
u32* outb) {
ASSERT(fo_curve < 0x29);
// undefined4 uVar1;
u32 uVar2;
int iVar3;
int iVar4;
u32 uVar5;
int iVar6;
int iVar7;
int iVar8;
u32 uVar9;
u32 uVar10;
uVar10 = 0;
WaitSema(g_EarTransSema);
if (outa) {
*outa = 0;
}
if (outb) {
*outb = 0;
}
if (unktable[fo_curve + 0x84] != 0) {
SignalSema(g_EarTransSema);
return vol;
}
if (unktable[fo_curve + 0x58] != 0) {
trans = gEarTrans + 3;
}
switch (fo_curve) {
case 9:
case 0xb:
case 0x1c:
case 0x1e:
iVar8 = gEarTrans[3] - *trans;
iVar3 = gEarTrans[4] - trans[1];
iVar7 = gEarTrans[5] - trans[2];
uVar2 = 3;
if (outa) {
LAB_0000d094:
*outa = uVar2;
}
break;
case 10:
case 0x13:
case 0x1d:
case 0x26:
iVar8 = 0;
iVar3 = gEarTrans[1] - trans[1];
iVar7 = 0;
if (outa) {
*outa = 2;
}
goto LAB_0000d0a4;
default:
iVar8 = gEarTrans[0] - *trans;
iVar7 = gEarTrans[2] - trans[2];
iVar3 = gEarTrans[1] - trans[1];
if (outa) {
uVar2 = 1;
goto LAB_0000d094;
}
}
if (iVar8 < 0) {
iVar8 = -iVar8;
}
LAB_0000d0a4:
if (iVar3 < 0) {
iVar3 = -iVar3;
}
if (iVar7 < 0) {
iVar7 = -iVar7;
}
fo_min = fo_min << 8;
fo_max = fo_max << 8;
uVar9 = 0;
iVar6 = iVar3;
if (iVar3 < iVar7) {
iVar6 = iVar7;
}
iVar4 = fo_max;
if (fo_max < iVar8) {
iVar4 = iVar8;
}
if (iVar4 < iVar6) {
iVar4 = iVar6;
}
while (0x7fff < iVar4) {
fo_max = fo_max >> 1;
fo_min = fo_min >> 1;
iVar8 = iVar8 >> 1;
iVar3 = iVar3 >> 1;
iVar7 = iVar7 >> 1;
uVar9 = uVar9 + 1;
iVar4 = iVar4 >> 1;
}
if (gCamScale != 0x10000) {
iVar8 = iVar8 * gCamScale >> 0x10;
iVar3 = iVar3 * gCamScale >> 0x10;
iVar7 = iVar7 * gCamScale >> 0x10;
if (0x10000 < gCamScale) {
iVar6 = iVar3;
if (iVar3 < iVar7) {
iVar6 = iVar7;
}
iVar4 = fo_max;
if (fo_max < iVar8) {
iVar4 = iVar8;
}
if (iVar4 < iVar6) {
iVar4 = iVar6;
}
while (0x7fff < iVar4) {
fo_max = fo_max >> 1;
fo_min = fo_min >> 1;
iVar8 = iVar8 >> 1;
iVar3 = iVar3 >> 1;
iVar7 = iVar7 >> 1;
uVar9 = uVar9 + 1;
iVar4 = iVar4 >> 1;
}
}
}
if ((outb) || (((iVar8 <= fo_max && (iVar3 <= fo_max)) && (iVar7 <= fo_max)))) {
uVar10 = iVar8 * iVar8 + iVar3 * iVar3 + iVar7 * iVar7;
iVar8 = 0;
if (uVar10 != 0) {
uVar5 = 0;
while ((uVar10 & 0xc0000000) == 0) {
uVar10 = uVar10 << 2;
uVar5 = uVar5 + 1;
}
iVar8 = (int)(u32)sqrt_table[uVar10 >> 0x18] >> (uVar5 & 0x1f);
}
if (outb) {
*outb = iVar8 << (uVar9 & 0x1f);
}
uVar10 = vol;
if ((fo_min < iVar8) && (uVar10 = 0, iVar8 < fo_max)) {
uVar10 = iVar8 - fo_min;
uVar9 = fo_max - fo_min;
while (0xffff < uVar10) {
uVar10 = uVar10 >> 1;
uVar9 = (int)uVar9 >> 1;
}
uVar5 = (uVar10 << 0x10) / uVar9;
if (uVar9 == 0) {
ASSERT_NOT_REACHED();
}
uVar10 = vol;
if (uVar5 != 0x10000) {
uVar10 = uVar5 * uVar5 >> 0x10;
uVar10 = gCurves[fo_curve].c * uVar5 + gCurves[fo_curve].b * uVar10 +
gCurves[fo_curve].d * 0x10000 +
gCurves[fo_curve].a * (uVar10 * uVar5 >> 0x10) >>
0xc;
if ((int)uVar10 < 0) {
uVar10 = 0;
} else {
if (0x10000 < uVar10) {
uVar10 = 0x10000;
}
}
uVar10 = (int)(uVar10 * vol) >> 0x10;
}
}
}
if ((fo_curve == 0xb) && (uVar10 < 0x180)) {
uVar10 = 0x180;
}
SignalSema(g_EarTransSema);
return uVar10;
}
constexpr s16 unk_table_2[2056] = {
0xB4, 0x0, 0xB4, 0x0, 0x5A, 0x5A, 0x10E, 0x10E, 0xB4, 0x0, 0xB4, 0x0, 0x5A,
0x5A, 0x10E, 0x10E, 0xB4, 0x0, 0xB4, 0x0, 0x5A, 0x5A, 0x10E, 0x10E, 0xB4, 0x0,
0xB4, 0x0, 0x5A, 0x5A, 0x10E, 0x10E, 0xB4, 0x0, 0xB4, 0x0, 0x5A, 0x5A, 0x10E,
0x10E, 0xB3, 0x1, 0xB5, 0x167, 0x5B, 0x59, 0x10D, 0x10F, 0xB3, 0x1, 0xB5, 0x167,
0x5B, 0x59, 0x10D, 0x10F, 0xB3, 0x1, 0xB5, 0x167, 0x5B, 0x59, 0x10D, 0x10F, 0xB3,
0x1, 0xB5, 0x167, 0x5B, 0x59, 0x10D, 0x10F, 0xB2, 0x2, 0xB6, 0x166, 0x5C, 0x58,
0x10C, 0x110, 0xB2, 0x2, 0xB6, 0x166, 0x5C, 0x58, 0x10C, 0x110, 0xB2, 0x2, 0xB6,
0x166, 0x5C, 0x58, 0x10C, 0x110, 0xB2, 0x2, 0xB6, 0x166, 0x5C, 0x58, 0x10C, 0x110,
0xB2, 0x2, 0xB6, 0x166, 0x5C, 0x58, 0x10C, 0x110, 0xB1, 0x3, 0xB7, 0x165, 0x5D,
0x57, 0x10B, 0x111, 0xB1, 0x3, 0xB7, 0x165, 0x5D, 0x57, 0x10B, 0x111, 0xB1, 0x3,
0xB7, 0x165, 0x5D, 0x57, 0x10B, 0x111, 0xB1, 0x3, 0xB7, 0x165, 0x5D, 0x57, 0x10B,
0x111, 0xB0, 0x4, 0xB8, 0x164, 0x5E, 0x56, 0x10A, 0x112, 0xB0, 0x4, 0xB8, 0x164,
0x5E, 0x56, 0x10A, 0x112, 0xB0, 0x4, 0xB8, 0x164, 0x5E, 0x56, 0x10A, 0x112, 0xB0,
0x4, 0xB8, 0x164, 0x5E, 0x56, 0x10A, 0x112, 0xB0, 0x4, 0xB8, 0x164, 0x5E, 0x56,
0x10A, 0x112, 0xAF, 0x5, 0xB9, 0x163, 0x5F, 0x55, 0x109, 0x113, 0xAF, 0x5, 0xB9,
0x163, 0x5F, 0x55, 0x109, 0x113, 0xAF, 0x5, 0xB9, 0x163, 0x5F, 0x55, 0x109, 0x113,
0xAF, 0x5, 0xB9, 0x163, 0x5F, 0x55, 0x109, 0x113, 0xAE, 0x6, 0xBA, 0x162, 0x60,
0x54, 0x108, 0x114, 0xAE, 0x6, 0xBA, 0x162, 0x60, 0x54, 0x108, 0x114, 0xAE, 0x6,
0xBA, 0x162, 0x60, 0x54, 0x108, 0x114, 0xAE, 0x6, 0xBA, 0x162, 0x60, 0x54, 0x108,
0x114, 0xAE, 0x6, 0xBA, 0x162, 0x60, 0x54, 0x108, 0x114, 0xAD, 0x7, 0xBB, 0x161,
0x61, 0x53, 0x107, 0x115, 0xAD, 0x7, 0xBB, 0x161, 0x61, 0x53, 0x107, 0x115, 0xAD,
0x7, 0xBB, 0x161, 0x61, 0x53, 0x107, 0x115, 0xAD, 0x7, 0xBB, 0x161, 0x61, 0x53,
0x107, 0x115, 0xAC, 0x8, 0xBC, 0x160, 0x62, 0x52, 0x106, 0x116, 0xAC, 0x8, 0xBC,
0x160, 0x62, 0x52, 0x106, 0x116, 0xAC, 0x8, 0xBC, 0x160, 0x62, 0x52, 0x106, 0x116,
0xAC, 0x8, 0xBC, 0x160, 0x62, 0x52, 0x106, 0x116, 0xAC, 0x8, 0xBC, 0x160, 0x62,
0x52, 0x106, 0x116, 0xAB, 0x9, 0xBD, 0x15F, 0x63, 0x51, 0x105, 0x117, 0xAB, 0x9,
0xBD, 0x15F, 0x63, 0x51, 0x105, 0x117, 0xAB, 0x9, 0xBD, 0x15F, 0x63, 0x51, 0x105,
0x117, 0xAB, 0x9, 0xBD, 0x15F, 0x63, 0x51, 0x105, 0x117, 0xAB, 0x9, 0xBD, 0x15F,
0x63, 0x51, 0x105, 0x117, 0xAA, 0xA, 0xBE, 0x15E, 0x64, 0x50, 0x104, 0x118, 0xAA,
0xA, 0xBE, 0x15E, 0x64, 0x50, 0x104, 0x118, 0xAA, 0xA, 0xBE, 0x15E, 0x64, 0x50,
0x104, 0x118, 0xAA, 0xA, 0xBE, 0x15E, 0x64, 0x50, 0x104, 0x118, 0xA9, 0xB, 0xBF,
0x15D, 0x65, 0x4F, 0x103, 0x119, 0xA9, 0xB, 0xBF, 0x15D, 0x65, 0x4F, 0x103, 0x119,
0xA9, 0xB, 0xBF, 0x15D, 0x65, 0x4F, 0x103, 0x119, 0xA9, 0xB, 0xBF, 0x15D, 0x65,
0x4F, 0x103, 0x119, 0xA9, 0xB, 0xBF, 0x15D, 0x65, 0x4F, 0x103, 0x119, 0xA8, 0xC,
0xC0, 0x15C, 0x66, 0x4E, 0x102, 0x11A, 0xA8, 0xC, 0xC0, 0x15C, 0x66, 0x4E, 0x102,
0x11A, 0xA8, 0xC, 0xC0, 0x15C, 0x66, 0x4E, 0x102, 0x11A, 0xA8, 0xC, 0xC0, 0x15C,
0x66, 0x4E, 0x102, 0x11A, 0xA8, 0xC, 0xC0, 0x15C, 0x66, 0x4E, 0x102, 0x11A, 0xA7,
0xD, 0xC1, 0x15B, 0x67, 0x4D, 0x101, 0x11B, 0xA7, 0xD, 0xC1, 0x15B, 0x67, 0x4D,
0x101, 0x11B, 0xA7, 0xD, 0xC1, 0x15B, 0x67, 0x4D, 0x101, 0x11B, 0xA7, 0xD, 0xC1,
0x15B, 0x67, 0x4D, 0x101, 0x11B, 0xA6, 0xE, 0xC2, 0x15A, 0x68, 0x4C, 0x100, 0x11C,
0xA6, 0xE, 0xC2, 0x15A, 0x68, 0x4C, 0x100, 0x11C, 0xA6, 0xE, 0xC2, 0x15A, 0x68,
0x4C, 0x100, 0x11C, 0xA6, 0xE, 0xC2, 0x15A, 0x68, 0x4C, 0x100, 0x11C, 0xA6, 0xE,
0xC2, 0x15A, 0x68, 0x4C, 0x100, 0x11C, 0xA5, 0xF, 0xC3, 0x159, 0x69, 0x4B, 0xFF,
0x11D, 0xA5, 0xF, 0xC3, 0x159, 0x69, 0x4B, 0xFF, 0x11D, 0xA5, 0xF, 0xC3, 0x159,
0x69, 0x4B, 0xFF, 0x11D, 0xA5, 0xF, 0xC3, 0x159, 0x69, 0x4B, 0xFF, 0x11D, 0xA5,
0xF, 0xC3, 0x159, 0x69, 0x4B, 0xFF, 0x11D, 0xA4, 0x10, 0xC4, 0x158, 0x6A, 0x4A,
0xFE, 0x11E, 0xA4, 0x10, 0xC4, 0x158, 0x6A, 0x4A, 0xFE, 0x11E, 0xA4, 0x10, 0xC4,
0x158, 0x6A, 0x4A, 0xFE, 0x11E, 0xA4, 0x10, 0xC4, 0x158, 0x6A, 0x4A, 0xFE, 0x11E,
0xA4, 0x10, 0xC4, 0x158, 0x6A, 0x4A, 0xFE, 0x11E, 0xA3, 0x11, 0xC5, 0x157, 0x6B,
0x49, 0xFD, 0x11F, 0xA3, 0x11, 0xC5, 0x157, 0x6B, 0x49, 0xFD, 0x11F, 0xA3, 0x11,
0xC5, 0x157, 0x6B, 0x49, 0xFD, 0x11F, 0xA3, 0x11, 0xC5, 0x157, 0x6B, 0x49, 0xFD,
0x11F, 0xA3, 0x11, 0xC5, 0x157, 0x6B, 0x49, 0xFD, 0x11F, 0xA2, 0x12, 0xC6, 0x156,
0x6C, 0x48, 0xFC, 0x120, 0xA2, 0x12, 0xC6, 0x156, 0x6C, 0x48, 0xFC, 0x120, 0xA2,
0x12, 0xC6, 0x156, 0x6C, 0x48, 0xFC, 0x120, 0xA2, 0x12, 0xC6, 0x156, 0x6C, 0x48,
0xFC, 0x120, 0xA2, 0x12, 0xC6, 0x156, 0x6C, 0x48, 0xFC, 0x120, 0xA1, 0x13, 0xC7,
0x155, 0x6D, 0x47, 0xFB, 0x121, 0xA1, 0x13, 0xC7, 0x155, 0x6D, 0x47, 0xFB, 0x121,
0xA1, 0x13, 0xC7, 0x155, 0x6D, 0x47, 0xFB, 0x121, 0xA1, 0x13, 0xC7, 0x155, 0x6D,
0x47, 0xFB, 0x121, 0xA1, 0x13, 0xC7, 0x155, 0x6D, 0x47, 0xFB, 0x121, 0xA0, 0x14,
0xC8, 0x154, 0x6E, 0x46, 0xFA, 0x122, 0xA0, 0x14, 0xC8, 0x154, 0x6E, 0x46, 0xFA,
0x122, 0xA0, 0x14, 0xC8, 0x154, 0x6E, 0x46, 0xFA, 0x122, 0xA0, 0x14, 0xC8, 0x154,
0x6E, 0x46, 0xFA, 0x122, 0xA0, 0x14, 0xC8, 0x154, 0x6E, 0x46, 0xFA, 0x122, 0x9F,
0x15, 0xC9, 0x153, 0x6F, 0x45, 0xF9, 0x123, 0x9F, 0x15, 0xC9, 0x153, 0x6F, 0x45,
0xF9, 0x123, 0x9F, 0x15, 0xC9, 0x153, 0x6F, 0x45, 0xF9, 0x123, 0x9F, 0x15, 0xC9,
0x153, 0x6F, 0x45, 0xF9, 0x123, 0x9F, 0x15, 0xC9, 0x153, 0x6F, 0x45, 0xF9, 0x123,
0x9E, 0x16, 0xCA, 0x152, 0x70, 0x44, 0xF8, 0x124, 0x9E, 0x16, 0xCA, 0x152, 0x70,
0x44, 0xF8, 0x124, 0x9E, 0x16, 0xCA, 0x152, 0x70, 0x44, 0xF8, 0x124, 0x9E, 0x16,
0xCA, 0x152, 0x70, 0x44, 0xF8, 0x124, 0x9E, 0x16, 0xCA, 0x152, 0x70, 0x44, 0xF8,
0x124, 0x9D, 0x17, 0xCB, 0x151, 0x71, 0x43, 0xF7, 0x125, 0x9D, 0x17, 0xCB, 0x151,
0x71, 0x43, 0xF7, 0x125, 0x9D, 0x17, 0xCB, 0x151, 0x71, 0x43, 0xF7, 0x125, 0x9D,
0x17, 0xCB, 0x151, 0x71, 0x43, 0xF7, 0x125, 0x9D, 0x17, 0xCB, 0x151, 0x71, 0x43,
0xF7, 0x125, 0x9D, 0x17, 0xCB, 0x151, 0x71, 0x43, 0xF7, 0x125, 0x9C, 0x18, 0xCC,
0x150, 0x72, 0x42, 0xF6, 0x126, 0x9C, 0x18, 0xCC, 0x150, 0x72, 0x42, 0xF6, 0x126,
0x9C, 0x18, 0xCC, 0x150, 0x72, 0x42, 0xF6, 0x126, 0x9C, 0x18, 0xCC, 0x150, 0x72,
0x42, 0xF6, 0x126, 0x9C, 0x18, 0xCC, 0x150, 0x72, 0x42, 0xF6, 0x126, 0x9B, 0x19,
0xCD, 0x14F, 0x73, 0x41, 0xF5, 0x127, 0x9B, 0x19, 0xCD, 0x14F, 0x73, 0x41, 0xF5,
0x127, 0x9B, 0x19, 0xCD, 0x14F, 0x73, 0x41, 0xF5, 0x127, 0x9B, 0x19, 0xCD, 0x14F,
0x73, 0x41, 0xF5, 0x127, 0x9B, 0x19, 0xCD, 0x14F, 0x73, 0x41, 0xF5, 0x127, 0x9A,
0x1A, 0xCE, 0x14E, 0x74, 0x40, 0xF4, 0x128, 0x9A, 0x1A, 0xCE, 0x14E, 0x74, 0x40,
0xF4, 0x128, 0x9A, 0x1A, 0xCE, 0x14E, 0x74, 0x40, 0xF4, 0x128, 0x9A, 0x1A, 0xCE,
0x14E, 0x74, 0x40, 0xF4, 0x128, 0x9A, 0x1A, 0xCE, 0x14E, 0x74, 0x40, 0xF4, 0x128,
0x9A, 0x1A, 0xCE, 0x14E, 0x74, 0x40, 0xF4, 0x128, 0x99, 0x1B, 0xCF, 0x14D, 0x75,
0x3F, 0xF3, 0x129, 0x99, 0x1B, 0xCF, 0x14D, 0x75, 0x3F, 0xF3, 0x129, 0x99, 0x1B,
0xCF, 0x14D, 0x75, 0x3F, 0xF3, 0x129, 0x99, 0x1B, 0xCF, 0x14D, 0x75, 0x3F, 0xF3,
0x129, 0x99, 0x1B, 0xCF, 0x14D, 0x75, 0x3F, 0xF3, 0x129, 0x99, 0x1B, 0xCF, 0x14D,
0x75, 0x3F, 0xF3, 0x129, 0x98, 0x1C, 0xD0, 0x14C, 0x76, 0x3E, 0xF2, 0x12A, 0x98,
0x1C, 0xD0, 0x14C, 0x76, 0x3E, 0xF2, 0x12A, 0x98, 0x1C, 0xD0, 0x14C, 0x76, 0x3E,
0xF2, 0x12A, 0x98, 0x1C, 0xD0, 0x14C, 0x76, 0x3E, 0xF2, 0x12A, 0x98, 0x1C, 0xD0,
0x14C, 0x76, 0x3E, 0xF2, 0x12A, 0x97, 0x1D, 0xD1, 0x14B, 0x77, 0x3D, 0xF1, 0x12B,
0x97, 0x1D, 0xD1, 0x14B, 0x77, 0x3D, 0xF1, 0x12B, 0x97, 0x1D, 0xD1, 0x14B, 0x77,
0x3D, 0xF1, 0x12B, 0x97, 0x1D, 0xD1, 0x14B, 0x77, 0x3D, 0xF1, 0x12B, 0x97, 0x1D,
0xD1, 0x14B, 0x77, 0x3D, 0xF1, 0x12B, 0x97, 0x1D, 0xD1, 0x14B, 0x77, 0x3D, 0xF1,
0x12B, 0x96, 0x1E, 0xD2, 0x14A, 0x78, 0x3C, 0xF0, 0x12C, 0x96, 0x1E, 0xD2, 0x14A,
0x78, 0x3C, 0xF0, 0x12C, 0x96, 0x1E, 0xD2, 0x14A, 0x78, 0x3C, 0xF0, 0x12C, 0x96,
0x1E, 0xD2, 0x14A, 0x78, 0x3C, 0xF0, 0x12C, 0x96, 0x1E, 0xD2, 0x14A, 0x78, 0x3C,
0xF0, 0x12C, 0x96, 0x1E, 0xD2, 0x14A, 0x78, 0x3C, 0xF0, 0x12C, 0x95, 0x1F, 0xD3,
0x149, 0x79, 0x3B, 0xEF, 0x12D, 0x95, 0x1F, 0xD3, 0x149, 0x79, 0x3B, 0xEF, 0x12D,
0x95, 0x1F, 0xD3, 0x149, 0x79, 0x3B, 0xEF, 0x12D, 0x95, 0x1F, 0xD3, 0x149, 0x79,
0x3B, 0xEF, 0x12D, 0x95, 0x1F, 0xD3, 0x149, 0x79, 0x3B, 0xEF, 0x12D, 0x95, 0x1F,
0xD3, 0x149, 0x79, 0x3B, 0xEF, 0x12D, 0x94, 0x20, 0xD4, 0x148, 0x7A, 0x3A, 0xEE,
0x12E, 0x94, 0x20, 0xD4, 0x148, 0x7A, 0x3A, 0xEE, 0x12E, 0x94, 0x20, 0xD4, 0x148,
0x7A, 0x3A, 0xEE, 0x12E, 0x94, 0x20, 0xD4, 0x148, 0x7A, 0x3A, 0xEE, 0x12E, 0x94,
0x20, 0xD4, 0x148, 0x7A, 0x3A, 0xEE, 0x12E, 0x94, 0x20, 0xD4, 0x148, 0x7A, 0x3A,
0xEE, 0x12E, 0x94, 0x20, 0xD4, 0x148, 0x7A, 0x3A, 0xEE, 0x12E, 0x93, 0x21, 0xD5,
0x147, 0x7B, 0x39, 0xED, 0x12F, 0x93, 0x21, 0xD5, 0x147, 0x7B, 0x39, 0xED, 0x12F,
0x93, 0x21, 0xD5, 0x147, 0x7B, 0x39, 0xED, 0x12F, 0x93, 0x21, 0xD5, 0x147, 0x7B,
0x39, 0xED, 0x12F, 0x93, 0x21, 0xD5, 0x147, 0x7B, 0x39, 0xED, 0x12F, 0x93, 0x21,
0xD5, 0x147, 0x7B, 0x39, 0xED, 0x12F, 0x92, 0x22, 0xD6, 0x146, 0x7C, 0x38, 0xEC,
0x130, 0x92, 0x22, 0xD6, 0x146, 0x7C, 0x38, 0xEC, 0x130, 0x92, 0x22, 0xD6, 0x146,
0x7C, 0x38, 0xEC, 0x130, 0x92, 0x22, 0xD6, 0x146, 0x7C, 0x38, 0xEC, 0x130, 0x92,
0x22, 0xD6, 0x146, 0x7C, 0x38, 0xEC, 0x130, 0x92, 0x22, 0xD6, 0x146, 0x7C, 0x38,
0xEC, 0x130, 0x92, 0x22, 0xD6, 0x146, 0x7C, 0x38, 0xEC, 0x130, 0x91, 0x23, 0xD7,
0x145, 0x7D, 0x37, 0xEB, 0x131, 0x91, 0x23, 0xD7, 0x145, 0x7D, 0x37, 0xEB, 0x131,
0x91, 0x23, 0xD7, 0x145, 0x7D, 0x37, 0xEB, 0x131, 0x91, 0x23, 0xD7, 0x145, 0x7D,
0x37, 0xEB, 0x131, 0x91, 0x23, 0xD7, 0x145, 0x7D, 0x37, 0xEB, 0x131, 0x91, 0x23,
0xD7, 0x145, 0x7D, 0x37, 0xEB, 0x131, 0x91, 0x23, 0xD7, 0x145, 0x7D, 0x37, 0xEB,
0x131, 0x90, 0x24, 0xD8, 0x144, 0x7E, 0x36, 0xEA, 0x132, 0x90, 0x24, 0xD8, 0x144,
0x7E, 0x36, 0xEA, 0x132, 0x90, 0x24, 0xD8, 0x144, 0x7E, 0x36, 0xEA, 0x132, 0x90,
0x24, 0xD8, 0x144, 0x7E, 0x36, 0xEA, 0x132, 0x90, 0x24, 0xD8, 0x144, 0x7E, 0x36,
0xEA, 0x132, 0x90, 0x24, 0xD8, 0x144, 0x7E, 0x36, 0xEA, 0x132, 0x8F, 0x25, 0xD9,
0x143, 0x7F, 0x35, 0xE9, 0x133, 0x8F, 0x25, 0xD9, 0x143, 0x7F, 0x35, 0xE9, 0x133,
0x8F, 0x25, 0xD9, 0x143, 0x7F, 0x35, 0xE9, 0x133, 0x8F, 0x25, 0xD9, 0x143, 0x7F,
0x35, 0xE9, 0x133, 0x8F, 0x25, 0xD9, 0x143, 0x7F, 0x35, 0xE9, 0x133, 0x8F, 0x25,
0xD9, 0x143, 0x7F, 0x35, 0xE9, 0x133, 0x8F, 0x25, 0xD9, 0x143, 0x7F, 0x35, 0xE9,
0x133, 0x8F, 0x25, 0xD9, 0x143, 0x7F, 0x35, 0xE9, 0x133, 0x8E, 0x26, 0xDA, 0x142,
0x80, 0x34, 0xE8, 0x134, 0x8E, 0x26, 0xDA, 0x142, 0x80, 0x34, 0xE8, 0x134, 0x8E,
0x26, 0xDA, 0x142, 0x80, 0x34, 0xE8, 0x134, 0x8E, 0x26, 0xDA, 0x142, 0x80, 0x34,
0xE8, 0x134, 0x8E, 0x26, 0xDA, 0x142, 0x80, 0x34, 0xE8, 0x134, 0x8E, 0x26, 0xDA,
0x142, 0x80, 0x34, 0xE8, 0x134, 0x8E, 0x26, 0xDA, 0x142, 0x80, 0x34, 0xE8, 0x134,
0x8D, 0x27, 0xDB, 0x141, 0x81, 0x33, 0xE7, 0x135, 0x8D, 0x27, 0xDB, 0x141, 0x81,
0x33, 0xE7, 0x135, 0x8D, 0x27, 0xDB, 0x141, 0x81, 0x33, 0xE7, 0x135, 0x8D, 0x27,
0xDB, 0x141, 0x81, 0x33, 0xE7, 0x135, 0x8D, 0x27, 0xDB, 0x141, 0x81, 0x33, 0xE7,
0x135, 0x8D, 0x27, 0xDB, 0x141, 0x81, 0x33, 0xE7, 0x135, 0x8D, 0x27, 0xDB, 0x141,
0x81, 0x33, 0xE7, 0x135, 0x8C, 0x28, 0xDC, 0x140, 0x82, 0x32, 0xE6, 0x136, 0x8C,
0x28, 0xDC, 0x140, 0x82, 0x32, 0xE6, 0x136, 0x8C, 0x28, 0xDC, 0x140, 0x82, 0x32,
0xE6, 0x136, 0x8C, 0x28, 0xDC, 0x140, 0x82, 0x32, 0xE6, 0x136, 0x8C, 0x28, 0xDC,
0x140, 0x82, 0x32, 0xE6, 0x136, 0x8C, 0x28, 0xDC, 0x140, 0x82, 0x32, 0xE6, 0x136,
0x8C, 0x28, 0xDC, 0x140, 0x82, 0x32, 0xE6, 0x136, 0x8C, 0x28, 0xDC, 0x140, 0x82,
0x32, 0xE6, 0x136, 0x8B, 0x29, 0xDD, 0x13F, 0x83, 0x31, 0xE5, 0x137, 0x8B, 0x29,
0xDD, 0x13F, 0x83, 0x31, 0xE5, 0x137, 0x8B, 0x29, 0xDD, 0x13F, 0x83, 0x31, 0xE5,
0x137, 0x8B, 0x29, 0xDD, 0x13F, 0x83, 0x31, 0xE5, 0x137, 0x8B, 0x29, 0xDD, 0x13F,
0x83, 0x31, 0xE5, 0x137, 0x8B, 0x29, 0xDD, 0x13F, 0x83, 0x31, 0xE5, 0x137, 0x8B,
0x29, 0xDD, 0x13F, 0x83, 0x31, 0xE5, 0x137, 0x8B, 0x29, 0xDD, 0x13F, 0x83, 0x31,
0xE5, 0x137, 0x8A, 0x2A, 0xDE, 0x13E, 0x84, 0x30, 0xE4, 0x138, 0x8A, 0x2A, 0xDE,
0x13E, 0x84, 0x30, 0xE4, 0x138, 0x8A, 0x2A, 0xDE, 0x13E, 0x84, 0x30, 0xE4, 0x138,
0x8A, 0x2A, 0xDE, 0x13E, 0x84, 0x30, 0xE4, 0x138, 0x8A, 0x2A, 0xDE, 0x13E, 0x84,
0x30, 0xE4, 0x138, 0x8A, 0x2A, 0xDE, 0x13E, 0x84, 0x30, 0xE4, 0x138, 0x8A, 0x2A,
0xDE, 0x13E, 0x84, 0x30, 0xE4, 0x138, 0x8A, 0x2A, 0xDE, 0x13E, 0x84, 0x30, 0xE4,
0x138, 0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x89, 0x2B, 0xDF, 0x13D,
0x85, 0x2F, 0xE3, 0x139, 0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x89,
0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F,
0xE3, 0x139, 0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x89, 0x2B, 0xDF,
0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139,
0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x88, 0x2C, 0xE0, 0x13C, 0x86,
0x2E, 0xE2, 0x13A, 0x88, 0x2C, 0xE0, 0x13C, 0x86, 0x2E, 0xE2, 0x13A, 0x88, 0x2C,
0xE0, 0x13C, 0x86, 0x2E, 0xE2, 0x13A, 0x88, 0x2C, 0xE0, 0x13C, 0x86, 0x2E, 0xE2,
0x13A, 0x88, 0x2C, 0xE0, 0x13C, 0x86, 0x2E, 0xE2, 0x13A, 0x88, 0x2C, 0xE0, 0x13C,
0x86, 0x2E, 0xE2, 0x13A, 0x88, 0x2C, 0xE0, 0x13C, 0x86, 0x2E, 0xE2, 0x13A, 0x88,
0x2C, 0xE0, 0x13C, 0x86, 0x2E, 0xE2, 0x13A, 0x87, 0x2D, 0xE1, 0x13B, 0x87, 0x2D,
0xE1, 0x13B,
};
s32 CalculateAngle(s32* trans, u32 fo_curve, u32 param_3) {
u32 uVar2;
int iVar3;
u32 uVar4;
u32 uVar5;
int iVar6;
int iVar7;
u32 uVar8;
ASSERT(fo_curve < 0x29);
WaitSema(g_EarTransSema);
if (unktable[fo_curve] != 0) {
if (unktable[fo_curve + 0x2c] == 0) {
if (unktable[fo_curve + 0x58] != 0) {
trans = gEarTrans + 3;
}
iVar6 = trans[1];
iVar3 = gCamTrans[0] - *trans;
iVar7 = gCamTrans[2] - trans[2];
} else {
iVar7 = gCamForward[0] - gCamTrans[0];
iVar3 = gCamTrans[2] - gCamForward[2];
iVar6 = gCamForward[1];
}
iVar6 = gCamTrans[1] - iVar6;
if (((iVar3 + 0x200168U | iVar6 + 0x200168U | iVar7 + 0x200168U) & 0xffc00000) != 0) {
if (iVar3 < 0) {
iVar3 = iVar3 + 0x3ff;
}
iVar3 = iVar3 >> 10;
if (iVar6 < 0) {
iVar6 = iVar6 + 0x3ff;
}
iVar6 = iVar6 >> 10;
if (iVar7 < 0) {
iVar7 = iVar7 + 0x3ff;
}
iVar7 = iVar7 >> 10;
}
uVar8 = iVar3 * gCamLeft[0] + iVar6 * gCamLeft[1] + iVar7 * gCamLeft[2];
uVar5 = uVar8;
if ((int)uVar8 < 0) {
uVar5 = -uVar8;
}
uVar2 = iVar3 * gCamForward[0] + iVar6 * gCamForward[1] + iVar7 * gCamForward[2];
uVar4 = uVar2;
if ((int)uVar2 < 0) {
uVar4 = -uVar2;
}
if ((0x1ffff < (int)uVar5) || (0x1ffff < (int)uVar4)) {
uVar4 = (int)uVar4 >> 8;
uVar5 = (int)uVar5 >> 8;
}
if ((uVar4 != 0) || (uVar5 != 0)) {
uVar8 = (uVar8 & 0x80000000) >> 0x1e | uVar2 >> 0x1f;
if ((int)uVar4 < (int)uVar5) {
if (uVar5 == 0) {
ASSERT_NOT_REACHED();
}
uVar8 = uVar8 | (int)(uVar4 << 8) / (int)uVar5 << 3 | 4;
} else {
if (uVar4 == 0) {
ASSERT_NOT_REACHED();
}
uVar8 = uVar8 | (int)(uVar5 << 8) / (int)uVar4 << 3;
}
ASSERT(uVar8 < 2056);
iVar3 = (int)(short)unk_table_2[uVar8];
iVar6 = iVar3;
if (((param_3 != 0) && (iVar6 = iVar3, g_CameraInvert != 0)) && (iVar6 = 0, iVar3 != 0)) {
iVar6 = 0x168 - iVar3;
}
SignalSema(g_EarTransSema);
return iVar6;
}
}
SignalSema(g_EarTransSema);
return 0;
}
s32 GetVolume(SoundInfo* sound) {
return CalculateFalloffVolume(sound->params.trans, sound->params.volume, sound->params.fo_curve,
sound->params.fo_min, sound->params.fo_max, nullptr, nullptr);
}
s32 GetPan(SoundInfo* sound) {
return CalculateAngle(sound->params.trans, sound->params.fo_curve, 1);
}
void UpdateLocation(SoundInfo* sound) {
auto handle = snd_SoundIsStillPlaying(sound->sound_handle);
sound->sound_handle = handle;
if (handle == 0) {
sound->id = 0;
} else {
auto vol = GetVolume(sound);
if (vol == 0 && unktable[(int)(sound->params).fo_curve + 0xb0] == 0) {
snd_StopSound(sound->sound_handle);
} else {
auto pan = GetPan(sound);
// ovrld_log(LogCategory::WARN, "HACK: falling back to old version of setting vol/pan");
snd_SetSoundVolPan(handle, vol, pan);
// FUN_00013e0c(handle,4,0,pan,0,0);
// if ((short)(sound->params).mask < 0) {
// FUN_00013d6c(handle,vol,0x40);
// }
// else {
// snd_SetSoundVolPan(handle,vol,0xfffffffe,4);
// }
}
}
}
void UpdateAutoVol(SoundInfo* snd, int time) {
bool bVar1;
auto iVar6 = snd->auto_time;
auto iVar4 = snd->new_volume;
if (time < iVar6) {
auto iVar5 = iVar4;
if (iVar4 == -4) {
iVar5 = 0;
}
auto vol = (snd->params).volume;
int new_vol;
if (iVar6 == 0) {
ASSERT_NOT_REACHED();
}
iVar5 = ((iVar5 - vol) * time) / iVar6;
if (iVar5 < 0) {
new_vol = vol + iVar5;
bVar1 = new_vol < iVar4;
} else {
new_vol = vol + iVar5;
if (iVar5 < 1) {
new_vol = vol + 1;
}
bVar1 = iVar4 < new_vol;
}
(snd->params).volume = new_vol;
if (bVar1) {
(snd->params).volume = iVar4;
}
snd->auto_time = iVar6 - time;
} else {
if (iVar4 == -4) {
snd_StopSound(snd->sound_handle);
snd->id = 0;
} else {
(snd->params).volume = iVar4;
}
snd->auto_time = 0;
}
}
void UpdateVolume(SoundInfo* sound) {
auto handle = snd_SoundIsStillPlaying(sound->sound_handle);
sound->sound_handle = handle;
if (handle == 0) {
sound->id = 0;
} else {
if ((s16)(sound->params).mask < 0) {
// idk
snd_SetSoundVolPan(handle, GetVolume(sound), -2);
// FUN_00013d6c(handle, GetVolume(sound), 0x40, 4);
} else {
snd_SetSoundVolPan(handle, GetVolume(sound), -2);
}
}
}
void SetEarTrans(const s32* ear_trans0,
const s32* ear_trans1,
const s32* cam_trans,
const s32* cam_fwd,
const s32* cam_left,
s32 cam_scale,
bool cam_inverted) {
auto tick = snd_GetTick();
auto time = tick - gLastTick;
gLastTick = tick;
WaitSema(g_EarTransSema);
gEarTrans[0] = *ear_trans0;
g_CameraInvert = cam_inverted;
gEarTrans[1] = ear_trans0[1];
gEarTrans[2] = ear_trans0[2];
gEarTrans[3] = *ear_trans1;
gEarTrans[4] = ear_trans1[1];
gEarTrans[5] = ear_trans1[2];
gCamTrans[0] = *cam_trans;
gCamTrans[1] = cam_trans[1];
gCamTrans[2] = cam_trans[2];
gCamForward[0] = *cam_fwd;
gCamForward[1] = cam_fwd[1];
gCamForward[2] = cam_fwd[2];
gCamLeft[0] = *cam_left;
gCamLeft[1] = cam_left[1];
gCamLeft[2] = cam_left[2];
gCamScale = cam_scale;
SignalSema(g_EarTransSema);
for (auto& sound : gSounds) {
if (sound.id) {
if (sound.auto_time) {
UpdateAutoVol(&sound, time);
}
UpdateLocation(&sound);
}
}
auto* cmd = g_aVagCmds;
s32 iVar2 = 5;
do {
if ((cmd->music_flag == 0) && (cmd->maybe_sound_handler != 0)) {
if ((cmd->flags.scanned == 0) || (cmd->flags.bit8 != 0)) {
if (cmd->flags.bit20 == 0) {
if ((u32)cmd->play_volume < 0x11) {
cmd->play_volume = 0;
} else {
cmd->play_volume = cmd->play_volume - 0x10;
}
SetVAGVol(cmd);
if (cmd->play_volume != 0)
goto LAB_0000db94;
}
LAB_0000db78:
StopVagStream(cmd);
} else {
time = snd_SoundIsStillPlaying(cmd->id);
if (time != 0)
goto LAB_0000db88;
if (cmd->flags.bit20 != 0)
goto LAB_0000db78;
// CpuSuspendIntr(local_28);
cmd->flags.bit8 = 1;
// CpuResumeIntr(local_28[0]);
}
} else {
LAB_0000db88:
SetVAGVol(cmd);
}
LAB_0000db94:
iVar2 = iVar2 + -1;
cmd = cmd + 1;
if (iVar2 < 0) {
return;
}
} while (true);
}
void SetCurve(int param_1,
u32 param_2,
u32 param_3,
uint8_t param_4,
uint8_t param_5,
uint8_t param_6,
uint8_t param_7,
uint8_t param_8) {
gCurves[param_1].c = (param_3 - param_2) + -0x1000;
gCurves[param_1].d = 0x1000;
unktable[param_1 + 0xb0] = param_8;
gCurves[param_1].b = param_2 + param_3 * -3;
unktable[param_1] = param_4;
unktable[param_1 + 0x2c] = param_5;
unktable[param_1 + 0x58] = param_6;
unktable[param_1 + 0x84] = param_7;
gCurves[param_1].a = param_3 * 2;
}
void SetPlaybackMode(s32 mode) {
g_nPlaybackMode = mode;
snd_SetPlayBackMode(mode);
}
} // namespace jak3

View file

@ -0,0 +1,50 @@
#pragma once
#include "common/common_types.h"
#include "game/overlord/jak3/rpc_interface.h"
namespace jak3 {
void jak3_overlord_init_globals_ssound();
void InitSound();
extern s32 g_n989Semaphore;
extern bool g_bSoundEnable;
struct SoundInfo {
SoundName name;
s32 id;
s32 sound_handle;
s32 new_volume;
s32 auto_time;
SoundPlayParams params;
};
struct VolumePair {
s16 left;
s16 right;
};
SoundInfo* LookupSound(s32 id);
SoundInfo* AllocateSound();
int GetFalloffCurve(int fo_curve);
s32 GetVolume(SoundInfo* sound);
s32 GetPan(SoundInfo* sound);
void UpdateVolume(SoundInfo* sound);
void KillSoundsInGroup(u32 group);
void SetEarTrans(const s32* ear_trans0,
const s32* ear_trans1,
const s32* cam_trans,
const s32* cam_forward,
const s32* cam_left,
s32 cam_scale,
bool cam_inverted);
void SetPlaybackMode(s32 mode);
void SetCurve(int curve_idx, u32, u32, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);
u32 CalculateFalloffVolume(s32* trans, u32 vol, u32 fo_curve, u32 fo_min, u32 fo_max, u32*, u32*);
s32 CalculateAngle(s32* trans, u32 fo_curve, u32);
extern u32 g_anStreamVoice[6];
extern VolumePair g_aPanTable[361];
extern bool g_CameraInvert;
} // namespace jak3

View file

@ -0,0 +1,282 @@
#include "stream.h"
#include "common/util/Assert.h"
#include "common/util/FileUtil.h"
#include "game/overlord/jak3/iso_api.h"
#include "game/overlord/jak3/iso_cd.h"
#include "game/overlord/jak3/overlord.h"
#include "game/overlord/jak3/rpc_interface.h"
#include "game/overlord/jak3/streamlist.h"
#include "game/overlord/jak3/vag.h"
#include "game/sce/iop.h"
namespace jak3 {
using namespace iop;
constexpr int kStrBufSize = sizeof(RPC_Str_Cmd);
static RPC_Str_Cmd sSTRBuf;
constexpr int kNumPlayCmds = 4;
constexpr int kRpcBuf2Size = sizeof(RPC_Play_Cmd) * kNumPlayCmds;
static RPC_Play_Cmd sRPCBuf2[kNumPlayCmds];
constexpr int SECTOR_TABLE_SIZE = 512;
struct StrFileHeader {
u32 sectors[SECTOR_TABLE_SIZE]; // start of chunk, in sectors. including this sector.
u32 sizes[SECTOR_TABLE_SIZE]; // size of chunk, in bytes. always an integer number of sectors
};
static_assert(sizeof(StrFileHeader) == 0x1000, "Sector header size");
struct CacheEntry {
ISOFileDef* filedef = nullptr;
s32 countdown = 0;
StrFileHeader header;
};
constexpr int STR_INDEX_CACHE_SIZE = 4;
CacheEntry sCache[STR_INDEX_CACHE_SIZE];
void jak3_overlord_init_globals_stream() {}
u32 STRThread() {
sceSifQueueData dq;
sceSifServeData serve;
CpuDisableIntr();
sceSifInitRpc(0);
sceSifSetRpcQueue(&dq, GetThreadId());
sceSifRegisterRpc(&serve, RpcId::STR, RPC_STR, &sSTRBuf, kStrBufSize, nullptr, nullptr, &dq);
CpuEnableIntr();
sceSifRpcLoop(&dq);
return 0;
}
u32 PLAYThread() {
sceSifQueueData dq;
sceSifServeData serve;
CpuDisableIntr();
sceSifInitRpc(0);
sceSifSetRpcQueue(&dq, GetThreadId());
sceSifRegisterRpc(&serve, RpcId::PLAY, RPC_PLAY, &sRPCBuf2, kRpcBuf2Size, nullptr, nullptr, &dq);
CpuEnableIntr();
sceSifRpcLoop(&dq);
return 0;
}
void* RPC_STR(unsigned int, void* msg_in, int size) {
auto* msg = (RPC_Str_Cmd*)msg_in;
ASSERT(size == sizeof(RPC_Str_Cmd));
if (msg->section < 0) {
ovrld_log(LogCategory::STR_RPC, "RPC_STR loading full file {}", msg->basename);
// not a stream file - treat it like a normal load
auto* filedef = get_file_system()->Find(msg->basename);
if (filedef) {
msg->maxlen = LoadISOFileToEE(filedef, msg->address, msg->maxlen);
if (msg->maxlen) {
msg->result = 0;
return msg;
} else {
ovrld_log(LogCategory::WARN, "Failed to LoadISOFileToEE in RPC_STR for {}", msg->basename);
}
} else {
ovrld_log(LogCategory::WARN, "Failed to open {} for RPC STR", msg->basename);
}
} else {
// this is an animation load. Convert name:
ISOName animation_iso_name;
file_util::ISONameFromAnimationName(animation_iso_name.data, msg->basename);
auto* filedef = get_file_system()->FindIN(&animation_iso_name);
ovrld_log(LogCategory::STR_RPC, "STR_RPC for {} chunk {}", msg->basename, msg->section);
if (filedef) {
// found it! See if we've cached this animation's header.
int cache_entry = 0;
int oldest = INT32_MAX;
int oldest_idx = -1;
while (cache_entry < STR_INDEX_CACHE_SIZE && sCache[cache_entry].filedef != filedef) {
sCache[cache_entry].countdown--;
if (sCache[cache_entry].countdown < oldest) {
oldest_idx = cache_entry;
oldest = sCache[cache_entry].countdown;
}
cache_entry++;
}
if (cache_entry == STR_INDEX_CACHE_SIZE) {
// cache miss, we need to load the header to the header cache on the IOP
ovrld_log(LogCategory::STR_RPC,
"STR_RPC header cache miss - loading .str file header now.");
cache_entry = oldest_idx;
sCache[oldest_idx].filedef = filedef;
sCache[oldest_idx].countdown = INT32_MAX - 1;
if (!LoadISOFileToIOP(filedef, (u8*)&sCache[oldest_idx].header, sizeof(StrFileHeader))) {
ovrld_log(LogCategory::WARN, "STR_RPC failed to load .str file header for {}",
msg->basename);
msg->result = 1;
return msg;
}
}
// load data, using the cached header to find the location of the chunk.
if (!LoadISOFileChunkToEE(filedef, msg->address,
sCache[cache_entry].header.sizes[msg->section],
sCache[cache_entry].header.sectors[msg->section])) {
ovrld_log(LogCategory::WARN, "STR_RPC failed to load .str file chunk {} for {}",
msg->section, msg->basename);
msg->result = 1;
} else {
// successful load!
msg->maxlen = sCache[cache_entry].header.sizes[msg->section];
msg->result = 0;
return msg;
}
}
}
msg->result = 1;
return msg;
}
void* RPC_PLAY(unsigned int, void* msg_in, int size) {
static_assert(sizeof(RPC_Play_Cmd) == 256);
if (size <= 0) {
return msg_in;
}
auto* msg_array = (RPC_Play_Cmd*)msg_in;
for (u32 msg_idx = 0; msg_idx < size / sizeof(RPC_Play_Cmd); msg_idx++) {
auto* msg = &msg_array[msg_idx];
// the operation is stashed in the "result" field of the message
switch (msg->result) {
case 1: {
// remove vag streams by name
for (int s = 0; s < 4; s++) {
VagStreamData vsd;
if (msg->names[s].chars[0] != 0) {
// lg::warn("RPC PLAY remove {}", msg->names[s].chars);
strncpy(vsd.name, msg->names[s].chars, 0x30);
vsd.id = msg->id[s];
WaitSema(g_EEStreamsList.sema);
RemoveVagStreamFromList(&vsd, &g_EEStreamsList);
SignalSema(g_EEStreamsList.sema);
WaitSema(g_EEPlayList.sema);
RemoveVagStreamFromList(&vsd, &g_EEPlayList);
SignalSema(g_EEPlayList.sema);
}
}
} break;
case 2: {
// completely redefine the set of vag streams to queue up.
WaitSema(g_EEStreamsList.sema); // lock stream list
EmptyVagStreamList(&g_EEStreamsList); // clear all existing streams
// the first stream has the highest priority.
int priority = 9;
for (int s = 0; s < 4; s++) {
if (msg->names[s].chars[0] && msg->id[s]) {
// lg::warn("RPC PLAY queue {}", msg->names[s].chars);
// set up list entry for this stream
VagStreamData vsd;
strncpy(vsd.name, msg->names[s].chars, 0x30);
vsd.id = msg->id[s];
vsd.art_load = msg->address & 1 << (s & 0x1f) & 0xf;
vsd.movie_art_load = msg->address & 0x10 << (s & 0x1f) & 0xf0;
vsd.sound_handler = 0;
vsd.priority = priority;
// if we have an existing one, make sure it has the appropriate flags
auto* existing_vag = FindThisVagStream(vsd.name, vsd.id);
if (existing_vag) {
existing_vag->art_flag = (u32)(vsd.art_load != 0);
existing_vag->music_flag = 0;
existing_vag->movie_flag = (u32)(vsd.movie_art_load != 0);
if (vsd.art_load != 0) {
existing_vag->flags.art = 1;
}
if (existing_vag->movie_flag != 0) {
existing_vag->flags.movie = 1;
}
}
// add to list
InsertVagStreamInList(&vsd, &g_EEStreamsList);
}
if (priority == 8) {
priority = 2;
} else {
if (0 < priority) {
priority = priority + -1;
}
}
s = s + 1;
}
SignalSema(g_EEStreamsList.sema);
} break;
case 0: {
int priority = 9;
for (int s = 0; s < 4; s++) {
if (msg->names[s].chars[0] && msg->id[s]) {
// lg::warn("RPC PLAY play {}", msg->names[s].chars);
VagStreamData vsd;
strncpy(vsd.name, msg->names[s].chars, 0x30);
vsd.id = msg->id[s];
vsd.volume2 = msg->section;
vsd.group = msg->maxlen;
vsd.plugin_id = 0;
vsd.sound_handler = 0;
vsd.maybe_volume_3 = 0;
vsd.priority = priority;
auto* existing_vag = FindThisVagStream(msg->names[s].chars, vsd.id);
if (existing_vag != (ISO_VAGCommand*)0x0) {
existing_vag->play_volume = vsd.volume2;
existing_vag->play_group = vsd.group;
if (existing_vag->flags.running != 0)
goto LAB_000092a4;
}
WaitSema(g_EEPlayList.sema);
auto* already_playing = FindVagStreamInList(&vsd, &g_EEPlayList);
if (!already_playing) {
already_playing = InsertVagStreamInList(&vsd, &g_EEPlayList);
strncpy(already_playing->name, vsd.name, 0x30);
already_playing->id = vsd.id;
already_playing->priority = vsd.priority;
already_playing->sound_handler = vsd.sound_handler;
already_playing->plugin_id = vsd.plugin_id;
already_playing->unk1 = 0;
already_playing->art_load = 0;
already_playing->movie_art_load = 0;
}
SignalSema(g_EEPlayList.sema);
} else {
// lg::warn("RPC PLAY play (NONE)");
}
LAB_000092a4:
if (priority == 8) {
priority = 2;
} else {
if (0 < priority) {
priority = priority + -1;
}
}
}
} break;
}
}
return msg_in;
}
} // namespace jak3

View file

@ -0,0 +1,13 @@
#pragma once
#include "common/common_types.h"
namespace jak3 {
void jak3_overlord_init_globals_stream();
u32 PLAYThread();
u32 STRThread();
void* RPC_STR(unsigned int fno, void* msg, int size);
void* RPC_PLAY(unsigned int fno, void* msg, int size);
} // namespace jak3

View file

@ -0,0 +1,339 @@
#include "streamlist.h"
#include <cstring>
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "game/overlord/jak3/iso.h"
#include "game/overlord/jak3/vag.h"
#include "game/sce/iop.h"
namespace jak3 {
using namespace iop;
List g_RequestedStreamsList;
List g_NewStreamsList;
List g_EEStreamsList;
List g_EEPlayList;
void jak3_overlord_init_globals_streamlist() {
g_RequestedStreamsList = {};
g_NewStreamsList = {};
g_EEStreamsList = {};
g_EEPlayList = {};
}
void InitVagStreamList(List* list, int size, const char* name) {
strncpy(list->name, name, 8);
InitList(list, size, sizeof(VagStreamData));
auto* iter = list->next;
if (0 < size) {
do {
iter->in_use = 0;
strncpy(iter->name, "free", 0x30);
iter->group = 2;
iter->id = 0;
iter->sound_handler = 0;
iter->priority = 0;
iter->art_load = 0;
iter->movie_art_load = 0;
iter->unk2 = 0;
iter->unk1 = 0;
iter->volume2 = 0;
iter->maybe_volume_3 = 0;
iter = iter + 1;
size = size + -1;
} while (size != 0);
}
ASSERT(list->buffer);
}
VagStreamData* FindVagStreamInList(VagStreamData* stream, List* list) {
int iVar1;
VagStreamData* iter;
u32 max_idx;
u32 idx;
u32 uVar2;
VagStreamData* ret;
VagStreamData* pVVar3;
max_idx = list->count;
iter = list->next;
ret = nullptr;
idx = 0;
if (max_idx != 0) {
do {
uVar2 = idx;
pVVar3 = ret;
if ((iter->id != stream->id) || (iVar1 = strncmp(iter->name, stream->name, 0x30),
uVar2 = max_idx, pVVar3 = iter, iVar1 == 0)) {
ret = pVVar3;
idx = uVar2;
}
idx = idx + 1;
iter = iter->next;
} while (idx < max_idx);
}
return ret;
}
VagStreamData* GetVagStreamInList(u32 idx, List* list) {
VagStreamData* iter = nullptr;
if ((idx < (u32)list->count) && (iter = list->next, idx != 0)) {
do {
idx = idx - 1;
iter = iter->next;
} while (idx != 0);
}
return iter;
}
void EmptyVagStreamList(List* list) {
VagStreamData* elt;
u32 i;
u32 cnt;
cnt = list->count;
elt = (VagStreamData*)list->buffer;
i = 0;
if (cnt != 0) {
do {
i = i + 1;
strncpy(elt->name, "free", 0x30);
elt->group = 2;
elt->id = 0;
elt->sound_handler = 0;
elt->priority = 0;
elt->art_load = 0;
elt->movie_art_load = 0;
elt->unk2 = 0;
elt->unk1 = 0;
elt->volume2 = 0;
elt->maybe_volume_3 = 0;
elt->in_use = 0;
elt = elt + 1;
} while (i < cnt);
}
list->unk_flag = 1;
}
void RemoveVagStreamFromList(VagStreamData* stream, List* list) {
VagStreamData* elt = FindVagStreamInList(stream, list);
if (elt) {
elt->in_use = 0;
strncpy(elt->name, "free", 0x30);
elt->group = 2;
elt->id = 0;
elt->priority = 0;
elt->art_load = 0;
elt->movie_art_load = 0;
elt->unk1 = 0;
elt->volume2 = 0;
elt->maybe_volume_3 = 0;
elt->sound_handler = 0;
list->unk_flag = 1;
elt->unk2 = 0;
}
}
VagStreamData* InsertVagStreamInList(VagStreamData* user_stream, List* list) {
u32 uVar1;
VagStreamData* pVVar10;
VagStreamData* pVVar11;
VagStreamData* pVVar12;
u32 count = list->count;
VagStreamData* free_elt = nullptr;
u32 free_elt_idx = 0;
VagStreamData* iter = list->next;
if (count != 0) {
do {
if (iter->id == 0) {
free_elt = iter;
free_elt_idx = count;
}
free_elt_idx = free_elt_idx + 1;
iter = iter->next;
} while (free_elt_idx < count);
}
if ((free_elt != (VagStreamData*)0x0) &&
(uVar1 = 0, pVVar11 = list->next, pVVar12 = nullptr, count != 0)) {
do {
pVVar10 = pVVar11;
uVar1 = uVar1 + 1;
if (pVVar10->priority < user_stream->priority) {
list->unk_flag = 1;
free_elt->in_use = 1;
strncpy(free_elt->name, user_stream->name, 0x30);
free_elt->id = user_stream->id;
free_elt->plugin_id = user_stream->plugin_id;
free_elt->art_load = user_stream->art_load;
free_elt->movie_art_load = user_stream->movie_art_load;
free_elt->priority = user_stream->priority;
free_elt->sound_handler = user_stream->sound_handler;
free_elt->volume2 = user_stream->volume2;
free_elt->maybe_volume_3 = user_stream->maybe_volume_3;
free_elt->group = user_stream->group;
free_elt->unk1 = 0;
if (pVVar12 == (VagStreamData*)0x0) {
if (free_elt == pVVar10) {
return free_elt;
}
auto* prev = free_elt->next;
auto* next = free_elt->prev;
list->next = free_elt;
prev->prev = next;
pVVar11 = free_elt->prev;
pVVar10->prev = free_elt;
pVVar11->next = prev;
free_elt->prev = nullptr;
free_elt->next = pVVar10;
return free_elt;
}
if (free_elt == pVVar10) {
return free_elt;
}
auto* pVVar13 = free_elt->prev;
pVVar13->next = free_elt->next;
pVVar11 = pVVar12->next;
free_elt->next->prev = pVVar13;
free_elt->next = pVVar11;
pVVar10->prev = free_elt;
pVVar12->next = free_elt;
free_elt->prev = pVVar12;
return free_elt;
}
pVVar11 = pVVar10->next;
pVVar12 = pVVar10;
} while (uVar1 < count);
}
return free_elt;
}
void MergeVagStreamLists(List* list_a, List* list_b) {
VagStreamData* stream;
VagStreamData* pVVar1;
u32 uVar2;
u32 idx;
idx = 0;
uVar2 = 0;
LAB_0000fde8:
do {
stream = GetVagStreamInList(idx, list_a);
idx = idx + 1;
if (stream != (VagStreamData*)0x0) {
if (stream->id == 0)
goto LAB_0000fde8;
pVVar1 = FindVagStreamInList(stream, list_b);
if (pVVar1 == (VagStreamData*)0x0) {
InsertVagStreamInList(stream, list_b);
}
}
uVar2 = uVar2 + 1;
if (3 < uVar2) {
return;
}
} while (true);
}
void QueueNewStreamsFromList(List* list) {
VagStreamData* stream;
ISO_VAGCommand* pIVar1;
u32 uVar2;
u32 idx;
SetVagStreamsNotScanned();
idx = 0;
EmptyVagStreamList(&g_NewStreamsList);
g_NewStreamsList.unk_flag = 0;
uVar2 = 0;
LAB_0000fe94:
do {
stream = GetVagStreamInList(idx, list);
idx = idx + 1;
if (stream == (VagStreamData*)0x0) {
uVar2 = 4;
} else {
if (stream->id == 0)
goto LAB_0000fe94;
pIVar1 = FindThisVagStream(stream->name, stream->id);
if (pIVar1 == (ISO_VAGCommand*)0x0) {
pIVar1 = FindThisVagStream(stream->name, stream->id);
if (pIVar1 == (ISO_VAGCommand*)0x0) {
InsertVagStreamInList(stream, &g_NewStreamsList);
}
} else {
pIVar1->flags.scanned = 1;
if (pIVar1->stereo_sibling != (ISO_VAGCommand*)0x0) {
pIVar1->stereo_sibling->flags.scanned = 1;
}
if (stream->priority != pIVar1->priority_pq) {
SetNewVagCmdPri(pIVar1, stream->priority);
}
}
}
uVar2 = uVar2 + 1;
if (3 < uVar2) {
return;
}
} while (true);
}
void CheckPlayList(List* list) {
int count;
ISO_VAGCommand* cmd;
VagStreamData* iter;
count = list->count;
iter = list->next;
joined_r0x0000ff80:
do {
while (true) {
if (count == 0) {
return;
}
count = count + -1;
if (iter->id != 0)
break;
iter = iter->next;
}
cmd = FindThisVagStream(iter->name, iter->id);
} while (cmd == (ISO_VAGCommand*)0x0);
if (cmd->flags.running == 0)
goto code_r0x0000ffc4;
goto LAB_00010004;
code_r0x0000ffc4:
if (((cmd->flags.saw_chunks1 != 0) || (cmd->flags.file_disappeared != 0)) &&
(cmd->flags.nostart == 0)) {
IsoPlayVagStream(cmd);
LAB_00010004:
RemoveVagStreamFromList(iter, list);
}
goto joined_r0x0000ff80;
}
void StreamListThread() {
if (g_RequestedStreamsList.pending_data == 0) {
WaitSema(g_RequestedStreamsList.sema);
EmptyVagStreamList(&g_RequestedStreamsList);
g_RequestedStreamsList.unk_flag = 0;
// WaitSema(DAT_00015dd0);
// MergeVagStreamLists((List*)&g_PluginStreamsList, &g_RequestedStreamsList);
// SignalSema(DAT_00015dd0);
WaitSema(g_EEStreamsList.sema);
MergeVagStreamLists(&g_EEStreamsList, &g_RequestedStreamsList);
SignalSema(g_EEStreamsList.sema);
g_RequestedStreamsList.pending_data = 1;
SignalSema(g_RequestedStreamsList.sema);
WaitSema(g_EEPlayList.sema);
CheckPlayList(&g_EEPlayList);
SignalSema(g_EEPlayList.sema);
// WaitSema(DAT_0001e31c);
// CheckLfoList(&g_LfoStreamsList);
// SignalSema(DAT_0001e31c);
}
}
} // namespace jak3

View file

@ -0,0 +1,22 @@
#pragma once
#include "game/overlord/jak3/list.h"
namespace jak3 {
void jak3_overlord_init_globals_streamlist();
struct ISO_VAGCommand;
extern List g_RequestedStreamsList;
extern List g_NewStreamsList;
extern List g_EEStreamsList;
extern List g_EEPlayList;
void QueueNewStreamsFromList(List* list);
void RemoveVagStreamFromList(VagStreamData* entry, List* list);
void EmptyVagStreamList(List* list);
VagStreamData* InsertVagStreamInList(VagStreamData* entry, List* list);
VagStreamData* FindVagStreamInList(VagStreamData* entry, List* list);
void InitVagStreamList(List* list, int size, const char* name);
void StreamListThread();
} // namespace jak3

View file

@ -0,0 +1,6 @@
- pan stuff is wrong - it's now using a table from 989snd
- Check ssound.cpp for most of these issues, and bottom of vag.cpp
- in some cases, we're hitting different playback modes? see comment dolby crap, we hit this when going outside freedom hq
- changing file size
- UpdateVolume
- goal src update

1042
game/overlord/jak3/vag.cpp Normal file

File diff suppressed because it is too large Load diff

173
game/overlord/jak3/vag.h Normal file
View file

@ -0,0 +1,173 @@
#pragma once
#include "game/overlord/jak3/isocommon.h"
namespace jak3 {
void jak3_overlord_init_globals_vag();
extern bool g_bExtPause;
extern bool g_bExtResume;
struct ISO_VAGCommand : ISO_Hdr {
ISOFileDef* vag_file_def = nullptr; // 44 (actually INT file def?)
VagDirEntry* vag_dir_entry = nullptr; // 48
ISO_VAGCommand* stereo_sibling = nullptr; // 52
// pointer to IOP memory to DMA to SPU. Points to the data for the next new transfer.
const u8* dma_iop_mem_ptr = nullptr; // 56
// the DMA channel to upload to for sceCdVoiceTrans
int dma_chan = 0; // 60
// if not set, a pending dma interrupt will want to modify this command.
int safe_to_modify_dma = 0; // 64
u32 current_spu_address = 0; // 68
char name[48]; // 72
char overflow[16];
// SPU address of the next chunk to fill
// for stereo mode, there's a 0x2000 offset between the left and right audio. This stream_sram
// doesn't include that offset.
u32 stream_sram; // 124
u32 trap_sram; // 138
// spu voice for playback
int voice; // 132
int info_idx; // 136
int maybe_sound_handler = 0; // 140
void* lfo_callback = nullptr; // 144
int oog = 0; // 180
int dolby_pan_angle = 0; // 184
int clocka = 0; // 188
int clockb = 0; // 192
int clockc = 0; // 196
int clockd = 0; // 200
int unk_gvsp_len; // 204
int position_for_ee; // 208
int unk_gvsp_cntr; // 212
struct {
u8 bit0 = 0; // 216
u8 saw_chunks1 = 0; // 217
// will start the voice, but with a pitch of 0.
u8 paused = 0; // 218
u8 bit3 = 0; // 219
u8 running = 0; // 220
u8 clocks_set = 0; // 221, set by SetVagClock if it succeeds.
u8 file_disappeared = 0; // 222, set if SetVagClock notices that bBaseFile is gone!
u8 scanned = 0; // 223, set shortly after internal command is created.
u8 bit8 = 0;
u8 stop = 0; // 225, set if this is a non-plugin stream, and was stopped by StopVagStream.
u8 art = 0; // 226, set if this has art_flag set
// set if we are the non-main stereo command.
u8 stereo_secondary = 0; // 227
u8 bit12 = 0; // 228
u8 bit13 = 0; // 229
u8 bit14 = 0; // 230
u8 bit15 = 0; // 231
u8 bit16 = 0; // 232
u8 bit17 = 0; // 233
// set if SPU DMA has completed for an even or odd number of chunks of non-stereo audio.
u8 dma_complete_even_chunk_count = 0; // 234
u8 dma_complete_odd_chunk_count = 0; // 235
u8 bit20 = 0;
u8 bit21 = 0;
u8 bit22 = 0;
u8 nostart = 0;
u8 movie = 0;
u8 bit25 = 0;
} flags;
u32 pack_flags();
void set_all_flags_zero();
int unk_gvsp_state2 = 0; // 244
int num_isobuffered_chunks = 0; // 248
// if we need to do a second SPU DMA for stereo's second channel, the size of that transfer
int xfer_size; // 252
int vag_file_rate; // 256
int pitch1; // 260 pitch to use for playback, possibly overwritten
int pitch1_file; // 264 pitch to use for playback, from the file itself (sample rate)
int pitch_cmd; // 268 pitch mod command (?)
int error; // 272
int unk_spu_mem_offset; // 276
int unk_gvsp_flag; // 280
int play_volume = 0; // 284
int id = 0; // 288
int plugin_id = 0; // 292
int priority_pq = 0; // 296
int art_flag = 0; // 300
int music_flag = 0; // 304
int updated_trans = 0; // 308
int trans[3]; // 312
int fo_min; // 324
int fo_max; // 328
int fo_curve; // 332
int play_group = 0; // 336
int movie_flag = 0; // 340
};
struct VagStreamData {
VagStreamData* next = nullptr;
VagStreamData* prev = nullptr;
int in_use;
char name[0x30];
int id;
int plugin_id;
int sound_handler;
int art_load;
int movie_art_load;
int priority;
int unk2;
int unk1;
int volume2;
int maybe_volume_3;
int group;
};
extern ISO_VAGCommand g_aVagCmds[6];
extern int g_anMasterVolume[32];
extern bool voice_key_flags[0x30];
extern u32 voice_key_times[0x30];
extern u32 g_nTimeOfLastVoiceKey;
extern bool g_bRecentlyKeyedVoice;
int CalculateVAGPitch(int a, int b);
void BlockUntilVoiceSafe(int, u32);
void BlockUntilAllVoicesSafe();
void CheckVagStreamsProgress();
void MarkVoiceKeyedOnOff(int voice, u32 systime);
void UnPauseVAG(ISO_VAGCommand* cmd);
ISO_VAGCommand* FindMusicStreamName(const char* name);
ISO_VAGCommand* SmartAllocMusicVagCommand(const ISO_VAGCommand* user_command, int flag);
void InitVAGCmd(ISO_VAGCommand* cmd, int paused);
int HowManyBelowThisPriority(int pri);
ISO_VAGCommand* FindThisVagStream(const char* name, int id);
ISO_VAGCommand* SmartAllocVagCmd(ISO_VAGCommand* user_command);
void RemoveVagCmd(ISO_VAGCommand* cmd);
void SetNewVagCmdPri(ISO_VAGCommand* cmd, int pri);
void TerminateVAG(ISO_VAGCommand* cmd);
ISO_VAGCommand* FindThisMusicStream(const char* name, int id);
ISO_VAGCommand* FindVagStreamName(const char* name);
int AnyVagRunning();
void PauseVAG(ISO_VAGCommand* cmd);
void InitVagCmds();
void PauseVagStreams(bool music);
void UnPauseVagStreams(bool music);
void SetVagStreamsNoStart(int value);
ISO_VAGCommand* FindVagStreamId(int);
void SetVAGVol(ISO_VAGCommand* cmd);
void SetAllVagsVol(int);
void PauseVAGStreams();
ISO_VAGCommand* FindNotQueuedVagCmd();
void CalculateVAGVolumes(ISO_VAGCommand* cmd, int* l, int* r);
void SetVagStreamsNotScanned();
void VAG_MarkLoopEnd(uint8_t* data, int offset);
void VAG_MarkLoopStart(uint8_t* data);
void RestartVag(ISO_VAGCommand* cmd, int p);
extern u32 g_nPlaybackMode;
} // namespace jak3

View file

@ -0,0 +1,201 @@
#include "vblank_handler.h"
#include <cstring>
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "game/overlord/jak3/dma.h"
#include "game/overlord/jak3/iso.h"
#include "game/overlord/jak3/sbank.h"
#include "game/overlord/jak3/srpc.h"
#include "game/overlord/jak3/ssound.h"
#include "game/overlord/jak3/streamlist.h"
#include "game/overlord/jak3/vag.h"
#include "game/sce/iop.h"
namespace jak3 {
using namespace iop;
u32 g_nInfoEE = 0;
SoundIOPInfo g_SRPCSoundIOPInfo;
bool g_bVBlankInitialized = false;
s32 g_nVBlankThreadID = -1;
s32 g_nVBlankSemaphoreID = -1;
bool g_bVBlankRegistered = false;
u32 g_nIopTicks = 0;
u32 g_nFrameNum = 0;
void jak3_overlord_init_globals_vblank_handler() {
g_nInfoEE = 0;
g_SRPCSoundIOPInfo = {};
g_bVBlankInitialized = false;
g_nVBlankThreadID = -1;
g_nVBlankSemaphoreID = -1;
g_bVBlankRegistered = false;
g_nIopTicks = 0;
g_nFrameNum = 0;
}
int VBlankHandler(void*);
u32 VBlankThread();
void VBlank_Initialize() {
ThreadParam thread_param;
SemaParam sema_param;
if (g_bVBlankInitialized == 0) {
thread_param.attr = 0x2000000;
thread_param.stackSize = 0x800;
thread_param.initPriority = 0x34;
thread_param.option = 0;
thread_param.entry = VBlankThread;
strcpy(thread_param.name, "vblank");
g_nVBlankThreadID = CreateThread(&thread_param);
ASSERT(g_nVBlankThreadID >= 0);
sema_param.max_count = 200; // hack
sema_param.attr = 0;
sema_param.init_count = 0;
sema_param.option = 0;
g_nVBlankSemaphoreID = CreateSema(&sema_param);
ASSERT(g_nVBlankSemaphoreID >= 0);
int ret = StartThread(g_nVBlankThreadID, 0);
ASSERT(ret == 0);
RegisterVblankHandler(0, 0x40, VBlankHandler, 0);
g_bVBlankInitialized = true;
g_bVBlankRegistered = true;
}
}
int VBlankHandler(void*) {
if ((g_bVBlankInitialized != 0) && (-1 < g_nVBlankSemaphoreID)) {
SignalSema(g_nVBlankSemaphoreID); // was iSignalSema
}
return 1;
}
u32 VBlankThread() {
// char *pcVar1;
// int iVar2;
// uint uVar3;
// SoundBankInfo *pSVar4;
// ISO_VAGCommand *cmd;
// SoundBankInfo **ppSVar5;
// uint *puVar6;
// uint uVar7;
// int iVar8;
// int iVar9;
// SoundIOPInfo *local_30;
// void *local_2c;
// undefined4 local_28;
// undefined4 local_24;
// undefined4 local_20 [2];
do {
while ((g_bVBlankInitialized == 0 || (g_nVBlankSemaphoreID < 0))) {
DelayThread(1000000);
}
WaitSema(g_nVBlankSemaphoreID);
g_nIopTicks = g_nIopTicks + 1;
if (g_bSoundEnable != 0) {
CheckVagStreamsProgress();
if ((g_nIopTicks & 1U) != 0) {
StreamListThread();
}
if (g_nMusicFadeDir < 0) {
g_nMusicFade = g_nMusicFade + -0x200;
if (g_nMusicFade < 0) {
g_nMusicFade = 0;
LAB_00011f60:
g_nMusicFadeDir = 0;
}
} else {
if ((0 < g_nMusicFadeDir) &&
(g_nMusicFade = g_nMusicFade + 0x400, 0x10000 < g_nMusicFade)) {
g_nMusicFade = 0x10000;
goto LAB_00011f60;
}
}
if (g_nInfoEE) {
g_nFrameNum = g_nFrameNum + 1;
// puVar6 = g_SRPCSoundIOPInfo.stream_status;
for (int i = 0; i < 4; i++) {
auto* cmd = &g_aVagCmds[i];
u32 stream_status = cmd->pack_flags();
if ((cmd->flags.file_disappeared != 0) && (cmd->flags.paused == 0)) {
auto uVar3 = CalculateVAGPitch(0x400, cmd->pitch_cmd);
if (g_nFPS == 0) {
ASSERT_NOT_REACHED();
}
cmd->clockd = cmd->clockd + uVar3 / g_nFPS;
}
if ((cmd->flags.saw_chunks1 == 0) && (cmd->flags.clocks_set != 0)) {
g_SRPCSoundIOPInfo.stream_status[i] = stream_status;
g_SRPCSoundIOPInfo.stream_id[i] = cmd->id;
g_SRPCSoundIOPInfo.stream_position[i] = 0;
} else {
g_SRPCSoundIOPInfo.stream_status[i] = stream_status;
g_SRPCSoundIOPInfo.stream_id[i] = cmd->id;
g_SRPCSoundIOPInfo.stream_position[i] = cmd->position_for_ee;
}
}
// CpuSuspendIntr(local_20);
// CpuResumeIntr(local_20[0]);
g_SRPCSoundIOPInfo.iop_ticks = g_nIopTicks;
g_SRPCSoundIOPInfo.freemem = 12345; // hack
g_SRPCSoundIOPInfo.frame = g_nFrameNum;
g_SRPCSoundIOPInfo.freemem2 = QueryTotalFreeMemSize();
g_SRPCSoundIOPInfo.nocd = 0; // hack
g_SRPCSoundIOPInfo.dirtycd = 0; // hack
g_SRPCSoundIOPInfo.dupseg = -1;
g_SRPCSoundIOPInfo.diskspeed[0] = 0;
g_SRPCSoundIOPInfo.diskspeed[1] = 0;
g_SRPCSoundIOPInfo.lastspeed = 0;
memset(&g_SRPCSoundIOPInfo.sound_bank0[0], 0, 8 * 16);
if (gBanks[0]->in_use && gBanks[0]->loaded) {
strcpy(g_SRPCSoundIOPInfo.sound_bank0, gBanks[0]->m_name1);
}
if (gBanks[1]->in_use && gBanks[1]->loaded) {
strcpy(g_SRPCSoundIOPInfo.sound_bank1, gBanks[1]->m_name1);
}
if (gBanks[2]->in_use && gBanks[2]->loaded) {
strcpy(g_SRPCSoundIOPInfo.sound_bank2, gBanks[2]->m_name1);
}
if (gBanks[3]->in_use && gBanks[3]->loaded) {
strcpy(g_SRPCSoundIOPInfo.sound_bank3, gBanks[3]->m_name1);
}
if (gBanks[4]->in_use && gBanks[4]->loaded) {
strcpy(g_SRPCSoundIOPInfo.sound_bank4, gBanks[4]->m_name1);
}
if (gBanks[5]->in_use && gBanks[5]->loaded) {
strcpy(g_SRPCSoundIOPInfo.sound_bank5, gBanks[5]->m_name1);
}
if (gBanks[6]->in_use && gBanks[6]->loaded) {
strcpy(g_SRPCSoundIOPInfo.sound_bank6, gBanks[6]->m_name1);
}
if (gBanks[7]->in_use && gBanks[7]->loaded) {
strcpy(g_SRPCSoundIOPInfo.sound_bank7, gBanks[7]->m_name1);
}
for (int i = 0; i < 48; i++) {
g_SRPCSoundIOPInfo.chinfo[i] = (snd_GetVoiceStatus(i) != 1) - 1;
}
sceSifDmaData dma;
dma.data = &g_SRPCSoundIOPInfo;
dma.addr = (void*)(u64)g_nInfoEE;
dma.size = sizeof(g_SRPCSoundIOPInfo);
static_assert(sizeof(g_SRPCSoundIOPInfo) == 0x2d0);
dma.mode = 0;
/*dmaid =*/sceSifSetDma(&dma, 1);
}
}
RunDeferredVoiceTrans();
// Poll(&g_DvdDriver);
} while (true);
}
} // namespace jak3

View file

@ -0,0 +1,12 @@
#pragma once
#include "common/common_types.h"
#include "game/overlord/jak3/rpc_interface.h"
namespace jak3 {
void jak3_overlord_init_globals_vblank_handler();
void VBlank_Initialize();
extern u32 g_nInfoEE;
extern SoundIOPInfo g_SRPCSoundIOPInfo;
} // namespace jak3

View file

@ -78,6 +78,8 @@
#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"
@ -269,33 +271,36 @@ void iop_runner(SystemThreadInterface& iface, GameVersion version) {
iop::LIBRARY_register(&iop);
Gfx::register_vsync_callback([&iop]() { iop.kernel.signal_vblank(); });
jak1::dma_init_globals();
jak2::dma_init_globals();
if (version != GameVersion::Jak3) {
jak1::dma_init_globals();
jak2::dma_init_globals();
iso_init_globals();
jak1::iso_init_globals();
jak2::iso_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();
fake_iso_init_globals();
jak1::fake_iso_init_globals();
jak2::iso_cd_init_globals();
jak1::iso_queue_init_globals();
jak2::iso_queue_init_globals();
jak1::iso_queue_init_globals();
jak2::iso_queue_init_globals();
jak2::spusstreams_init_globals();
jak1::ramdisk_init_globals();
sbank_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();
// 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();
}
jak1::stream_init_globals();
jak2::stream_init_globals();
prof().end_event();
iface.initialization_complete();
@ -323,9 +328,11 @@ void iop_runner(SystemThreadInterface& iface, GameVersion version) {
jak1::start_overlord_wrapper(iop.overlord_argc, iop.overlord_argv, &complete);
break;
case GameVersion::Jak2:
case GameVersion::Jak3: // TODO: jak3 using jak2's overlord.
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();
}
@ -334,7 +341,6 @@ void iop_runner(SystemThreadInterface& iface, GameVersion version) {
{
auto p = scoped_prof("overlord-wait-for-init");
while (complete == false) {
prof().root_event();
iop.kernel.dispatch();
}
}
@ -344,7 +350,7 @@ void iop_runner(SystemThreadInterface& iface, GameVersion version) {
// IOP Kernel loop
while (!iface.get_want_exit() && !iop.want_exit) {
prof().root_event();
// 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();

View file

@ -174,6 +174,10 @@ void DelayThread(u32 usec) {
iop->kernel.DelayThread(usec);
}
void YieldThread() {
iop->kernel.YieldThread();
}
int sceCdBreak() {
return 1;
}
@ -199,16 +203,20 @@ s32 PollMbx(MsgPacket** recvmsg, int mbxid) {
return iop->kernel.PollMbx((void**)recvmsg, mbxid);
}
s32 ReceiveMbx(MsgPacket** recvmsg, int mbxid) {
return iop->kernel.ReceiveMbx((void**)recvmsg, mbxid);
}
s32 PeekMbx(s32 mbx) {
return iop->kernel.PeekMbx(mbx);
}
static int now = 0;
s32 MbxSize(s32 mbx) {
return iop->kernel.MbxSize(mbx);
}
void GetSystemTime(SysClock* time) {
time->lo = 0;
time->hi = now;
now += 10;
u32 GetSystemTimeLow() {
return iop->kernel.GetSystemTimeLow();
}
void SleepThread() {
@ -216,7 +224,7 @@ void SleepThread() {
}
s32 CreateSema(SemaParam* param) {
return iop->kernel.CreateSema(param->attr, param->option, param->max_count, param->init_count);
return iop->kernel.CreateSema(param->attr, param->option, param->init_count, param->max_count);
}
s32 WaitSema(s32 sema) {
@ -231,6 +239,22 @@ s32 PollSema(s32 sema) {
return iop->kernel.PollSema(sema);
}
s32 CreateEventFlag(const EventFlagParam* param) {
return iop->kernel.CreateEventFlag(param->attr, param->option, param->init_pattern);
}
s32 ClearEventFlag(s32 flag, u32 pattern) {
return iop->kernel.ClearEventFlag(flag, pattern);
}
s32 WaitEventFlag(s32 flag, u32 pattern, u32 mode) {
return iop->kernel.WaitEventFlag(flag, pattern, mode);
}
s32 SetEventFlag(s32 flag, u32 pattern) {
return iop->kernel.SetEventFlag(flag, pattern);
}
s32 WakeupThread(s32 thid) {
iop->kernel.WakeupThread(thid);
return 0;

View file

@ -30,6 +30,10 @@
#define SA_THFIFO 0
#define SA_THPRI 1
#define EW_AND 0
#define EW_OR 1
#define EW_CLEAR 0x10
class IOP;
namespace iop {
@ -56,7 +60,7 @@ struct sceCdRMode {
};
struct sceSifDmaData {
void* data;
const void* data;
void* addr;
unsigned int size;
unsigned int mode;
@ -85,7 +89,7 @@ struct ThreadParam {
int initPriority;
// added!
char name[64];
char name[64] = "";
};
struct SemaParam {
@ -95,6 +99,12 @@ struct SemaParam {
int32_t max_count;
};
struct EventFlagParam {
u32 attr;
u32 option;
u32 init_pattern;
};
// void PS2_RegisterIOP(IOP *iop);
int QueryTotalFreeMemSize();
void* AllocSysMemory(int type, unsigned long size, void* addr);
@ -105,6 +115,7 @@ void CpuDisableIntr();
void CpuEnableIntr();
void SleepThread();
void DelayThread(u32 usec);
void YieldThread();
s32 CreateThread(ThreadParam* param);
s32 ExitThread();
s32 StartThread(s32 thid, u32 arg);
@ -135,16 +146,23 @@ u32 sceSifSetDma(sceSifDmaData* sdd, int len);
s32 SendMbx(int mbxid, void* sendmsg);
s32 PollMbx(MsgPacket** recvmsg, int mbxid);
s32 ReceiveMbx(MsgPacket** recvmsg, int mbxid);
s32 PeekMbx(s32 mbx);
s32 MbxSize(s32 mbx);
s32 CreateMbx(MbxParam* param);
void GetSystemTime(SysClock* time);
u32 GetSystemTimeLow();
s32 CreateSema(SemaParam* param);
s32 WaitSema(s32 sema);
s32 SignalSema(s32 sema);
s32 PollSema(s32 sema);
s32 CreateEventFlag(const EventFlagParam* param);
s32 ClearEventFlag(s32 flag, u32 pattern);
s32 SetEventFlag(s32 flag, u32 pattern);
s32 WaitEventFlag(s32 flag, u32 pattern, u32 mode);
s32 RegisterVblankHandler(int edge, int priority, int (*handler)(void*), void* userdata);
void FlushDcache();

View file

@ -15,8 +15,9 @@ BlockSoundHandler::BlockSoundHandler(SoundBank& bank,
VoiceManager& vm,
s32 sfx_vol,
s32 sfx_pan,
SndPlayParams& params)
: m_group(sfx.VolGroup), m_sfx(sfx), m_vm(vm), m_bank(bank) {
SndPlayParams& params,
u32 sound_id)
: m_group(sfx.VolGroup), m_sfx(sfx), m_vm(vm), m_bank(bank), m_sound_id(sound_id) {
s32 vol, pan, pitch_mod, pitch_bend;
if (sfx_vol == -1) {
sfx_vol = sfx.Vol;

View file

@ -25,7 +25,8 @@ class BlockSoundHandler : public SoundHandler {
VoiceManager& vm,
s32 sfx_vol,
s32 sfx_pan,
SndPlayParams& params);
SndPlayParams& params,
u32 sound_id);
~BlockSoundHandler() override;
bool Tick() override;
@ -39,6 +40,7 @@ class BlockSoundHandler : public SoundHandler {
void SetPMod(s32 mod) override;
void SetRegister(u8 reg, u8 value) override { m_registers.at(reg) = value; };
void SetPBend(s32 bend) override;
u32 SoundID() const override { return m_sound_id; }
void DoGrain();
@ -86,5 +88,7 @@ class BlockSoundHandler : public SoundHandler {
s32 m_countdown{0};
u32 m_next_grain{0};
u32 m_sound_id{0};
};
} // namespace snd

View file

@ -193,6 +193,14 @@ void Player::StopSound(u32 sound_id) {
// m_handlers.erase(sound_id);
}
u32 Player::GetSoundID(u32 sound_handle) {
std::scoped_lock lock(mTickLock);
auto handler = mHandlers.find(sound_handle);
if (handler == mHandlers.end())
return -1;
return handler->second->SoundID();
}
void Player::SetSoundReg(u32 sound_id, u8 reg, u8 value) {
std::scoped_lock lock(mTickLock);
if (mHandlers.find(sound_id) == mHandlers.end()) {

View file

@ -48,6 +48,7 @@ class Player {
void SetMasterVolume(u32 group, s32 volume);
void UnloadBank(BankHandle bank_handle);
void StopSound(u32 sound_handle);
u32 GetSoundID(u32 sound_handle);
void SetPanTable(VolPair* pantable);
void SetPlaybackMode(s32 mode);
void PauseSound(s32 sound_handle);

View file

@ -18,7 +18,7 @@ std::optional<std::unique_ptr<SoundHandler>> SFXBlock::MakeHandler(VoiceManager&
return std::nullopt;
}
auto handler = std::make_unique<BlockSoundHandler>(*this, SFX, vm, vol, pan, params);
auto handler = std::make_unique<BlockSoundHandler>(*this, SFX, vm, vol, pan, params, sound_id);
return handler;
}

View file

@ -24,5 +24,6 @@ class SoundHandler {
virtual void SetPMod(s32 mod) = 0;
virtual void SetPBend(s32 /*mod*/){};
virtual void SetRegister(u8 /*reg*/, u8 /*value*/) {}
virtual u32 SoundID() const { return -1; }
};
} // namespace snd

View file

@ -9,7 +9,7 @@
#include "fmt/core.h"
std::shared_ptr<snd::Voice> voices[4];
std::shared_ptr<snd::Voice> voices[kNVoices];
u8 spu_memory[0x15160 * 10];
static sceSdTransIntrHandler trans_handler[2] = {nullptr, nullptr};
@ -21,7 +21,7 @@ u32 sceSdGetSwitch(u32 entry) {
}
snd::Voice* voice_from_entry(u32 entry) {
u32 it = entry & 3;
u32 it = entry % kNVoices;
return voices[it].get();
}
@ -66,7 +66,6 @@ void sceSdSetSwitch(u32 entry, u32 value) {
void sceSdSetAddr(u32 entry, u32 value) {
[[maybe_unused]] u32 core = entry & 1;
[[maybe_unused]] u32 voice_id = (entry >> 1) & 0x1f;
auto* voice = voice_from_entry(voice_id);
if (!voice) {
return;
@ -123,10 +122,10 @@ void sceSdSetTransIntrHandler(s32 channel, sceSdTransIntrHandler handler, void*
userdata[channel] = data;
}
u32 sceSdVoiceTrans(s32 channel, s32 mode, void* iop_addr, u32 spu_addr, u32 size) {
u32 sceSdVoiceTrans(s32 channel, s32 mode, const void* iop_addr, u32 spu_addr, u32 size) {
memcpy(&spu_memory[spu_addr], iop_addr, size);
if (trans_handler[channel] != nullptr) {
trans_handler[channel](channel, userdata);
trans_handler[channel](channel, userdata[channel]);
}
return size;
}

View file

@ -19,7 +19,8 @@
#define SD_S_KOFF (0x16 << 8)
#define SD_VOICE(_core, _v) ((_core) | ((_v) << 1))
extern std::shared_ptr<snd::Voice> voices[4];
constexpr int kNVoices = 8;
extern std::shared_ptr<snd::Voice> voices[kNVoices];
extern u8 spu_memory[0x15160 * 10];
using sceSdTransIntrHandler = int (*)(int, void*);
@ -30,4 +31,4 @@ void sceSdSetSwitch(u32 entry, u32 value);
void sceSdSetAddr(u32 entry, u32 value);
void sceSdSetParam(u32 entry, u32 value);
void sceSdSetTransIntrHandler(s32 channel, sceSdTransIntrHandler, void* data);
u32 sceSdVoiceTrans(s32 channel, s32 mode, void* iop_addr, u32 spu_addr, u32 size);
u32 sceSdVoiceTrans(s32 channel, s32 mode, const void* iop_addr, u32 spu_addr, u32 size);

View file

@ -105,6 +105,14 @@ void snd_StopSound(s32 sound_handle) {
}
}
u32 snd_GetSoundID(s32 sound_handle) {
if (player) {
return player->GetSoundID(sound_handle);
} else {
return -1;
}
}
void snd_SetSoundVolPan(s32 sound_handle, s32 vol, s32 pan) {
if (player) {
player->SetSoundVolPan(sound_handle, vol, pan);
@ -220,6 +228,26 @@ snd::BankHandle snd_BankLoadEx(const char* filename,
}
}
namespace {
bool started = false;
std::vector<u8> sbk_data;
} // namespace
void snd_BankLoadFromIOPPartialEx_Start() {
started = true;
sbk_data.clear();
}
void snd_BankLoadFromIOPPartialEx(const u8* data, u32 length, u32 spu_mem_loc, u32 spu_mem_size) {
sbk_data.insert(sbk_data.end(), data, data + length);
}
void snd_BankLoadFromIOPPartialEx_Completion() {
ASSERT(started);
started = false;
player->LoadBank(std::span(sbk_data));
sbk_data.clear();
}
s32 snd_GetVoiceStatus(s32 voice) {
// hacky thincg to say that voice 0 is uses allocated
if (voice == 0) {

View file

@ -35,6 +35,7 @@ void snd_SetPanTable(s16* table);
void snd_SetPlayBackMode(s32 mode);
s32 snd_SoundIsStillPlaying(s32 sound_handle);
void snd_StopSound(s32 sound_handle);
u32 snd_GetSoundID(s32 sound_handle);
void snd_SetSoundVolPan(s32 sound_handle, s32 vol, s32 pan);
void snd_SetMasterVolume(s32 which, s32 volume);
void snd_UnloadBank(snd::BankHandle bank_handle);
@ -69,6 +70,11 @@ snd::BankHandle snd_BankLoadEx(const char* filepath,
s32 data_offset,
u32 spu_mem_loc,
u32 spu_mem_size);
void snd_BankLoadFromIOPPartialEx_Start();
void snd_BankLoadFromIOPPartialEx(const u8* data, u32 length, u32 spu_mem_loc, u32 spu_mem_size);
void snd_BankLoadFromIOPPartialEx_Completion();
s32 snd_GetVoiceStatus(s32 voice);
s32 snd_GetFreeSPUDMA();
void snd_FreeSPUDMA(s32 channel);

View file

@ -2,6 +2,7 @@
#include <cstring>
#include "common/global_profiler/GlobalProfiler.h"
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "common/util/FileUtil.h"
@ -27,12 +28,27 @@ void IopThread::functionWrapper() {
}
}
IOP_Kernel::IOP_Kernel() {
// this ugly hack
threads.reserve(16);
CreateThread("null-thread", nullptr, 0);
CreateMbx();
CreateSema(0, 0, 0, 0);
kernel_thread = co_active();
m_start_time = time_point_cast<microseconds>(steady_clock::now());
}
/*
** -----------------------------------------------------------------------------
** Functions callable by threads
** -----------------------------------------------------------------------------
*/
u32 IOP_Kernel::GetSystemTimeLow() {
auto delta_time = time_point_cast<microseconds>(steady_clock::now()) - m_start_time;
return delta_time.count() * 36.864;
}
/*!
* Create a new thread. Will not run the thread.
*/
@ -89,6 +105,12 @@ void IOP_Kernel::SleepThread() {
leaveThread();
}
void IOP_Kernel::YieldThread() {
ASSERT(_currentThread);
_currentThread->state = IopThread::State::Ready;
leaveThread();
}
/*!
* Wake up a thread. Doesn't run it immediately though.
*/
@ -118,6 +140,105 @@ s32 IOP_Kernel::WaitSema(s32 id) {
return KE_OK;
}
s32 IOP_Kernel::ClearEventFlag(s32 id, u32 pattern) {
auto& ef = event_flags.at(id);
// yes, this seems backward, but the manual says this is how it works.
ef.value &= pattern;
return 0;
}
namespace {
bool event_flag_check(u32 pattern, u32 check_pattern, u32 mode) {
if (mode & 1) {
// or
return (pattern & check_pattern);
} else {
// and
return (pattern & check_pattern) == check_pattern;
}
}
} // namespace
s32 IOP_Kernel::WaitEventFlag(s32 flag, u32 pattern, u32 mode) {
auto& ef = event_flags.at(flag);
// check to see if we already match
if (event_flag_check(ef.value, pattern, mode)) {
if (mode & 0x10) {
ef.value = 0;
}
return KE_OK;
} else {
if (!ef.multiple_waiters_allowed && !ef.wait_list.empty()) {
lg::die("Multiple thread trying to wait on an event flag, but this option was not enabled.");
}
auto& wait_entry = ef.wait_list.emplace_back();
wait_entry.pattern = pattern;
wait_entry.mode = mode;
wait_entry.thread = _currentThread;
_currentThread->state = IopThread::State::Wait;
_currentThread->waitType = IopThread::Wait::EventFlag;
leaveThread();
return KE_OK;
}
}
s32 IOP_Kernel::SetEventFlag(s32 flag, u32 pattern) {
auto& ef = event_flags.at(flag);
ef.value |= pattern;
for (auto it = ef.wait_list.begin(); it != ef.wait_list.end();) {
if (event_flag_check(ef.value, it->pattern, it->mode)) {
if (it->mode & 0x10) {
ef.value = 0;
}
it->thread->waitType = IopThread::Wait::None;
it->thread->state = IopThread::State::Ready;
it = ef.wait_list.erase(it);
} else {
++it;
}
}
return KE_OK;
}
s32 IOP_Kernel::ReceiveMbx(void** msg, s32 id) {
auto& box = mbxs.at(id);
if (!box.messages.empty()) {
auto ret = PollMbx(msg, id);
ASSERT(ret == KE_OK);
return KE_OK;
}
ASSERT(!box.wait_thread); // don't know how to deal with this, hopefully doesn't come up.
box.wait_thread = _currentThread;
_currentThread->state = IopThread::State::Wait;
_currentThread->waitType = IopThread::Wait::Messagebox;
leaveThread();
auto ret = PollMbx(msg, id);
ASSERT(ret == KE_OK);
return KE_OK;
}
s32 IOP_Kernel::SendMbx(s32 mbx, void* value) {
ASSERT(mbx < (s32)mbxs.size());
auto& box = mbxs[mbx];
box.messages.push(value);
auto* to_run = box.wait_thread;
if (to_run) {
box.wait_thread = nullptr;
to_run->waitType = IopThread::Wait::None;
to_run->state = IopThread::State::Ready;
}
return 0;
}
s32 IOP_Kernel::SignalSema(s32 id) {
auto& sema = semas.at(id);
@ -265,6 +386,9 @@ std::optional<time_stamp> IOP_Kernel::dispatch() {
// Run until all threads are idle
IopThread* next = schedNext();
if (next) {
prof().root_event();
}
while (next != nullptr) {
// Check vblank interrupt
if (vblank_handler != nullptr && vblank_recieved) {
@ -272,6 +396,7 @@ std::optional<time_stamp> IOP_Kernel::dispatch() {
vblank_recieved = false;
}
// printf("[IOP Kernel] Dispatch %s (%d)\n", next->name.c_str(), next->thID);
auto p = scoped_prof(next->name.c_str());
runThread(next);
updateDelay();
processWakeups();

View file

@ -53,14 +53,10 @@ struct IopThread {
Dormant,
};
enum class Wait {
None,
Semaphore,
Delay,
};
enum class Wait { None, Semaphore, Delay, Messagebox, EventFlag };
IopThread(std::string n, void (*f)(), s32 ID, u32 priority)
: name(std::move(n)), function(f), priority(priority), thID(ID) {
IopThread(std::string n, void (*f)(), s32 ID, u32 pri)
: name(std::move(n)), function(f), priority(pri), thID(ID) {
thread = co_create(0x300000, functionWrapper);
}
@ -79,8 +75,12 @@ struct IopThread {
struct Semaphore {
enum class attribute { fifo, prio };
Semaphore(attribute attr, s32 option, s32 init_count, s32 max_count)
: attr(attr), option(option), count(init_count), initCount(init_count), maxCount(max_count) {}
Semaphore(attribute _attr, s32 _option, s32 init_count, s32 max_count)
: attr(_attr),
option(_option),
count(init_count),
initCount(init_count),
maxCount(max_count) {}
attribute attr{attribute::fifo};
u32 option{0};
@ -91,17 +91,26 @@ struct Semaphore {
std::list<IopThread*> wait_list;
};
struct EventFlagWaiter {
IopThread* thread = nullptr;
u32 pattern = 0;
u32 mode = 0;
};
struct EventFlag {
bool multiple_waiters_allowed = false;
u32 value = 0;
std::list<EventFlagWaiter> wait_list;
};
struct Messagebox {
std::queue<void*> messages;
IopThread* wait_thread = nullptr;
};
class IOP_Kernel {
public:
IOP_Kernel() {
// this ugly hack
threads.reserve(16);
CreateThread("null-thread", nullptr, 0);
CreateMbx();
CreateSema(0, 0, 0, 0);
kernel_thread = co_active();
}
IOP_Kernel();
s32 CreateThread(std::string n, void (*f)(), u32 priority);
s32 ExitThread();
void StartThread(s32 id);
@ -109,6 +118,7 @@ class IOP_Kernel {
void SleepThread();
void WakeupThread(s32 id);
void iWakeupThread(s32 id);
void YieldThread();
std::optional<time_stamp> dispatch();
void set_rpc_queue(iop::sceSifQueueData* qd, u32 thread);
void rpc_loop(iop::sceSifQueueData* qd);
@ -137,30 +147,29 @@ class IOP_Kernel {
*/
s32 PollMbx(void** msg, s32 mbx) {
ASSERT(mbx < (s32)mbxs.size());
s32 gotSomething = mbxs[mbx].empty() ? 0 : 1;
s32 gotSomething = mbxs[mbx].messages.empty() ? 0 : 1;
if (gotSomething) {
void* thing = mbxs[mbx].front();
void* thing = mbxs[mbx].messages.front();
if (msg) {
*msg = thing;
}
mbxs[mbx].pop();
mbxs[mbx].messages.pop();
}
return gotSomething ? KE_OK : KE_MBOX_NOMSG;
}
s32 PeekMbx(s32 mbx) { return !mbxs[mbx].empty(); }
s32 PeekMbx(s32 mbx) { return !mbxs[mbx].messages.empty(); }
s32 MbxSize(s32 mbx) { return mbxs[mbx].messages.size(); }
s32 ReceiveMbx(void** msg, s32 id);
/*!
* Push something into a mbx
*/
s32 SendMbx(s32 mbx, void* value) {
ASSERT(mbx < (s32)mbxs.size());
mbxs[mbx].push(value);
return 0;
}
s32 SendMbx(s32 mbx, void* value);
s32 CreateSema(s32 attr, s32 option, s32 init_count, s32 max_count) {
s32 id = semas.size();
@ -172,11 +181,27 @@ class IOP_Kernel {
s32 SignalSema(s32 id);
s32 PollSema(s32 id);
s32 CreateEventFlag(s32 attr, s32 option, u32 init_pattern) {
ASSERT(option == 0);
s32 id = event_flags.size();
auto& flag = event_flags.emplace_back();
flag.value = init_pattern;
flag.multiple_waiters_allowed = attr == 2;
return id;
}
s32 WaitEventFlag(s32 flag, u32 pattern, u32 mode);
s32 SetEventFlag(s32 flag, u32 pattern);
s32 ClearEventFlag(s32 id, u32 pattern);
s32 RegisterVblankHandler(int (*handler)(void*)) {
vblank_handler = handler;
return 0;
}
u32 GetSystemTimeLow();
void signal_vblank() { vblank_recieved = true; };
bool sif_busy(u32 id);
@ -198,17 +223,20 @@ class IOP_Kernel {
IopThread* schedNext();
std::optional<time_stamp> nextWakeup();
s32 (*vblank_handler)(void*);
s32 (*vblank_handler)(void*) = nullptr;
std::atomic_bool vblank_recieved = false;
cothread_t kernel_thread;
s32 _nextThID = 0;
IopThread* _currentThread = nullptr;
std::vector<IopThread> threads;
std::vector<std::queue<void*>> mbxs;
std::vector<Messagebox> mbxs;
std::vector<SifRecord> sif_records;
std::vector<Semaphore> semas;
std::vector<EventFlag> event_flags;
std::queue<int> wakeup_queue;
bool mainThreadSleep = false;
std::mutex sif_mtx, wakeup_mtx;
time_stamp m_start_time;
};

View file

@ -1,5 +1,7 @@
#include "iop_thread.h"
#include "common/global_profiler/GlobalProfiler.h"
#ifdef __linux__
#include <unistd.h>
#elif _WIN32
@ -57,6 +59,7 @@ void* IOP::iop_alloc(int size) {
void IOP::wait_run_iop(
std::chrono::time_point<std::chrono::steady_clock, std::chrono::microseconds> wakeup) {
auto p = scoped_prof("krnlw");
std::unique_lock<std::mutex> lk(run_cv_mutex);
iop_run_cv.wait_until(lk, wakeup);
}

View file

@ -905,8 +905,6 @@
(defstate wait (scene-player)
:virtual #t
:enter (behavior ((arg0 symbol))
(format 0 "scene-player: skipping scene~%")
(set! (-> self aborted?) #t)
(set-time! (-> self state-time))
(if (= (-> *game-info* demo-state) 101)
(set-setting! 'audio-language #f 0.0 5)
@ -1158,7 +1156,6 @@
)
)
)
(not (-> self aborted?))
)
(set-blackout-frames (seconds 0.1))
(suspend)
@ -1549,7 +1546,6 @@
(logclear! (-> *cpad-list* cpads 0 button0-rel 0) (pad-buttons triangle))
#t
)
(none)
)
false-func
)
@ -1577,10 +1573,6 @@
)
)
)
(#when PC_PORT ;; og:preserve-this until overlord2 is done
(backup-load-state-and-set-cmds *load-state* (-> self anim command-list))
(restore-load-state-and-cleanup *load-state*)
)
(if (and (-> self wait) *target* (focus-test? *target* grabbed))
(go-virtual release)
)

View file

@ -492,8 +492,9 @@
"WASSTAD4" "WASSTAD5" "WASSTAD6" "WASTOAD" "WASTURT")
;; Jak 3 has no MUS files
;; (copy-mus-files "" "TWEAKVAL")
(defstep :in "$ISO/RES/TWEAKVAL.MUS"
:tool 'copy
:out '("$OUT/iso/TWEAKVAL.MUS"))
;;;;;;;;;;;;;;;;;;;;;
;; Text
;;;;;;;;;;;;;;;;;;;;;
@ -531,6 +532,7 @@
"$OUT/iso/7COMMON.TXT"
"$OUT/iso/0SUBTI2.TXT"
"$OUT/iso/VAGDIR.AYB"
"$OUT/iso/TWEAKVAL.MUS"
,@(reverse *all-vis*)
,@(reverse *all-str*)
,@(reverse *all-sbk*)

View file

@ -1707,16 +1707,13 @@
(the-as
(function process-drawable symbol)
(if (logtest? (-> self scene scene-flags) (scene-flags scf1))
(lambda :behavior scene-player
()
(when (cpad-pressed? 0 triangle)
(set! (-> self aborted?) #t)
(logclear! (-> *cpad-list* cpads 0 button0-abs 0) (pad-buttons triangle))
(logclear! (-> *cpad-list* cpads 0 button0-rel 0) (pad-buttons triangle))
#t
)
(none)
)
(lambda :behavior scene-player () (when (cpad-pressed? 0 triangle)
(set! (-> self aborted?) #t)
(logclear! (-> *cpad-list* cpads 0 button0-abs 0) (pad-buttons triangle))
(logclear! (-> *cpad-list* cpads 0 button0-rel 0) (pad-buttons triangle))
#t
)
)
false-func
)
)
@ -2169,7 +2166,3 @@
)
this
)