jak-project/game/kernel/fileio.cpp
2020-08-29 19:13:29 -04:00

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;
}