Relocating Game Data
en

Relocating Game Data

Many games hardcode the path to their data during compilation. If you are looking to relocated the whole game so users can install it anywhere then you are probably going to have to patch the source code. The question is how do you find out what directory the executable is run from? "arg[0] is going to be unreliable. Luckily there are 3 ways I know of doing this. The first is to use the pid of the process and retrieve the path using the proc filesystem. The second way is use dlinfo(3C). The third way is to use getexecname(3C).

The Problem

First lets look at a example of the problem. You will notice that DATADIR is hardcoded in the source code. The hardcoding can also occur in a header file, and Makefile, or as an argument to a "configure". In the example loadData function call concatenates DATADIR with the name of the file and returns an open file.

    #include <stdio.h>
    #include <stdlib.h>
    #include <strings.h>
    // The file hero.png would normally be found at DATADIR/hero.png
    #define DATADIR "/usr/share/mygame"
 
    FILE \*heroImg;
 
    void playGame(){
        // Use your imagination
        return;
    }
 
    // Open a file from the data directory
    FILE \*loadData(char \*name){
        FILE \*dataFile;
        char \*pathName;
        int pathSize;
 
        pathSize=strlen(name)+strlen(DATADIR)+2;
        pathName=malloc(pathSize);
        (void)snprintf(pathName,pathSize,"%s/%s",DATADIR,name);
        dataFile=fopen(pathName,"r");
        if (dataFile==(FILE \*)NULL){
            fprintf(stderr,"ERROR: Data File %s not found\n",pathName);
            free(pathName);
            exit(1);
        }
        free(pathName);
        return(dataFile);
    }
 
    int main(int argc, char\*\* argv) {
        heroImg = loadData("hero.png");
        playGame();
        (void)fclose(heroImg);
        return (EXIT_SUCCESS);
    }

Lets have a look at the code for each of the possible solutions. Just the code for the loadData function call will be shown.

Solution 1 - /proc filesystem

You can find a symbolic link to a running executable from /proc/<PID>/path/a.out. A resolvepath(2) system call is then used to return the path which the symbolic link is pointing to.

    #include <unistd.h>
    #include <limits.h>
    // Open a file from the data directory
    FILE \*loadData(char \*name){
        FILE \*dataFile;
        char \*pathName;
        char \*procPath;
        char \*execPath;
        char \*tmpPath;
        char \*p;
        int pathSize;
        const pid~_t pid=getpid(); 
 
        procPath = malloc(PATH~_MAX+1);
        execPath = malloc(PATH~_MAX+1);
 
        // Get the symbolic link to the executable of the current process
        (void)snprintf(procPath,PATH~_MAX,"/proc/%d/path/a.out",pid);
 
        // From the symbolic link get the path to the executable
        if (resolvepath(procPath,execPath,PATH~_MAX)<1) {
            fprintf(stderr,"ERROR: resolvepath failed\n");
            free(procPath);
            exit(1);
        }
        free(procPath);
 
        // Split the path into a directory (execPath) and filename (p)
        if ((p=strrchr(execPath,'/'))==(char \*)NULL){
            fprintf(stderr,"ERROR: Something went really wrong!");
            exit(1);
        }
        \*(p++)=(char)0;
 
        // add everything together to make a path to the file
        pathName=malloc(PATH~_MAX+1);
        (void)snprintf(pathName,PATH~_MAX,"%s/../share/%s/%s",execPath,p,name);
        free(execPath);
        dataFile=fopen(pathName,"r");
        if (dataFile==(FILE \*)NULL){
            fprintf(stderr,"ERROR: Data File %s not found\n",pathName);
            free(pathName);
            exit(1);
        }
        free(pathName);
        return(dataFile);
    }

Solution 2 - dlinfo(3C)

The dlinfo(3C) library call can give the directory of the currently running process if you pass the RTLD_DI_ORIGIN request. In a couple of programs which I have used this method, I have had a clash with some unrelated definitions in the  header files needed for dlinfo and the application. To get around this, you need to copy the relevant definitions from the header files and include it in your code.

    #include <unistd.h>
    #include <dlfcn.h>
    #include <link.h>
    #include <limits.h> 
    FILE \*loadData(char \*name){
        FILE \*dataFile;
        char \*pathName;
        char \*execDir;
        char \*tmpPath;
        char \*p;
        int pathSize;
        const pid~_t pid=getpid(); 
 
        // get the origin of the currently process
        execDir = malloc(PATH~_MAX+1);
        if (dlinfo(RTLD~_SELF,RTLD~_DI~_ORIGIN,execDir)<0){
            fprintf(stderr,"ERROR: dlinfo failed\n");
            free(execDir);
            exit(1);
        }
 
        // make p = the filename of the executable
        if ((p=strrchr(getexecname(),'/'))==(char \*)NULL){
            p=(char \*)getexecname();
        } else {
            p++;
        }

        pathName=malloc(PATH_MAX+1);
        (void)snprintf(pathName,PATH_MAX,"%s/../share/%s/%s",execDir,p,name);
        free(execDir);
        dataFile=fopen(pathName,"r");
        if (dataFile==(FILE \*)NULL){
            fprintf(stderr,"ERROR: Data File %s not found\n",pathName);
            free(pathName);
            exit(1);
        }
        free(pathName);
        return(dataFile);
}

Solution 3 - getexecname(3C)

The last example code is just using the getexecname(3C) function call.

    #include <unistd.h>
    #include <limits.h>
    FILE \*loadData(char \*name){
        FILE \*dataFile;
        char \*pathName;
        char \*execDir;
        const char \*execName = getexecname();
        char \*tmpPath;
        char \*p;
        int pathSize;
        const pid~_t pid=getpid(); 
 
        execDir = malloc(PATH~_MAX+1);
        // Check if execname is a absolute path or not
        if (execName[0]=='/'){
            strncpy(execDir,execName,PATH~_MAX);
        } else {
            char cwdbuf[PATH~_MAX];
            snprintf(execDir,PATH~_MAX,"%s/%s",getcwd(cwdbuf,PATH~_MAX),execName);
        }
 
        // Split the path into a directory (execPath) and filename (p)
        if ((p=strrchr(execDir,'/'))==(char \*)NULL){
            fprintf(stderr,"ERROR: Something went really wrong!");
            free(execDir);
            exit(1);
        }
        \*(p++)=(char)0;
 
        pathName=malloc(PATH~_MAX+1);
        (void)snprintf(pathName,PATH~_MAX,"%s/../share/%s/%s",execDir,p,name);
        free(execDir);
 
        dataFile=fopen(pathName,"r");
        if (dataFile==(FILE \*)NULL){
            fprintf(stderr,"ERROR: Data File %s not found\n",pathName);
            free(pathName);
            exit(1);
        }
        free(pathName);
        return(dataFile);
    }

Conclusion

As you can see there is atleast 3 ways to solve this problem. You might want to write your own path relocating function which you can easily patch into the game code.

Good Luck with your porting!

Tags:
Created by admin on 2009/10/26 12:08
Last modified by admin on 2009/10/26 12:08

Collectives


XWiki Enterprise 2.7.1.34853 - Documentation