Skip to content

Commit

Permalink
Add savefile writing prevention for stdio functions
Browse files Browse the repository at this point in the history
  • Loading branch information
clementgallet committed May 8, 2016
1 parent be67570 commit e9abac2
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 31 deletions.
214 changes: 183 additions & 31 deletions src/libTAS/fileio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "../shared/Config.h"
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <fcntl.h>
#include <map>
Expand Down Expand Up @@ -86,6 +87,7 @@ void link_sdlfileio(void)
namespace orig {
static FILE *(*fopen) (const char *filename, const char *modes) = nullptr;
static FILE *(*fopen64) (const char *filename, const char *modes) = nullptr;
static int (*fclose) (FILE *stream) = nullptr;
static int (*fprintf) (FILE *stream, const char *format, ...) = nullptr;
static int (*vfprintf) (FILE *s, const char *format, va_list arg) = nullptr;
static int (*fputc) (int c, FILE *stream) = nullptr;
Expand All @@ -94,6 +96,67 @@ namespace orig {
size_t n, FILE *s) = nullptr;
}

static std::map<FILE*, std::string> stdio_savefiles;

static bool isWriteable(const char *modes)
{
if (strcmp(modes, "r") || strcmp(modes, "rb"))
return false;
return true;
}

static bool isSaveFile(const char *file)
{
if (!config.prevent_savefiles)
return false;

if (!file)
return false;

/* Check if file is a dev file */
struct stat filestat;
int rv = stat(file, &filestat);

if (rv == -1) {
/*
* If the file does not exists,
* we consider it as a savefile
*/
if (errno == ENOENT)
return true;

/* For any other error, let's say no */
return false;
}

/* Check if the file is a regular file */
if (S_ISREG(filestat.st_mode))
return true;

return false;
}

static bool isSaveFile(const char *file, const char *modes)
{
if (!isWriteable(modes))
return false;

static bool inited = 0;
if (!inited) {
/*
* Normally, we shouldn't have to clear the posix_savefiles map,
* as it is clearly during creation. However, games break without
* clearing it. I suppose it is because we are using the map
* before it had time to initialize, and it seems clearing it
* is enough to make it usable.
*/
stdio_savefiles.clear();
inited = 1;
}

return isSaveFile(file);
}

FILE *fopen (const char *filename, const char *modes)
{
if (!orig::fopen) {
Expand All @@ -112,7 +175,13 @@ FILE *fopen (const char *filename, const char *modes)
debuglogstdio(LCF_FILEIO, "%s call with filename %s and mode %s", __func__, filename, modes);
else
debuglogstdio(LCF_FILEIO, "%s call with null filename", __func__);
return orig::fopen(filename, modes);

FILE* f = orig::fopen(filename, modes);

if (isSaveFile(filename, modes))
stdio_savefiles[f] = std::string(filename); // non NULL file is tested in isSaveFile()

return f;
}

FILE *fopen64 (const char *filename, const char *modes)
Expand All @@ -130,13 +199,63 @@ FILE *fopen64 (const char *filename, const char *modes)
else
debuglogstdio(LCF_FILEIO, "%s call with null filename", __func__);

return orig::fopen64(filename, modes);
FILE* f = orig::fopen64(filename, modes);

if (isSaveFile(filename, modes))
stdio_savefiles[f] = std::string(filename); // non NULL file is tested in isSaveFile()

return f;
}

int fclose (FILE *stream)
{
if (!orig::fclose) {
link_stdiofileio();
if (!orig::fclose) {
printf("Failed to link fclose\n");
return 0;
}
}

debuglogstdio(LCF_FILEIO, "%s call", __func__);

int rv = orig::fclose(stream);

if (stdio_savefiles.find(stream) != stdio_savefiles.end()) {
debuglog(LCF_FILEIO, " close savefile ", stdio_savefiles[stream]);
stdio_savefiles.erase(stream);
}

return rv;

}

int fprintf (FILE *stream, const char *format, ...)
{
DEBUGLOGCALL(LCF_FILEIO);

if (config.prevent_savefiles) {
if (stdio_savefiles.find(stream) != stdio_savefiles.end()) {
debuglog(LCF_FILEIO, " prevent write to ", stdio_savefiles[stream]);

/*
* We still have to compute the number of characters that the
* game think have been written. Using vsnprintf for this.
*
* We don't need to create a buffer containing all written characters
* to get access to that number, so only using a one-length array
*/
char c;

va_list args;
va_start(args, format);
int ret = vsnprintf(&c, 1, format, args);
va_end(args);

return ret;
}
}

/* We cannot pass the arguments to fprintf_real. However, we
* can build a va_list and pass it to vfprintf
*/
Expand All @@ -150,18 +269,53 @@ int fprintf (FILE *stream, const char *format, ...)
int vfprintf (FILE *s, const char *format, va_list arg)
{
DEBUGLOGCALL(LCF_FILEIO);

if (config.prevent_savefiles) {
if (stdio_savefiles.find(s) != stdio_savefiles.end()) {
debuglog(LCF_FILEIO, " prevent write to ", stdio_savefiles[s]);

/*
* We still have to compute the number of characters that the
* game think have been written. Using vsnprintf for this.
*
* We don't need to create a buffer containing all written characters
* to get access to that number, so only using a one-length array
*/
char c;

int ret = vsnprintf(&c, 1, format, arg);
return ret;
}
}

return orig::vfprintf(s, format, arg);
}

int fputc (int c, FILE *stream)
{
DEBUGLOGCALL(LCF_FILEIO);

if (config.prevent_savefiles) {
if (stdio_savefiles.find(stream) != stdio_savefiles.end()) {
debuglog(LCF_FILEIO, " prevent write to ", stdio_savefiles[stream]);
return c;
}
}

return orig::fputc(c, stream);
}

int putc (int c, FILE *stream)
{
DEBUGLOGCALL(LCF_FILEIO);

if (config.prevent_savefiles) {
if (stdio_savefiles.find(stream) != stdio_savefiles.end()) {
debuglog(LCF_FILEIO, " prevent write to ", stdio_savefiles[stream]);
return c;
}
}

return orig::putc(c, stream);
}

Expand All @@ -174,6 +328,16 @@ size_t fwrite (const void *ptr, size_t size, size_t n, FILE *s)
return 0;
}
}

if (config.prevent_savefiles) {
if (stdio_savefiles.find(s) != stdio_savefiles.end()) {
//debuglog(LCF_FILEIO, " prevent write to ", stdio_savefiles[s]);
if (size == 0)
return 0;
return n;
}
}

//DEBUGLOGCALL(LCF_FILEIO);
//debuglogstdio(LCF_FILEIO, "%s call", __func__);
return orig::fwrite(ptr, size, n, s);
Expand All @@ -183,6 +347,7 @@ void link_stdiofileio(void)
{
LINK_NAMESPACE(fopen, nullptr);
LINK_NAMESPACE(fopen64, nullptr);
LINK_NAMESPACE(fclose, nullptr);
LINK_NAMESPACE(fprintf, nullptr);
LINK_NAMESPACE(vfprintf, nullptr);
LINK_NAMESPACE(fputc, nullptr);
Expand All @@ -205,46 +370,33 @@ namespace orig {

static std::map<int, std::string> posix_savefiles;

static bool isSaveFile(const char *file, int oflag)
static bool isWriteable(int oflag)
{
if (!config.prevent_savefiles)
if ((oflag & 0x3) == O_RDONLY)
return false;
return true;
}

if (!file)
static bool isSaveFile(const char *file, int oflag)
{
/* Check if file is writeable */
if (!isWriteable(oflag))
return false;

static bool inited = 0;
if (!inited) {
/*
* Normally, we shouldn't have to clear the posix_savefiles map,
* as it is clearly during creation. However, games break without
* clearing it. I suppose it is because we are using the map
* before it had time to initialize, and it seems clearing it
* is enough to make it usable.
*/
posix_savefiles.clear();
inited = 1;
}

/* Check if file is writeable */
if ((oflag & 0x3) == O_RDONLY)
return false;

/* Check if file is a dev file */
struct stat filestat;
int rv = stat(file, &filestat);

if (rv == -1) {
/*
* If the file does not exists,
* we consider it as a savefile
*/
if (errno == ENOENT)
return true;

/* For any other error, let's say no */
return false;
}

/* Check if the file is a regular file */
if (S_ISREG(filestat.st_mode))
return true;

return false;

return isSaveFile(file);
}

int open (const char *file, int oflag, ...)
Expand Down
3 changes: 3 additions & 0 deletions src/libTAS/fileio.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ OVERRIDE SDL_RWops *SDL_RWFromFP(FILE * fp, SDL_bool autoclose);
OVERRIDE FILE *fopen (const char *filename, const char *modes);
OVERRIDE FILE *fopen64 (const char *filename, const char *modes);

/* Close STREAM. */
OVERRIDE int fclose (FILE *stream);

/* Write formatted output to STREAM. */
OVERRIDE int fprintf (FILE *stream, const char *format, ...);

Expand Down

0 comments on commit e9abac2

Please sign in to comment.