mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-27 16:31:14 +00:00
515 lines
14 KiB
C++
515 lines
14 KiB
C++
/*!
|
|
* @file fileio.cpp
|
|
* GOAL Low-Level File I/O and String Utilities
|
|
* DONE!
|
|
*/
|
|
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
#include "game/sce/stubs.h"
|
|
#include "fileio.h"
|
|
#include "kprint.h"
|
|
|
|
namespace {
|
|
// buffer for file paths. This might be static char buffer[512]. Maybe 633 is the line number?
|
|
char buffer_633[512];
|
|
} // namespace
|
|
|
|
void fileio_init_globals() {
|
|
memset(buffer_633, 0, 512);
|
|
}
|
|
|
|
using namespace ee;
|
|
|
|
/*!
|
|
* Return pointer to null terminator of string.
|
|
* const is for losers.
|
|
* DONE, EXACT
|
|
*/
|
|
char* strend(char* str) {
|
|
while (*str)
|
|
str++;
|
|
return str;
|
|
}
|
|
|
|
/*!
|
|
* An implementation of Huffman decoding.
|
|
* In this limited decoder, your data must have lower two bits equal to zero.
|
|
* @param loc_ptr pointer to pointer to data to read (will be modified to point to next word)
|
|
* @return decoded word
|
|
* UNUSED, EXACT
|
|
*/
|
|
u32 ReadHufWord(u8** loc_ptr) {
|
|
u8* loc = *loc_ptr; // pointer to data to read
|
|
u32 value = *(u32*)loc; // read word
|
|
u8* next_loc = loc + 1; // next data to read
|
|
u32 length = value & 3; // length of word is stored in lower two bits.
|
|
switch (length) {
|
|
case 0: // already all set.
|
|
break;
|
|
|
|
case 1:
|
|
value = (value & 0xfc) | (loc[1] << 8);
|
|
next_loc = loc + 2;
|
|
break;
|
|
|
|
case 2:
|
|
value = (value & 0xfc) | (loc[1] << 8) | (loc[2] << 0x10);
|
|
next_loc = loc + 3;
|
|
break;
|
|
|
|
case 3:
|
|
value = (value & 0xfc) | (loc[1] << 8) | (loc[2] << 0x10) | (loc[3] << 0x18);
|
|
next_loc = loc + 4;
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
// update location pointer
|
|
*loc_ptr = next_loc;
|
|
return value;
|
|
}
|
|
|
|
/*!
|
|
* Copy a string from src to dst. The null terminator is copied too.
|
|
* This is identical to normal strcpy.
|
|
* DONE, EXACT
|
|
*/
|
|
void kstrcpy(char* dst, const char* src) {
|
|
char* dst_ptr = dst;
|
|
const char* src_ptr = src;
|
|
|
|
while (*src_ptr != 0) {
|
|
*dst_ptr = *src_ptr;
|
|
src_ptr++;
|
|
dst_ptr++;
|
|
}
|
|
*dst_ptr = 0;
|
|
}
|
|
|
|
/*!
|
|
* Copy a string from src to dst, making all letters upper case.
|
|
* The null terminator is copied too.
|
|
* DONE, EXACT
|
|
*/
|
|
void kstrcpyup(char* dst, const char* src) {
|
|
while (*src) {
|
|
char c = *src;
|
|
if (c >= 'a' && c <= 'z') { // A-Z,a-z
|
|
c -= 0x20;
|
|
}
|
|
*dst = c;
|
|
dst++;
|
|
src++;
|
|
}
|
|
*dst = 0;
|
|
}
|
|
|
|
/*!
|
|
* Concatenate two strings. Src is added to dest.
|
|
* The new string is null terminated. No bounds checking is done.
|
|
* DONE, EXACT
|
|
*/
|
|
void kstrcat(char* dest, const char* src) {
|
|
// seek to end of first string
|
|
while (*dest) {
|
|
dest++;
|
|
}
|
|
// copy second string
|
|
while (*src) {
|
|
*dest = *src;
|
|
src++;
|
|
dest++;
|
|
}
|
|
// null terminate
|
|
*dest = 0;
|
|
}
|
|
|
|
/*!
|
|
* Concatenate two strings with a maximum length for the resulting string
|
|
* The maximum length should be larger than the length of the original string.
|
|
* The resulting string will be truncated when it reaches the given length.
|
|
* The null terminator is added, but doesn't count toward the length.
|
|
* DONE, EXACT
|
|
*/
|
|
void kstrncat(char* dest, const char* src, s32 count) {
|
|
// seek to null terminator of first string, count length
|
|
s32 i = 0;
|
|
while (*dest) {
|
|
dest++;
|
|
i++;
|
|
}
|
|
|
|
// append second string, not exceeding length
|
|
while (*src && (i < count)) {
|
|
*dest = *src;
|
|
src++;
|
|
dest++;
|
|
i++;
|
|
}
|
|
|
|
// null terminate
|
|
*dest = 0;
|
|
}
|
|
|
|
/*!
|
|
* Insert the pad char at the beginning of a string, count times.
|
|
* DONE, EXACT
|
|
*/
|
|
char* kstrinsert(char* str, char pad, s32 count) {
|
|
// shift string+null terminator to the right.
|
|
s32 len = strlen(str);
|
|
while (len > -1) {
|
|
str[len + count] = str[len];
|
|
len--;
|
|
}
|
|
|
|
// pad
|
|
len = 0;
|
|
while (len < count) {
|
|
str[len++] = pad;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/*!
|
|
* Get filename from path.
|
|
* This function is renamed to basename_goal so it doesn't conflict with "basename" that is
|
|
* already defined on my computer.
|
|
* For example:
|
|
* a/b/c.e will return c.e
|
|
* a\b\c.e will return c.e
|
|
* asdf.asdf will return asdf.asdf
|
|
* DONE, EXACT
|
|
*/
|
|
char* basename_goal(char* s) {
|
|
char* input = s;
|
|
char* pt = s;
|
|
|
|
// seek to end
|
|
for (;;) {
|
|
char c = *pt;
|
|
if (c) {
|
|
pt++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Original code, has memory bug.
|
|
// back up...
|
|
for (;;) {
|
|
if (pt < input) {
|
|
return input;
|
|
}
|
|
pt--;
|
|
char c = *pt;
|
|
// until we hit a slash.
|
|
if (c == '\\' || c == '/') { // slashes
|
|
return pt + 1; // and return one past
|
|
}
|
|
}
|
|
*/
|
|
|
|
// back up...
|
|
for (;;) {
|
|
if (pt <= input) {
|
|
return input;
|
|
}
|
|
pt--;
|
|
char c = *pt;
|
|
// until we hit a slash.
|
|
if (c == '\\' || c == '/') { // slashes
|
|
return pt + 1; // and return one past
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Turn file name into file's path.
|
|
* DONE, EXACT
|
|
*/
|
|
char* DecodeFileName(const char* name) {
|
|
char* result;
|
|
// names starting with $ are special:
|
|
if (name[0] == '$') {
|
|
if (!strncmp(name, "$TEXTURE/", 9)) {
|
|
result = MakeFileName(TX_PAGE_FILE_TYPE, name + 9, 0);
|
|
} else if (!strncmp(name, "$ART_GROUP/", 0xb)) {
|
|
result = MakeFileName(ART_GROUP_FILE_TYPE, name + 0xb, 0);
|
|
} else if (!strncmp(name, "$LEVEL/", 7)) {
|
|
int len = (int)strlen(name);
|
|
if (name[len - 4] == '.') {
|
|
result = MakeFileName(LEVEL_WITH_EXTENSION_FILE_TYPE, name + 7, 0);
|
|
} else {
|
|
// level files can omit a file type if desired
|
|
result = MakeFileName(LEVEL_FILE_TYPE, name + 7, 0);
|
|
}
|
|
} else if (!strncmp(name, "$DATA/", 6)) {
|
|
result = MakeFileName(DATA_FILE_TYPE, name + 6, 0);
|
|
} else if (!strncmp(name, "$CODE/", 6)) {
|
|
result = MakeFileName(CODE_FILE_TYPE, name + 6, 0);
|
|
} else if (!strncmp(name, "$RES/", 5)) {
|
|
result = MakeFileName(RES_FILE_TYPE, name + 5, 0);
|
|
} else {
|
|
printf("[ERROR] DecodeFileName: UNKNOWN FILE NAME %s\n", name);
|
|
result = nullptr;
|
|
}
|
|
} else {
|
|
// if no special prefix is given, assume $CODE
|
|
result = MakeFileName(CODE_FILE_TYPE, name, 0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*!
|
|
* Build a file name based on type.
|
|
* @param type: the file type.
|
|
* @param name: the file name
|
|
* @param new_string: if true, allocate a new global string for file name.
|
|
* will otherwise use a static buffer.
|
|
* DONE, Had unused int, char*, and MakeFileNameInfo params.
|
|
*/
|
|
char* MakeFileName(int type, const char* name, int new_string) {
|
|
// start with network filesystem
|
|
kstrcpy(buffer_633, "host:");
|
|
char* buf = strend(buffer_633);
|
|
|
|
// prefix to build directory
|
|
char prefix[64];
|
|
kstrcpy(prefix, FOLDER_PREFIX);
|
|
|
|
// build file name
|
|
if (type == LISTENER_TO_KERNEL_FILE_TYPE) {
|
|
kstrcpy(buf,
|
|
"kernel/LISTENERTOKERNEL"); // unused (I guess this is an old method to transfer data?)
|
|
} else if (type == KERNEL_TO_LISTENER_FILE_TYPE) {
|
|
kstrcpy(buf,
|
|
"kernel/KERNELTOLISTENER"); // unused (I guess this is an old method to transfer data?)
|
|
} else if (type == CODE_FILE_TYPE) {
|
|
sprintf(buf, "game/obj/%s.o", name); // game object file (CODE)
|
|
} else if (type == GAMEPAD_FILE_TYPE) {
|
|
sprintf(buffer_633, "pad:0"); // I guess the gamepad could be opened like a file at some point?
|
|
} else if (type == LISTENER_TO_KERNEL_LOCK_FILE_TYPE) {
|
|
kstrcpy(buf, "kernel/LISTENERTOKERNEL_LOCK"); // unused (likely used for LISTENERTOKERNEL?)
|
|
} else if (type == KERNEL_TO_LISTENER_LOCK_FILE_TYPE) {
|
|
kstrcpy(buf, "kernel/KERNELTOLISTENER_LOCK"); // unused (likley used for KERNELTOLISTENER?)
|
|
} else if (type == IOP_MODULE_FILE_TYPE) { // IOP module, overwrite the whole thing.
|
|
// this is unused, even by the remaining code to load IOP modules from the network.
|
|
// note this uses host0, which I believe is the PS2 TOOL's built in Linux SBC.
|
|
sprintf(buffer_633, "host0:/usr/local/sce/iop/modules/%s.irx", name);
|
|
} else if (type == DATA_FILE_TYPE) {
|
|
// GOAL object file, but containing data instead of code.
|
|
// likely packed by a tool that isn't the GOAL compiler.
|
|
sprintf(buf, "%sdata/%s.go", prefix, name);
|
|
} else if (type == TX_PAGE_FILE_TYPE) {
|
|
// Texture Page
|
|
// part of level files, so it has a version number.
|
|
sprintf(buf, "%sdata/texture-page%d/%s.go", prefix, TX_PAGE_VERSION, name);
|
|
} else if (type == JA_FILE_TYPE) {
|
|
// Art JA (joint animation? no idea)
|
|
// part of level files, so it has a version number
|
|
sprintf(buf, "%sdd_next/artdata%d/%s-ja.go", prefix, ART_FILE_VERSION, name);
|
|
} else if (type == JG_FILE_TYPE) {
|
|
// Art JG (joint group? no idea)
|
|
// part of level files, so it has a version number
|
|
sprintf(buf, "%sdd_next/artdata%d/%s-jg.go", prefix, ART_FILE_VERSION, name);
|
|
} else if (type == MA_FILE_TYPE) {
|
|
// Art MA (??)
|
|
// part of level files, so it has a version number
|
|
sprintf(buf, "%sdd_next/artdata%d/%s-ma.go", prefix, ART_FILE_VERSION, name);
|
|
} else if (type == MG_FILE_TYPE) {
|
|
// Art MG (??)
|
|
// part of level files, so it has a version number
|
|
sprintf(buf, "%sdd_next/artdata%d/%s-mg.go", prefix, ART_FILE_VERSION, name);
|
|
} else if (type == TG_FILE_TYPE) {
|
|
// unused, DATA TG file
|
|
sprintf(buf, "%sdata/%s-tg.go", prefix, name);
|
|
} else if (type == LEVEL_FILE_TYPE) {
|
|
// Level main file.
|
|
// part of level files, so it has a version number (a high one, 30!)
|
|
sprintf(buf, "%sdata/level%d/%s-bt.go", prefix, LEVEL_FILE_VERSION, name);
|
|
} else if (type == ART_GROUP_FILE_TYPE) {
|
|
// Level art group file.
|
|
// part of level files, so it has a version number
|
|
sprintf(buf, "%sdata/art-group%d/%s-ag.go", prefix, ART_FILE_VERSION, name);
|
|
} else if (type == VS_FILE_TYPE) {
|
|
// Level vs file, unused, unknown
|
|
// possibly early visibility file?
|
|
sprintf(buf, "%sdata/level%d/%s-vs.go", prefix, LEVEL_FILE_VERSION, name);
|
|
} else if (type == TX_FILE_TYPE) {
|
|
// Resource? TX file? some sort of texture?
|
|
sprintf(buf, "%sdata/res%d/%s-tx.go", prefix, RES_FILE_VERSION, name);
|
|
} else if (type == VS_BIN_FILE_TYPE) {
|
|
// level VS bin
|
|
// perhaps another format of early visibility data
|
|
sprintf(buf, "%sdata/level%d/%s-vs.bin", prefix, LEVEL_FILE_VERSION, name);
|
|
} else if (type == DGO_TXT_FILE_TYPE) {
|
|
// Text file in the DGO directory?
|
|
// Could have contained a list of files inside the DGO.
|
|
sprintf(buf, "%sdata/dgo%d/%s.txt", prefix, DGO_FILE_VERSION, name);
|
|
} else if (type == LEVEL_WITH_EXTENSION_FILE_TYPE) {
|
|
// Level file, but with an extension already on it.
|
|
sprintf(buf, "%sdata/level%d/%s", prefix, LEVEL_FILE_VERSION, name);
|
|
} else if (type == DATA_DGO_FILE_TYPE) {
|
|
// data DGO file (unused, all DGO/CGOs loaded through IOP)
|
|
sprintf(buf, "%sdata/dgo%d/%s.dgo", prefix, DGO_FILE_VERSION, name);
|
|
} else if (type == GAME_DGO_FILE_TYPE) {
|
|
// game DGO file (unused, all DGO/CGOs loaded through IOP)
|
|
sprintf(buf, "game/dgo%d/%s.dgo", DGO_FILE_VERSION, name);
|
|
} else if (type == DATA_CGO_FILE_TYPE) {
|
|
// data CGO file (unused, all DGO/CGOs loaded through IOP)
|
|
sprintf(buf, "%sdata/dgo%d/%s.cgo", prefix, DGO_FILE_VERSION, name);
|
|
} else if (type == GAME_CGO_FILE_TYPE) {
|
|
// game CGO file (unused, all DGO/CGOs loaded through IOP)
|
|
sprintf(buf, "game/dgo%d/%s.cgo", DGO_FILE_VERSION, name);
|
|
} else if (type == CNT_FILE_TYPE) {
|
|
// game cnt file (continue point?)
|
|
sprintf(buf, "%sdata/res%d/game-cnt.go", prefix, RES_FILE_VERSION);
|
|
} else if (type == RES_FILE_TYPE) {
|
|
// RES go file?
|
|
sprintf(buf, "%sdata/res%d/%s.go", prefix, RES_FILE_VERSION, name);
|
|
} else if (type == REFPLANT_FILE_TYPE) {
|
|
// REFPLANT? no idea
|
|
static char nextDir[] = "/";
|
|
sprintf(buf, "%sconfig_data/refplant/%s", nextDir, name);
|
|
} else {
|
|
printf("UNKNOWN FILE TYPE %d\n", type);
|
|
}
|
|
|
|
char* result;
|
|
if (!new_string) {
|
|
// return pointer to static filename buffer
|
|
result = buffer_633;
|
|
} else {
|
|
// or create a new string on the global heap.
|
|
int l = (int)strlen(buffer_633);
|
|
result = (char*)kmalloc(kglobalheap, l + 1, 0, "filename").c();
|
|
kstrcpy(result, buffer_633);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*!
|
|
* Does the file exist? No. It doesn't.
|
|
* @return 0 always, even if the file exists.
|
|
* DONE, EXACT, UNUSED
|
|
*/
|
|
u32 FileExists(const char* name) {
|
|
(void)name;
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
* Does nothing. Likely is supposed to delete a file.
|
|
* @param name
|
|
* DONE, EXACT, UNUSED
|
|
*/
|
|
void FileDelete(const char* name) {
|
|
(void)name;
|
|
}
|
|
|
|
/*!
|
|
* Does nothing. Likely is supposed to copy a file.
|
|
* @param a
|
|
* @param b
|
|
* DONE, EXACT, UNUSED
|
|
*/
|
|
void FileCopy(const char* a, const char* b) {
|
|
(void)a;
|
|
(void)b;
|
|
}
|
|
|
|
/*!
|
|
* Determine the file length in bytes.
|
|
* DONE, EXACT
|
|
*/
|
|
s32 FileLength(char* filename) {
|
|
s32 fd = sceOpen(filename, SCE_RDONLY);
|
|
if (fd < 0) {
|
|
MsgErr("dkernel: file length !open \'%s\' (%d)\n", filename, fd);
|
|
sceClose(fd);
|
|
return 0xfffffffb;
|
|
} else {
|
|
s32 rv = sceLseek(fd, 0, SCE_SEEK_END);
|
|
sceClose(fd);
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Load a file into memory
|
|
* @param name : file name
|
|
* @param heap : heap to allocate into, if memory is null
|
|
* @param memory : memory to load into. If null, allocates on the given kheap (with 64 extra bytes)
|
|
* @param malloc_flags : flags for the kmalloc
|
|
* @param size_out : file size is written here, if it's not null
|
|
* @return pointer to file data
|
|
* DONE, EXACT
|
|
*/
|
|
Ptr<u8> FileLoad(char* name, Ptr<kheapinfo> heap, Ptr<u8> memory, u32 malloc_flags, s32* size_out) {
|
|
s32 fd = sceOpen(name, SCE_RDONLY);
|
|
if (fd < 0) {
|
|
MsgErr("dkernel: file read !open \'%s\' (%d)\n", name, fd);
|
|
sceClose(fd);
|
|
return Ptr<u8>(0xfffffffb);
|
|
}
|
|
|
|
// determine size
|
|
s32 initial_pos = sceLseek(fd, 0, SCE_SEEK_CUR);
|
|
s32 size = sceLseek(fd, 0, SCE_SEEK_END);
|
|
sceLseek(fd, initial_pos, SCE_SEEK_SET);
|
|
|
|
if (size > 0) {
|
|
if (memory.offset == 0) {
|
|
memory = kmalloc(heap, size + 0x40, malloc_flags, name);
|
|
}
|
|
if (memory.offset == 0) {
|
|
MsgErr("dkernel: mem full for file read: '%s' (%d bytes)\n", name, size);
|
|
return Ptr<u8>(0xfffffffd);
|
|
}
|
|
|
|
s32 read_amount = sceRead(fd, memory.c(), size);
|
|
if (read_amount == size) {
|
|
sceClose(fd);
|
|
if (size_out)
|
|
*size_out = size;
|
|
return memory;
|
|
} else {
|
|
MsgErr("dkernel: can't read full file (%d of %d): '%s'\n", read_amount, size, name);
|
|
sceClose(fd);
|
|
return Ptr<u8>(0xfffffffb);
|
|
}
|
|
} else {
|
|
return Ptr<u8>(0);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Write a file.
|
|
* DONE, EXACT
|
|
*/
|
|
s32 FileSave(char* name, u8* data, s32 size) {
|
|
s32 fd = sceOpen(name, SCE_WRONLY | SCE_TRUNC | SCE_CREAT);
|
|
if (fd < 0) {
|
|
MsgErr("dkernel: file write !open '%s'\n", name);
|
|
sceClose(fd);
|
|
return 0xfffffffa;
|
|
}
|
|
|
|
if (size != 0) {
|
|
s32 written = sceWrite(fd, data, size);
|
|
if (written != size) {
|
|
MsgErr("dkernel: can't write full file '%s'\n", name);
|
|
sceClose(fd);
|
|
return 0xfffffffa;
|
|
}
|
|
}
|
|
|
|
sceClose(fd);
|
|
return 0;
|
|
} |