ModFS enables a small, sandboxed file system for mods. It allows to store and retrieve binary and text files, no matter their content.
Each mod has its own file system, and can allow other mods to read its files.
Each ModFS file system:
- Has a maximum size of 32 MB (
MOD_FS_MAX_SIZE). Files can be any size, as long as the cumulative sum of file sizes doesn't exceed this limit. - Can store at most 512 files (
MOD_FS_MAX_FILES). - Is stored on disk as a
.modfsfile, which is a ZIP file, containing all files written in it.
The ModFS files are located in the sav directory at the usual save file location:
- Windows:
%appdata%/sm64coopdx - Linux:
~/.local/share/sm64coopdx - MacOS:
~/Library/Application Support/sm64coopdx
- The maximum filepath length is 256 characters (
MOD_FS_MAX_PATH), including the NUL terminator. - Filepaths have the following restrictions:
- Cannot start, end or have two or more consecutive
/ - Can contain only valid ASCII characters, no
*or\ - Cannot be called
properties.json(this name is reserved for ModFS internal properties) - Only the following extensions (and extension-less files) are allowed:
- text:
.txt,.json,.ini,.sav - actors:
.bin,.col - behaviors:
.bhv - textures:
.tex,.png - levels:
.lvl - audio:
.m64,.aiff,.mp3,.ogg
- text:
- Cannot start, end or have two or more consecutive
The object holding the file system of the mod.
All fields are immutable.
| Name | Type |
|---|---|
| mod | Mod |
| modPath | string |
| numFiles | integer |
| totalSize | integer |
| isPublic | boolean |
Fields can be accessed in Lua with the dot . character:
print("The ModFS " .. modFs.modPath .. " contains " .. modFs.numFiles .. " files.")| Name | Reference |
|---|---|
| get_filename | mod_fs_get_filename |
| get_file | mod_fs_get_file |
| create_file | mod_fs_create_file |
| move_file | mod_fs_move_file |
| copy_file | mod_fs_copy_file |
| delete_file | mod_fs_delete_file |
| clear | mod_fs_clear |
| save | mod_fs_save |
| delete | mod_fs_delete |
| set_public | mod_fs_set_public |
Methods can be called in Lua with the colon : character:
print("The first file of ModFS " .. modFs.modPath .. " is named " .. modFs:get_filename(0) .. ".")A handle to a ModFS file.
All fields are immutable.
| Field | Type |
|---|---|
| modFs | ModFs |
| filepath | string |
| size | integer |
| offset | integer |
| isText | boolean |
| isPublic | boolean |
Fields can be accessed in Lua with the dot . character:
print("The ModFS file " .. file.filepath .. " is " .. file.size .. " bytes long.")| Name | Reference |
|---|---|
| read_bool | mod_fs_file_read_bool |
| read_integer | mod_fs_file_read_integer |
| read_number | mod_fs_file_read_number |
| read_bytes | mod_fs_file_read_bytes |
| read_string | mod_fs_file_read_string |
| read_line | mod_fs_file_read_line |
| write_bool | mod_fs_file_write_bool |
| write_integer | mod_fs_file_write_integer |
| write_number | mod_fs_file_write_number |
| write_bytes | mod_fs_file_write_bytes |
| write_string | mod_fs_file_write_string |
| write_line | mod_fs_file_write_line |
| seek | mod_fs_file_seek |
| rewind | mod_fs_file_rewind |
| is_eof | mod_fs_file_is_eof |
| fill | mod_fs_file_fill |
| erase | mod_fs_file_erase |
| set_text_mode | mod_fs_file_set_text_mode |
| set_public | mod_fs_file_set_public |
Methods can be called in Lua with the colon : character:
file:erase(file.size)
print("The ModFS file " .. file.filepath .. " is now empty.")All errors coming from ModFS functions are not blocking. However, they appear in the console and raise the "Mod has script errors" message.
- The function
mod_fs_hide_errorscan suppress the ModFS errors from the console. - Use the function
mod_fs_get_last_errorto retrieve the last error raised by ModFS. This function always return an error message if an error occurred, even if errors are hidden.
One of the strengths of this feature is its interactions with other existing features of sm64coopdx:
- Load models with
smlua_model_util_get_id - Load textures with
get_texture_info - Load collisions with
smlua_collision_util_get - Load sequences with
smlua_audio_utils_replace_sequence - Load audio streams with
audio_stream_load - Load audio samples with
audio_sample_load
These functions can take a ModFS URI as argument instead of a resource name.
Generate a ModFS URI from a ModFs object with the following code:
local uri = string.format(MOD_FS_URI_FORMAT, modFs.modPath, "<filepath>")Here are some examples:
-- Models
local custom_geo_uri = string.format(MOD_FS_URI_FORMAT, modFs.modPath, "custom_geo.bin")
local E_MODEL_CUSTOM = smlua_model_util_get_id(custom_geo_uri)
-- Textures (both PNG and TEX)
local texture_png_uri = string.format(MOD_FS_URI_FORMAT, modFs.modPath, "texture.png")
local TEXTURE_PNG = get_texture_info(texture_png_uri)
local texture_tex_uri = string.format(MOD_FS_URI_FORMAT, modFs.modPath, "texture.tex")
local TEXTURE_TEX = get_texture_info(texture_tex_uri)
-- Collisions
local custom_col_uri = string.format(MOD_FS_URI_FORMAT, modFs.modPath, "custom_col.col")
local COL_CUSTOM = smlua_collision_util_get(custom_col_uri)
-- Sequences
local custom_m64_uri = string.format(MOD_FS_URI_FORMAT, modFs.modPath, "custom.m64")
smlua_audio_utils_replace_sequence(SEQ_LEVEL_GRASS, 0x11, 0x80, custom_m64_uri)
-- Streams
local custom_stream_uri = string.format(MOD_FS_URI_FORMAT, modFs.modPath, "custom_stream.mp3")
local custom_stream = audio_stream_load(custom_stream_uri)
-- Samples
local custom_sample_uri = string.format(MOD_FS_URI_FORMAT, modFs.modPath, "custom_sample.mp3")
local custom_sample = audio_sample_load(custom_sample_uri)Use the following piece of code to always retrieve a valid ModFs object:
local modFs = mod_fs_get() or mod_fs_create()If the ModFS for the current mod doesn't exist, it will create one.
Use the following piece of code to always retrieve a valid ModFsFile object:
local file = modFs:get_file("myfile.txt") or modFs:create_file("myfile.txt", true)Like previously, if the file doesn't exist, it will create one.
To make sure the file is empty when requested, add the following line to clear the existing file content.
file:erase(file.size)The get_file method of a ModFs object opens a file only if the file is not loaded yet. Subsequent calls with the same filename will return the file handle without resetting its offset or mode.
For example, one function could write to a file while another could read from the same file, so it's better to set the appropriate file offset and mode when it's needed before starting reading/writing:
local file = modFs:get_file("myfile.txt")
file:set_text_mode(true) -- Set mode to text
file:rewind() -- Reset offset to the beginning of the fileAlways use ModFs and ModFsFile objects methods over regular functions.
It's more clear that way and helps to reduce errors:
-- Don't
local file = mod_fs_create_file(modFs, "myfile.txt", true)
-- Do
local file = modFs:create_file("myfile.txt", true)-- Don't
mod_fs_file_write_string(file, "some text")
-- Do
file:write_string("some text")In addition to error messages that can be retrieved with mod_fs_get_last_error, almost all ModFS functions have a boolean return value indicating if the function succeeded or failed.
if not modFs:delete_file("somefile") then
print(mod_fs_get_last_error())
endModFS are not saved automatically when writing to files.
The mod has to explicitly call the method save to save its ModFS on the disk.
modFs:save()