jak-project/game/overlord/iso_cd.cpp
Tyler Wilding 4d751af38e
logs: replace every fmt::print with a lg call instead (#1368)
Favors the `lg` namespace over `fmt` directly, as this will output the
logs to a file / has log levels.

I also made assertion errors go to a file, this unfortunately means
importing `lg` and hence `fmt` which was attempted to be avoided before.
But I'm not sure how else to do this aspect without re-inventing the
file logging.

We have a lot of commented out prints as well that we should probably
cleanup at some point / switch them to trace level and default to `info`
level.

I noticed the pattern of disabling debug logs behind some boolean,
something to consider cleaning up in the future -- if our logs were more
structured (knowing where they are coming from) then a lot this
boilerplate could be eliminated.

Closes #1358
2022-10-01 11:58:36 -04:00

991 lines
31 KiB
C++

/*!
* @file iso_cd.cpp
* IsoFs API for accessing the CD/DVD drive.
*/
#include "iso_cd.h"
#include <cstring>
#include "isocommon.h"
#include "overlord.h"
#include "soundcommon.h"
#include "srpc.h"
#include "common/log/log.h"
#include "game/sce/iop.h"
#include "game/sce/stubs.h"
// iso_cd is an implementation of the IsoFs API for loading files from a CD/DVD with an ISO and/or
// DUP filesystem.
// The DUP filesystem is a custom Naughty Dog filesystem which attempts to hide
// files. The DUP filesystem also stores all files twice on the disk and will try reading from the
// other copy if it reading the first copy encounters errors. The DUP filesystem is unused.
using namespace iop;
typedef int (*mmode_func)(int);
// Drive State
// sector to read from (for DUP files, sector of the first copy of the file)
u32 _sector;
// number of sectors to read
u32 _sectors;
// number of retries in the current read
u32 _retries;
// buffer to read into
void* _buffer;
// set to 0 or 1 to indicate if the first or second copy of DUP files should be used.
uint32_t _dupseg;
// the actual sector to read from (differs from _sector when reading second copy of DUP file)
uint32_t _real_sector;
// set 1 if the current read was continuous from the previous read (didn't require a seek)
uint32_t _continuous;
// time when the current read was started
SysClock _starttime;
// time when the current read has ended
SysClock _endtime;
// Globals
u32 gDirtyCd; // set when we're waiting on a read which has errors
u32 gNoCD; // set when we believe the game disc has been removed.
static u32 sNumFiles; // number of files (includes both ISO and DUP files)
static u32 sArea1; // Sector where the first copy of DUP files live.
static u32 sAreaDiff; // Sectors in between the first and second copy of files.
u32 pirated; // do we think the game is pirated?
mmode_func cdmmode = nullptr; // function to call to set the expected media (CD/DVD)
static sceCdRMode sNominalMode; // drive settings for "nominal" reading
static sceCdRMode sStreamMode; // drive settings for "streaming" reading
static sceCdRMode* sMode; // pointer to currently selected read mode
static LoadStackEntry* sReadInfo; // LoadStackEntry for currently reading file
static u8* sSecBuffer[3]; // Buffers for a single sector
u32 add_files; // Should we add files we discover to the sFiles list?
static FileRecord sFiles[MAX_ISO_FILES]; // Info for all files on the disc
u32 CD_ID_SectorNum; // Sector of the DISK.ID file
s32 CD_ID_Sector[SECTOR_SIZE / 4]; // Contents of the DISK.ID file
s32 CD_ID_SectorSum; // Sum of the CD_ID_SECTOR array
LoadStackEntry sLoadStack[MAX_OPEN_FILES]; // List of all files that are "open"
static u32 sound_bank_loads; // might be a static variable in a function?
IsoFs iso_cd_; // IsoFs function pointers
constexpr int TIME_SIZE = 16; // how many samples for read timing
s32 _times[TIME_SIZE]; // read timing data
s32 _timesix;
s32 _tsamps[2];
s32 _tkps[2];
s32 gLastSpeed;
s32 gDiskSpeed[2];
s32 gDupSeg;
u32 ReadU32(u8* buffer);
u32 ReadSectorsNow(uint32_t sector, uint32_t len, void* buffer);
u32 ReadDirectory(uint32_t sector, uint32_t size, uint32_t secBufID);
void DecodeDUP(u8* buffer);
void LoadMusicTweaks(u8* buffer);
void LoadDiscID();
u32 CheckDiscID();
void SetRealSector();
void CD_WaitReturn();
static int FS_Init(u8* buffer);
static FileRecord* FS_Find(const char* name);
static FileRecord* FS_FindIN(const char* iso_name);
static uint32_t FS_GetLength(FileRecord* fr);
static LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset);
static LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset);
static void FS_Close(LoadStackEntry* fd);
static uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len);
static uint32_t FS_SyncRead();
static uint32_t FS_LoadSoundBank(char*, void*);
static uint32_t FS_LoadMusic(char*, void*);
static void FS_PollDrive();
void iso_cd_init_globals() {
_sector = 0;
_sectors = 0;
_retries = 0;
gDirtyCd = 0;
gNoCD = 0;
_dupseg = 0;
_real_sector = 0;
_continuous = 0;
_buffer = nullptr;
memset(&_starttime, 0, sizeof(SysClock));
memset(&_endtime, 0, sizeof(SysClock));
sNumFiles = 0;
sArea1 = 0;
sAreaDiff = 0;
pirated = 0;
cdmmode = nullptr;
sNominalMode.trycount = 0;
sNominalMode.spindlctrl = 1;
sNominalMode.datapattern = 0;
sNominalMode.pad = 0;
sStreamMode.trycount = 0xf;
sStreamMode.spindlctrl = 0;
sStreamMode.datapattern = 0;
sStreamMode.pad = 0;
sMode = &sStreamMode;
sReadInfo = nullptr;
memset(sSecBuffer, 0, sizeof(sSecBuffer));
add_files = 0;
memset(sFiles, 0, sizeof(sFiles));
CD_ID_SectorNum = 0;
memset(CD_ID_Sector, 0, sizeof(CD_ID_Sector));
CD_ID_SectorSum = 0;
memset(sLoadStack, 0, sizeof(sLoadStack));
sound_bank_loads = 0;
iso_cd_.init = FS_Init;
iso_cd_.find = FS_Find;
iso_cd_.find_in = FS_FindIN;
iso_cd_.get_length = FS_GetLength;
iso_cd_.open = FS_Open;
iso_cd_.open_wad = FS_OpenWad;
iso_cd_.close = FS_Close;
iso_cd_.begin_read = FS_BeginRead;
iso_cd_.sync_read = FS_SyncRead;
iso_cd_.load_sound_bank = FS_LoadSoundBank;
iso_cd_.load_music = FS_LoadMusic;
iso_cd_.poll_drive = FS_PollDrive;
memset(_times, 0, sizeof(_times));
memset(_tsamps, 0, sizeof(_tsamps));
memset(_tkps, 0, sizeof(_tkps));
_timesix = 0;
memset(gDiskSpeed, 0, sizeof(gDiskSpeed));
gLastSpeed = 0;
gDupSeg = 0;
}
/*!
* Read unaligned uint32_t from buffer. ISO file systems may have 32-bit values that aren't word
* aligned. DONE, EXACT
*/
uint32_t ReadU32(u8* data) {
return (uint32_t)data[0] + (((uint32_t)data[1]) * 0x100) + (((uint32_t)data[2]) * 0x10000) +
(((uint32_t)data[3]) * 0x1000000);
}
/*!
* Read from disc, immediately (blocking), into a local buffer. Will retry if needed, setting
* gDirtyCd. This does not use the DUP file system or drive state, so this should not be used
* outside of initialization. Will clear gDirtyCd on successful read. Returns 1 on success, 0 on
* sceCdRead failure, or otherwise retries forever until sceCdGetError is OK.
* The length is in terms of sectors.
* DONE, EXACT
*/
u32 ReadSectorsNow(uint32_t sector, uint32_t len, void* buffer) {
// reset the sector state to break any continuous reads in progress
_sector = 0;
// retry loop for read
while (true) {
// Start async read from DVD...
if (sceCdRead(sector, len, buffer, sMode) == 0) {
// if this fails, it indicates catastrophic failure of the DVD drive, so give up immediately.
return 0;
}
// Wait for read to finish (0x00 is blocking)
sceCdSync(0);
// check for error
if (sceCdGetError() == 0) {
// no error, we are good!
break;
}
// we got an error. Try again, and set a dirty flag so the EE knows we're having trouble
gDirtyCd = 1;
}
// success! clear the dirty flag and return!
gDirtyCd = 0;
return 1;
}
/*!
* Read ISO file systems directory tree, adding files to the filerecord table if add_files is set.
* Regardless of add_files is not set, it will be set for folders under NAUGHTY.DOG.
* This feature is used on demos with multiple games and Jak is in the NAUGHTY.DOG folder.
* Recursively walks the tree.
* There is a stack of single sector buffers used to recursively read the directories.
* Returns 1 on success and 0 on failure.
* Running out of sector buffers because of too many nested folders is considered success?
* DONE
*/
u32 ReadDirectory(uint32_t sector, uint32_t size, uint32_t secBufID) {
if (secBufID < 3) {
// grab our buffer from the stack
u8* buffer = sSecBuffer[secBufID];
uint32_t lsector = sector;
int32_t lsize = size;
// loop over sector reads
while (lsize > 0) {
// ISO low-level read
if (!ReadSectorsNow(lsector, 1, buffer)) {
lg::info("[OVERLORD ISO CD] Failed to read sector in ReadDirectory!");
return 0;
}
u8* lbuffer = buffer;
// loop over stuff in the sector
while ((*lbuffer != 0) && (lbuffer < buffer + SECTOR_SIZE)) {
u8 dir_record_size = *lbuffer;
if ((lbuffer[0x21] != 0) && (lbuffer[0x21] != 1)) { // skip over whatever these things are
uint32_t extent = ReadU32(lbuffer + 2);
uint32_t dir_size = ReadU32(lbuffer + 10);
uint32_t name_len = lbuffer[0x20];
bool is_directory = true;
if ((lbuffer[0x1f + name_len] == ';') && (lbuffer[0x20 + name_len] == '1')) {
is_directory = false;
}
if (is_directory) {
if (!add_files) {
// don't add file by default, but add files if we recurse in the NAUGHTY.DOG folder
if (!memcmp(lbuffer + 0x21, "NAUGHTY.DOG", 0xb)) {
add_files = true;
ReadDirectory(extent, dir_size, secBufID + 1);
add_files = false;
}
} else {
// otherwise just recurse
ReadDirectory(extent, dir_size, secBufID + 1);
}
} else {
if (sNumFiles == MAX_ISO_FILES) {
lg::info("[OVERLORD ISO CD] There are too many files on the disc!");
return 0;
}
if (add_files) {
lbuffer[0x1f + name_len] = 0; // null terminate the name
MakeISOName(sFiles[sNumFiles].name, (char*)(lbuffer + 0x21));
sFiles[sNumFiles].location = extent;
sFiles[sNumFiles].size = dir_size;
sNumFiles++;
}
}
}
lbuffer += dir_record_size;
}
lsector++;
lsize -= 0x800;
}
} else {
lg::info("[OVERLORD ISO CD] ReadDirectory ran out of sector buffers!");
}
return 1;
}
struct DupIndexEntry {
// 20 bytes
char name[12];
u32 location;
u32 size;
};
/*!
* DUP code. The DUP files aren't on the disc, so this doesn't do anything.
* This would allow for hidden files that aren't in the standard ISO format, presumably to make
* pirating harder? The DUP files store the location of these hidden files. Also it support having
* two copies of some files. There are two areas, both of which are identical. But it was never
* used.
*/
void DecodeDUP(u8* buffer) {
(void)buffer;
// set sArea1 to point to an impossibly large sector - if DUP initialization fails this means no
// file will be in the DUP zones.
sArea1 = 0x7fffffff;
char iso_name[16];
// all three of these will fail.
MakeISOName(iso_name, "Z1INDEX.DUP");
FileRecord* index_file = FS_FindIN(iso_name);
MakeISOName(iso_name, "Z3AREA1.DUP");
FileRecord* area1_file = FS_FindIN(iso_name);
MakeISOName(iso_name, "Z5AREA2.DUP");
FileRecord* area2_file = FS_FindIN(iso_name);
// Note - this reads 4 sectors, but the buffer only has enough room for 3 sectors.
// So this code would likely cause a crash if it was run.
// Maybe this is why it was removed?
// Or maybe there used to be 4 init buffers, but one was removed once they gave up on DUP?
if (index_file && area1_file && area2_file && ReadSectorsNow(index_file->location, 4, buffer)) {
sArea1 = area1_file->location; // marks start of 1st zone
sAreaDiff = area2_file->location - area1_file->location; // difference between zones
// make sure we have enough room to store all entries
if (sNumFiles + *(s32*)(buffer) <= MAX_ISO_FILES) {
// read entries
DupIndexEntry* dup_entries = (DupIndexEntry*)(((u8*)buffer) + 4);
for (int i = 0; i < *(s32*)(buffer); i++) {
*(s32*)(&sFiles[sNumFiles].name) = *(s32*)(&dup_entries[i].name);
*(s32*)(&sFiles[sNumFiles].name + 4) = *(s32*)(&dup_entries[i].name + 4);
*(s32*)(&sFiles[sNumFiles].name + 8) = *(s32*)(&dup_entries[i].name + 8);
sFiles[sNumFiles].size = dup_entries[i].size;
sFiles[sNumFiles].location = dup_entries[i].location;
sNumFiles++;
}
}
}
}
/*!
* Load the TWEAKVAL.MUS file into the gMusicTweakInfo file.
* Only works if the file is less than 1 sector long.
* If loading fails, writes a 0 to the first 32-bits of gMusicTweakInfo
* @param buffer a sector buffer which will be used
*/
void LoadMusicTweaks(u8* buffer) {
char iso_name[16];
MakeISOName(iso_name, "TWEAKVAL.MUS");
FileRecord* fr = FS_FindIN(iso_name);
if (!fr || !ReadSectorsNow(fr->location, 1, buffer)) {
gMusicTweakInfo.TweakCount = 0;
lg::warn("[OVERLORD ISO CD] Failed to load music tweaks!");
} else {
memcpy((void*)&gMusicTweakInfo, buffer, sizeof(MusicTweaks));
}
}
/*!
* Load the DISK ID file and compute the sum.
* This is used as a checksum to make sure the disc is correct.
* A literal sum is not a great checksum
* Also, this function name and the file on the disc itself spell dis{c,k} differently.
*
* If there is no DISK_ID.DIZ file, uses whatever is stored at 0x400 instead.
*/
void LoadDiscID() {
char iso_name[16];
MakeISOName(iso_name, "DISK_ID.DIZ");
FileRecord* fr = FS_FindIN(iso_name);
if (!fr) {
lg::warn(
"[OVERLORD ISO CD] LoadDiscID failed to find DISK_ID.DIZ, using sector 0x400 instead!");
CD_ID_SectorNum = 0x400;
} else {
CD_ID_SectorNum = fr->location;
}
ReadSectorsNow(CD_ID_SectorNum, 1, &CD_ID_Sector);
CD_ID_SectorSum = 0;
for (uint32_t i = 0; i < SECTOR_SIZE / 4; i++) {
CD_ID_SectorSum += CD_ID_Sector[i];
}
lg::info("[OVERLORD] DISK_ID.DIZ OK 0x{:x}", CD_ID_SectorSum);
}
/*!
* Verify that the DISK ID file has not changed. Returns 1 if it is good.
*/
u32 CheckDiskID() {
if (ReadSectorsNow(CD_ID_SectorNum, 1, CD_ID_Sector) == 0) {
// failed to read CD ID data
return 0;
}
int sum = 0;
for (uint32_t i = 0; i < SECTOR_SIZE / 4; i++) {
sum += CD_ID_Sector[i];
}
return sum == CD_ID_SectorSum;
}
/*!
* Set _real_sector in preparation for a read, based on the requested _sector.
* This has logic for a system which has a double copy of some data on the disc and can pick between
* two different copies. This selection is done with the dupseg flag.
*/
void SetRealSector() {
// if we are below sArea1, it's not a duplicated file, so ignore the dupseg flag and read directly
if (_sector < sArea1 || _dupseg == 0) {
_real_sector = _sector;
} else {
// it's a duplicated file, and duplicate read is enabled, so get the area 2 sector.
_real_sector = _sector + sAreaDiff;
lg::warn("[OVERLORD] Warning, adjusting real sector in SetRealSector");
}
// we suspect the game is pirated, load the wrong sector.
if (pirated) {
_real_sector += 3;
lg::warn("Pirated!");
}
}
/*!
* Initialize the ISO CD system and builds file record table.
* This is an ISO_FS API Function.
* Also loads music tweaks/DISK ID
* @param buffer : a buffer larger enough to hold 3 sectors
* this buffer can be freed immediately this returns
* Return 0 on success.
*/
int FS_Init(u8* buffer) {
// determine disk type
int disk_type = SCECdDETCT;
while (disk_type = sceCdGetDiskType(), disk_type == SCECdDETCT) {
// This SleepThread will cause the Overlord initialization to lock up. It's called with an
// argument of 10000, but SleepThread accepts no arguments. Probably they meant to call
// DelayThread. It ends up working because the drive already knows the disk type at this point.
SleepThread();
}
// what is this. it's crazy. why?
if (disk_type <= SCECdPS2DVD || disk_type < SCECdCDDA || disk_type <= SCECdDVDV ||
disk_type != SCECdIllegalMedia) {
// we are actually using the CD drive, so set the mmode function to the SCE function.
// This is called in FS_LoadMusic. If you call this with the wrong media type, it locks up.
// I guess this is an attempt at making convoluted anti-piracy code so it's harder to find
// calls to sceCdMmode with static analysis. But they left in debug symbols and the variable
// is called "cdmmode", which is not a very sneaky way to hide it! (At least on the EE it's
// called aybabtu and is a GOAL symbol which is way harder to figure out.) Also it seems like
// the primary mode of piracy they were concerned with is somebody swapping a DVD with a CD?
cdmmode = sceCdMmode;
// verify the disc is a DVD.
sceCdMmode(SCECdDVD);
// set up sector buffers used for initialization reads.
for (int i = 0; i < 3; i++) {
sSecBuffer[i] = buffer + i * SECTOR_SIZE;
}
// read primary volume descriptor into buffer
if (!ReadSectorsNow(0x10, 1, sSecBuffer[0])) {
lg::warn("[OVERLORD ISO CD] Failed to read primary volume descriptor");
return 1;
}
// check volume descriptor identifier
if (memcmp(sSecBuffer[0] + 1, "CD001", 5)) {
lg::warn("[OVERLORD ISO CD] Got the wrong volume descriptor identifier");
char* cptr = (char*)sSecBuffer[0] + 1;
printf("%c%c%c%c%c\n", cptr[0], cptr[1], cptr[2], cptr[3], cptr[4]);
return 1;
}
// read path table into buffer
uint32_t path_table_sector = ReadU32(sSecBuffer[0] + 0x8c);
if (!ReadSectorsNow(path_table_sector, 1, sSecBuffer[0])) {
lg::warn("[OVERLORD ISO CD] Failed to read path");
return 1;
}
// read path table's extent into buffer
uint32_t path_table_extent = ReadU32(sSecBuffer[0] + 2);
if (!ReadSectorsNow(path_table_extent, 1, sSecBuffer[0])) {
lg::warn("[OVERLORD ISO CD] Failed to read path table extent");
}
// read root directory
add_files = true;
uint32_t dir_size = ReadU32(sSecBuffer[0] + 10);
if (!ReadDirectory(path_table_extent, dir_size, 0)) {
lg::warn("[OVERLORD ISO CD] Failed to ReadDirectory");
return 1;
}
// load filesystem stuff
DecodeDUP(sSecBuffer[0]);
LoadMusicTweaks(sSecBuffer[0]);
LoadDiscID();
// there's some sort of weird loop here over all file that does nothing.
// my guess is its some commented out print thing?
// empty load stack
for (int i = 0; i < MAX_OPEN_FILES; i++) {
sLoadStack[i].fr = nullptr;
}
// kill sector buffers
for (int i = 0; i < 3; i++) {
sSecBuffer[i] = nullptr;
}
return 0;
} else {
lg::warn("[OVERLORD ISO CD] Bad Media Type!");
return 1;
}
}
/*!
* Find a file on the disc and return a FileRecord.
* This is an ISO FS API Function
*/
FileRecord* FS_Find(const char* name) {
char name_buff[16];
MakeISOName(name_buff, name);
return FS_FindIN(name_buff);
}
/*!
* Find a file on the disc. Uses the ISO name of the file.
* This can be generated with MakeISOFile
* This is an ISO FS API Function
* There is a weird anti-piracy thing in here to prevent people from making copies with less than
* 1 GB of data? I guess you could remove the audio in languages you don't care about and put in
* on a CD, and this would block this from happening.
*/
FileRecord* FS_FindIN(const char* iso_name) {
const uint32_t* buff = (const uint32_t*)iso_name;
for (;;) { // this loop will spin forever if you have < 1 GB of files
uint32_t size = 0; // total sum of file sizes
uint32_t count = 0;
while (count < sNumFiles) {
const uint32_t* ref = (uint32_t*)sFiles[count].name;
if (ref[0] == buff[0] && ref[1] == buff[1] && ref[2] == buff[2]) {
return sFiles + count;
}
size += sFiles[count].size;
count++;
}
// if we get here, we haven't found the file, we should return 0 to indicate we don't have it
// however, if we haven't found 1 GB of files after searching the whole thing
// we assume that we've pirated the game and should continue looping
// Note that the game attempts to load DUP files which will fails and will hit this condition.
buff +=
3; // to make this look less suspicious, lets increment buff. also will crash eventually.
if (0x3fffffff < size) {
return nullptr; // we got 1 GB of files, okay to return
}
// we didn't get 1 GB of files, you're a pirate.
lg::warn("Pirated!");
}
}
/*!
* Determine the length of a file.
* This is an ISO FS API Function
*/
uint32_t FS_GetLength(FileRecord* fr) {
return fr->size;
}
/*!
* Open a file by putting it on the load stack.
* Set the offset to 0 or -1 if you do not want to have an offset.
* This is an ISO FS API Function
*/
LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset) {
lg::info("[OVERLORD] FS Open {}", fr->name);
LoadStackEntry* selected = nullptr;
// find first unused spot on load stack.
for (uint32_t i = 0; i < MAX_OPEN_FILES; i++) {
if (!sLoadStack[i].fr) {
selected = sLoadStack + i;
selected->fr = fr;
selected->location = fr->location;
if (offset != -1) {
selected->location += offset;
}
return selected;
}
}
lg::warn("[OVERLORD ISO CD] Failed to FS_Open {}", fr->name);
ExitIOP();
return nullptr;
}
/*!
* Open a file by putting it on the load stack.
* Like Open, but allows an offset of -1 to be applied.
* This is an ISO FS API Function
*/
LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset) {
printf("[OVERLORD] FS Open %s\n", fr->name);
LoadStackEntry* selected = nullptr;
for (uint32_t i = 0; i < MAX_OPEN_FILES; i++) {
if (!sLoadStack[i].fr) {
selected = sLoadStack + i;
selected->fr = fr;
selected->location = fr->location + offset;
return selected;
}
}
lg::warn("[OVERLORD ISO CD] Failed to use FS_OpenWad {}", fr->name);
ExitIOP();
return nullptr;
}
/*!
* Close an open file.
* This is an ISO FS API Function
*/
void FS_Close(LoadStackEntry* fd) {
lg::info("[OVERLORD] FS Close {}", fd->fr->name);
if (fd == sReadInfo) {
// the file is currently being read, so lets try to finish out the read, if possible.
int count = 0;
// the non-blocking sync, so we don't get stuck here on a catastrophic error.
while (sceCdSync(1)) {
DelayThread(1000); // wait 1 ms and allow other stuff to run.
count++;
if (count == 1000) { // waited too long to close this file
sceCdBreak(); // interrupt the read
break;
}
}
sReadInfo = nullptr;
}
// close the FD
fd->fr = nullptr;
}
/*!
* Begin reading! Returns FS_READ_OK on success (always)
* This is an ISO FS API Function
*/
uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len) {
// set the reading state:
// I guess continuous stream buffer reads don't count as continuous?
_continuous = (len == BUFFER_PAGE_SIZE) && (fd->location == (_sector + _sectors));
_sector = fd->location;
int32_t real_size = len;
if (len < 0) {
// not sure what this is about...
lg::warn("[OVERLORD ISO CD] Negative length warning!");
real_size = len + 0x7ff;
}
_sectors = real_size >> 11;
_retries = 0;
_buffer = buffer;
GetSystemTime(&_starttime);
// compute _real_sector
SetRealSector();
while (!sceCdRead(_real_sector, _sectors, _buffer, sMode)) {
// error starting the read. this is bad and possibly indicates somebody took the CD out.
// lets wait for the CD to be ready again...
CD_WaitReturn();
_retries++;
if (_sector >= sArea1) {
// the original file we tried to read is duplicated...
// so lets try reading the other copy of it!
_dupseg = 1 - _dupseg;
_continuous = 0; // mark as noncontinuous read
SetRealSector(); // recompute!
}
}
// ??? this is strangely set up.
if (len < 0) {
len = len + 0x7ff;
}
fd->location += (len >> 0xb);
// set sReadInfo to point to the current read.
sReadInfo = fd;
return CMD_STATUS_IN_PROGRESS;
}
/*!
* Wait for current read to complete!
* This is an ISO FS API Function
* @return
*/
uint32_t FS_SyncRead() {
// make sure a read is actually in progress
if (!sReadInfo) {
return CMD_STATUS_READ_ERR;
}
// remember when we start doing a SyncRead.
SysClock now;
GetSystemTime(&now);
// block and wait for completion
sceCdSync(0);
// remember when we sync.
GetSystemTime(&_endtime);
// Loop to check if read succeed and start an additional read if not.
while (sceCdGetError()) {
// no, it didn't, lets retry
_retries++;
// toggle dupseg
if (_sector >= sArea1) {
_dupseg = 1 - _dupseg;
_continuous = 0;
SetRealSector();
}
// try until a read starts...
while (!sceCdRead(_real_sector, _sectors, _buffer, sMode)) {
// read start failed, possibly CD is removed
CD_WaitReturn();
// retry!
_retries++;
// toggle dupseg if possible
if (_sector >= sArea1) {
_dupseg = 1 - _dupseg;
_continuous = 0;
SetRealSector();
}
}
// read has started
// set dirty cd to indicate we had trouble
gDirtyCd = 1;
// wait for read to finish...
sceCdSync(0);
// if the read/sync fails, the loop will go again.
}
// Read complete! Mark CD as not dirty and clear active read!
gDirtyCd = 0;
sReadInfo = nullptr;
// Optionally do some timing checks
// (note that these never run because we don't have DUP files)
// continuous read with no failures from dup zone
// more than half the time spent in FS_SyncRead
// Basically it tries to learn about which segment does worse in "too slow" reads
// by averaging all historical too slow reads. Once the other is winning by a certain amount
// it will swap. It will also swap if there isn't enough samples on one.
if (_retries == 0 && _continuous && _sectors >= sArea1 &&
(_endtime.hi - _starttime.hi) / 2 < (_endtime.hi - now.hi)) {
// record the time
_times[_timesix++] = _endtime.hi - _starttime.hi;
// if we filled the time buffer
if (_timesix == TIME_SIZE) {
// compute total time
s32 total_time = 0;
for (s32 i = 0; i < TIME_SIZE; i++) {
total_time += _times[i];
}
// determine read speed
gLastSpeed = 0x69780000 / (total_time >> 4); // todo - work out this constant
// add to average kps for this seg
if (_tsamps[_dupseg] < 0x40000) {
_tkps[_dupseg] = _tkps[_dupseg] + gLastSpeed;
_tsamps[_dupseg] = _tsamps[_dupseg];
}
// average speed of this segment
gDiskSpeed[_dupseg] = _tkps[_dupseg] / _tsamps[_dupseg];
_timesix = 0;
if (_tsamps[0] < 8) {
// not much information about segment 0
if (_dupseg) {
// and we aren't reading segment 0...
// so let's read segment 0
_dupseg = 0;
_sector = 0;
}
} else {
// got enough info about segment 0.
if (_tsamps[1] < 8) {
// not enough information about segment 1
if (!_dupseg) {
// and not reading, so lets read it.
_dupseg = 1;
_sector = 0;
}
} else {
// enough info about both.
if ((_tkps[1] / _tsamps[1] + 0x32) < (_tkps[0] / _tsamps[0])) {
// section 0 wins by at least 0x32, lets use it if we aren't already
if (_dupseg) {
_dupseg = 0;
_sector = 0;
}
} else if ((_tkps[0] / _tsamps[0] + 0x32) < (_tkps[1] / _tsamps[1])) {
if (!_dupseg) {
_dupseg = 1;
_sector = 0;
}
}
}
}
// set our current decision in a global for the EE to read.
gDupSeg = _dupseg;
}
}
return CMD_STATUS_IN_PROGRESS;
}
/*!
* Load a SoundBank now. Doesn't do any fancy read stuff.
*/
uint32_t FS_LoadSoundBank(char* name, void* buffer) {
char full_name[32]; // may actually be 8, but lets be safe
// ??? todo this is probably a field of the buffer.
u32 header_size;
if (*(s32*)(((u8*)buffer) + 0x14) == 0x65) {
header_size = 1;
} else {
header_size = 10;
}
if (strlen(name) > 16) {
printf("[OVERLORD ISO CD] FS_LoadSoundBank has an invalid name!\n");
}
// append .sbk
strcpy(full_name, name);
strcat(full_name, ".sbk");
FileRecord* fr = FS_Find(full_name);
if (!fr) {
printf("[OVERLORD ISO CD] FS_LoadSoundBank cannot find bank %s, loading empty instead.\n",
full_name);
fr = FS_Find("empty1.sbk");
}
// hack to do a read now (the Sound Bank loads bypass all the other fancy loading stuff evidently)
_sector = fr->location;
SetRealSector();
// loop until we read header successfully.
// don't set retries or dirty cd
while (!ReadSectorsNow(_real_sector, header_size, buffer)) {
// ReadSectorsNow will only return if the read fails to start. in this case we assume the disc
// was removed:
CD_WaitReturn();
// we don't increment retries...
if (_sector >= sArea1) {
_dupseg = 1 - _dupseg;
_continuous = 0;
SetRealSector();
}
}
// now have the sound library do a load.
// (this time we set dirty cd if it fails, but no retries)
auto load_status = snd_BankLoadByLoc(_real_sector + header_size, 0);
while (!load_status && snd_GetLastLoadError() < 0x100) {
CD_WaitReturn();
if (_sector >= sArea1) {
_dupseg = 1 - _dupseg;
_continuous = 0;
SetRealSector();
}
load_status = snd_BankLoadByLoc(_real_sector + header_size, 0);
if (!load_status) {
gDirtyCd = 1;
}
}
gDirtyCd = 0;
// pirate check sometimes
sound_bank_loads++;
if ((sound_bank_loads & 7) == 0) {
pirated = 1;
// check that one file is past sector 0x80000 (approx 1 GB)
for (u32 i = 0; i < sNumFiles; i++) {
if (sFiles[i].location + (sFiles[i].size >> 11) > 0x80000) {
pirated = 0;
}
}
}
snd_ResolveBankXREFS();
PrintBankInfo((SoundBank*)buffer);
_sector = 0;
// ??? todo this is probably a field of the buffer.
*(s32*)(((u8*)buffer) + 0x10) = load_status;
return 0;
}
/*!
* Load a music file. Load now, doesn't do fancy reading stuff.
*/
uint32_t FS_LoadMusic(char* name, void* buffer) {
char full_name[32]; // may actually be 8, but lets be safe
if (strlen(name) > 16) {
printf("[OVERLORD ISO CD] FS_LoadMusic has an invalid name!\n");
}
// append .mus
strcpy(full_name, name);
strcat(full_name, ".mus");
FileRecord* fr = FS_Find(full_name);
if (!fr) {
printf("[OVERLORD ISO CD] FS_LoadMusic cannot find bank %s.\n", full_name);
return 6;
}
_sector = fr->location;
SetRealSector();
// another "piracy" check to make sure the media is the correct type...
(*cdmmode)(SCECdDVD);
// now have the sound library do a load.
auto load_status = snd_BankLoadByLoc(_real_sector, 0);
// TODO magic constant 0x100
while (!load_status && snd_GetLastLoadError() < 0x100) {
CD_WaitReturn();
if (_sector >= sArea1) {
_dupseg = 1 - _dupseg;
_continuous = 0;
SetRealSector();
}
load_status = snd_BankLoadByLoc(_real_sector, 0);
if (!load_status) {
gDirtyCd = 1;
}
}
gDirtyCd = 0;
snd_ResolveBankXREFS();
_sector = 0;
*(s32*)buffer = load_status;
return 0;
}
/*!
* Make sure the drive is happy.
* NOTE - only call this when the drive should have nothing to do!
*/
void FS_PollDrive() {
if (sceCdDiskReady(1) == SCECdNotReady) { // non-blocking
CD_WaitReturn();
}
}
/*!
* Wait for the game CD/DVD to be put back in the playstation.
* Only call this if you think the CD/DVD has been removed, as requires a seek.
*/
void CD_WaitReturn() {
gNoCD = 1;
do {
while (sceCdDiskReady(1) == SCECdNotReady) {
}
} while (!CheckDiskID());
gNoCD = 0;
}