UnityFS development near completion

In the last few days I have been trying to write a .NET binding for the C library PhysicsFS(https://icculus.org/physfs/) ready for usage in Unity3d. Whilst there was no direct problem writing wrappers for the methods and adjusting some stuff for improved compatibility, the issue was debugging it.

As you are probably aware debugging in Unity itself is already a huge pain. Now imagine trying to figure out if the bug you encountered origins from a modified C library which is being used by a managed .NET C# library using tons of unsafe code which is being referenced by some scripts in Unity. In case there is a way to deal with this effectively it is far beyond my knowledge.

So I ended up writing my own implementation purely in managed C#. Much easier to debug and maintain. Also gives me a better understanding about what is going on behind the scenes, since well … its my own code. I figured that calling it UnityFS sounds like a good idea.

It has the same basic functionality as PhysicsFS, but only supports the .zip archive format thanks to the DotNetZip(https://dotnetzip.codeplex.com/) library. I am planning on adding a way to write custom archive handlers to UnityFS before the release of version 1.0.

Since I’ll have to wait anyways with writing the documentation(I signed up for the https://readthedocs.com/ beta, which might take some time to get invited) I decided to write some examples in Unity. I’ll just go ahead and post them here so that you can picture for yourself how UnityFS works and decide if you are going to purchase the final product once it will be released on the Asset Store.

Reading Files

            string data = Application.dataPath + @"\UnityFS\Demo\Read Data\";
            // Okay, here we go:
            // First, lets create a new UnityFS.FileSystem. A FileSystem does what it says,
            // it manages our virtual FileSystem.
            FileSystem fs = new FileSystem();
            // Let us add a directory to our file system. When looking for a file, the file
            // system will now include this directory.
            fs.AddSearchPath(data + @"files\search1");

            // Now that we have set up a path for the file system to use, let's look for a
            // file inside of it:
            // Note that since we added file "files\search1" path to our FileSystem, "search1"
            // is the root of our virtual file system.
            UnityFSFile ourFile = fs.GetFile("file.txt");

            // Great! We now have access to the file "file.txt" can can perform various actions
            // with it.
            // Let's just do what I'd assume most people want to achieve with UnityFS: Get the
            // file contents. We can recieve the file content either as byte array or as a string.
            // For the sake for convenience let's just recieve a string from the file:
            Debug.Log("file.txt contains: " + ourFile.ReadAsString());

            // So far, so good. But what do we do if we want to iterate though a directory?
            // So let's get all files in the "more-files" directory (sub-dir of the real directory
            // "search1". Also we are only looking for .txt files in order to avoid any pesky
            // meta files.
            UnityFSFile[] moreFiles = fs.GetFiles("more-files", "*.txt");
            foreach (UnityFSFile file in moreFiles)
            {
                Debug.Log("Found file " + file + "!");
            }

            // Awesome! We can now iterate though our file system. But sometimes we want to have
            // more than one source for data. So let's go ahead and add new search path!
            fs.AddSearchPath(data + @"files\search2");
            // If we are taking a look at the file structure in the Unity explorer, we can see that
            // "files\search2" also contains a file.txt file! Let's see what happens when we 
            // try to reccieve the contents of it:
            // Note that we can still use the "ourFile" UnityFSFile.
            Debug.Log("file.txt contains: " + ourFile.ReadAsString());
            // "Why that?" you might ask? Well the answer is rather simple. When adding a new 
            // search path to our file system, the virtual directory is just "expanded". See
            // it as if all files in "files\search2" were just copied into the root of our 
            // fake directory. This caused the old "file.txt" to be overwritten.
            // Obviously, no actual file copying and deleting is done as that would be way 
            // too slow and not really clean. This means that we can also remove search paths
            // at any given time:
            fs.RemoveSearchPath(data + @"files\search2");

            // But Let's assume that "files\search2" contains the file "other-file.txt" which 
            // we really need. So we need to find a way to add the directory to our search path
            // whithout overwriting anything.
            // In order to go this, we use the second parameter which allows to to specify the
            // index of the search path. When looking for a file the file system will first look
            // at the directory with the index 0, then 1 and so on. Since "files\search1" was
            // added first it has the index 0. Unless specified the default index for new paths
            // is 0, and thus effectively overwrites all other files.
            fs.AddSearchPath(data + @"files\search2", 1);
            UnityFSFile otherFile = fs.GetFile("other-file.txt");
            Debug.Log("other-file.txt contains: " + otherFile.ReadAsString());
            Debug.Log("file.txt contains: " + ourFile.ReadAsString());

            // We can obviously add more paths at any time to any given index.
            fs.AddSearchPath(data + @"files\search3");
            UnityFSFile[] rootFiles = fs.GetFiles("", "*.txt");
            foreach (UnityFSFile file in rootFiles)
            {
                Debug.Log(file + " -> Path: " + file.GetSearchPath());
            }

UnityFS Demo Project Chapter 1 Output

Writing Files

            string data = Application.dataPath + @"\UnityFS\Demo\Write Data\";
            // Let's set up a basic search path
            FileSystem fs = new FileSystem();
            fs.AddSearchPath(data + @"files\search1");

            // Time to read some data.
            UnityFSFile userData = fs.GetFile("userdata.txt");
            string userDataStr = userData.ReadAsString();
            Debug.Log("Current user data: " + userDataStr);

            // Okay. Nothing new so far. Now it's time to write new data. Let's say the player
            // just finished playing and we want to write his user data back to the file.
            // But we can't just overwrite the file in our search path. It is called SEARCH path
            // for a reason. We have to specifiy a special write path where all modfied files
            // are saved to. The write path is automatically included as a search path and is
            // ALWAYS the first directory UnityFS searches for files.
            fs.SetWritePath(data + @"files\write");
            string newUserData = FancyMethod();
            userData.WriteAsString(newUserData);
            Debug.Log("Reading \"user-data.txt\" from file: " + userData.ReadAsString());

            // Time to clean up! For some reason we decide to delete the user data. Keep in mind
            // that you can only delete files in the write directory. Files outside of the
            // write directory are READ-ONLY for UnityFS. 
            // This is a design decision aiming to extend the security when giving users the
            // option to write/delete files by for example scripting.
            fs.DeleteFile(userData);
            // Since only our file in "files\write" has been deleted we can still access the "old"
            // user data file:
            Debug.Log("Reading \"user-data.txt\" from file (again ...): " + userData.ReadAsString());

UnityFS Demo Project Chapter 2 Output

Archives

 string data = Application.dataPath + @"\UnityFS\Demo\Archives\";
 // Just the usual stuff ...
 FileSystem fs = new FileSystem();
 fs.AddSearchPath(data + @"files\archive1.zip");
 // Oh wait! Did we just add an archive as a search path? Yes we did! UnityFS can 
 // read, iterate, etc archives in the same way as it can normal directories.
 // Let's apply what we learned in chapter 1 & 2:

 // Which files are even contained in the root folder?
 UnityFSFile[] rootFiles = fs.GetFiles("");
 foreach (UnityFSFile file in rootFiles)
 {
     Debug.Log(file + " -> Path: " + file.GetSearchPath());
 }

 // Let's read a specific file from the archive.
 UnityFSFile archiveFile = fs.GetFile("read-me.txt");
 Debug.Log("ReadMe: " + archiveFile.ReadAsString());

UnityFS Demo Project Chapter 3 Output

UnityFS Demo Project File Structure (1)For reference, I included a screenshot of the file structure of the demo project. Upon completion UnityFS will released as a compiled DLL file. I have not decided on the final price yet.

UnityFS development near completion

Leave a comment