Tutorial 10: Using the filesystem

It’s been a while since I put up a tutorial and I hope that by now you have a basic understanding of programming for the Wii. So that I’m able to keep producing these tutorials I’ll try to focus on small pieces of code instead of the whole application and how the code can be applied. The explanation of Simon was a bit too long ;). This tutorial is also a bit longer than I expected. You can grab the PDF of this tutorial here: codemii-tutorial-10

In this tutorial we’ll cover initialising the filesystem, creating/deleting directories/files and reading/writing files. Thanks to joedj for his FAT init code from FTPii v0.0.5.

Note: This tutorial is for DevKitPPC r15. Users of r16 will have to prepend SD:/ whenever accessing the filesystem.

You need to begin firstly with adding -lfat to your LIBS: in your makefile. The second step is including <fat.h> in your makefile.

Initialising the filesystem

First things first, before we can start reading/writing files or creating/deleting directories we need to initialise the filesystem (FAT in this case). It’s just a simple piece of code which does this for us.

if (!fatInitDefault()) {
printf("Unable to initialise FAT subsystem, exiting.\n");
exit(0);
}

The above just reads, if we can’t initialise the FAT filesystem, then print an error message and exit.

The next thing to check (which isn’t mandatory) is to see if we can open the filesystem which is done with the code below.

if (!can_open_root_fs()) {
printf("Unable to open root filesystem, exiting.\n");
exit(0);
}

bool can_open_root_fs() {
DIR_ITER *root = diropen("/");
if (root) {
dirclose(root);
return true;
}
return false;
}

We use diropen to check if we are able to open the root directory and if so it returns true.

Ok, so we can open the filesystem. We can add another check to make sure that we can actually change the current working directory to the root (/).

if (chdir("/")) {
printf("Could not change to root directory, exiting.\n");
exit(0);
}

And now we’re done with the initialisation. We can place this all in a function to make things a bit cleaner. We call our own die() function if something has failed.
It’s always good practise to unmount the filesystem when exiting your application. This can be done by calling fatUnmount(0);.

void initialise_fat() {
if (!fatInitDefault()) die("Unable to initialise FAT subsystem, exiting.\n");
if (!can_open_root_fs()) die("Unable to open root filesystem, exiting.\n");
if (chdir("/")) die("Could not change to root directory, exiting.\n");
}

bool can_open_root_fs() {
DIR_ITER *root = diropen("/");
if (root) {
dirclose(root);
return true;
}
return false;
}

void die(char *msg) {
perror(msg);
sleep(5);
fatUnmount(0);
exit(0);
}

As you can see above the die function accepts a string of characters (denoted by char *) and then prints out the string using perror(). The thing about perror is it will print out the string supplied as well as any error that has occurred. For example if you are trying to open a file and it doesn’t exist, perror might print something like “No such file or directory” which is always helpful.

After that we just pause for 5 seconds, unmount the filesystem and exit the application.

Creating Directories

We can create a directory by using mkdir. mkdir takes two arguments, the directory to create and the permissions to set on the directory. We can ignore the permissions and just set them as 0777 (everyone has access to read, write and delete).

mkdir("/test", 0777); 

The above would create a directory called /test on the root of the device we are accessing. mkdir returns 0 if the directory was created successfully otherwise it returns -1.

We can check for errors by doing the below.

if (mkdir("/test", 0777) == -1) {
die("Could not create /test directory.\n");
}

This will run the mkdir command and if it returns -1 it will print the string specified and perror() will print out the error message.

Removing Directories/Files

We can remove a directory or a file by using unlink(path);

unlink("/test/myfile.txt");
unlink("/test");

The above would remove the file called myfile.txt and then the directory /test.

We can also check for errors exactly the same as mkdir.

if (unlink("/test") == -1) {
die("Could not delete directory/file.\n");
}

Reading Files

There are various ways to read files. We’ll stick to the easiest way which is reading the file one line at time so that we may process each line. This might be useful say if you’re developing a game that has multiple levels and each line might represent an object’s x and y co-ordinates.

First we need to open the file in read mode as below.

FILE *f = fopen ("myfile.txt", "rb");

Next we check if the file was able to be opened for reading.

// If file doesn't exist or can't open it then we can grab the latest file
if (f == NULL) {
die("Could not open myfile.txt file for reading.\n");
}

Otherwise if it was fine, we can continue.

else {
char file_line [20];
int line_number = 0;
while (fgets (file_line, 20, f)) {
printf("Line %i: %s\n",line_number, file_line);
line_number++;
}
fclose(f);
}

We need store each line to a variable so that we are able to process the line. This variable is file_line and is an array of characters which holds 20 characters (I’m assuming that our x and y co-ordinates are between 1 to 3 characters in length, so 20 is more than enough). If say you had a long line of text, you would have to change 20 to say 500 or more depending on how long each line was.
After that we just have the line_number which just counts the number of lines.

Now we reach the while loop, here we use the fgets function. fgets allows us to specify the amount of characters to read in the line or until the line ends. The first argument is a pointer to our array of characters which is where the information from the file will temporarily be stored. The second argument is the number of characters to read, again we use 20. The third argument is the pointer to the file, which is f in this case.

In every loop, file_line will be updated with the next line from the myfile.txt file. This continues until we reach the end of the file. We then print out the line number and the contents of the line at each loop.

After we have reached the end of the file, we can close the file by using fclose(f);

So let’s say that our file contains the following data which represents x and y co-ordinates:
32 58
34 48
234 99
385 234
453 347

We need a place to store the x and y co-ordinates, a simple example is:

int x_pos[] = { 0, 0, 0, 0, 0};
int y_pos[] = { 0, 0, 0, 0, 0};

We would need to modify our while loop to break up each line, as we need the x and y co-ordinates in different variables.

Here we can use the string tokenizer (strok) to break up the line. strtok takes 2 arguments, the string to tokenize and a list of delimiters; which just means the characters to find that will determine a split in the string.

For our example the delimiters would just be a space (” “) which would break the line into 2 parts. The delimiter isn’t included in any of the parts.

Now if we where to store each part directory into the integer variable the compiler would complain (char being stored in an int), so we need to change the string of characters to an int. This can be done using atoi(string) which just converts the string into an integer.

while (fgets (file_line, 20, f)) {
char *temp_string;
temp_string = strtok (file_line, " ");
if (temp_string != NULL) {
x_pos[line_number] = atoi(temp_string);
temp_string = strtok (NULL, " ");
if (temp_string != NULL) {
y_pos[line_number] = atoi(temp_string);
}
}
line_number++;
}

We firstly assign a blank pointer. We run strtok on our file_line variable which contains the line of text.

temp_string would then be equal to the first token in the line of text, which is “32”. Since temp_string has a value, we assign element 0 of x_pos with the integer value of 32.

We run strtok again, this time with the first argument of NULL. You use the NULL argument in strtok if you wish to continue breaking up a string.

The next token is “58” which we assign the integer value of 58 to element 0 of y_pos.
We then increase the line_number, and so on.

Writing files

As with reading files, writing files can be done in different ways. A simple way of writing files would use the fputs function which takes 2 arguments, the string to write to the file and the pointer to the file.

FILE *f = fopen ("myfile.txt", "wb");

if (f == NULL) {
die("Could not open myfile.txt file for writing.\n");
}
else {
int x;
for (x = 0; x < line_number; x++) {
char temp_string[20];
sprintf(temp_string, "%i %i", x_pos[x], y_pos[x]);
fputs (temp_string ,f);
}
fclose(f);
}

Note that “rb” has changed to “wb” (the w means write).

We’ll use the same example as in the reading example, except in this case we wish to write our x and y co-ordinates to the file.
We’ll assume that you have the line_number variable and that it equals 5. We do a for loop until we reach line_number (5).

So we need to do the opposite of strtok, so that we are able to add the two numbers and a space in between. We also need to change the integer values of x and y into strings.

We can do this using sprintf which has 3 arguments and is just like printf except it prints the output to a string. The arguments are the string to output to, the text to print and the variables to print.

We have the temp_string variable which is empty, we do a sprintf which converts “%i %i” to “32 58”. After we convert the integers to a string, we write this temp_string to our file and repeat until we are done.

Note: I have experienced issues when using sprintf multiple times in a row which can cause issues with the variables sprintf is writing to.

And that’s it; we’re done for this tutorial. You should now know how to preform different functions on files/folders which should make your life easier if you are say making a multi-level game or wish to save some simple settings to a file. Remember that there is a lot more error checking that could be done but for the purposes of this tutorial it’s just too much to do.

See you next time…

33 Responses to “Tutorial 10: Using the filesystem”

  1. Jake says:

    Thx teknecal! As usual!

  2. Joshua says:

    thanks for this great tutorial. file management is something that tends to be a little mystifying on consoles and often gets neglected, but regardless this is an invaluable resource to new coders and devs. cheers!

  3. scognito says:

    Hi,
    nice tutorial 🙂
    Anyway is there a way to open files on DVD using the IOS_Ioctl functions?
    Just an example, nothing more.
    Thanks

  4. PaceMaker says:

    FYI, there are two significant bugs in the libfat library.

    1. mkdir() doesn’t set the Modify Time or Accessed Time when it creates a directory. This is particularly a problem when using stat() on the directory and expecting a non-zero time. To see this issue, just mkdir() on the SD Card on the Wii, then do a ‘dir’ on the SD card in Windows.

    2. stat() doesn’t adjust the date properly and will return negative numbers for the Modified time.

    There is a bug logged here: https://sourceforge.net/tracker2/?func=detail&aid=2635047&group_id=114505&atid=668551

    P.S. Nice Tutorial.

  5. typo says:

    there are alot of typos in this tut…fix them

    • teknecal says:

      I found one or two and have fixed them. If you are talking about words like initialise, they are correct for me at least as I’m in Australia 😉

  6. gunman says:

    Thank you for the tut, m8! But I have a stupid question: Now if I learn the C programming Langauge from a book I lent in a library; there are no Wii commands…. -.- Where can I read them? The devkitPPC DoxyGen Doc hasn’t all of the commands like fopen() etc. in it. I was not able to find them =(
    I know all the syntax, but I don’t know where to look up the whole commands (is there a list?)

    Please help!
    Thanks in advance! keep on doing, teknecal!!!

  7. fishears says:

    Another great tutorial, thank you. Is there any chance you will do it as a PDF? I’ve got them all downloaded and this one kind of breaks the series…. not that I’m a control freak or anything, I just like neatness 😉

  8. Pvt Ryan says:

    2 questions:

    1) Note: This tutorial is for DevKitPPC r15. Users of r16 will have to append SD:/ whenever accessing the filesystem.

    I take it you mean prepend?

    2) Your abnormal exits (e.g. inside die()) would it not be better to call exit(1)? as 0 is generally a successful run to completion, where as a non-zero is indicitive of a failure in the program.

  9. Hey man!

    Very good tutorials!

    But I have a question…

    Do you know what kind of libraries I have to include to get the FAT functions?
    I tried the “fat.h”, but the compiler says that can’t find this file…

    What is the right library?

    (I’m using r16)

    Tnx! 🙂

  10. teknecal says:

    Just looking at this now I forgot to include fatUnmount(0);
    Call that before your application exits.

  11. Cyrus Sanii says:

    Great tutorials! Keep them coming!
    ======================

    I am having 1 problem though with this one. When I run my code the numbers do not seem to be output properly. My datafile has “8 9” as the 1st line(no quotes).

    char file_line[20] = {0},*temp_string;
    int x_pos[10],y_pos[10],line_number=0;

    while (fgets (file_line, 20, fp))
    {
    printf(“%d) %s”,line_number,file_line); <–This works right I see #’s
    temp_string = strtok (file_line, ” “);
    if (temp_string != NULL)
    {
    x_pos[line_number] = atoi(temp_string);
    temp_string = strtok (NULL, ” “);
    if (temp_string != NULL)
    {
    y_pos[line_number] = atoi(temp_string);
    }
    }
    line_number++;
    printf(” (%i,%i) (%d,%d)\n”,x_pos[line_number],y_pos[line_number],x_pos[line_number],y_pos[line_number]); ***PROBLEM LINE***
    }

    The last printf statement gives me (-2144893848,0) (-2144893848,0)
    I tried both %i and %d to see if any difference. Both not working.
    Any idea what is wrong? I think I cut-n-pasted the code so it should work.

    • teknecal says:

      Try to place the printf before the line_number++; line and see if that works.

      • Cyrus Sanii says:

        That was it!!!
        I can’t believe it. I have a whole game running and when I tried to add writing/reading the high score I miss that!

        Thanks! The high score section has been causing me more problems than writing the rest of the game….

  12. Bodwad says:

    Hey great tutorials, got stuck on this one though as I cant get it to compile with the ‘DIR_ITER’ variable. The compile error says

    ‘DIR_ITER’ undeclared (first use in this function)

    Am I missing any includes? :o)
    I have the includes at the top of my file as follows;

    #include
    #include
    #include
    #include

    Hope you can help, thanks :o)

  13. Is there a way to access information stored on an external hard drive using this method?

    • teknecal says:

      Yes, you would need the latest version of libfat and libogc (for completeness) and use something like diropen(“usb:/”), etc. You can access the sd card like diropen(“sd:/”);

  14. Okay, and thank you for the fast reply!

  15. For some reason I’m getting a warning saying that I am implicitly stating fatInitDefault();.

  16. Michael says:

    Is there any way that you could upload a “finished” project? Up until now I have been depending on them to check to see if I did everything right.

    …And right now I know I’m not doing it right, because I am getting some massive errors.

    P.S. This is an amazing service that you are providing, please know that it is extremely appreciated!

    • teknecal says:

      Sure, I’ll start posting the compiled dol files for new tutorials (I never actually compiled this project, though I should have).

      What kind of errors are you having?

      • Michael says:

        Well, the compiler doesn’t know what “DIR_ITER” is; I’m assuming it’s defined in a header file. Which one I don’t know. I included fat.h though, if that matters.

        The line “if (mkdir(“/test”, 0777) == -1)”
        …gives the error of “implicit declaration of function ‘mkdir'”

        Wha? It must be that I’m missing a header file. I… hope.

  17. Tilespace says:

    I was thinking it was time to join and here I am.
    This will keep me busy at night.

    Tilespace

Leave a Reply