diff --git a/src/FileMgr.cpp b/src/FileMgr.cpp new file mode 100644 index 00000000..3aad9794 --- /dev/null +++ b/src/FileMgr.cpp @@ -0,0 +1,294 @@ +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include "common.h" +#include "patcher.h" +#include "FileMgr.h" + +/* + * Windows FILE is BROKEN for GTA. + * + * We need to support mapping between LF and CRLF for text files + * but we do NOT want to end the file at the first sight of a SUB character. + * So here is a simple implementation of a FILE interface that works like GTA expects. + */ + +struct myFILE +{ + bool isText; + FILE *file; +}; + +#define NUMFILES 20 +static myFILE myfiles[NUMFILES]; + +/* Force file to open as binary but remember if it was text mode */ +static int +myfopen(const char *filename, const char *mode) +{ + int fd; + char realmode[10], *p; + + for(fd = 1; fd < NUMFILES; fd++) + if(myfiles[fd].file == nil) + goto found; + return 0; // no free fd +found: + myfiles[fd].isText = strchr(mode, 'b') == nil; + p = realmode; + while(*mode) + if(*mode != 't' && *mode != 'b') + *p++ = *mode++; + else + mode++; + *p++ = 'b'; + *p = '\0'; + myfiles[fd].file = fopen(filename, realmode); + if(myfiles[fd].file == nil) + return 0; + return fd; +} + +static void +myfclose(int fd) +{ + assert(fd < NUMFILES); + if(myfiles[fd].file){ + fclose(myfiles[fd].file); + myfiles[fd].file = nil; + } +} + +static int +myfgetc(int fd) +{ + int c; + c = fgetc(myfiles[fd].file); + if(myfiles[fd].isText && c == 015){ + /* translate CRLF to LF */ + c = fgetc(myfiles[fd].file); + if(c == 012) + return c; + ungetc(c, myfiles[fd].file); + return 015; + } + return c; +} + +static int +myfputc(int c, int fd) +{ + /* translate LF to CRLF */ + if(myfiles[fd].isText && c == 012) + fputc(015, myfiles[fd].file); + return fputc(c, myfiles[fd].file); +} + +static char* +myfgets(char *buf, int len, int fd) +{ + int c; + char *p; + + p = buf; + len--; // NUL byte + while(len--){ + c = myfgetc(fd); + if(c == EOF){ + if(p == buf) + return nil; + break; + } + *p++ = c; + if(c == '\n') + break; + } + *p = '\0'; + return buf; +} + +static int +myfread(void *buf, size_t elt, size_t n, int fd) +{ + if(myfiles[fd].isText){ + char *p; + size_t i; + int c; + + n *= elt; + p = (char*)buf; + for(i = 0; i < n; i++){ + c = myfgetc(fd); + if(c == EOF) + break; + *p++ = c; + } + return i / elt; + } + return fread(buf, elt, n, myfiles[fd].file); +} + +static int +myfwrite(void *buf, size_t elt, size_t n, int fd) +{ + if(myfiles[fd].isText){ + char *p; + size_t i; + int c; + + n *= elt; + p = (char*)buf; + for(i = 0; i < n; i++){ + c = *p++; + myfputc(c, fd); + if(feof(myfiles[fd].file)) // is this right? + break; + } + return i / elt; + } + return fwrite(buf, elt, n, myfiles[fd].file); +} + +static int +myfseek(int fd, long offset, int whence) +{ + return fseek(myfiles[fd].file, offset, whence); +} + +static int +myfeof(int fd) +{ + return feof(myfiles[fd].file); +} + + +char *CFileMgr::ms_rootDirName = (char*)0x5F18F8; +char *CFileMgr::ms_dirName = (char*)0x713CA8; + +void +CFileMgr::Initialise(void) +{ + _getcwd(ms_rootDirName, 128); + strcat(ms_rootDirName, "\\"); +} + +void +CFileMgr::ChangeDir(const char *dir) +{ + if(*dir == '\\'){ + strcpy(ms_dirName, ms_rootDirName); + dir++; + } + if(*dir != '\0'){ + strcat(ms_dirName, dir); + // BUG in the game it seems, it's off by one + if(dir[strlen(dir)-1] != '\\') + strcat(ms_dirName, "\\"); + } + chdir(ms_dirName); +} + +void +CFileMgr::SetDir(const char *dir) +{ + strcpy(ms_dirName, ms_rootDirName); + if(*dir != '\0'){ + strcat(ms_dirName, dir); + // BUG in the game it seems, it's off by one + if(dir[strlen(dir)-1] != '\\') + strcat(ms_dirName, "\\"); + } + chdir(ms_dirName); +} + +void +CFileMgr::SetDirMyDocuments(void) +{ + SetDir(""); // better start at the root if user directory is relative + chdir(GetUserDirectory()); +} + +int +CFileMgr::LoadFile(const char *file, uint8 *buf, int unused, const char *mode) +{ + int fd; + int n, len; + + fd = myfopen(file, mode); + if(fd == 0) + return 0; + len = 0; + do{ + n = myfread(buf + len, 1, 0x4000, fd); + if(n < 0) + return -1; + len += n; + }while(n == 0x4000); + buf[len] = 0; + myfclose(fd); + return len; +} + +int +CFileMgr::OpenFile(const char *file, const char *mode) +{ + return myfopen(file, mode); +} + +int +CFileMgr::OpenFileForWriting(const char *file) +{ + return OpenFile(file, "wb"); +} + +int +CFileMgr::Read(int fd, char *buf, int len) +{ + return myfread(buf, 1, len, fd); +} + +int +CFileMgr::Write(int fd, char *buf, int len) +{ + return myfwrite(buf, 1, len, fd); +} + +bool +CFileMgr::Seek(int fd, int offset, int whence) +{ + return !!myfseek(fd, offset, whence); +} + +char* +CFileMgr::ReadLine(int fd, char *buf, int len) +{ + return myfgets(buf, len, fd); +} + +void +CFileMgr::CloseFile(int fd) +{ + myfclose(fd); +} + +int +CFileMgr::GetErrorReadWrite(int fd) +{ + return myfeof(fd); +} + +STARTPATCHES + InjectHook(0x478F80, CFileMgr::Initialise, PATCH_JUMP); + InjectHook(0x478FB0, CFileMgr::ChangeDir, PATCH_JUMP); + InjectHook(0x479020, CFileMgr::SetDir, PATCH_JUMP); + InjectHook(0x479080, CFileMgr::SetDirMyDocuments, PATCH_JUMP); + InjectHook(0x479090, CFileMgr::LoadFile, PATCH_JUMP); + InjectHook(0x479100, CFileMgr::OpenFile, PATCH_JUMP); + InjectHook(0x479120, CFileMgr::OpenFileForWriting, PATCH_JUMP); + InjectHook(0x479140, CFileMgr::Read, PATCH_JUMP); + InjectHook(0x479160, CFileMgr::Write, PATCH_JUMP); + InjectHook(0x479180, CFileMgr::Seek, PATCH_JUMP); + InjectHook(0x4791D0, CFileMgr::ReadLine, PATCH_JUMP); + InjectHook(0x479200, CFileMgr::CloseFile, PATCH_JUMP); + InjectHook(0x479210, CFileMgr::GetErrorReadWrite, PATCH_JUMP); +ENDPATCHES diff --git a/src/FileMgr.h b/src/FileMgr.h new file mode 100644 index 00000000..a77ae6fa --- /dev/null +++ b/src/FileMgr.h @@ -0,0 +1,21 @@ +#pragma once + +class CFileMgr +{ + static char *ms_rootDirName; //[128]; + static char *ms_dirName; //[128]; +public: + static void Initialise(void); + static void ChangeDir(const char *dir); + static void SetDir(const char *dir); + static void SetDirMyDocuments(void); + static int LoadFile(const char *file, uint8 *buf, int unused, const char *mode); + static int OpenFile(const char *file, const char *mode); + static int OpenFileForWriting(const char *file); + static int Read(int fd, char *buf, int len); + static int Write(int fd, char *buf, int len); + static bool Seek(int fd, int offset, int whence); + static char *ReadLine(int fd, char *buf, int len); + static void CloseFile(int fd); + static int GetErrorReadWrite(int fd); +}; diff --git a/src/common.h b/src/common.h index e55e5b52..272e5017 100644 --- a/src/common.h +++ b/src/common.h @@ -5,6 +5,7 @@ #pragma warning(disable: 4244) // int to float #pragma warning(disable: 4800) // int to bool #pragma warning(disable: 4838) // narrowing conversion +#pragma warning(disable: 4996) // POSIX names #include #include @@ -61,6 +62,8 @@ extern RsGlobalType &RsGlobal; #define SCREENW (RsGlobal.maximumWidth) #define SCREENH (RsGlobal.maximumHeight) +char *GetUserDirectory(void); + struct GlobalScene { RpWorld *world; diff --git a/src/main.cpp b/src/main.cpp index b17a30a3..b7d1d151 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ -#include "common.h" +#include #include +#include "common.h" #include "patcher.h" #include "Renderer.h" #include "debugmenu_public.h" @@ -36,6 +37,16 @@ mysrand(unsigned int seed) myrand_seed = seed; } +// platform stuff +char* +GetUserDirectory(void) +{ + static char path[MAX_PATH]; + strcpy(path, "userfiles"); + mkdir(path); + return path; +} + int (*open_script_orig)(const char *path, const char *mode); int