ModFs improvements (#907)
Some checks failed
Build coop / build-linux (push) Has been cancelled
Build coop / build-steamos (push) Has been cancelled
Build coop / build-windows-opengl (push) Has been cancelled
Build coop / build-windows-directx (push) Has been cancelled
Build coop / build-macos-arm (push) Has been cancelled
Build coop / build-macos-intel (push) Has been cancelled

* zip + json properties; check existing file in create file

* smlua_audio_utils_replace_sequence

* audio_stream_load, audio_sample_load, smlua_model_util_get_id

* get_texture_info + can also load PNG files

* smlua_collision_util_get

* add wildcard in properties files + set text mode

* filepath restrictions

* Some mod_storage improvements

- Cache mod storage files into a map to reduce file I/O
- Fix a bug in mod_storage_save
- Add mod_storage_load_all that returns all keys/values as a table

* shutdown; fix buffer overflow; fix warnings; lua table

* reject binary files starting with MZ or ELF

* function members

* better doc

* adding file rewind

* ModFS guide; replace yaml by ini; read string buffer changes
This commit is contained in:
PeachyPeach 2025-10-21 19:42:06 +02:00 committed by GitHub
parent 25838f13bc
commit 32f395fb0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 37469 additions and 10542 deletions

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
python3 ./autogen/gen_math.py $1
python3 ./autogen/convert_functions.py $1
python3 ./autogen/convert_structs.py $1
python3 ./autogen/gen_hooks.py $1
python3 ./autogen/convert_functions.py $1
python3 ./autogen/convert_constants.py $1
python3 ./autogen/extract_display_lists.py $1

View file

@ -5,6 +5,7 @@ from vec_types import *
usf_types = ['u8', 'u16', 'u32', 'u64', 's8', 's16', 's32', 's64', 'f32', 'f64']
vec_types = list(VEC_TYPES.keys())
typedef_pointers = ['BehaviorScript', 'ObjectAnimPointer', 'Collision', 'LevelScript', 'Trajectory', 'Texture']
cobject_function_identifier = 'FUNCTION'
type_mappings = {
'char': 's8',
@ -117,6 +118,12 @@ def translate_type_to_lvt(ptype, allowArrays=False):
if ptype == "LuaFunction":
return "LVT_LUAFUNCTION"
if ptype == "LuaTable":
return "LVT_LUATABLE"
if ptype == cobject_function_identifier:
return "LVT_FUNCTION"
if "struct" in ptype:
if pointerLvl > 1:
return "LVT_???"
@ -194,6 +201,12 @@ def translate_type_to_lot(ptype, allowArrays=True):
if ptype == 'LuaFunction':
return 'LOT_NONE'
if ptype == 'LuaTable':
return 'LOT_NONE'
if ptype == cobject_function_identifier:
return 'LOT_NONE'
if ptype in override_types:
return 'LOT_' + ptype.upper()
@ -280,6 +293,12 @@ def translate_type_to_lua(ptype):
if ptype == 'LuaFunction':
return '`Lua Function` ()', None
if ptype == 'LuaTable':
return '`table`', None
if ptype == cobject_function_identifier:
return cobject_function_identifier, None
if ptype.count('*') == 1 and '???' not in translate_type_to_lvt(ptype):
ptype = ptype.replace('const', '').replace('*', '').strip()
s = '`Pointer` <`%s`>' % translate_type_to_lua(ptype)[0].replace('`', '').strip()

View file

@ -67,7 +67,7 @@ exclude_constants = {
"src/pc/djui/djui_console.h": [ "CONSOLE_MAX_TMP_BUFFER" ],
"src/pc/lua/smlua_hooks.h": [ "MAX_HOOKED_MOD_MENU_ELEMENTS", "^HOOK_RETURN_.*", "^ACTION_HOOK_.*", "^MOD_MENU_ELEMENT_.*" ],
"src/pc/djui/djui_panel_menu.h": [ "RAINBOW_TEXT_LEN" ],
"src/pc/mods/mod_fs.h": [ "MOD_FS_DIRECTORY", "MOD_FS_EXTENSION", "MOD_FS_VERSION", "INT_TYPE_MAX", "FLOAT_TYPE_MAX", "FILE_SEEK_MAX" ],
"src/pc/mods/mod_fs.h": [ "INT_TYPE_MAX", "FLOAT_TYPE_MAX", "FILE_SEEK_MAX" ],
}
include_constants = {
@ -139,6 +139,7 @@ defined_values = {
'VERSION_JP': False,
'VERSION_SH': False,
'F3DEX_GBI_2': True,
'DEVELOPMENT': False,
}
############################################################################
@ -255,15 +256,17 @@ def process_define(filename, line, inIfBlock):
val = val.replace('(u8)', '')
val = val.replace('(u64)', '')
val = re.sub(r'\.\d+f', '', val)
val = val.strip()
for p in val.split(' '):
if p.startswith('0x'):
continue
p = re.sub(r'0x[a-fA-F0-9]+', '', p)
if re.search(r'[a-z]', p) != None and "VERSION_TEXT" not in line and "SM64COOPDX_VERSION" not in line:
if 'gCurrentObject' not in line and verbose:
print('UNRECOGNIZED DEFINE: ' + line)
return None
if not (val.startswith('"') and val.endswith('"') and '"' not in val[1:-1]):
for p in val.split(' '):
if p.startswith('0x'):
continue
p = re.sub(r'0x[a-fA-F0-9]+', '', p)
if re.search(r'[a-z]', p) != None and "VERSION_TEXT" not in line and "SM64COOPDX_VERSION" not in line:
if 'gCurrentObject' not in line and verbose:
print('UNRECOGNIZED DEFINE: ' + line)
return None
if not allowed_identifier(filename, ident):
return None

View file

@ -138,6 +138,8 @@ override_disallowed_functions = {
"src/game/first_person_cam.h": [ "first_person_update" ],
"src/pc/lua/utils/smlua_collision_utils.h": [ "collision_find_surface_on_ray" ],
"src/engine/behavior_script.h": [ "stub_behavior_script_2", "cur_obj_update" ],
"src/pc/mods/mod_storage.h": [ "mod_storage_shutdown" ],
"src/pc/mods/mod_fs.h": [ "mod_fs_read_file_from_uri", "mod_fs_shutdown" ],
"src/pc/utils/misc.h": [ "str_.*", "file_get_line", "delta_interpolate_(normal|rgba|mtx)", "detect_and_skip_mtx_interpolation", "precise_delay_f64" ],
"src/engine/lighting_engine.h": [ "le_calculate_vertex_lighting", "le_clear", "le_shutdown" ],
}
@ -813,6 +815,8 @@ def build_param(fid, param, i):
return ' %s %s = smlua_to_bytestring(L, %d);\n' % (ptype, pid, i)
elif ptype == 'LuaFunction':
return ' %s %s = smlua_to_lua_function(L, %d);\n' % (ptype, pid, i)
elif ptype == 'LuaTable':
return ' %s %s = smlua_to_lua_table(L, %d);\n' % (ptype, pid, i)
elif translate_type_to_lot(ptype) == 'LOT_POINTER':
lvt = translate_type_to_lvt(ptype)
return ' %s %s = (%s)smlua_to_cpointer(L, %d, %s);\n' % (ptype, pid, ptype, i, lvt)
@ -868,6 +872,8 @@ def build_call(function):
lfunc = 'lua_pushstring'
elif ftype == 'ByteString':
lfunc = 'smlua_push_bytestring'
elif ftype == 'LuaTable':
lfunc = 'smlua_push_lua_table'
elif translate_type_to_lot(ftype) == 'LOT_POINTER':
lvt = translate_type_to_lvt(ftype)
return ' smlua_push_pointer(L, %s, (void*)%s, NULL);\n' % (lvt, ccall)

View file

@ -323,6 +323,14 @@ def parse_struct(struct_str, sortFields = False):
field['identifier'] = field_id.strip()
field['field_str'] = field_str
# handle function members
if field['type'].startswith(cobject_function_identifier):
field_function = field['identifier']
field_type, field_id = field['type'].split()
field['type'] = field_type.strip()
field['identifier'] = field_id.strip('"').strip()
field['function'] = field_function.strip()
struct['fields'].append(field)
if identifier == 'Object':
@ -486,6 +494,9 @@ def get_struct_field_info(struct, field):
if fid in override_field_mutable[sid] or '*' in override_field_mutable[sid]:
fimmutable = 'false'
if ftype == cobject_function_identifier:
fimmutable = 'true'
if not ('char' in ftype and '[' in ftype and 'unsigned' not in ftype):
array_match = re.search(r'\[([^\]]+)\]', ftype)
if array_match:
@ -508,6 +519,7 @@ def build_struct(struct):
# build up table and track column width
field_table = []
field_functions = []
for field in struct['fields']:
fid, ftype, fimmutable, lvt, lot, size = get_struct_field_info(struct, field)
@ -533,22 +545,40 @@ def build_struct(struct):
startStr += '#ifndef ' + override_field_version_excludes[fid] + '\n'
endStr += '\n#endif'
startStr += ' { '
row.append(startStr )
row.append('"%s", ' % fid )
row.append('%s, ' % lvt )
row.append('offsetof(%s%s, %s), ' % (struct_str, name, field['identifier']))
row.append('%s, ' % fimmutable )
row.append('%s, ' % lot )
row.append('%s, ' % size )
row.append('sizeof(%s)' % ftype )
row.append(endStr )
if ftype == cobject_function_identifier:
row.append(startStr )
row.append('"%s", ' % fid )
row.append('%s, ' % lvt )
row.append('(size_t) FUNCTION__%s, ' % (field['function']))
row.append('%s, ' % fimmutable )
row.append('%s, ' % lot )
row.append('%s, ' % size )
row.append('sizeof(const char *)' )
row.append(endStr )
field_functions.append(field['function'])
else:
row.append(startStr )
row.append('"%s", ' % fid )
row.append('%s, ' % lvt )
row.append('offsetof(%s%s, %s), ' % (struct_str, name, field['identifier']))
row.append('%s, ' % fimmutable )
row.append('%s, ' % lot )
row.append('%s, ' % size )
row.append('sizeof(%s)' % ftype )
row.append(endStr )
field_table.append(row)
field_table_str, field_count = table_to_string(field_table)
field_count_define = 'LUA_%s_FIELD_COUNT' % identifier_to_caps(sid)
struct_lot = 'LOT_%s' % sid.upper()
s = "#define %s $[STRUCTFIELDCOUNT]\n" % field_count_define
s = ''
if field_functions:
for field_function in field_functions:
s += 'static const char FUNCTION__%s[] = "%s";\n' % (field_function, field_function)
s += '\n'
s += "#define %s $[STRUCTFIELDCOUNT]\n" % field_count_define
s += "static struct LuaObjectField s%sFields[%s] = {\n" % (sid, field_count_define)
s += field_table_str
s += '};\n'
@ -648,6 +678,21 @@ def build_includes():
############################################################################
# HACK: Parse docs/functions.md to find the page where the function is documented
function_links = {}
def doc_find_function_link(function):
if not function_links:
with open('docs/lua/functions.md') as f:
lines = f.readlines()
for line in lines:
line = line.replace(' ', '').strip()
res, n = re.subn(r'^-\[(.*)\]\((.*)\)', '\\1,\\2', line)
if n != 0:
fname, flink = res.split(',')
function_links[fname] = flink
return function_links.get(function, '')
def doc_struct_index(structs):
s = '# Supported Structs\n'
for struct in structs:
@ -664,26 +709,30 @@ def doc_struct_field(struct, field):
sid = struct['identifier']
if sid in override_field_invisible:
if fid in override_field_invisible[sid]:
return ''
return '', False
if sid in override_field_deprecated:
if fid in override_field_deprecated[sid]:
return ''
return '', False
if '???' in lvt or '???' in lot:
return ''
return '', False
ftype, flink = translate_type_to_lua(ftype)
if ftype == cobject_function_identifier:
flink = doc_find_function_link(field['function'])
return '| %s | [`%s`](%s) |\n' % (fid, field['function'], flink), True
restrictions = ('', 'read-only')[fimmutable == 'true']
global total_fields
total_fields += 1
if flink:
return '| %s | [%s](%s) | %s |\n' % (fid, ftype, flink, restrictions)
return '| %s | [%s](%s) | %s |\n' % (fid, ftype, flink, restrictions), False
return '| %s | %s | %s |\n' % (fid, ftype, restrictions)
return '| %s | %s | %s |\n' % (fid, ftype, restrictions), False
def doc_struct_object_fields(struct):
@ -698,7 +747,8 @@ def doc_struct_object_fields(struct):
s += "| Field | Type | Access |\n"
s += "| ----- | ---- | ------ |\n"
s += doc_struct_field(struct, field)
line, _ = doc_struct_field(struct, field)
s += line
return s
@ -709,17 +759,27 @@ def doc_struct(struct):
s += "| Field | Type | Access |\n"
s += "| ----- | ---- | ------ |\n"
# build doc table
field_table = []
field_functions = ''
for field in struct['fields']:
if 'object_field' in field and field['object_field'] == True:
continue
s += doc_struct_field(struct, field)
line, isFunction = doc_struct_field(struct, field)
if isFunction:
field_functions += line
else:
s += line
if sid == 'Object':
s += doc_struct_object_fields(struct)
# functions
if field_functions:
s += '\n**Functions:**\n\n'
s += "| Name | Reference |\n"
s += "| ---- | --------- |\n"
s += field_functions
s += '\n[:arrow_up_small:](#)\n\n<br />\n'
return s
@ -742,6 +802,32 @@ def doc_structs(structs):
def_pointers = []
# HACK: Parse autogen/lua_definitions/functions.lua to find the function signature
function_signatures = {}
def get_function_signature(function):
if not function_signatures:
with open('autogen/lua_definitions/functions.lua') as f:
lines = f.readlines()
function_params = []
function_return = None
for line in lines:
if line.startswith('--- @param'):
function_params.append(line.split()[2:4])
elif line.startswith('--- @return'):
function_return = line.split()[2]
elif line.startswith('function'):
sig = 'fun('
sig += ', '.join(['%s: %s' % (param_name, param_type) for param_name, param_type in function_params])
sig += ')'
if function_return:
sig += ': %s' % (function_return)
function_name = line.replace('(', ' ').split()[1]
function_signatures[function_name] = sig
function_params.clear()
function_return = None
return function_signatures.get(function, 'function')
def def_struct(struct):
sid = struct['identifier']
@ -763,7 +849,12 @@ def def_struct(struct):
ftype, flink = translate_type_to_lua(ftype)
ftype = translate_to_def(ftype)
# try to get the function signature
if ftype == cobject_function_identifier:
ftype = get_function_signature(field['function'])
else:
ftype = translate_to_def(ftype)
if ftype.startswith('Pointer_') and ftype not in def_pointers:
def_pointers.append(ftype)

View file

@ -1,6 +1,7 @@
import os
import re
import sys
from common import cobject_function_identifier
def extract_structs(filename):
with open(filename) as file:
@ -36,6 +37,9 @@ def extract_structs(filename):
while (' ' in txt):
txt = txt.replace(' ', ' ')
# handle function members (NOT function pointers)
txt = re.sub(f'{cobject_function_identifier}\\((.*),(.*)\\)', f'{cobject_function_identifier} \\1 \\2', txt)
# strip macros
txt = re.sub(r'[^a-zA-Z0-9_][A-Z0-9_]+\(.*\)', '', txt)

View file

@ -4557,14 +4557,20 @@ GRAB_POS_BOWSER = 3 --- @type MarioGrabPosGSCId
--- | `GRAB_POS_BOWSER`
--- @type integer
MOD_FS_MAX_SIZE = 0x1000000
MOD_FS_MAX_SIZE = 0x2000000
--- @type integer
MOD_FS_MAX_FILES = 0x100
MOD_FS_MAX_FILES = 0x200
--- @type integer
MOD_FS_MAX_PATH = 0x100
--- @type string
MOD_FS_URI_PREFIX = "modfs:/"
--- @type string
MOD_FS_URI_FORMAT = "modfs:/%s/%s"
INT_TYPE_U8 = 0 --- @type ModFsFileIntType
INT_TYPE_U16 = 1 --- @type ModFsFileIntType
INT_TYPE_U32 = 2 --- @type ModFsFileIntType
@ -6758,6 +6764,9 @@ R_CBUTTONS = CONT_F
--- @type integer
D_CBUTTONS = CONT_D
--- @type string
PALETTES_DIRECTORY = "palettes"
--- @type integer
MAX_PRESET_PALETTES = 128
@ -11169,5 +11178,11 @@ VERSION_NUMBER = 41
--- @type integer
MINOR_VERSION_NUMBER = 0
--- @type string
GAME_NAME = "sm64coopdx"
--- @type string
WINDOW_NAME = "Super Mario 64 Coop Deluxe"
--- @type integer
MAX_VERSION_LENGTH = 128

View file

@ -7435,25 +7435,6 @@ function mod_fs_create()
-- ...
end
--- @return boolean
--- Deletes the modfs object of the active mod if it exists. Returns true on success
function mod_fs_delete()
-- ...
end
--- @return boolean
--- Saves the modfs object of the active mod if it exists. Returns true on success
function mod_fs_save()
-- ...
end
--- @param pub boolean
--- @return boolean
--- Marks the modfs object of the active mod as public (i.e. readable by other mods) if it exists. Returns true on success
function mod_fs_set_public(pub)
-- ...
end
--- @param modFs ModFs
--- @param index integer
--- @return string
@ -7514,6 +7495,28 @@ function mod_fs_clear(modFs)
-- ...
end
--- @param modFs ModFs
--- @return boolean
--- Saves the provided `modFs` to persistent storage. Returns true on success
function mod_fs_save(modFs)
-- ...
end
--- @param modFs ModFs
--- @return boolean
--- Removes the provided `modFs` from persistent storage and deletes its object. Returns true on success
function mod_fs_delete(modFs)
-- ...
end
--- @param modFs ModFs
--- @param pub boolean
--- @return boolean
--- Marks the provided `modFs` as public (i.e. readable by other mods). Returns true on success
function mod_fs_set_public(modFs, pub)
-- ...
end
--- @param file ModFsFile
--- @return boolean
--- Reads a boolean from a binary modfs `file`
@ -7618,6 +7621,13 @@ function mod_fs_file_seek(file, offset, origin)
-- ...
end
--- @param file ModFsFile
--- @return boolean
--- Sets the current position of a modfs `file` to its beginning. Returns true on success
function mod_fs_file_rewind(file)
-- ...
end
--- @param file ModFsFile
--- @return boolean
--- Returns true if the provided modfs `file` has reached its end of file
@ -7642,6 +7652,14 @@ function mod_fs_file_erase(file, length)
-- ...
end
--- @param file ModFsFile
--- @param text boolean
--- @return boolean
--- Marks the provided modfs `file` as text. Returns true on success
function mod_fs_file_set_text_mode(file, text)
-- ...
end
--- @param file ModFsFile
--- @param pub boolean
--- @return boolean
@ -7707,6 +7725,12 @@ function mod_storage_load_bool(key)
-- ...
end
--- @return table
--- Loads all keys and values in mod storage as strings and returns them as a table
function mod_storage_load_all()
-- ...
end
--- @param key string
--- @return boolean
--- Checks if a `key` is in mod storage

View file

@ -1280,7 +1280,7 @@
--- @field public customBehaviorIndex integer
--- @class ModAudio
--- @field public file ModFile
--- @field public filepath string
--- @field public isStream boolean
--- @field public baseVolume number
--- @field public loaded boolean
@ -1304,6 +1304,16 @@
--- @field public numFiles integer
--- @field public totalSize integer
--- @field public isPublic boolean
--- @field public get_filename fun(modFs: ModFs, index: integer): string
--- @field public get_file fun(modFs: ModFs, filepath: string): ModFsFile
--- @field public create_file fun(modFs: ModFs, filepath: string, text: boolean): ModFsFile
--- @field public move_file fun(modFs: ModFs, oldpath: string, newpath: string, overwriteExisting: boolean): boolean
--- @field public copy_file fun(modFs: ModFs, srcpath: string, dstpath: string, overwriteExisting: boolean): boolean
--- @field public delete_file fun(modFs: ModFs, filepath: string): boolean
--- @field public clear fun(modFs: ModFs): boolean
--- @field public save fun(modFs: ModFs): boolean
--- @field public delete fun(modFs: ModFs): boolean
--- @field public set_public fun(modFs: ModFs, pub: boolean): boolean
--- @class ModFsFile
--- @field public modFs ModFs
@ -1312,6 +1322,25 @@
--- @field public offset integer
--- @field public isText boolean
--- @field public isPublic boolean
--- @field public read_bool fun(file: ModFsFile): boolean
--- @field public read_integer fun(file: ModFsFile, intType: ModFsFileIntType): integer
--- @field public read_number fun(file: ModFsFile, floatType: ModFsFileFloatType): number
--- @field public read_bytes fun(file: ModFsFile, length: integer): string
--- @field public read_string fun(file: ModFsFile): string
--- @field public read_line fun(file: ModFsFile): string
--- @field public write_bool fun(file: ModFsFile, value: boolean): boolean
--- @field public write_integer fun(file: ModFsFile, value: integer, intType: ModFsFileIntType): boolean
--- @field public write_number fun(file: ModFsFile, value: number, floatType: ModFsFileFloatType): boolean
--- @field public write_bytes fun(file: ModFsFile, bytestring: string): boolean
--- @field public write_string fun(file: ModFsFile, str: string): boolean
--- @field public write_line fun(file: ModFsFile, str: string): boolean
--- @field public seek fun(file: ModFsFile, offset: integer, origin: ModFsFileSeek): boolean
--- @field public rewind fun(file: ModFsFile): boolean
--- @field public is_eof fun(file: ModFsFile): boolean
--- @field public fill fun(file: ModFsFile, byte: integer, length: integer): boolean
--- @field public erase fun(file: ModFsFile, length: integer): boolean
--- @field public set_text_mode fun(file: ModFsFile, text: boolean): boolean
--- @field public set_public fun(file: ModFsFile, pub: boolean): boolean
--- @class ModeTransitionInfo
--- @field public newMode integer

View file

@ -37,17 +37,17 @@ void dynos_generate_packs(const char* directory);
// -- geos -- //
void dynos_actor_override(struct Object* obj, void** aSharedChild);
void dynos_add_actor_custom(s32 modIndex, s32 modFileIndex, const char *filePath, const char* geoName);
bool dynos_add_actor_custom(s32 modIndex, s32 modFileIndex, const char *filePath, const char* geoName);
const void* dynos_geolayout_get(const char *name);
bool dynos_actor_get_mod_index_and_token(struct GraphNode *graphNode, u32 tokenIndex, s32 *modIndex, s32 *modFileIndex, const char **token);
void dynos_actor_register_modified_graph_node(struct GraphNode *node);
// -- collisions -- //
void dynos_add_collision(const char *filePath, const char* collisionName);
bool dynos_add_collision(const char *filePath, const char* collisionName);
Collision* dynos_collision_get(const char* collisionName);
// -- textures -- //
void dynos_add_texture(const char *filePath, const char* textureName);
bool dynos_add_texture(const char *filePath, const char* textureName);
bool dynos_texture_get(const char* textureName, struct TextureInfo* outTextureInfo);
bool dynos_texture_get_from_data(const Texture *tex, struct TextureInfo* outTextureInfo);
void dynos_texture_override_set(const char* textureName, struct TextureInfo* overrideTextureInfo);

View file

@ -897,7 +897,7 @@ void DynOS_Pack_AddTex(PackData* aPackData, DataNode<TexData>* aTexData);
//
std::map<const void *, ActorGfx> &DynOS_Actor_GetValidActors();
void DynOS_Actor_AddCustom(s32 aModIndex, s32 aModFileIndex, const SysPath &aFilename, const char *aActorName);
bool DynOS_Actor_AddCustom(s32 aModIndex, s32 aModFileIndex, const SysPath &aFilename, const char *aActorName);
const void *DynOS_Actor_GetLayoutFromName(const char *aActorName);
bool DynOS_Actor_GetModIndexAndToken(const GraphNode *aGraphNode, u32 aTokenIndex, s32 *outModIndex, s32 *outModFileIndex, const char **outToken);
ActorGfx* DynOS_Actor_GetActorGfx(const GraphNode* aGraphNode);
@ -925,7 +925,7 @@ u8 *DynOS_Tex_ConvertToRGBA32(const u8 *aData, u64 aLength, s32 aFormat, s32 aSi
bool DynOS_Tex_Import(void **aOutput, void *aPtr, s32 aTile, void *aGfxRApi, void **aHashMap, void *aPool, u32 *aPoolPos, u32 aPoolSize);
void DynOS_Tex_Activate(DataNode<TexData>* aNode, bool aCustomTexture);
void DynOS_Tex_Deactivate(DataNode<TexData>* aNode);
void DynOS_Tex_AddCustom(const SysPath &aFilename, const char *aTexName);
bool DynOS_Tex_AddCustom(const SysPath &aFilename, const char *aTexName);
bool DynOS_Tex_Get(const char* aTexName, struct TextureInfo* aOutTexInfo);
bool DynOS_Tex_GetFromData(const Texture *aTex, struct TextureInfo* aOutTexInfo);
void DynOS_Tex_Override_Set(const char* textureName, struct TextureInfo* aOverrideTexInfo);
@ -963,7 +963,7 @@ void DynOS_Bhv_ModShutdown();
// Col Manager
//
void DynOS_Col_Activate(const SysPath &aFilePath, const char *aCollisionName);
bool DynOS_Col_Activate(const SysPath &aFilePath, const char *aCollisionName);
Collision* DynOS_Col_Get(const char* collisionName);
void DynOS_Col_ModShutdown();

View file

@ -1,6 +1,10 @@
#include "dynos.cpp.h"
#include <zlib.h>
extern "C" {
#include "pc/mods/mod_fs.h"
}
static const u64 DYNOS_BIN_COMPRESS_MAGIC = 0x4E4942534F4E5944llu;
static FILE *sFile = NULL;
static u8 *sBufferUncompressed = NULL;
@ -154,9 +158,75 @@ bool DynOS_Bin_Compress(const SysPath &aFilename) {
return true;
}
static BinFile *DynOS_Bin_Decompress_ModFs(const SysPath &aFilename) {
DynOS_Bin_Compress_Init();
// Read file data
void *_Buffer = NULL;
u32 _Size = 0;
if (!mod_fs_read_file_from_uri(aFilename.c_str(), &_Buffer, &_Size)) {
DynOS_Bin_Compress_Free();
return NULL;
}
sBufferCompressed = (u8 *) _Buffer;
sLengthCompressed = _Size;
// Check file length
u64 _LengthHeader = (u64) (sizeof(u64) + sizeof(u64));
if (!DynOS_Bin_Compress_Check(
sLengthCompressed >= _LengthHeader,
__FUNCTION__, aFilename.c_str(), "Empty file"
)) return NULL;
// Compare with magic constant
// If not equal, it's not a compressed file
u64 _Magic = ((u64 *) _Buffer)[0];
if (_Magic != DYNOS_BIN_COMPRESS_MAGIC) {
BinFile *_BinFile = BinFile::OpenB(sBufferCompressed, sLengthCompressed);
DynOS_Bin_Compress_Free();
return _BinFile;
}
PrintNoNewLine("Decompressing file \"%s\"...", aFilename.c_str());
// Read expected uncompressed file size
sLengthUncompressed = ((u64 *) _Buffer)[1];
sLengthCompressed -= _LengthHeader;
u8 *_BufferCompressed = sBufferCompressed + _LengthHeader;
// Allocate memory for uncompressed buffer
if (!DynOS_Bin_Compress_Check(
(sBufferUncompressed = (u8 *) calloc(sLengthUncompressed, sizeof(u8))) != NULL,
__FUNCTION__, aFilename.c_str(), "Cannot allocate memory for decompression"
)) return NULL;
// Uncompress data
uLongf _LengthUncompressed = (uLongf)sLengthUncompressed;
int uncompressRc = uncompress(sBufferUncompressed, &_LengthUncompressed, _BufferCompressed, sLengthCompressed);
sLengthUncompressed = _LengthUncompressed;
if (!DynOS_Bin_Compress_Check(
uncompressRc == Z_OK,
__FUNCTION__, aFilename.c_str(), "Cannot uncompress data"
)) {
PrintError("ERROR: uncompress rc: %d, length uncompressed: %lu, length compressed: %lu, length header: %lu", uncompressRc, sLengthUncompressed, sLengthCompressed, _LengthHeader);
return NULL;
}
Print("uncompress rc: %d, length uncompressed: %lu, length compressed: %lu, length header: %lu", uncompressRc, sLengthUncompressed, sLengthCompressed, _LengthHeader);
// Return uncompressed data as a BinFile
BinFile *_BinFile = BinFile::OpenB(sBufferUncompressed, sLengthUncompressed);
DynOS_Bin_Compress_Free();
Print(" Done.");
return _BinFile;
}
BinFile *DynOS_Bin_Decompress(const SysPath &aFilename) {
DynOS_Bin_Compress_Init();
// Check modfs
if (is_mod_fs_file(aFilename.c_str())) {
return DynOS_Bin_Decompress_ModFs(aFilename);
}
// Open input file
if (!DynOS_Bin_Compress_Check(
(sFile = f_open_r(aFilename.c_str())) != NULL,

View file

@ -2,8 +2,11 @@
extern "C" {
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb/stb_image_write.h"
#include "pc/mods/mod_fs.h"
}
#define PNG_SIGNATURE 0x0A1A0A0D474E5089llu
///////////
// Utils //
///////////
@ -304,46 +307,67 @@ DataNode<TexData>* DynOS_Tex_LoadFromBinary(const SysPath &aPackFolder, const Sy
// Load data from binary file
DataNode<TexData>* _TexNode = NULL;
BinFile *_File = BinFile::OpenR(aFilename.c_str());
BinFile *_File = NULL;
if (is_mod_fs_file(aFilename.c_str())) {
void *_Buffer = NULL;
u32 _Size = 0;
if (mod_fs_read_file_from_uri(aFilename.c_str(), &_Buffer, &_Size)) {
_File = BinFile::OpenB((const u8 *) _Buffer, _Size);
free(_Buffer);
}
} else {
_File = BinFile::OpenR(aFilename.c_str());
}
if (!_File) { return NULL; }
u8 type = _File->Read<u8>();
if (type == DATA_TYPE_TEXTURE) {
// load png-texture
_TexNode = New<DataNode<TexData>>();
_TexNode->mData = New<TexData>();
_TexNode->mName.Read(_File);
_TexNode->mData = New<TexData>();
_TexNode->mData->mPngData.Read(_File);
BinFile::Close(_File);
} else if (type == DATA_TYPE_TEXTURE_RAW) {
// load raw-texture
_TexNode = New<DataNode<TexData>>();
_TexNode->mName.Read(_File);
_TexNode->mData = New<TexData>();
_TexNode->mData->mRawFormat = _File->Read<s32>();
_TexNode->mData->mRawSize = _File->Read<s32>();
_TexNode->mData->mRawWidth = _File->Read<s32>();
_TexNode->mData->mRawHeight = _File->Read<s32>();
_TexNode->mData->mRawData.Read(_File);
} else if ((_File->SetOffset(0), _File->Read<u64>() == PNG_SIGNATURE)) {
_File->SetOffset(0);
// load PNG file
_TexNode = New<DataNode<TexData>>();
_TexNode->mName = aFilename.c_str();
_TexNode->mData = New<TexData>();
_TexNode->mData->mPngData.Resize(_File->Size());
_File->Read<u8>(_TexNode->mData->mPngData.begin(), _File->Size());
}
BinFile::Close(_File);
if (_TexNode) {
// For some reason texture nodes are indexed to DynosCustomTexs by their node name,
// and not by `aTexName`, but DynOS_Tex_Get searches for `aTexName`...
// Normally, this doesn't cause any issue, but things go wrong when `aTexName`
// is not the same as the texture node name (which is the case for modfs files).
if (is_mod_fs_file(aFilename.c_str())) {
_TexNode->mName = aTexName;
}
if (aAddToPack) {
if (!_Pack) { _Pack = DynOS_Pack_Add(aPackFolder); }
DynOS_Pack_AddTex(_Pack, _TexNode);
}
return _TexNode;
} else if (type != DATA_TYPE_TEXTURE_RAW) {
BinFile::Close(_File);
return NULL;
}
// load raw-texture
_TexNode = New<DataNode<TexData>>();
_TexNode->mData = New<TexData>();
_TexNode->mName.Read(_File);
_TexNode->mData->mRawFormat = _File->Read<s32>();
_TexNode->mData->mRawSize = _File->Read<s32>();
_TexNode->mData->mRawWidth = _File->Read<s32>();
_TexNode->mData->mRawHeight = _File->Read<s32>();
_TexNode->mData->mRawData.Read(_File);
BinFile::Close(_File);
if (aAddToPack) {
if (!_Pack) { _Pack = DynOS_Pack_Add(aPackFolder); }
DynOS_Pack_AddTex(_Pack, _TexNode);
}
return _TexNode;

View file

@ -115,8 +115,8 @@ void dynos_actor_override(struct Object* obj, void** aSharedChild) {
DynOS_Actor_Override(obj, aSharedChild);
}
void dynos_add_actor_custom(s32 modIndex, s32 modFileIndex, const char *filePath, const char* geoName) {
DynOS_Actor_AddCustom(modIndex, modFileIndex, filePath, geoName);
bool dynos_add_actor_custom(s32 modIndex, s32 modFileIndex, const char *filePath, const char* geoName) {
return DynOS_Actor_AddCustom(modIndex, modFileIndex, filePath, geoName);
}
const void* dynos_geolayout_get(const char *name) {
@ -133,8 +133,8 @@ void dynos_actor_register_modified_graph_node(struct GraphNode *node) {
// -- collisions -- //
void dynos_add_collision(const char *filePath, const char* collisionName) {
DynOS_Col_Activate(filePath, collisionName);
bool dynos_add_collision(const char *filePath, const char* collisionName) {
return DynOS_Col_Activate(filePath, collisionName);
}
Collision* dynos_collision_get(const char* collisionName) {
@ -143,9 +143,9 @@ Collision* dynos_collision_get(const char* collisionName) {
// -- textures -- //
void dynos_add_texture(const char *filePath, const char* textureName) {
bool dynos_add_texture(const char *filePath, const char* textureName) {
SysPath _FilePath = filePath;
DynOS_Tex_AddCustom(_FilePath, textureName);
return DynOS_Tex_AddCustom(_FilePath, textureName);
}
bool dynos_texture_get(const char* textureName, struct TextureInfo* outTextureInfo) {

View file

@ -9,6 +9,7 @@ extern "C" {
#include "game/object_list_processor.h"
#include "pc/configfile.h"
#include "pc/lua/smlua_hooks.h"
#include "pc/mods/mod_fs.h"
}
// Static maps/arrays
@ -31,7 +32,7 @@ std::map<const void *, ActorGfx> &DynOS_Actor_GetValidActors() {
return DynosValidActors();
}
void DynOS_Actor_AddCustom(s32 aModIndex, s32 aModFileIndex, const SysPath &aFilename, const char *aActorName) {
bool DynOS_Actor_AddCustom(s32 aModIndex, s32 aModFileIndex, const SysPath &aFilename, const char *aActorName) {
const void* georef = DynOS_Builtin_Actor_GetFromName(aActorName);
u16 actorLen = strlen(aActorName);
@ -42,7 +43,7 @@ void DynOS_Actor_AddCustom(s32 aModIndex, s32 aModFileIndex, const SysPath &aFil
if (!_GfxData) {
PrintError(" ERROR: Couldn't load Actor Binary \"%s\" from \"%s\"", actorName, aFilename.c_str());
free(actorName);
return;
return false;
}
_GfxData->mModIndex = aModIndex;
_GfxData->mModFileIndex = aModFileIndex;
@ -51,7 +52,7 @@ void DynOS_Actor_AddCustom(s32 aModIndex, s32 aModFileIndex, const SysPath &aFil
if (!geoLayout) {
PrintError(" ERROR: Couldn't load geo layout for \"%s\"", actorName);
free(actorName);
return;
return false;
}
// Alloc and init the actors gfx list
@ -63,7 +64,7 @@ void DynOS_Actor_AddCustom(s32 aModIndex, s32 aModFileIndex, const SysPath &aFil
if (!actorGfx.mGraphNode) {
PrintError(" ERROR: Couldn't load graph node for \"%s\"", actorName);
free(actorName);
return;
return false;
}
actorGfx.mGraphNode->georef = georef;
@ -76,6 +77,7 @@ void DynOS_Actor_AddCustom(s32 aModIndex, s32 aModFileIndex, const SysPath &aFil
// Add to list
DynOS_Actor_Valid(georef, actorGfx);
free(actorName);
return true;
}
const void *DynOS_Actor_GetLayoutFromName(const char *aActorName) {
@ -115,6 +117,13 @@ const void *DynOS_Actor_GetLayoutFromName(const char *aActorName) {
}
}
// check modfs file
if (is_mod_fs_file(aActorName)) {
if (DynOS_Actor_AddCustom(gLuaActiveMod->index, -1, aActorName, aActorName)) {
return DynOS_Actor_GetLayoutFromName(aActorName);
}
}
return NULL;
}
@ -222,7 +231,6 @@ void DynOS_Actor_Override(struct Object* obj, void** aSharedChild) {
}
}
*aSharedChild = (void*)it->second.mGraphNode;
}

View file

@ -1,17 +1,21 @@
#include "dynos.cpp.h"
extern "C" {
#include "pc/mods/mod_fs.h"
}
static Array<Pair<const char*, DataNode<Collision>*>>& DynosCollisions() {
static Array<Pair<const char*, DataNode<Collision>*>> sDynosCollisions;
return sDynosCollisions;
}
void DynOS_Col_Activate(const SysPath &aFilename, const char *aCollisionName) {
bool DynOS_Col_Activate(const SysPath &aFilename, const char *aCollisionName) {
auto& _DynosCollisions = DynosCollisions();
// check for duplicates
for (s32 i = 0; i < _DynosCollisions.Count(); ++i) {
if (!strcmp(_DynosCollisions[i].first, aCollisionName)) {
return;
return true;
}
}
@ -24,11 +28,12 @@ void DynOS_Col_Activate(const SysPath &aFilename, const char *aCollisionName) {
DataNode<Collision>* _Node = DynOS_Col_LoadFromBinary(aFilename, collisionName);
if (!_Node) {
free(collisionName);
return;
return false;
}
// Add to collisions
_DynosCollisions.Add({ collisionName, _Node });
return true;
}
Collision* DynOS_Col_Get(const char* collisionName) {
@ -51,6 +56,13 @@ Collision* DynOS_Col_Get(const char* collisionName) {
}
}
// check modfs file
if (is_mod_fs_file(collisionName)) {
if (DynOS_Col_Activate(collisionName, collisionName)) {
return DynOS_Col_Get(collisionName);
}
}
// check builtin collisions
return (Collision*)DynOS_Builtin_Col_GetFromName(collisionName);
}

View file

@ -4,6 +4,7 @@
extern "C" {
#include "pc/gfx/gfx.h"
#include "pc/gfx/gfx_rendering_api.h"
#include "pc/mods/mod_fs.h"
}
struct OverrideTexture {
@ -423,13 +424,13 @@ void DynOS_Tex_Deactivate(DataNode<TexData>* aNode) {
_Schedule.Add(aNode);
}
void DynOS_Tex_AddCustom(const SysPath &aFilename, const char *aTexName) {
bool DynOS_Tex_AddCustom(const SysPath &aFilename, const char *aTexName) {
auto& _DynosCustomTexs = DynosCustomTexs();
// check for duplicates
for (s32 i = 0; i < _DynosCustomTexs.Count(); ++i) {
if (!strcmp(_DynosCustomTexs[i].first, aTexName)) {
return;
return true;
}
}
@ -444,7 +445,9 @@ void DynOS_Tex_AddCustom(const SysPath &aFilename, const char *aTexName) {
free(_TexName);
if (_Node) {
DynOS_Tex_Activate(_Node, true);
return true;
}
return false;
}
#define CONVERT_TEXINFO(texName) { \
@ -488,6 +491,13 @@ bool DynOS_Tex_Get(const char* aTexName, struct TextureInfo* aOutTexInfo) {
}
}
// check modfs file
if (is_mod_fs_file(aTexName)) {
if (DynOS_Tex_AddCustom(aTexName, aTexName)) {
return DynOS_Tex_Get(aTexName, aOutTexInfo);
}
}
// check builtin textures
const struct BuiltinTexInfo* info = DynOS_Builtin_Tex_GetInfoFromName(aTexName);
if (!info) {

View file

@ -1,190 +0,0 @@
import os, sys, re
MOD_FS_MAGIC = "MODFSSM64COOPDX"
MOD_FS_HEADER_SIZE = 32
MOD_FS_EXTENSION = ".modfs"
MOD_FS_VERSION = 1
MOD_FS_MAX_SIZE = 0x1000000
MOD_FS_MAX_FILES = 0x100
MOD_FS_MAX_PATH = 0x100
def usage():
print("""
Directory to modfs:
python dir2modfs.py <dirpath> [--set-public] [--set-file-public <files>...]
Parameters:
dirpath Path to directory to turn into a .modfs file
Options:
--set-public Set modfs file as public (readable by other mods)
--set-file-public <files> Set the provided files as public (readable by other mods)
modfs to directory:
python dir2modfs.py <filepath> --extract
Parameters:
filepath Path to modfs file to extract files from
""")
exit(0)
def is_binary_file(bytes: bytes):
textchars = bytearray({0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x1B} | set(range(0x20, 0x100)) - {0x7F})
return bool(bytes.translate(None, textchars))
def get_files(dirpath: str, public_files: list):
files = []
for root, _, filenames in os.walk(dirpath):
for filename in filenames:
relpath = os.path.join(root, filename)
filepath = relpath.removeprefix(dirpath).strip("/\\").replace('\\', '/')
is_public = False
for public_file in public_files:
if re.match(public_file, relpath) or re.match(public_file, filepath):
is_public = True
break
files.append({
"relpath": relpath,
"filepath": filepath,
"is_public": is_public,
"is_text": False,
"data": None
})
return files
def convert(dirpath: str, set_public: bool, public_files: list):
dirpath = dirpath.rstrip("/\\")
files = sorted(get_files(dirpath, public_files), key=lambda file: file["filepath"])
if len(files) > MOD_FS_MAX_FILES:
raise Exception(f"Max number of files exceeded: {len(files)} (max is: {MOD_FS_MAX_FILES})")
total_size = 0
for file in files:
filepath = file["filepath"]
if len(filepath) >= MOD_FS_MAX_PATH:
raise Exception(f"{filepath} - Exceeded filepath length: {len(filepath)} (max is: {MOD_FS_MAX_PATH-1})")
with open(file["relpath"], "rb") as f:
data = f.read()
total_size += len(data)
if total_size > MOD_FS_MAX_SIZE:
raise Exception(f"{filepath} - Total size exceeded: {total_size} (max is: {MOD_FS_MAX_SIZE})")
file["data"] = data
file["is_text"] = not is_binary_file(data)
# write file
destpath = dirpath + MOD_FS_EXTENSION
with open(destpath, "wb") as f:
# magic + version
f.write(MOD_FS_MAGIC.encode())
f.write(MOD_FS_VERSION.to_bytes(1, byteorder="little", signed=False))
# header
f.write(len(files).to_bytes(2, byteorder="little", signed=False))
f.write(set_public.to_bytes(1, byteorder="little", signed=False))
# padding (empty space for future versions)
padding = MOD_FS_HEADER_SIZE - f.tell()
f.write(b'\0' * padding)
# files
for file in files:
# filepath
f.write(len(file["filepath"]).to_bytes(2, byteorder="little", signed=False))
f.write(file["filepath"].encode())
# data
f.write(len(file["data"]).to_bytes(4, byteorder="little", signed=False))
f.write(file["is_public"].to_bytes(1, byteorder="little", signed=False))
f.write(file["is_text"].to_bytes(1, byteorder="little", signed=False))
f.write(file["data"])
# summary
print("")
print(f"Directory: {dirpath}")
print(f"Num files: {len(files)}")
print(f"Total size: {total_size}")
print(f"Is public: {set_public}")
filepaths_max = max(8, len(max([file["filepath"] for file in files], key=len)))
sizes_max = max(4, len(max([str(len(file["data"])) for file in files], key=len)))
print("")
print(f"{'FILEPATH'.ljust(filepaths_max)} {'SIZE'.rjust(sizes_max)} {'TEXT'.rjust(5)} {'PUBLIC'.rjust(6)}")
print(f"{'--------'.ljust(filepaths_max)} {'----'.rjust(sizes_max)} {'----'.rjust(5)} {'------'.rjust(6)}")
for file in files:
filepath = file["filepath"]
size = str(len(file["data"]))
is_text = str(file["is_text"])
is_public = str(file["is_public"])
print(f"{filepath.ljust(filepaths_max)} {size.rjust(sizes_max)} {is_text.rjust(5)} {is_public.rjust(6)}")
def extract(filepath: str):
if not filepath.endswith(MOD_FS_EXTENSION):
raise Exception("Not a modfs file")
with open(filepath, "rb") as f:
# magic + version
magic = f.read(len(MOD_FS_MAGIC)).decode()
if magic != MOD_FS_MAGIC:
raise Exception("Not a modfs file")
version = int.from_bytes(f.read(1), byteorder="little", signed=False)
if version != MOD_FS_VERSION:
raise Exception("Version mismatch")
# header
num_files = int.from_bytes(f.read(2), byteorder="little", signed=False)
is_public = bool.from_bytes(f.read(1), byteorder="little", signed=False)
# padding (empty space for future versions)
f.seek(MOD_FS_HEADER_SIZE, 0)
# create directory
dirpath = filepath.removesuffix(MOD_FS_EXTENSION)
os.makedirs(dirpath, exist_ok=True)
# files
for _ in range(num_files):
# filepath
filepath_len = int.from_bytes(f.read(2), byteorder="little", signed=False)
filepath = os.path.join(dirpath, f.read(filepath_len).decode())
# data
file_size = int.from_bytes(f.read(4), byteorder="little", signed=False)
file_is_public = bool.from_bytes(f.read(1), byteorder="little", signed=False)
file_is_text = bool.from_bytes(f.read(1), byteorder="little", signed=False)
file_data = f.read(file_size)
# write file
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, "wb") as g:
g.write(file_data)
print(f"Extracted file of size {file_size} to: {filepath}")
def main(argc: int, argv: list):
if argc < 2:
usage()
if "--extract" in argv:
extract(argv[1])
else:
set_public = "--set-public" in argv
set_file_public_index = argv.index("--set-file-public") if "--set-file-public" in argv else argc
convert(argv[1], set_public, argv[set_file_public_index+1:])
if __name__ == "__main__":
main(len(sys.argv), sys.argv)

View file

@ -2144,6 +2144,8 @@
- MOD_FS_MAX_SIZE
- MOD_FS_MAX_FILES
- MOD_FS_MAX_PATH
- MOD_FS_URI_PREFIX
- MOD_FS_URI_FORMAT
### [enum ModFsFileIntType](#ModFsFileIntType)
| Identifier | Value |
@ -2970,6 +2972,7 @@
<br />
## [player_palette.h](#player_palette.h)
- PALETTES_DIRECTORY
- MAX_PRESET_PALETTES
### [enum PlayerPart](#PlayerPart)
@ -4722,6 +4725,12 @@
- VERSION_TEXT
- VERSION_NUMBER
- MINOR_VERSION_NUMBER
- GAME_NAME
- WINDOW_NAME
- GAME_NAME
- WINDOW_NAME
- GAME_NAME
- WINDOW_NAME
- MAX_VERSION_LENGTH
[:arrow_up_small:](#)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1354,9 +1354,6 @@
- [mod_fs_get](functions-5.md#mod_fs_get)
- [mod_fs_reload](functions-5.md#mod_fs_reload)
- [mod_fs_create](functions-5.md#mod_fs_create)
- [mod_fs_delete](functions-5.md#mod_fs_delete)
- [mod_fs_save](functions-5.md#mod_fs_save)
- [mod_fs_set_public](functions-5.md#mod_fs_set_public)
- [mod_fs_get_filename](functions-5.md#mod_fs_get_filename)
- [mod_fs_get_file](functions-5.md#mod_fs_get_file)
- [mod_fs_create_file](functions-5.md#mod_fs_create_file)
@ -1364,6 +1361,9 @@
- [mod_fs_copy_file](functions-5.md#mod_fs_copy_file)
- [mod_fs_delete_file](functions-5.md#mod_fs_delete_file)
- [mod_fs_clear](functions-5.md#mod_fs_clear)
- [mod_fs_save](functions-5.md#mod_fs_save)
- [mod_fs_delete](functions-5.md#mod_fs_delete)
- [mod_fs_set_public](functions-5.md#mod_fs_set_public)
- [mod_fs_file_read_bool](functions-5.md#mod_fs_file_read_bool)
- [mod_fs_file_read_integer](functions-5.md#mod_fs_file_read_integer)
- [mod_fs_file_read_number](functions-5.md#mod_fs_file_read_number)
@ -1377,9 +1377,11 @@
- [mod_fs_file_write_string](functions-5.md#mod_fs_file_write_string)
- [mod_fs_file_write_line](functions-5.md#mod_fs_file_write_line)
- [mod_fs_file_seek](functions-5.md#mod_fs_file_seek)
- [mod_fs_file_rewind](functions-5.md#mod_fs_file_rewind)
- [mod_fs_file_is_eof](functions-5.md#mod_fs_file_is_eof)
- [mod_fs_file_fill](functions-5.md#mod_fs_file_fill)
- [mod_fs_file_erase](functions-5.md#mod_fs_file_erase)
- [mod_fs_file_set_text_mode](functions-5.md#mod_fs_file_set_text_mode)
- [mod_fs_file_set_public](functions-5.md#mod_fs_file_set_public)
- [mod_fs_hide_errors](functions-5.md#mod_fs_hide_errors)
- [mod_fs_get_last_error](functions-5.md#mod_fs_get_last_error)
@ -1393,6 +1395,7 @@
- [mod_storage_load](functions-5.md#mod_storage_load)
- [mod_storage_load_number](functions-5.md#mod_storage_load_number)
- [mod_storage_load_bool](functions-5.md#mod_storage_load_bool)
- [mod_storage_load_all](functions-5.md#mod_storage_load_all)
- [mod_storage_exists](functions-5.md#mod_storage_exists)
- [mod_storage_remove](functions-5.md#mod_storage_remove)
- [mod_storage_clear](functions-5.md#mod_storage_clear)
@ -1516,232 +1519,232 @@
<br />
- object_helpers.c
- [clear_move_flag](functions-5.md#clear_move_flag)
- [set_room_override](functions-5.md#set_room_override)
- [obj_update_pos_from_parent_transformation](functions-5.md#obj_update_pos_from_parent_transformation)
- [obj_apply_scale_to_matrix](functions-5.md#obj_apply_scale_to_matrix)
- [create_transformation_from_matrices](functions-5.md#create_transformation_from_matrices)
- [obj_set_held_state](functions-5.md#obj_set_held_state)
- [lateral_dist_between_objects](functions-5.md#lateral_dist_between_objects)
- [dist_between_objects](functions-5.md#dist_between_objects)
- [dist_between_object_and_point](functions-5.md#dist_between_object_and_point)
- [cur_obj_forward_vel_approach_upward](functions-5.md#cur_obj_forward_vel_approach_upward)
- [approach_f32_signed](functions-5.md#approach_f32_signed)
- [approach_f32_symmetric](functions-5.md#approach_f32_symmetric)
- [approach_s16_symmetric](functions-5.md#approach_s16_symmetric)
- [cur_obj_rotate_yaw_toward](functions-5.md#cur_obj_rotate_yaw_toward)
- [obj_angle_to_object](functions-5.md#obj_angle_to_object)
- [obj_pitch_to_object](functions-5.md#obj_pitch_to_object)
- [obj_angle_to_point](functions-5.md#obj_angle_to_point)
- [obj_turn_toward_object](functions-5.md#obj_turn_toward_object)
- [obj_set_parent_relative_pos](functions-5.md#obj_set_parent_relative_pos)
- [obj_set_pos](functions-5.md#obj_set_pos)
- [obj_set_angle](functions-5.md#obj_set_angle)
- [obj_set_move_angle](functions-5.md#obj_set_move_angle)
- [obj_set_face_angle](functions-5.md#obj_set_face_angle)
- [obj_set_gfx_angle](functions-5.md#obj_set_gfx_angle)
- [obj_set_gfx_pos](functions-5.md#obj_set_gfx_pos)
- [obj_set_gfx_scale](functions-5.md#obj_set_gfx_scale)
- [spawn_water_droplet](functions-5.md#spawn_water_droplet)
- [obj_build_relative_transform](functions-5.md#obj_build_relative_transform)
- [cur_obj_move_using_vel](functions-5.md#cur_obj_move_using_vel)
- [obj_copy_graph_y_offset](functions-5.md#obj_copy_graph_y_offset)
- [obj_copy_pos_and_angle](functions-5.md#obj_copy_pos_and_angle)
- [obj_copy_pos](functions-5.md#obj_copy_pos)
- [obj_copy_angle](functions-5.md#obj_copy_angle)
- [obj_set_gfx_pos_from_pos](functions-5.md#obj_set_gfx_pos_from_pos)
- [obj_init_animation](functions-5.md#obj_init_animation)
- [linear_mtxf_mul_vec3f](functions-5.md#linear_mtxf_mul_vec3f)
- [linear_mtxf_transpose_mul_vec3f](functions-5.md#linear_mtxf_transpose_mul_vec3f)
- [obj_apply_scale_to_transform](functions-5.md#obj_apply_scale_to_transform)
- [obj_copy_scale](functions-5.md#obj_copy_scale)
- [obj_scale_xyz](functions-5.md#obj_scale_xyz)
- [obj_scale](functions-5.md#obj_scale)
- [cur_obj_scale](functions-5.md#cur_obj_scale)
- [cur_obj_init_animation](functions-5.md#cur_obj_init_animation)
- [cur_obj_init_animation_with_sound](functions-5.md#cur_obj_init_animation_with_sound)
- [obj_init_animation_with_accel_and_sound](functions-5.md#obj_init_animation_with_accel_and_sound)
- [cur_obj_init_animation_with_accel_and_sound](functions-5.md#cur_obj_init_animation_with_accel_and_sound)
- [obj_init_animation_with_sound](functions-5.md#obj_init_animation_with_sound)
- [cur_obj_enable_rendering_and_become_tangible](functions-5.md#cur_obj_enable_rendering_and_become_tangible)
- [cur_obj_enable_rendering](functions-5.md#cur_obj_enable_rendering)
- [cur_obj_disable_rendering_and_become_intangible](functions-5.md#cur_obj_disable_rendering_and_become_intangible)
- [cur_obj_disable_rendering](functions-5.md#cur_obj_disable_rendering)
- [cur_obj_unhide](functions-5.md#cur_obj_unhide)
- [cur_obj_hide](functions-5.md#cur_obj_hide)
- [cur_obj_set_pos_relative](functions-5.md#cur_obj_set_pos_relative)
- [cur_obj_set_pos_relative_to_parent](functions-5.md#cur_obj_set_pos_relative_to_parent)
- [cur_obj_enable_rendering_2](functions-5.md#cur_obj_enable_rendering_2)
- [cur_obj_unused_init_on_floor](functions-5.md#cur_obj_unused_init_on_floor)
- [obj_set_face_angle_to_move_angle](functions-5.md#obj_set_face_angle_to_move_angle)
- [get_object_list_from_behavior](functions-5.md#get_object_list_from_behavior)
- [cur_obj_nearest_object_with_behavior](functions-5.md#cur_obj_nearest_object_with_behavior)
- [cur_obj_dist_to_nearest_object_with_behavior](functions-5.md#cur_obj_dist_to_nearest_object_with_behavior)
- [cur_obj_find_nearest_pole](functions-5.md#cur_obj_find_nearest_pole)
- [cur_obj_find_nearest_object_with_behavior](functions-5.md#cur_obj_find_nearest_object_with_behavior)
- [cur_obj_count_objects_with_behavior](functions-5.md#cur_obj_count_objects_with_behavior)
- [find_unimportant_object](functions-5.md#find_unimportant_object)
- [count_unimportant_objects](functions-5.md#count_unimportant_objects)
- [count_objects_with_behavior](functions-5.md#count_objects_with_behavior)
- [find_object_with_behavior](functions-5.md#find_object_with_behavior)
- [cur_obj_find_nearby_held_actor](functions-5.md#cur_obj_find_nearby_held_actor)
- [cur_obj_reset_timer_and_subaction](functions-5.md#cur_obj_reset_timer_and_subaction)
- [cur_obj_change_action](functions-5.md#cur_obj_change_action)
- [cur_obj_set_vel_from_mario_vel](functions-5.md#cur_obj_set_vel_from_mario_vel)
- [cur_obj_reverse_animation](functions-5.md#cur_obj_reverse_animation)
- [cur_obj_extend_animation_if_at_end](functions-5.md#cur_obj_extend_animation_if_at_end)
- [cur_obj_check_if_near_animation_end](functions-5.md#cur_obj_check_if_near_animation_end)
- [cur_obj_check_if_at_animation_end](functions-5.md#cur_obj_check_if_at_animation_end)
- [cur_obj_check_anim_frame](functions-5.md#cur_obj_check_anim_frame)
- [cur_obj_check_anim_frame_in_range](functions-5.md#cur_obj_check_anim_frame_in_range)
- [cur_obj_check_frame_prior_current_frame](functions-5.md#cur_obj_check_frame_prior_current_frame)
- [mario_is_in_air_action](functions-5.md#mario_is_in_air_action)
- [mario_is_dive_sliding](functions-5.md#mario_is_dive_sliding)
- [cur_obj_set_y_vel_and_animation](functions-5.md#cur_obj_set_y_vel_and_animation)
- [cur_obj_unrender_and_reset_state](functions-5.md#cur_obj_unrender_and_reset_state)
- [cur_obj_move_after_thrown_or_dropped](functions-5.md#cur_obj_move_after_thrown_or_dropped)
- [cur_obj_get_thrown_or_placed](functions-5.md#cur_obj_get_thrown_or_placed)
- [cur_obj_get_dropped](functions-5.md#cur_obj_get_dropped)
- [mario_set_flag](functions-5.md#mario_set_flag)
- [cur_obj_clear_interact_status_flag](functions-5.md#cur_obj_clear_interact_status_flag)
- [obj_mark_for_deletion](functions-5.md#obj_mark_for_deletion)
- [cur_obj_disable](functions-5.md#cur_obj_disable)
- [cur_obj_become_intangible](functions-5.md#cur_obj_become_intangible)
- [cur_obj_become_tangible](functions-5.md#cur_obj_become_tangible)
- [obj_become_tangible](functions-5.md#obj_become_tangible)
- [cur_obj_update_floor_height](functions-5.md#cur_obj_update_floor_height)
- [cur_obj_update_floor_height_and_get_floor](functions-5.md#cur_obj_update_floor_height_and_get_floor)
- [apply_drag_to_value](functions-5.md#apply_drag_to_value)
- [cur_obj_apply_drag_xz](functions-5.md#cur_obj_apply_drag_xz)
- [cur_obj_move_xz](functions-5.md#cur_obj_move_xz)
- [cur_obj_move_update_underwater_flags](functions-5.md#cur_obj_move_update_underwater_flags)
- [cur_obj_move_update_ground_air_flags](functions-5.md#cur_obj_move_update_ground_air_flags)
- [cur_obj_move_y_and_get_water_level](functions-5.md#cur_obj_move_y_and_get_water_level)
- [cur_obj_move_y](functions-5.md#cur_obj_move_y)
- [cur_obj_unused_resolve_wall_collisions](functions-5.md#cur_obj_unused_resolve_wall_collisions)
- [abs_angle_diff](functions-5.md#abs_angle_diff)
- [cur_obj_move_xz_using_fvel_and_yaw](functions-5.md#cur_obj_move_xz_using_fvel_and_yaw)
- [cur_obj_move_y_with_terminal_vel](functions-5.md#cur_obj_move_y_with_terminal_vel)
- [cur_obj_compute_vel_xz](functions-5.md#cur_obj_compute_vel_xz)
- [increment_velocity_toward_range](functions-5.md#increment_velocity_toward_range)
- [obj_check_if_collided_with_object](functions-5.md#obj_check_if_collided_with_object)
- [cur_obj_set_behavior](functions-5.md#cur_obj_set_behavior)
- [obj_set_behavior](functions-5.md#obj_set_behavior)
- [cur_obj_has_behavior](functions-5.md#cur_obj_has_behavior)
- [obj_has_behavior](functions-5.md#obj_has_behavior)
- [cur_obj_lateral_dist_from_obj_to_home](functions-5.md#cur_obj_lateral_dist_from_obj_to_home)
- [cur_obj_lateral_dist_from_mario_to_home](functions-5.md#cur_obj_lateral_dist_from_mario_to_home)
- [cur_obj_lateral_dist_to_home](functions-5.md#cur_obj_lateral_dist_to_home)
- [cur_obj_outside_home_square](functions-5.md#cur_obj_outside_home_square)
- [cur_obj_outside_home_rectangle](functions-5.md#cur_obj_outside_home_rectangle)
- [cur_obj_set_pos_to_home](functions-5.md#cur_obj_set_pos_to_home)
- [cur_obj_set_pos_to_home_and_stop](functions-5.md#cur_obj_set_pos_to_home_and_stop)
- [cur_obj_shake_y](functions-5.md#cur_obj_shake_y)
- [cur_obj_start_cam_event](functions-5.md#cur_obj_start_cam_event)
- [set_mario_interact_hoot_if_in_range](functions-5.md#set_mario_interact_hoot_if_in_range)
- [obj_set_billboard](functions-5.md#obj_set_billboard)
- [obj_set_cylboard](functions-5.md#obj_set_cylboard)
- [cur_obj_set_billboard_if_vanilla_cam](functions-5.md#cur_obj_set_billboard_if_vanilla_cam)
- [obj_set_hitbox_radius_and_height](functions-5.md#obj_set_hitbox_radius_and_height)
- [obj_set_hurtbox_radius_and_height](functions-5.md#obj_set_hurtbox_radius_and_height)
- [cur_obj_set_hitbox_radius_and_height](functions-5.md#cur_obj_set_hitbox_radius_and_height)
- [cur_obj_set_hurtbox_radius_and_height](functions-5.md#cur_obj_set_hurtbox_radius_and_height)
- [obj_spawn_loot_coins](functions-5.md#obj_spawn_loot_coins)
- [obj_spawn_loot_blue_coins](functions-5.md#obj_spawn_loot_blue_coins)
- [obj_spawn_loot_yellow_coins](functions-5.md#obj_spawn_loot_yellow_coins)
- [cur_obj_spawn_loot_coin_at_mario_pos](functions-5.md#cur_obj_spawn_loot_coin_at_mario_pos)
- [cur_obj_abs_y_dist_to_home](functions-5.md#cur_obj_abs_y_dist_to_home)
- [cur_obj_advance_looping_anim](functions-5.md#cur_obj_advance_looping_anim)
- [cur_obj_detect_steep_floor](functions-5.md#cur_obj_detect_steep_floor)
- [cur_obj_resolve_wall_collisions](functions-5.md#cur_obj_resolve_wall_collisions)
- [cur_obj_update_floor](functions-5.md#cur_obj_update_floor)
- [cur_obj_update_floor_and_resolve_wall_collisions](functions-5.md#cur_obj_update_floor_and_resolve_wall_collisions)
- [cur_obj_update_floor_and_walls](functions-5.md#cur_obj_update_floor_and_walls)
- [cur_obj_move_standard](functions-5.md#cur_obj_move_standard)
- [cur_obj_within_12k_bounds](functions-5.md#cur_obj_within_12k_bounds)
- [cur_obj_move_using_vel_and_gravity](functions-5.md#cur_obj_move_using_vel_and_gravity)
- [cur_obj_move_using_fvel_and_gravity](functions-5.md#cur_obj_move_using_fvel_and_gravity)
- [obj_set_pos_relative](functions-5.md#obj_set_pos_relative)
- [cur_obj_angle_to_home](functions-5.md#cur_obj_angle_to_home)
- [obj_set_gfx_pos_at_obj_pos](functions-5.md#obj_set_gfx_pos_at_obj_pos)
- [obj_translate_local](functions-5.md#obj_translate_local)
- [obj_build_transform_from_pos_and_angle](functions-5.md#obj_build_transform_from_pos_and_angle)
- [obj_set_throw_matrix_from_transform](functions-5.md#obj_set_throw_matrix_from_transform)
- [obj_build_transform_relative_to_parent](functions-5.md#obj_build_transform_relative_to_parent)
- [obj_create_transform_from_self](functions-5.md#obj_create_transform_from_self)
- [cur_obj_rotate_move_angle_using_vel](functions-5.md#cur_obj_rotate_move_angle_using_vel)
- [cur_obj_rotate_face_angle_using_vel](functions-5.md#cur_obj_rotate_face_angle_using_vel)
- [cur_obj_set_face_angle_to_move_angle](functions-5.md#cur_obj_set_face_angle_to_move_angle)
- [cur_obj_follow_path](functions-5.md#cur_obj_follow_path)
- [chain_segment_init](functions-5.md#chain_segment_init)
- [random_f32_around_zero](functions-5.md#random_f32_around_zero)
- [obj_scale_random](functions-5.md#obj_scale_random)
- [obj_translate_xyz_random](functions-5.md#obj_translate_xyz_random)
- [obj_translate_xz_random](functions-5.md#obj_translate_xz_random)
- [obj_build_vel_from_transform](functions-5.md#obj_build_vel_from_transform)
- [cur_obj_set_pos_via_transform](functions-5.md#cur_obj_set_pos_via_transform)
- [cur_obj_reflect_move_angle_off_wall](functions-5.md#cur_obj_reflect_move_angle_off_wall)
- [cur_obj_spawn_particles](functions-5.md#cur_obj_spawn_particles)
- [obj_set_hitbox](functions-5.md#obj_set_hitbox)
- [signum_positive](functions-5.md#signum_positive)
- [cur_obj_wait_then_blink](functions-5.md#cur_obj_wait_then_blink)
- [cur_obj_is_mario_ground_pounding_platform](functions-5.md#cur_obj_is_mario_ground_pounding_platform)
- [obj_is_mario_ground_pounding_platform](functions-5.md#obj_is_mario_ground_pounding_platform)
- [spawn_mist_particles](functions-5.md#spawn_mist_particles)
- [spawn_mist_particles_with_sound](functions-5.md#spawn_mist_particles_with_sound)
- [cur_obj_push_mario_away](functions-5.md#cur_obj_push_mario_away)
- [cur_obj_push_mario_away_from_cylinder](functions-5.md#cur_obj_push_mario_away_from_cylinder)
- [bhv_dust_smoke_loop](functions-5.md#bhv_dust_smoke_loop)
- [stub_obj_helpers_3](functions-5.md#stub_obj_helpers_3)
- [cur_obj_scale_over_time](functions-5.md#cur_obj_scale_over_time)
- [cur_obj_set_pos_to_home_with_debug](functions-5.md#cur_obj_set_pos_to_home_with_debug)
- [stub_obj_helpers_4](functions-5.md#stub_obj_helpers_4)
- [cur_obj_is_mario_on_platform](functions-5.md#cur_obj_is_mario_on_platform)
- [cur_obj_is_any_player_on_platform](functions-5.md#cur_obj_is_any_player_on_platform)
- [cur_obj_shake_y_until](functions-5.md#cur_obj_shake_y_until)
- [cur_obj_move_up_and_down](functions-5.md#cur_obj_move_up_and_down)
- [spawn_star_with_no_lvl_exit](functions-5.md#spawn_star_with_no_lvl_exit)
- [spawn_base_star_with_no_lvl_exit](functions-5.md#spawn_base_star_with_no_lvl_exit)
- [bit_shift_left](functions-5.md#bit_shift_left)
- [cur_obj_mario_far_away](functions-5.md#cur_obj_mario_far_away)
- [is_mario_moving_fast_or_in_air](functions-5.md#is_mario_moving_fast_or_in_air)
- [is_item_in_array](functions-5.md#is_item_in_array)
- [bhv_init_room](functions-5.md#bhv_init_room)
- [cur_obj_enable_rendering_if_mario_in_room](functions-5.md#cur_obj_enable_rendering_if_mario_in_room)
- [cur_obj_set_hitbox_and_die_if_attacked](functions-5.md#cur_obj_set_hitbox_and_die_if_attacked)
- [obj_explode_and_spawn_coins](functions-5.md#obj_explode_and_spawn_coins)
- [cur_obj_if_hit_wall_bounce_away](functions-5.md#cur_obj_if_hit_wall_bounce_away)
- [cur_obj_hide_if_mario_far_away_y](functions-5.md#cur_obj_hide_if_mario_far_away_y)
- [obj_is_hidden](functions-5.md#obj_is_hidden)
- [enable_time_stop](functions-5.md#enable_time_stop)
- [enable_time_stop_if_alone](functions-5.md#enable_time_stop_if_alone)
- [disable_time_stop](functions-5.md#disable_time_stop)
- [set_time_stop_flags](functions-5.md#set_time_stop_flags)
- [set_time_stop_flags_if_alone](functions-5.md#set_time_stop_flags_if_alone)
- [clear_time_stop_flags](functions-5.md#clear_time_stop_flags)
- [cur_obj_can_mario_activate_textbox](functions-5.md#cur_obj_can_mario_activate_textbox)
- [cur_obj_can_mario_activate_textbox_2](functions-5.md#cur_obj_can_mario_activate_textbox_2)
- [cur_obj_end_dialog](functions-5.md#cur_obj_end_dialog)
- [cur_obj_has_model](functions-5.md#cur_obj_has_model)
- [cur_obj_align_gfx_with_floor](functions-5.md#cur_obj_align_gfx_with_floor)
- [mario_is_within_rectangle](functions-5.md#mario_is_within_rectangle)
- [cur_obj_shake_screen](functions-5.md#cur_obj_shake_screen)
- [obj_attack_collided_from_other_object](functions-5.md#obj_attack_collided_from_other_object)
- [cur_obj_was_attacked_or_ground_pounded](functions-5.md#cur_obj_was_attacked_or_ground_pounded)
- [obj_copy_behavior_params](functions-5.md#obj_copy_behavior_params)
- [cur_obj_init_animation_and_anim_frame](functions-5.md#cur_obj_init_animation_and_anim_frame)
- [cur_obj_init_animation_and_check_if_near_end](functions-5.md#cur_obj_init_animation_and_check_if_near_end)
- [cur_obj_init_animation_and_extend_if_at_end](functions-5.md#cur_obj_init_animation_and_extend_if_at_end)
- [cur_obj_check_grabbed_mario](functions-5.md#cur_obj_check_grabbed_mario)
- [player_performed_grab_escape_action](functions-5.md#player_performed_grab_escape_action)
- [cur_obj_unused_play_footstep_sound](functions-5.md#cur_obj_unused_play_footstep_sound)
- [enable_time_stop_including_mario](functions-5.md#enable_time_stop_including_mario)
- [disable_time_stop_including_mario](functions-5.md#disable_time_stop_including_mario)
- [cur_obj_check_interacted](functions-5.md#cur_obj_check_interacted)
- [cur_obj_spawn_loot_blue_coin](functions-5.md#cur_obj_spawn_loot_blue_coin)
- [cur_obj_spawn_star_at_y_offset](functions-5.md#cur_obj_spawn_star_at_y_offset)
- [cur_obj_set_home_once](functions-5.md#cur_obj_set_home_once)
- [get_trajectory_length](functions-5.md#get_trajectory_length)
- [clear_move_flag](functions-6.md#clear_move_flag)
- [set_room_override](functions-6.md#set_room_override)
- [obj_update_pos_from_parent_transformation](functions-6.md#obj_update_pos_from_parent_transformation)
- [obj_apply_scale_to_matrix](functions-6.md#obj_apply_scale_to_matrix)
- [create_transformation_from_matrices](functions-6.md#create_transformation_from_matrices)
- [obj_set_held_state](functions-6.md#obj_set_held_state)
- [lateral_dist_between_objects](functions-6.md#lateral_dist_between_objects)
- [dist_between_objects](functions-6.md#dist_between_objects)
- [dist_between_object_and_point](functions-6.md#dist_between_object_and_point)
- [cur_obj_forward_vel_approach_upward](functions-6.md#cur_obj_forward_vel_approach_upward)
- [approach_f32_signed](functions-6.md#approach_f32_signed)
- [approach_f32_symmetric](functions-6.md#approach_f32_symmetric)
- [approach_s16_symmetric](functions-6.md#approach_s16_symmetric)
- [cur_obj_rotate_yaw_toward](functions-6.md#cur_obj_rotate_yaw_toward)
- [obj_angle_to_object](functions-6.md#obj_angle_to_object)
- [obj_pitch_to_object](functions-6.md#obj_pitch_to_object)
- [obj_angle_to_point](functions-6.md#obj_angle_to_point)
- [obj_turn_toward_object](functions-6.md#obj_turn_toward_object)
- [obj_set_parent_relative_pos](functions-6.md#obj_set_parent_relative_pos)
- [obj_set_pos](functions-6.md#obj_set_pos)
- [obj_set_angle](functions-6.md#obj_set_angle)
- [obj_set_move_angle](functions-6.md#obj_set_move_angle)
- [obj_set_face_angle](functions-6.md#obj_set_face_angle)
- [obj_set_gfx_angle](functions-6.md#obj_set_gfx_angle)
- [obj_set_gfx_pos](functions-6.md#obj_set_gfx_pos)
- [obj_set_gfx_scale](functions-6.md#obj_set_gfx_scale)
- [spawn_water_droplet](functions-6.md#spawn_water_droplet)
- [obj_build_relative_transform](functions-6.md#obj_build_relative_transform)
- [cur_obj_move_using_vel](functions-6.md#cur_obj_move_using_vel)
- [obj_copy_graph_y_offset](functions-6.md#obj_copy_graph_y_offset)
- [obj_copy_pos_and_angle](functions-6.md#obj_copy_pos_and_angle)
- [obj_copy_pos](functions-6.md#obj_copy_pos)
- [obj_copy_angle](functions-6.md#obj_copy_angle)
- [obj_set_gfx_pos_from_pos](functions-6.md#obj_set_gfx_pos_from_pos)
- [obj_init_animation](functions-6.md#obj_init_animation)
- [linear_mtxf_mul_vec3f](functions-6.md#linear_mtxf_mul_vec3f)
- [linear_mtxf_transpose_mul_vec3f](functions-6.md#linear_mtxf_transpose_mul_vec3f)
- [obj_apply_scale_to_transform](functions-6.md#obj_apply_scale_to_transform)
- [obj_copy_scale](functions-6.md#obj_copy_scale)
- [obj_scale_xyz](functions-6.md#obj_scale_xyz)
- [obj_scale](functions-6.md#obj_scale)
- [cur_obj_scale](functions-6.md#cur_obj_scale)
- [cur_obj_init_animation](functions-6.md#cur_obj_init_animation)
- [cur_obj_init_animation_with_sound](functions-6.md#cur_obj_init_animation_with_sound)
- [obj_init_animation_with_accel_and_sound](functions-6.md#obj_init_animation_with_accel_and_sound)
- [cur_obj_init_animation_with_accel_and_sound](functions-6.md#cur_obj_init_animation_with_accel_and_sound)
- [obj_init_animation_with_sound](functions-6.md#obj_init_animation_with_sound)
- [cur_obj_enable_rendering_and_become_tangible](functions-6.md#cur_obj_enable_rendering_and_become_tangible)
- [cur_obj_enable_rendering](functions-6.md#cur_obj_enable_rendering)
- [cur_obj_disable_rendering_and_become_intangible](functions-6.md#cur_obj_disable_rendering_and_become_intangible)
- [cur_obj_disable_rendering](functions-6.md#cur_obj_disable_rendering)
- [cur_obj_unhide](functions-6.md#cur_obj_unhide)
- [cur_obj_hide](functions-6.md#cur_obj_hide)
- [cur_obj_set_pos_relative](functions-6.md#cur_obj_set_pos_relative)
- [cur_obj_set_pos_relative_to_parent](functions-6.md#cur_obj_set_pos_relative_to_parent)
- [cur_obj_enable_rendering_2](functions-6.md#cur_obj_enable_rendering_2)
- [cur_obj_unused_init_on_floor](functions-6.md#cur_obj_unused_init_on_floor)
- [obj_set_face_angle_to_move_angle](functions-6.md#obj_set_face_angle_to_move_angle)
- [get_object_list_from_behavior](functions-6.md#get_object_list_from_behavior)
- [cur_obj_nearest_object_with_behavior](functions-6.md#cur_obj_nearest_object_with_behavior)
- [cur_obj_dist_to_nearest_object_with_behavior](functions-6.md#cur_obj_dist_to_nearest_object_with_behavior)
- [cur_obj_find_nearest_pole](functions-6.md#cur_obj_find_nearest_pole)
- [cur_obj_find_nearest_object_with_behavior](functions-6.md#cur_obj_find_nearest_object_with_behavior)
- [cur_obj_count_objects_with_behavior](functions-6.md#cur_obj_count_objects_with_behavior)
- [find_unimportant_object](functions-6.md#find_unimportant_object)
- [count_unimportant_objects](functions-6.md#count_unimportant_objects)
- [count_objects_with_behavior](functions-6.md#count_objects_with_behavior)
- [find_object_with_behavior](functions-6.md#find_object_with_behavior)
- [cur_obj_find_nearby_held_actor](functions-6.md#cur_obj_find_nearby_held_actor)
- [cur_obj_reset_timer_and_subaction](functions-6.md#cur_obj_reset_timer_and_subaction)
- [cur_obj_change_action](functions-6.md#cur_obj_change_action)
- [cur_obj_set_vel_from_mario_vel](functions-6.md#cur_obj_set_vel_from_mario_vel)
- [cur_obj_reverse_animation](functions-6.md#cur_obj_reverse_animation)
- [cur_obj_extend_animation_if_at_end](functions-6.md#cur_obj_extend_animation_if_at_end)
- [cur_obj_check_if_near_animation_end](functions-6.md#cur_obj_check_if_near_animation_end)
- [cur_obj_check_if_at_animation_end](functions-6.md#cur_obj_check_if_at_animation_end)
- [cur_obj_check_anim_frame](functions-6.md#cur_obj_check_anim_frame)
- [cur_obj_check_anim_frame_in_range](functions-6.md#cur_obj_check_anim_frame_in_range)
- [cur_obj_check_frame_prior_current_frame](functions-6.md#cur_obj_check_frame_prior_current_frame)
- [mario_is_in_air_action](functions-6.md#mario_is_in_air_action)
- [mario_is_dive_sliding](functions-6.md#mario_is_dive_sliding)
- [cur_obj_set_y_vel_and_animation](functions-6.md#cur_obj_set_y_vel_and_animation)
- [cur_obj_unrender_and_reset_state](functions-6.md#cur_obj_unrender_and_reset_state)
- [cur_obj_move_after_thrown_or_dropped](functions-6.md#cur_obj_move_after_thrown_or_dropped)
- [cur_obj_get_thrown_or_placed](functions-6.md#cur_obj_get_thrown_or_placed)
- [cur_obj_get_dropped](functions-6.md#cur_obj_get_dropped)
- [mario_set_flag](functions-6.md#mario_set_flag)
- [cur_obj_clear_interact_status_flag](functions-6.md#cur_obj_clear_interact_status_flag)
- [obj_mark_for_deletion](functions-6.md#obj_mark_for_deletion)
- [cur_obj_disable](functions-6.md#cur_obj_disable)
- [cur_obj_become_intangible](functions-6.md#cur_obj_become_intangible)
- [cur_obj_become_tangible](functions-6.md#cur_obj_become_tangible)
- [obj_become_tangible](functions-6.md#obj_become_tangible)
- [cur_obj_update_floor_height](functions-6.md#cur_obj_update_floor_height)
- [cur_obj_update_floor_height_and_get_floor](functions-6.md#cur_obj_update_floor_height_and_get_floor)
- [apply_drag_to_value](functions-6.md#apply_drag_to_value)
- [cur_obj_apply_drag_xz](functions-6.md#cur_obj_apply_drag_xz)
- [cur_obj_move_xz](functions-6.md#cur_obj_move_xz)
- [cur_obj_move_update_underwater_flags](functions-6.md#cur_obj_move_update_underwater_flags)
- [cur_obj_move_update_ground_air_flags](functions-6.md#cur_obj_move_update_ground_air_flags)
- [cur_obj_move_y_and_get_water_level](functions-6.md#cur_obj_move_y_and_get_water_level)
- [cur_obj_move_y](functions-6.md#cur_obj_move_y)
- [cur_obj_unused_resolve_wall_collisions](functions-6.md#cur_obj_unused_resolve_wall_collisions)
- [abs_angle_diff](functions-6.md#abs_angle_diff)
- [cur_obj_move_xz_using_fvel_and_yaw](functions-6.md#cur_obj_move_xz_using_fvel_and_yaw)
- [cur_obj_move_y_with_terminal_vel](functions-6.md#cur_obj_move_y_with_terminal_vel)
- [cur_obj_compute_vel_xz](functions-6.md#cur_obj_compute_vel_xz)
- [increment_velocity_toward_range](functions-6.md#increment_velocity_toward_range)
- [obj_check_if_collided_with_object](functions-6.md#obj_check_if_collided_with_object)
- [cur_obj_set_behavior](functions-6.md#cur_obj_set_behavior)
- [obj_set_behavior](functions-6.md#obj_set_behavior)
- [cur_obj_has_behavior](functions-6.md#cur_obj_has_behavior)
- [obj_has_behavior](functions-6.md#obj_has_behavior)
- [cur_obj_lateral_dist_from_obj_to_home](functions-6.md#cur_obj_lateral_dist_from_obj_to_home)
- [cur_obj_lateral_dist_from_mario_to_home](functions-6.md#cur_obj_lateral_dist_from_mario_to_home)
- [cur_obj_lateral_dist_to_home](functions-6.md#cur_obj_lateral_dist_to_home)
- [cur_obj_outside_home_square](functions-6.md#cur_obj_outside_home_square)
- [cur_obj_outside_home_rectangle](functions-6.md#cur_obj_outside_home_rectangle)
- [cur_obj_set_pos_to_home](functions-6.md#cur_obj_set_pos_to_home)
- [cur_obj_set_pos_to_home_and_stop](functions-6.md#cur_obj_set_pos_to_home_and_stop)
- [cur_obj_shake_y](functions-6.md#cur_obj_shake_y)
- [cur_obj_start_cam_event](functions-6.md#cur_obj_start_cam_event)
- [set_mario_interact_hoot_if_in_range](functions-6.md#set_mario_interact_hoot_if_in_range)
- [obj_set_billboard](functions-6.md#obj_set_billboard)
- [obj_set_cylboard](functions-6.md#obj_set_cylboard)
- [cur_obj_set_billboard_if_vanilla_cam](functions-6.md#cur_obj_set_billboard_if_vanilla_cam)
- [obj_set_hitbox_radius_and_height](functions-6.md#obj_set_hitbox_radius_and_height)
- [obj_set_hurtbox_radius_and_height](functions-6.md#obj_set_hurtbox_radius_and_height)
- [cur_obj_set_hitbox_radius_and_height](functions-6.md#cur_obj_set_hitbox_radius_and_height)
- [cur_obj_set_hurtbox_radius_and_height](functions-6.md#cur_obj_set_hurtbox_radius_and_height)
- [obj_spawn_loot_coins](functions-6.md#obj_spawn_loot_coins)
- [obj_spawn_loot_blue_coins](functions-6.md#obj_spawn_loot_blue_coins)
- [obj_spawn_loot_yellow_coins](functions-6.md#obj_spawn_loot_yellow_coins)
- [cur_obj_spawn_loot_coin_at_mario_pos](functions-6.md#cur_obj_spawn_loot_coin_at_mario_pos)
- [cur_obj_abs_y_dist_to_home](functions-6.md#cur_obj_abs_y_dist_to_home)
- [cur_obj_advance_looping_anim](functions-6.md#cur_obj_advance_looping_anim)
- [cur_obj_detect_steep_floor](functions-6.md#cur_obj_detect_steep_floor)
- [cur_obj_resolve_wall_collisions](functions-6.md#cur_obj_resolve_wall_collisions)
- [cur_obj_update_floor](functions-6.md#cur_obj_update_floor)
- [cur_obj_update_floor_and_resolve_wall_collisions](functions-6.md#cur_obj_update_floor_and_resolve_wall_collisions)
- [cur_obj_update_floor_and_walls](functions-6.md#cur_obj_update_floor_and_walls)
- [cur_obj_move_standard](functions-6.md#cur_obj_move_standard)
- [cur_obj_within_12k_bounds](functions-6.md#cur_obj_within_12k_bounds)
- [cur_obj_move_using_vel_and_gravity](functions-6.md#cur_obj_move_using_vel_and_gravity)
- [cur_obj_move_using_fvel_and_gravity](functions-6.md#cur_obj_move_using_fvel_and_gravity)
- [obj_set_pos_relative](functions-6.md#obj_set_pos_relative)
- [cur_obj_angle_to_home](functions-6.md#cur_obj_angle_to_home)
- [obj_set_gfx_pos_at_obj_pos](functions-6.md#obj_set_gfx_pos_at_obj_pos)
- [obj_translate_local](functions-6.md#obj_translate_local)
- [obj_build_transform_from_pos_and_angle](functions-6.md#obj_build_transform_from_pos_and_angle)
- [obj_set_throw_matrix_from_transform](functions-6.md#obj_set_throw_matrix_from_transform)
- [obj_build_transform_relative_to_parent](functions-6.md#obj_build_transform_relative_to_parent)
- [obj_create_transform_from_self](functions-6.md#obj_create_transform_from_self)
- [cur_obj_rotate_move_angle_using_vel](functions-6.md#cur_obj_rotate_move_angle_using_vel)
- [cur_obj_rotate_face_angle_using_vel](functions-6.md#cur_obj_rotate_face_angle_using_vel)
- [cur_obj_set_face_angle_to_move_angle](functions-6.md#cur_obj_set_face_angle_to_move_angle)
- [cur_obj_follow_path](functions-6.md#cur_obj_follow_path)
- [chain_segment_init](functions-6.md#chain_segment_init)
- [random_f32_around_zero](functions-6.md#random_f32_around_zero)
- [obj_scale_random](functions-6.md#obj_scale_random)
- [obj_translate_xyz_random](functions-6.md#obj_translate_xyz_random)
- [obj_translate_xz_random](functions-6.md#obj_translate_xz_random)
- [obj_build_vel_from_transform](functions-6.md#obj_build_vel_from_transform)
- [cur_obj_set_pos_via_transform](functions-6.md#cur_obj_set_pos_via_transform)
- [cur_obj_reflect_move_angle_off_wall](functions-6.md#cur_obj_reflect_move_angle_off_wall)
- [cur_obj_spawn_particles](functions-6.md#cur_obj_spawn_particles)
- [obj_set_hitbox](functions-6.md#obj_set_hitbox)
- [signum_positive](functions-6.md#signum_positive)
- [cur_obj_wait_then_blink](functions-6.md#cur_obj_wait_then_blink)
- [cur_obj_is_mario_ground_pounding_platform](functions-6.md#cur_obj_is_mario_ground_pounding_platform)
- [obj_is_mario_ground_pounding_platform](functions-6.md#obj_is_mario_ground_pounding_platform)
- [spawn_mist_particles](functions-6.md#spawn_mist_particles)
- [spawn_mist_particles_with_sound](functions-6.md#spawn_mist_particles_with_sound)
- [cur_obj_push_mario_away](functions-6.md#cur_obj_push_mario_away)
- [cur_obj_push_mario_away_from_cylinder](functions-6.md#cur_obj_push_mario_away_from_cylinder)
- [bhv_dust_smoke_loop](functions-6.md#bhv_dust_smoke_loop)
- [stub_obj_helpers_3](functions-6.md#stub_obj_helpers_3)
- [cur_obj_scale_over_time](functions-6.md#cur_obj_scale_over_time)
- [cur_obj_set_pos_to_home_with_debug](functions-6.md#cur_obj_set_pos_to_home_with_debug)
- [stub_obj_helpers_4](functions-6.md#stub_obj_helpers_4)
- [cur_obj_is_mario_on_platform](functions-6.md#cur_obj_is_mario_on_platform)
- [cur_obj_is_any_player_on_platform](functions-6.md#cur_obj_is_any_player_on_platform)
- [cur_obj_shake_y_until](functions-6.md#cur_obj_shake_y_until)
- [cur_obj_move_up_and_down](functions-6.md#cur_obj_move_up_and_down)
- [spawn_star_with_no_lvl_exit](functions-6.md#spawn_star_with_no_lvl_exit)
- [spawn_base_star_with_no_lvl_exit](functions-6.md#spawn_base_star_with_no_lvl_exit)
- [bit_shift_left](functions-6.md#bit_shift_left)
- [cur_obj_mario_far_away](functions-6.md#cur_obj_mario_far_away)
- [is_mario_moving_fast_or_in_air](functions-6.md#is_mario_moving_fast_or_in_air)
- [is_item_in_array](functions-6.md#is_item_in_array)
- [bhv_init_room](functions-6.md#bhv_init_room)
- [cur_obj_enable_rendering_if_mario_in_room](functions-6.md#cur_obj_enable_rendering_if_mario_in_room)
- [cur_obj_set_hitbox_and_die_if_attacked](functions-6.md#cur_obj_set_hitbox_and_die_if_attacked)
- [obj_explode_and_spawn_coins](functions-6.md#obj_explode_and_spawn_coins)
- [cur_obj_if_hit_wall_bounce_away](functions-6.md#cur_obj_if_hit_wall_bounce_away)
- [cur_obj_hide_if_mario_far_away_y](functions-6.md#cur_obj_hide_if_mario_far_away_y)
- [obj_is_hidden](functions-6.md#obj_is_hidden)
- [enable_time_stop](functions-6.md#enable_time_stop)
- [enable_time_stop_if_alone](functions-6.md#enable_time_stop_if_alone)
- [disable_time_stop](functions-6.md#disable_time_stop)
- [set_time_stop_flags](functions-6.md#set_time_stop_flags)
- [set_time_stop_flags_if_alone](functions-6.md#set_time_stop_flags_if_alone)
- [clear_time_stop_flags](functions-6.md#clear_time_stop_flags)
- [cur_obj_can_mario_activate_textbox](functions-6.md#cur_obj_can_mario_activate_textbox)
- [cur_obj_can_mario_activate_textbox_2](functions-6.md#cur_obj_can_mario_activate_textbox_2)
- [cur_obj_end_dialog](functions-6.md#cur_obj_end_dialog)
- [cur_obj_has_model](functions-6.md#cur_obj_has_model)
- [cur_obj_align_gfx_with_floor](functions-6.md#cur_obj_align_gfx_with_floor)
- [mario_is_within_rectangle](functions-6.md#mario_is_within_rectangle)
- [cur_obj_shake_screen](functions-6.md#cur_obj_shake_screen)
- [obj_attack_collided_from_other_object](functions-6.md#obj_attack_collided_from_other_object)
- [cur_obj_was_attacked_or_ground_pounded](functions-6.md#cur_obj_was_attacked_or_ground_pounded)
- [obj_copy_behavior_params](functions-6.md#obj_copy_behavior_params)
- [cur_obj_init_animation_and_anim_frame](functions-6.md#cur_obj_init_animation_and_anim_frame)
- [cur_obj_init_animation_and_check_if_near_end](functions-6.md#cur_obj_init_animation_and_check_if_near_end)
- [cur_obj_init_animation_and_extend_if_at_end](functions-6.md#cur_obj_init_animation_and_extend_if_at_end)
- [cur_obj_check_grabbed_mario](functions-6.md#cur_obj_check_grabbed_mario)
- [player_performed_grab_escape_action](functions-6.md#player_performed_grab_escape_action)
- [cur_obj_unused_play_footstep_sound](functions-6.md#cur_obj_unused_play_footstep_sound)
- [enable_time_stop_including_mario](functions-6.md#enable_time_stop_including_mario)
- [disable_time_stop_including_mario](functions-6.md#disable_time_stop_including_mario)
- [cur_obj_check_interacted](functions-6.md#cur_obj_check_interacted)
- [cur_obj_spawn_loot_blue_coin](functions-6.md#cur_obj_spawn_loot_blue_coin)
- [cur_obj_spawn_star_at_y_offset](functions-6.md#cur_obj_spawn_star_at_y_offset)
- [cur_obj_set_home_once](functions-6.md#cur_obj_set_home_once)
- [get_trajectory_length](functions-6.md#get_trajectory_length)
<br />
@ -1956,234 +1959,234 @@
<br />
- smlua_level_utils.h
- [smlua_level_util_change_area](functions-6.md#smlua_level_util_change_area)
- [smlua_level_util_get_info](functions-6.md#smlua_level_util_get_info)
- [smlua_level_util_get_info_from_short_name](functions-6.md#smlua_level_util_get_info_from_short_name)
- [smlua_level_util_get_info_from_course_num](functions-6.md#smlua_level_util_get_info_from_course_num)
- [level_register](functions-6.md#level_register)
- [level_is_vanilla_level](functions-6.md#level_is_vanilla_level)
- [warp_to_warpnode](functions-6.md#warp_to_warpnode)
- [warp_to_level](functions-6.md#warp_to_level)
- [warp_restart_level](functions-6.md#warp_restart_level)
- [warp_to_start_level](functions-6.md#warp_to_start_level)
- [warp_exit_level](functions-6.md#warp_exit_level)
- [warp_to_castle](functions-6.md#warp_to_castle)
- [smlua_level_util_change_area](functions-7.md#smlua_level_util_change_area)
- [smlua_level_util_get_info](functions-7.md#smlua_level_util_get_info)
- [smlua_level_util_get_info_from_short_name](functions-7.md#smlua_level_util_get_info_from_short_name)
- [smlua_level_util_get_info_from_course_num](functions-7.md#smlua_level_util_get_info_from_course_num)
- [level_register](functions-7.md#level_register)
- [level_is_vanilla_level](functions-7.md#level_is_vanilla_level)
- [warp_to_warpnode](functions-7.md#warp_to_warpnode)
- [warp_to_level](functions-7.md#warp_to_level)
- [warp_restart_level](functions-7.md#warp_restart_level)
- [warp_to_start_level](functions-7.md#warp_to_start_level)
- [warp_exit_level](functions-7.md#warp_exit_level)
- [warp_to_castle](functions-7.md#warp_to_castle)
<br />
- smlua_misc_utils.h
- [get_network_area_timer](functions-6.md#get_network_area_timer)
- [get_area_update_counter](functions-6.md#get_area_update_counter)
- [get_temp_s32_pointer](functions-6.md#get_temp_s32_pointer)
- [deref_s32_pointer](functions-6.md#deref_s32_pointer)
- [djui_popup_create_global](functions-6.md#djui_popup_create_global)
- [djui_is_popup_disabled](functions-6.md#djui_is_popup_disabled)
- [djui_set_popup_disabled_override](functions-6.md#djui_set_popup_disabled_override)
- [djui_reset_popup_disabled_override](functions-6.md#djui_reset_popup_disabled_override)
- [djui_is_playerlist_open](functions-6.md#djui_is_playerlist_open)
- [djui_attempting_to_open_playerlist](functions-6.md#djui_attempting_to_open_playerlist)
- [djui_get_playerlist_page_index](functions-6.md#djui_get_playerlist_page_index)
- [djui_menu_get_font](functions-6.md#djui_menu_get_font)
- [djui_menu_get_theme](functions-6.md#djui_menu_get_theme)
- [djui_is_playerlist_ping_visible](functions-6.md#djui_is_playerlist_ping_visible)
- [get_dialog_box_state](functions-6.md#get_dialog_box_state)
- [get_dialog_id](functions-6.md#get_dialog_id)
- [get_last_star_or_key](functions-6.md#get_last_star_or_key)
- [set_last_star_or_key](functions-6.md#set_last_star_or_key)
- [get_last_completed_course_num](functions-6.md#get_last_completed_course_num)
- [set_last_completed_course_num](functions-6.md#set_last_completed_course_num)
- [get_last_completed_star_num](functions-6.md#get_last_completed_star_num)
- [set_last_completed_star_num](functions-6.md#set_last_completed_star_num)
- [get_got_file_coin_hi_score](functions-6.md#get_got_file_coin_hi_score)
- [set_got_file_coin_hi_score](functions-6.md#set_got_file_coin_hi_score)
- [get_save_file_modified](functions-6.md#get_save_file_modified)
- [set_save_file_modified](functions-6.md#set_save_file_modified)
- [hud_hide](functions-6.md#hud_hide)
- [hud_show](functions-6.md#hud_show)
- [hud_is_hidden](functions-6.md#hud_is_hidden)
- [hud_get_value](functions-6.md#hud_get_value)
- [hud_set_value](functions-6.md#hud_set_value)
- [hud_render_power_meter](functions-6.md#hud_render_power_meter)
- [hud_render_power_meter_interpolated](functions-6.md#hud_render_power_meter_interpolated)
- [hud_get_flash](functions-6.md#hud_get_flash)
- [hud_set_flash](functions-6.md#hud_set_flash)
- [act_select_hud_hide](functions-6.md#act_select_hud_hide)
- [act_select_hud_show](functions-6.md#act_select_hud_show)
- [act_select_hud_is_hidden](functions-6.md#act_select_hud_is_hidden)
- [is_game_paused](functions-6.md#is_game_paused)
- [is_transition_playing](functions-6.md#is_transition_playing)
- [allocate_mario_action](functions-6.md#allocate_mario_action)
- [get_hand_foot_pos_x](functions-6.md#get_hand_foot_pos_x)
- [get_hand_foot_pos_y](functions-6.md#get_hand_foot_pos_y)
- [get_hand_foot_pos_z](functions-6.md#get_hand_foot_pos_z)
- [get_mario_anim_part_pos](functions-6.md#get_mario_anim_part_pos)
- [get_current_save_file_num](functions-6.md#get_current_save_file_num)
- [save_file_get_using_backup_slot](functions-6.md#save_file_get_using_backup_slot)
- [save_file_set_using_backup_slot](functions-6.md#save_file_set_using_backup_slot)
- [movtexqc_register](functions-6.md#movtexqc_register)
- [get_water_level](functions-6.md#get_water_level)
- [set_water_level](functions-6.md#set_water_level)
- [course_is_main_course](functions-6.md#course_is_main_course)
- [get_ttc_speed_setting](functions-6.md#get_ttc_speed_setting)
- [set_ttc_speed_setting](functions-6.md#set_ttc_speed_setting)
- [get_time](functions-6.md#get_time)
- [get_date_and_time](functions-6.md#get_date_and_time)
- [get_envfx](functions-6.md#get_envfx)
- [set_override_envfx](functions-6.md#set_override_envfx)
- [get_global_timer](functions-6.md#get_global_timer)
- [get_dialog_response](functions-6.md#get_dialog_response)
- [get_local_discord_id](functions-6.md#get_local_discord_id)
- [get_coopnet_id](functions-6.md#get_coopnet_id)
- [get_volume_master](functions-6.md#get_volume_master)
- [get_volume_level](functions-6.md#get_volume_level)
- [get_volume_sfx](functions-6.md#get_volume_sfx)
- [get_volume_env](functions-6.md#get_volume_env)
- [set_volume_master](functions-6.md#set_volume_master)
- [set_volume_level](functions-6.md#set_volume_level)
- [set_volume_sfx](functions-6.md#set_volume_sfx)
- [set_volume_env](functions-6.md#set_volume_env)
- [get_environment_region](functions-6.md#get_environment_region)
- [set_environment_region](functions-6.md#set_environment_region)
- [mod_file_exists](functions-6.md#mod_file_exists)
- [get_active_mod](functions-6.md#get_active_mod)
- [set_window_title](functions-6.md#set_window_title)
- [reset_window_title](functions-6.md#reset_window_title)
- [get_os_name](functions-6.md#get_os_name)
- [geo_get_current_root](functions-6.md#geo_get_current_root)
- [geo_get_current_master_list](functions-6.md#geo_get_current_master_list)
- [geo_get_current_perspective](functions-6.md#geo_get_current_perspective)
- [geo_get_current_camera](functions-6.md#geo_get_current_camera)
- [geo_get_current_held_object](functions-6.md#geo_get_current_held_object)
- [texture_to_lua_table](functions-6.md#texture_to_lua_table)
- [get_texture_name](functions-6.md#get_texture_name)
- [get_network_area_timer](functions-7.md#get_network_area_timer)
- [get_area_update_counter](functions-7.md#get_area_update_counter)
- [get_temp_s32_pointer](functions-7.md#get_temp_s32_pointer)
- [deref_s32_pointer](functions-7.md#deref_s32_pointer)
- [djui_popup_create_global](functions-7.md#djui_popup_create_global)
- [djui_is_popup_disabled](functions-7.md#djui_is_popup_disabled)
- [djui_set_popup_disabled_override](functions-7.md#djui_set_popup_disabled_override)
- [djui_reset_popup_disabled_override](functions-7.md#djui_reset_popup_disabled_override)
- [djui_is_playerlist_open](functions-7.md#djui_is_playerlist_open)
- [djui_attempting_to_open_playerlist](functions-7.md#djui_attempting_to_open_playerlist)
- [djui_get_playerlist_page_index](functions-7.md#djui_get_playerlist_page_index)
- [djui_menu_get_font](functions-7.md#djui_menu_get_font)
- [djui_menu_get_theme](functions-7.md#djui_menu_get_theme)
- [djui_is_playerlist_ping_visible](functions-7.md#djui_is_playerlist_ping_visible)
- [get_dialog_box_state](functions-7.md#get_dialog_box_state)
- [get_dialog_id](functions-7.md#get_dialog_id)
- [get_last_star_or_key](functions-7.md#get_last_star_or_key)
- [set_last_star_or_key](functions-7.md#set_last_star_or_key)
- [get_last_completed_course_num](functions-7.md#get_last_completed_course_num)
- [set_last_completed_course_num](functions-7.md#set_last_completed_course_num)
- [get_last_completed_star_num](functions-7.md#get_last_completed_star_num)
- [set_last_completed_star_num](functions-7.md#set_last_completed_star_num)
- [get_got_file_coin_hi_score](functions-7.md#get_got_file_coin_hi_score)
- [set_got_file_coin_hi_score](functions-7.md#set_got_file_coin_hi_score)
- [get_save_file_modified](functions-7.md#get_save_file_modified)
- [set_save_file_modified](functions-7.md#set_save_file_modified)
- [hud_hide](functions-7.md#hud_hide)
- [hud_show](functions-7.md#hud_show)
- [hud_is_hidden](functions-7.md#hud_is_hidden)
- [hud_get_value](functions-7.md#hud_get_value)
- [hud_set_value](functions-7.md#hud_set_value)
- [hud_render_power_meter](functions-7.md#hud_render_power_meter)
- [hud_render_power_meter_interpolated](functions-7.md#hud_render_power_meter_interpolated)
- [hud_get_flash](functions-7.md#hud_get_flash)
- [hud_set_flash](functions-7.md#hud_set_flash)
- [act_select_hud_hide](functions-7.md#act_select_hud_hide)
- [act_select_hud_show](functions-7.md#act_select_hud_show)
- [act_select_hud_is_hidden](functions-7.md#act_select_hud_is_hidden)
- [is_game_paused](functions-7.md#is_game_paused)
- [is_transition_playing](functions-7.md#is_transition_playing)
- [allocate_mario_action](functions-7.md#allocate_mario_action)
- [get_hand_foot_pos_x](functions-7.md#get_hand_foot_pos_x)
- [get_hand_foot_pos_y](functions-7.md#get_hand_foot_pos_y)
- [get_hand_foot_pos_z](functions-7.md#get_hand_foot_pos_z)
- [get_mario_anim_part_pos](functions-7.md#get_mario_anim_part_pos)
- [get_current_save_file_num](functions-7.md#get_current_save_file_num)
- [save_file_get_using_backup_slot](functions-7.md#save_file_get_using_backup_slot)
- [save_file_set_using_backup_slot](functions-7.md#save_file_set_using_backup_slot)
- [movtexqc_register](functions-7.md#movtexqc_register)
- [get_water_level](functions-7.md#get_water_level)
- [set_water_level](functions-7.md#set_water_level)
- [course_is_main_course](functions-7.md#course_is_main_course)
- [get_ttc_speed_setting](functions-7.md#get_ttc_speed_setting)
- [set_ttc_speed_setting](functions-7.md#set_ttc_speed_setting)
- [get_time](functions-7.md#get_time)
- [get_date_and_time](functions-7.md#get_date_and_time)
- [get_envfx](functions-7.md#get_envfx)
- [set_override_envfx](functions-7.md#set_override_envfx)
- [get_global_timer](functions-7.md#get_global_timer)
- [get_dialog_response](functions-7.md#get_dialog_response)
- [get_local_discord_id](functions-7.md#get_local_discord_id)
- [get_coopnet_id](functions-7.md#get_coopnet_id)
- [get_volume_master](functions-7.md#get_volume_master)
- [get_volume_level](functions-7.md#get_volume_level)
- [get_volume_sfx](functions-7.md#get_volume_sfx)
- [get_volume_env](functions-7.md#get_volume_env)
- [set_volume_master](functions-7.md#set_volume_master)
- [set_volume_level](functions-7.md#set_volume_level)
- [set_volume_sfx](functions-7.md#set_volume_sfx)
- [set_volume_env](functions-7.md#set_volume_env)
- [get_environment_region](functions-7.md#get_environment_region)
- [set_environment_region](functions-7.md#set_environment_region)
- [mod_file_exists](functions-7.md#mod_file_exists)
- [get_active_mod](functions-7.md#get_active_mod)
- [set_window_title](functions-7.md#set_window_title)
- [reset_window_title](functions-7.md#reset_window_title)
- [get_os_name](functions-7.md#get_os_name)
- [geo_get_current_root](functions-7.md#geo_get_current_root)
- [geo_get_current_master_list](functions-7.md#geo_get_current_master_list)
- [geo_get_current_perspective](functions-7.md#geo_get_current_perspective)
- [geo_get_current_camera](functions-7.md#geo_get_current_camera)
- [geo_get_current_held_object](functions-7.md#geo_get_current_held_object)
- [texture_to_lua_table](functions-7.md#texture_to_lua_table)
- [get_texture_name](functions-7.md#get_texture_name)
<br />
- smlua_model_utils.h
- [smlua_model_util_get_id](functions-6.md#smlua_model_util_get_id)
- [smlua_model_util_get_id](functions-7.md#smlua_model_util_get_id)
<br />
- smlua_obj_utils.h
- [spawn_sync_object](functions-6.md#spawn_sync_object)
- [spawn_non_sync_object](functions-6.md#spawn_non_sync_object)
- [obj_has_behavior_id](functions-6.md#obj_has_behavior_id)
- [obj_has_model_extended](functions-6.md#obj_has_model_extended)
- [obj_get_model_id_extended](functions-6.md#obj_get_model_id_extended)
- [obj_set_model_extended](functions-6.md#obj_set_model_extended)
- [get_trajectory](functions-6.md#get_trajectory)
- [geo_get_current_object](functions-6.md#geo_get_current_object)
- [get_current_object](functions-6.md#get_current_object)
- [get_dialog_object](functions-6.md#get_dialog_object)
- [get_cutscene_focus](functions-6.md#get_cutscene_focus)
- [get_secondary_camera_focus](functions-6.md#get_secondary_camera_focus)
- [set_cutscene_focus](functions-6.md#set_cutscene_focus)
- [set_secondary_camera_focus](functions-6.md#set_secondary_camera_focus)
- [obj_get_first](functions-6.md#obj_get_first)
- [obj_get_first_with_behavior_id](functions-6.md#obj_get_first_with_behavior_id)
- [obj_get_first_with_behavior_id_and_field_s32](functions-6.md#obj_get_first_with_behavior_id_and_field_s32)
- [obj_get_first_with_behavior_id_and_field_f32](functions-6.md#obj_get_first_with_behavior_id_and_field_f32)
- [obj_get_next](functions-6.md#obj_get_next)
- [obj_get_next_with_same_behavior_id](functions-6.md#obj_get_next_with_same_behavior_id)
- [obj_get_next_with_same_behavior_id_and_field_s32](functions-6.md#obj_get_next_with_same_behavior_id_and_field_s32)
- [obj_get_next_with_same_behavior_id_and_field_f32](functions-6.md#obj_get_next_with_same_behavior_id_and_field_f32)
- [obj_get_nearest_object_with_behavior_id](functions-6.md#obj_get_nearest_object_with_behavior_id)
- [obj_count_objects_with_behavior_id](functions-6.md#obj_count_objects_with_behavior_id)
- [obj_get_collided_object](functions-6.md#obj_get_collided_object)
- [obj_get_field_u32](functions-6.md#obj_get_field_u32)
- [obj_get_field_s32](functions-6.md#obj_get_field_s32)
- [obj_get_field_f32](functions-6.md#obj_get_field_f32)
- [obj_get_field_s16](functions-6.md#obj_get_field_s16)
- [obj_set_field_u32](functions-6.md#obj_set_field_u32)
- [obj_set_field_s32](functions-6.md#obj_set_field_s32)
- [obj_set_field_f32](functions-6.md#obj_set_field_f32)
- [obj_set_field_s16](functions-6.md#obj_set_field_s16)
- [obj_get_temp_spawn_particles_info](functions-6.md#obj_get_temp_spawn_particles_info)
- [get_temp_object_hitbox](functions-6.md#get_temp_object_hitbox)
- [obj_is_attackable](functions-6.md#obj_is_attackable)
- [obj_is_breakable_object](functions-6.md#obj_is_breakable_object)
- [obj_is_bully](functions-6.md#obj_is_bully)
- [obj_is_coin](functions-6.md#obj_is_coin)
- [obj_is_exclamation_box](functions-6.md#obj_is_exclamation_box)
- [obj_is_grabbable](functions-6.md#obj_is_grabbable)
- [obj_is_mushroom_1up](functions-6.md#obj_is_mushroom_1up)
- [obj_is_secret](functions-6.md#obj_is_secret)
- [obj_is_valid_for_interaction](functions-6.md#obj_is_valid_for_interaction)
- [obj_check_hitbox_overlap](functions-6.md#obj_check_hitbox_overlap)
- [obj_check_overlap_with_hitbox_params](functions-6.md#obj_check_overlap_with_hitbox_params)
- [obj_set_vel](functions-6.md#obj_set_vel)
- [obj_move_xyz](functions-6.md#obj_move_xyz)
- [set_whirlpools](functions-6.md#set_whirlpools)
- [spawn_sync_object](functions-7.md#spawn_sync_object)
- [spawn_non_sync_object](functions-7.md#spawn_non_sync_object)
- [obj_has_behavior_id](functions-7.md#obj_has_behavior_id)
- [obj_has_model_extended](functions-7.md#obj_has_model_extended)
- [obj_get_model_id_extended](functions-7.md#obj_get_model_id_extended)
- [obj_set_model_extended](functions-7.md#obj_set_model_extended)
- [get_trajectory](functions-7.md#get_trajectory)
- [geo_get_current_object](functions-7.md#geo_get_current_object)
- [get_current_object](functions-7.md#get_current_object)
- [get_dialog_object](functions-7.md#get_dialog_object)
- [get_cutscene_focus](functions-7.md#get_cutscene_focus)
- [get_secondary_camera_focus](functions-7.md#get_secondary_camera_focus)
- [set_cutscene_focus](functions-7.md#set_cutscene_focus)
- [set_secondary_camera_focus](functions-7.md#set_secondary_camera_focus)
- [obj_get_first](functions-7.md#obj_get_first)
- [obj_get_first_with_behavior_id](functions-7.md#obj_get_first_with_behavior_id)
- [obj_get_first_with_behavior_id_and_field_s32](functions-7.md#obj_get_first_with_behavior_id_and_field_s32)
- [obj_get_first_with_behavior_id_and_field_f32](functions-7.md#obj_get_first_with_behavior_id_and_field_f32)
- [obj_get_next](functions-7.md#obj_get_next)
- [obj_get_next_with_same_behavior_id](functions-7.md#obj_get_next_with_same_behavior_id)
- [obj_get_next_with_same_behavior_id_and_field_s32](functions-7.md#obj_get_next_with_same_behavior_id_and_field_s32)
- [obj_get_next_with_same_behavior_id_and_field_f32](functions-7.md#obj_get_next_with_same_behavior_id_and_field_f32)
- [obj_get_nearest_object_with_behavior_id](functions-7.md#obj_get_nearest_object_with_behavior_id)
- [obj_count_objects_with_behavior_id](functions-7.md#obj_count_objects_with_behavior_id)
- [obj_get_collided_object](functions-7.md#obj_get_collided_object)
- [obj_get_field_u32](functions-7.md#obj_get_field_u32)
- [obj_get_field_s32](functions-7.md#obj_get_field_s32)
- [obj_get_field_f32](functions-7.md#obj_get_field_f32)
- [obj_get_field_s16](functions-7.md#obj_get_field_s16)
- [obj_set_field_u32](functions-7.md#obj_set_field_u32)
- [obj_set_field_s32](functions-7.md#obj_set_field_s32)
- [obj_set_field_f32](functions-7.md#obj_set_field_f32)
- [obj_set_field_s16](functions-7.md#obj_set_field_s16)
- [obj_get_temp_spawn_particles_info](functions-7.md#obj_get_temp_spawn_particles_info)
- [get_temp_object_hitbox](functions-7.md#get_temp_object_hitbox)
- [obj_is_attackable](functions-7.md#obj_is_attackable)
- [obj_is_breakable_object](functions-7.md#obj_is_breakable_object)
- [obj_is_bully](functions-7.md#obj_is_bully)
- [obj_is_coin](functions-7.md#obj_is_coin)
- [obj_is_exclamation_box](functions-7.md#obj_is_exclamation_box)
- [obj_is_grabbable](functions-7.md#obj_is_grabbable)
- [obj_is_mushroom_1up](functions-7.md#obj_is_mushroom_1up)
- [obj_is_secret](functions-7.md#obj_is_secret)
- [obj_is_valid_for_interaction](functions-7.md#obj_is_valid_for_interaction)
- [obj_check_hitbox_overlap](functions-7.md#obj_check_hitbox_overlap)
- [obj_check_overlap_with_hitbox_params](functions-7.md#obj_check_overlap_with_hitbox_params)
- [obj_set_vel](functions-7.md#obj_set_vel)
- [obj_move_xyz](functions-7.md#obj_move_xyz)
- [set_whirlpools](functions-7.md#set_whirlpools)
<br />
- smlua_text_utils.h
- [smlua_text_utils_reset_all](functions-6.md#smlua_text_utils_reset_all)
- [smlua_text_utils_dialog_get](functions-6.md#smlua_text_utils_dialog_get)
- [smlua_text_utils_dialog_replace](functions-6.md#smlua_text_utils_dialog_replace)
- [smlua_text_utils_dialog_restore](functions-6.md#smlua_text_utils_dialog_restore)
- [smlua_text_utils_dialog_is_replaced](functions-6.md#smlua_text_utils_dialog_is_replaced)
- [smlua_text_utils_allocate_dialog](functions-6.md#smlua_text_utils_allocate_dialog)
- [smlua_text_utils_course_acts_replace](functions-6.md#smlua_text_utils_course_acts_replace)
- [smlua_text_utils_secret_star_replace](functions-6.md#smlua_text_utils_secret_star_replace)
- [smlua_text_utils_course_name_replace](functions-6.md#smlua_text_utils_course_name_replace)
- [smlua_text_utils_course_name_get](functions-6.md#smlua_text_utils_course_name_get)
- [smlua_text_utils_course_name_mod_index](functions-6.md#smlua_text_utils_course_name_mod_index)
- [smlua_text_utils_course_name_reset](functions-6.md#smlua_text_utils_course_name_reset)
- [smlua_text_utils_act_name_replace](functions-6.md#smlua_text_utils_act_name_replace)
- [smlua_text_utils_act_name_get](functions-6.md#smlua_text_utils_act_name_get)
- [smlua_text_utils_act_name_mod_index](functions-6.md#smlua_text_utils_act_name_mod_index)
- [smlua_text_utils_act_name_reset](functions-6.md#smlua_text_utils_act_name_reset)
- [smlua_text_utils_castle_secret_stars_replace](functions-6.md#smlua_text_utils_castle_secret_stars_replace)
- [smlua_text_utils_castle_secret_stars_get](functions-6.md#smlua_text_utils_castle_secret_stars_get)
- [smlua_text_utils_castle_secret_stars_mod_index](functions-6.md#smlua_text_utils_castle_secret_stars_mod_index)
- [smlua_text_utils_castle_secret_stars_reset](functions-6.md#smlua_text_utils_castle_secret_stars_reset)
- [smlua_text_utils_extra_text_replace](functions-6.md#smlua_text_utils_extra_text_replace)
- [smlua_text_utils_extra_text_get](functions-6.md#smlua_text_utils_extra_text_get)
- [smlua_text_utils_extra_text_mod_index](functions-6.md#smlua_text_utils_extra_text_mod_index)
- [smlua_text_utils_extra_text_reset](functions-6.md#smlua_text_utils_extra_text_reset)
- [smlua_text_utils_get_language](functions-6.md#smlua_text_utils_get_language)
- [smlua_text_utils_reset_all](functions-7.md#smlua_text_utils_reset_all)
- [smlua_text_utils_dialog_get](functions-7.md#smlua_text_utils_dialog_get)
- [smlua_text_utils_dialog_replace](functions-7.md#smlua_text_utils_dialog_replace)
- [smlua_text_utils_dialog_restore](functions-7.md#smlua_text_utils_dialog_restore)
- [smlua_text_utils_dialog_is_replaced](functions-7.md#smlua_text_utils_dialog_is_replaced)
- [smlua_text_utils_allocate_dialog](functions-7.md#smlua_text_utils_allocate_dialog)
- [smlua_text_utils_course_acts_replace](functions-7.md#smlua_text_utils_course_acts_replace)
- [smlua_text_utils_secret_star_replace](functions-7.md#smlua_text_utils_secret_star_replace)
- [smlua_text_utils_course_name_replace](functions-7.md#smlua_text_utils_course_name_replace)
- [smlua_text_utils_course_name_get](functions-7.md#smlua_text_utils_course_name_get)
- [smlua_text_utils_course_name_mod_index](functions-7.md#smlua_text_utils_course_name_mod_index)
- [smlua_text_utils_course_name_reset](functions-7.md#smlua_text_utils_course_name_reset)
- [smlua_text_utils_act_name_replace](functions-7.md#smlua_text_utils_act_name_replace)
- [smlua_text_utils_act_name_get](functions-7.md#smlua_text_utils_act_name_get)
- [smlua_text_utils_act_name_mod_index](functions-7.md#smlua_text_utils_act_name_mod_index)
- [smlua_text_utils_act_name_reset](functions-7.md#smlua_text_utils_act_name_reset)
- [smlua_text_utils_castle_secret_stars_replace](functions-7.md#smlua_text_utils_castle_secret_stars_replace)
- [smlua_text_utils_castle_secret_stars_get](functions-7.md#smlua_text_utils_castle_secret_stars_get)
- [smlua_text_utils_castle_secret_stars_mod_index](functions-7.md#smlua_text_utils_castle_secret_stars_mod_index)
- [smlua_text_utils_castle_secret_stars_reset](functions-7.md#smlua_text_utils_castle_secret_stars_reset)
- [smlua_text_utils_extra_text_replace](functions-7.md#smlua_text_utils_extra_text_replace)
- [smlua_text_utils_extra_text_get](functions-7.md#smlua_text_utils_extra_text_get)
- [smlua_text_utils_extra_text_mod_index](functions-7.md#smlua_text_utils_extra_text_mod_index)
- [smlua_text_utils_extra_text_reset](functions-7.md#smlua_text_utils_extra_text_reset)
- [smlua_text_utils_get_language](functions-7.md#smlua_text_utils_get_language)
<br />
- sound_init.h
- [reset_volume](functions-6.md#reset_volume)
- [raise_background_noise](functions-6.md#raise_background_noise)
- [lower_background_noise](functions-6.md#lower_background_noise)
- [disable_background_sound](functions-6.md#disable_background_sound)
- [enable_background_sound](functions-6.md#enable_background_sound)
- [play_menu_sounds](functions-6.md#play_menu_sounds)
- [play_painting_eject_sound](functions-6.md#play_painting_eject_sound)
- [play_infinite_stairs_music](functions-6.md#play_infinite_stairs_music)
- [set_background_music](functions-6.md#set_background_music)
- [fadeout_music](functions-6.md#fadeout_music)
- [fadeout_level_music](functions-6.md#fadeout_level_music)
- [play_cutscene_music](functions-6.md#play_cutscene_music)
- [play_shell_music](functions-6.md#play_shell_music)
- [stop_shell_music](functions-6.md#stop_shell_music)
- [play_cap_music](functions-6.md#play_cap_music)
- [fadeout_cap_music](functions-6.md#fadeout_cap_music)
- [stop_cap_music](functions-6.md#stop_cap_music)
- [reset_volume](functions-7.md#reset_volume)
- [raise_background_noise](functions-7.md#raise_background_noise)
- [lower_background_noise](functions-7.md#lower_background_noise)
- [disable_background_sound](functions-7.md#disable_background_sound)
- [enable_background_sound](functions-7.md#enable_background_sound)
- [play_menu_sounds](functions-7.md#play_menu_sounds)
- [play_painting_eject_sound](functions-7.md#play_painting_eject_sound)
- [play_infinite_stairs_music](functions-7.md#play_infinite_stairs_music)
- [set_background_music](functions-7.md#set_background_music)
- [fadeout_music](functions-7.md#fadeout_music)
- [fadeout_level_music](functions-7.md#fadeout_level_music)
- [play_cutscene_music](functions-7.md#play_cutscene_music)
- [play_shell_music](functions-7.md#play_shell_music)
- [stop_shell_music](functions-7.md#stop_shell_music)
- [play_cap_music](functions-7.md#play_cap_music)
- [fadeout_cap_music](functions-7.md#fadeout_cap_music)
- [stop_cap_music](functions-7.md#stop_cap_music)
<br />
- spawn_sound.h
- [cur_obj_play_sound_1](functions-6.md#cur_obj_play_sound_1)
- [cur_obj_play_sound_2](functions-6.md#cur_obj_play_sound_2)
- [create_sound_spawner](functions-6.md#create_sound_spawner)
- [calc_dist_to_volume_range_1](functions-6.md#calc_dist_to_volume_range_1)
- [calc_dist_to_volume_range_2](functions-6.md#calc_dist_to_volume_range_2)
- [cur_obj_play_sound_1](functions-7.md#cur_obj_play_sound_1)
- [cur_obj_play_sound_2](functions-7.md#cur_obj_play_sound_2)
- [create_sound_spawner](functions-7.md#create_sound_spawner)
- [calc_dist_to_volume_range_1](functions-7.md#calc_dist_to_volume_range_1)
- [calc_dist_to_volume_range_2](functions-7.md#calc_dist_to_volume_range_2)
<br />
- surface_collision.h
- [find_wall_collisions](functions-6.md#find_wall_collisions)
- [find_ceil_height](functions-6.md#find_ceil_height)
- [find_floor_height](functions-6.md#find_floor_height)
- [find_water_level](functions-6.md#find_water_level)
- [find_poison_gas_level](functions-6.md#find_poison_gas_level)
- [set_find_wall_direction](functions-6.md#set_find_wall_direction)
- [closest_point_to_triangle](functions-6.md#closest_point_to_triangle)
- [find_wall_collisions](functions-7.md#find_wall_collisions)
- [find_ceil_height](functions-7.md#find_ceil_height)
- [find_floor_height](functions-7.md#find_floor_height)
- [find_water_level](functions-7.md#find_water_level)
- [find_poison_gas_level](functions-7.md#find_poison_gas_level)
- [set_find_wall_direction](functions-7.md#set_find_wall_direction)
- [closest_point_to_triangle](functions-7.md#closest_point_to_triangle)
<br />

View file

@ -1,3 +1,5 @@
## [:rewind: Lua Reference](../lua.md)
# How to use the Lighting Engine
## Section 1: Preparation

View file

@ -1,3 +1,5 @@
## [:rewind: Lua Reference](../lua.md)
# How to use `gMarioStates`
## Section 1: What is `gMarioStates`?

272
docs/lua/guides/modfs.md Normal file
View file

@ -0,0 +1,272 @@
## [:rewind: Lua Reference](../lua.md)
# ModFS
`ModFS` enables a small, sandboxed file system for mods. It allows to store and retrieve binary and text files, no matter their content.<br>
Each mod has its own file system, and can allow other mods to read its files.
<br>
## Specs
### File system
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 `.modfs` file, 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`
### Files
- 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`
<br>
## [`ModFs`](../structs.md#ModFs)
The object holding the file system of the mod.
### Fields
All fields are immutable.
| Name | Type |
| ----- | ---- |
| mod | [Mod](../structs.md#Mod) |
| modPath | `string` |
| numFiles | `integer` |
| totalSize | `integer` |
| isPublic | `boolean` |
Fields can be accessed in Lua with the dot `.` character:
```lua
print("The ModFS " .. modFs.modPath .. " contains " .. modFs.numFiles .. " files.")
```
### Methods
| Name | Reference |
| ---- | --------- |
| get_filename | [`mod_fs_get_filename`](../functions-5.md#mod_fs_get_filename) |
| get_file | [`mod_fs_get_file`](../functions-5.md#mod_fs_get_file) |
| create_file | [`mod_fs_create_file`](../functions-5.md#mod_fs_create_file) |
| move_file | [`mod_fs_move_file`](../functions-5.md#mod_fs_move_file) |
| copy_file | [`mod_fs_copy_file`](../functions-5.md#mod_fs_copy_file) |
| delete_file | [`mod_fs_delete_file`](../functions-5.md#mod_fs_delete_file) |
| clear | [`mod_fs_clear`](../functions-5.md#mod_fs_clear) |
| save | [`mod_fs_save`](../functions-5.md#mod_fs_save) |
| delete | [`mod_fs_delete`](../functions-5.md#mod_fs_delete) |
| set_public | [`mod_fs_set_public`](../functions-5.md#mod_fs_set_public) |
Methods can be called in Lua with the colon `:` character:
```lua
print("The first file of ModFS " .. modFs.modPath .. " is named " .. modFs:get_filename(0) .. ".")
```
<br>
## [`ModFsFile`](../structs.md#ModFsFile)
A handle to a ModFS file.
### Fields
All fields are immutable.
| Field | Type |
| ----- | ---- |
| modFs | [ModFs](../structs.md#ModFs) |
| filepath | `string` |
| size | `integer` |
| offset | `integer` |
| isText | `boolean` |
| isPublic | `boolean` |
Fields can be accessed in Lua with the dot `.` character:
```lua
print("The ModFS file " .. file.filepath .. " is " .. file.size .. " bytes long.")
```
### Methods
| Name | Reference |
| ---- | --------- |
| read_bool | [`mod_fs_file_read_bool`](../functions-5.md#mod_fs_file_read_bool) |
| read_integer | [`mod_fs_file_read_integer`](../functions-5.md#mod_fs_file_read_integer) |
| read_number | [`mod_fs_file_read_number`](../functions-5.md#mod_fs_file_read_number) |
| read_bytes | [`mod_fs_file_read_bytes`](../functions-5.md#mod_fs_file_read_bytes) |
| read_string | [`mod_fs_file_read_string`](../functions-5.md#mod_fs_file_read_string) |
| read_line | [`mod_fs_file_read_line`](../functions-5.md#mod_fs_file_read_line) |
| write_bool | [`mod_fs_file_write_bool`](../functions-5.md#mod_fs_file_write_bool) |
| write_integer | [`mod_fs_file_write_integer`](../functions-5.md#mod_fs_file_write_integer) |
| write_number | [`mod_fs_file_write_number`](../functions-5.md#mod_fs_file_write_number) |
| write_bytes | [`mod_fs_file_write_bytes`](../functions-5.md#mod_fs_file_write_bytes) |
| write_string | [`mod_fs_file_write_string`](../functions-5.md#mod_fs_file_write_string) |
| write_line | [`mod_fs_file_write_line`](../functions-5.md#mod_fs_file_write_line) |
| seek | [`mod_fs_file_seek`](../functions-5.md#mod_fs_file_seek) |
| rewind | [`mod_fs_file_rewind`](../functions-5.md#mod_fs_file_rewind) |
| is_eof | [`mod_fs_file_is_eof`](../functions-5.md#mod_fs_file_is_eof) |
| fill | [`mod_fs_file_fill`](../functions-5.md#mod_fs_file_fill) |
| erase | [`mod_fs_file_erase`](../functions-5.md#mod_fs_file_erase) |
| set_text_mode | [`mod_fs_file_set_text_mode`](../functions-5.md#mod_fs_file_set_text_mode) |
| set_public | [`mod_fs_file_set_public`](../functions-5.md#mod_fs_file_set_public) |
Methods can be called in Lua with the colon `:` character:
```lua
file:erase(file.size)
print("The ModFS file " .. file.filepath .. " is now empty.")
```
<br>
## Error handling
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_errors`](../functions-5.md#mod_fs_hide_errors) can suppress the ModFS errors from the console.
- Use the function [`mod_fs_get_last_error`](../functions-5.md#mod_fs_get_last_error) to retrieve the last error raised by ModFS. This function always return an error message if an error occurred, even if errors are hidden.
<br>
## Usage with other sm64coopdx features
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.<br>
Generate a ModFS URI from a `ModFs` object with the following code:
```lua
local uri = string.format(MOD_FS_URI_FORMAT, modFs.modPath, "<filepath>")
```
Here are some examples:
```lua
-- 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)
```
<br>
## Good practices
### Always valid `ModFs` object
Use the following piece of code to always retrieve a valid `ModFs` object:
```lua
local modFs = mod_fs_get() or mod_fs_create()
```
If the ModFS for the current mod doesn't exist, it will create one.
<br>
### Always valid `ModFsFile` object
Use the following piece of code to always retrieve a valid `ModFsFile` object:
```lua
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.<br>
To make sure the file is empty when requested, add the following line to clear the existing file content.
```lua
file:erase(file.size)
```
<br>
### Correctly initialize a file
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.<br>
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:
```lua
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 file
```
<br>
### Methods over functions
Always use `ModFs` and `ModFsFile` objects methods over regular functions.<br>
It's more clear that way and helps to reduce errors:
```lua
-- Don't
local file = mod_fs_create_file(modFs, "myfile.txt", true)
-- Do
local file = modFs:create_file("myfile.txt", true)
```
```lua
-- Don't
mod_fs_file_write_string(file, "some text")
-- Do
file:write_string("some text")
```
<br>
### Handle possible failures
In addition to error messages that can be retrieved with [`mod_fs_get_last_error`](../functions-5.md#mod_fs_get_last_error), almost all ModFS functions have a boolean return value indicating if the function succeeded or failed.
```lua
if not modFs:delete_file("somefile") then
print(mod_fs_get_last_error())
end
```
<br>
### Don't forget to save
ModFS are not saved automatically when writing to files.<br>
The mod has to explicitly call the method `save` to save its ModFS on the disk.
```lua
modFs:save()
```

View file

@ -1,3 +1,5 @@
## [:rewind: Lua Reference](../lua.md)
# Every Behavior's Object List
| Behavior | Object List |

View file

@ -34,6 +34,8 @@ Save file locations:
- [Hooks](guides/hooks.md)
- [gMarioStates](guides/mario-state.md)
- [Behavior Object Lists](guides/object-lists.md)
- [Lighting Engine](guides/lighting-engine.md)
- [ModFS](guides/modfs.md)
## Important notes on player indices

View file

@ -1956,7 +1956,7 @@
| Field | Type | Access |
| ----- | ---- | ------ |
| file | [ModFile](structs.md#ModFile) | |
| filepath | `string` | read-only |
| isStream | `boolean` | read-only |
| baseVolume | `number` | |
| loaded | `boolean` | read-only |
@ -2002,6 +2002,21 @@
| totalSize | `integer` | read-only |
| isPublic | `boolean` | read-only |
**Functions:**
| Name | Reference |
| ---- | --------- |
| get_filename | [`mod_fs_get_filename`](functions-5.md#mod_fs_get_filename) |
| get_file | [`mod_fs_get_file`](functions-5.md#mod_fs_get_file) |
| create_file | [`mod_fs_create_file`](functions-5.md#mod_fs_create_file) |
| move_file | [`mod_fs_move_file`](functions-5.md#mod_fs_move_file) |
| copy_file | [`mod_fs_copy_file`](functions-5.md#mod_fs_copy_file) |
| delete_file | [`mod_fs_delete_file`](functions-5.md#mod_fs_delete_file) |
| clear | [`mod_fs_clear`](functions-5.md#mod_fs_clear) |
| save | [`mod_fs_save`](functions-5.md#mod_fs_save) |
| delete | [`mod_fs_delete`](functions-5.md#mod_fs_delete) |
| set_public | [`mod_fs_set_public`](functions-5.md#mod_fs_set_public) |
[:arrow_up_small:](#)
<br />
@ -2017,6 +2032,30 @@
| isText | `boolean` | read-only |
| isPublic | `boolean` | read-only |
**Functions:**
| Name | Reference |
| ---- | --------- |
| read_bool | [`mod_fs_file_read_bool`](functions-5.md#mod_fs_file_read_bool) |
| read_integer | [`mod_fs_file_read_integer`](functions-5.md#mod_fs_file_read_integer) |
| read_number | [`mod_fs_file_read_number`](functions-5.md#mod_fs_file_read_number) |
| read_bytes | [`mod_fs_file_read_bytes`](functions-5.md#mod_fs_file_read_bytes) |
| read_string | [`mod_fs_file_read_string`](functions-5.md#mod_fs_file_read_string) |
| read_line | [`mod_fs_file_read_line`](functions-5.md#mod_fs_file_read_line) |
| write_bool | [`mod_fs_file_write_bool`](functions-5.md#mod_fs_file_write_bool) |
| write_integer | [`mod_fs_file_write_integer`](functions-5.md#mod_fs_file_write_integer) |
| write_number | [`mod_fs_file_write_number`](functions-5.md#mod_fs_file_write_number) |
| write_bytes | [`mod_fs_file_write_bytes`](functions-5.md#mod_fs_file_write_bytes) |
| write_string | [`mod_fs_file_write_string`](functions-5.md#mod_fs_file_write_string) |
| write_line | [`mod_fs_file_write_line`](functions-5.md#mod_fs_file_write_line) |
| seek | [`mod_fs_file_seek`](functions-5.md#mod_fs_file_seek) |
| rewind | [`mod_fs_file_rewind`](functions-5.md#mod_fs_file_rewind) |
| is_eof | [`mod_fs_file_is_eof`](functions-5.md#mod_fs_file_is_eof) |
| fill | [`mod_fs_file_fill`](functions-5.md#mod_fs_file_fill) |
| erase | [`mod_fs_file_erase`](functions-5.md#mod_fs_file_erase) |
| set_text_mode | [`mod_fs_file_set_text_mode`](functions-5.md#mod_fs_file_set_text_mode) |
| set_public | [`mod_fs_file_set_public`](functions-5.md#mod_fs_file_set_public) |
[:arrow_up_small:](#)
<br />

View file

@ -8,6 +8,7 @@
#include "pc/network/moderator_list.h"
#include "pc/debuglog.h"
#include "pc/lua/utils/smlua_level_utils.h"
#include "pc/mods/mods_utils.h"
#include "level_table.h"
#ifdef DEVELOPMENT
#include "pc/dev/chat.h"
@ -39,10 +40,6 @@ static struct NetworkPlayer* chat_get_network_player(const char* name) {
return NULL;
}
static bool str_starts_with(const char* pre, const char* str) {
return strncmp(pre, str, strlen(pre)) == 0;
}
static void chat_construct_player_message(struct NetworkPlayer* np, char* msg) {
char built[256] = { 0 };
snprintf(built, 256, "\\#fff982\\");
@ -124,7 +121,7 @@ bool exec_chat_command(char* command) {
return true;
}
if (str_starts_with("/kick ", command)) {
if (str_starts_with(command, "/kick ")) {
if (gNetworkType != NT_SERVER && !npl->moderator) {
djui_chat_message_create(DLANG(CHAT, NO_PERMS));
return true;
@ -152,7 +149,7 @@ bool exec_chat_command(char* command) {
return true;
}
if (str_starts_with("/ban ", command)) {
if (str_starts_with(command, "/ban ")) {
if (gNetworkType != NT_SERVER && !npl->moderator) {
djui_chat_message_create(DLANG(CHAT, NO_PERMS));
return true;
@ -180,7 +177,7 @@ bool exec_chat_command(char* command) {
return true;
}
if (str_starts_with("/permban ", command)) {
if (str_starts_with(command, "/permban ")) {
if (gNetworkType != NT_SERVER && !npl->moderator) {
djui_chat_message_create(DLANG(CHAT, NO_PERMS));
return true;
@ -208,7 +205,7 @@ bool exec_chat_command(char* command) {
return true;
}
if (str_starts_with("/moderator ", command)) {
if (str_starts_with(command, "/moderator ")) {
if (gNetworkType != NT_SERVER) {
djui_chat_message_create(DLANG(CHAT, SERVER_ONLY));
return true;
@ -237,7 +234,7 @@ bool exec_chat_command(char* command) {
return true;
}
if (str_starts_with("/nametags ", command)) {
if (str_starts_with(command, "/nametags ")) {
char *option = &command[10];
if (strcmp("show-tag", option) == 0) {
gNametagsSettings.showSelfTag = !gNametagsSettings.showSelfTag;

View file

@ -8,15 +8,12 @@
#include "pc/network/moderator_list.h"
#include "pc/debuglog.h"
#include "pc/lua/utils/smlua_level_utils.h"
#include "pc/mods/mods_utils.h"
#include "level_table.h"
#include "game/save_file.h"
#ifdef DEVELOPMENT
static bool str_starts_with(const char* pre, char* str) {
return strncmp(pre, str, strlen(pre)) == 0;
}
// For case insensitivity
static const char *upper(char *str) {
static char buffer[50];
@ -74,7 +71,7 @@ bool exec_dev_chat_command(char* command) {
return true;
}
if (str_starts_with("/warp ", command)) {
if (str_starts_with(command, "/warp ")) {
static const struct { const char *name; s32 num; } sLevelNumByName[] = {
#undef STUB_LEVEL
#undef DEFINE_LEVEL
@ -169,7 +166,7 @@ bool exec_dev_chat_command(char* command) {
return true;
}
if (str_starts_with("/lua ", command)) {
if (str_starts_with(command, "/lua ")) {
smlua_exec_str(&command[5]);
return true;
}
@ -179,7 +176,7 @@ bool exec_dev_chat_command(char* command) {
return true;
}
if (str_starts_with("/luaf ", command)) {
if (str_starts_with(command, "/luaf ")) {
smlua_exec_file(&command[6]);
return true;
}

View file

@ -4,6 +4,8 @@
#include "game/hardcoded.h"
#include "pc/mods/mods.h"
#include "pc/mods/mods_utils.h"
#include "pc/mods/mod_storage.h"
#include "pc/mods/mod_fs.h"
#include "pc/crash_handler.h"
#include "pc/lua/utils/smlua_text_utils.h"
#include "pc/lua/utils/smlua_audio_utils.h"
@ -352,7 +354,7 @@ void smlua_init(void) {
for (int j = 0; j < mod->fileCount; j++) {
struct ModFile* file = &mod->files[j];
// skip loading non-lua files
if (!(str_ends_with(file->relativePath, ".lua") || str_ends_with(file->relativePath, ".luac"))) {
if (!(path_ends_with(file->relativePath, ".lua") || path_ends_with(file->relativePath, ".luac"))) {
continue;
}
@ -418,6 +420,8 @@ void smlua_shutdown(void) {
smlua_model_util_clear();
smlua_level_util_reset();
smlua_anim_util_reset();
mod_storage_shutdown();
mod_fs_shutdown();
lua_State* L = gLuaState;
if (L != NULL) {
lua_close(L);

View file

@ -13,4 +13,7 @@
// Optional parameters must be contiguous until the last parameter (a mandatory parameter following an optional parameter is not allowed)
#define OPTIONAL
// A macro to tell autogen the field `name` is a function member of the struct that calls `c_function`
#define FUNCTION(name, c_function)
#endif // SMLUA_AUTOGEN_H

View file

@ -98,7 +98,9 @@ static const char *sLuaLvtNames[] = {
[LVT_TRAJECTORY_P] = "Trajectory Pointer",
[LVT_TEXTURE_P] = "Texture Pointer",
[LVT_LUAFUNCTION] = "LuaFunction",
[LVT_LUATABLE] = "LuaTable",
[LVT_POINTER] = "Pointer",
[LVT_FUNCTION] = "Function",
[LVT_MAX] = "Max",
};
@ -549,6 +551,14 @@ static int smlua__get_field(lua_State* L) {
return 0;
}
// CObject function members
if (data->valueType == LVT_FUNCTION) {
const char *function = (const char *) data->valueOffset;
lua_getglobal(L, function);
LUA_STACK_CHECK_END(L);
return 1;
}
u8* p = ((u8*)(intptr_t)pointer) + data->valueOffset;
if (data->count == 1) {
if (smlua_push_field(L, p, data)) {

View file

@ -1,6 +1,8 @@
#ifndef SMLUA_COBJECT_H
#define SMLUA_COBJECT_H
#include "lua.h"
enum LuaValueType {
LVT_BOOL,
LVT_BOOL_P,
@ -31,7 +33,9 @@ enum LuaValueType {
LVT_TRAJECTORY_P,
LVT_TEXTURE_P,
LVT_LUAFUNCTION,
LVT_LUATABLE,
LVT_POINTER,
LVT_FUNCTION,
LVT_MAX,
};

View file

@ -1607,10 +1607,10 @@ static struct LuaObjectField sModFields[LUA_MOD_FIELD_COUNT] = {
#define LUA_MOD_AUDIO_FIELD_COUNT 4
static struct LuaObjectField sModAudioFields[LUA_MOD_AUDIO_FIELD_COUNT] = {
{ "baseVolume", LVT_F32, offsetof(struct ModAudio, baseVolume), false, LOT_NONE, 1, sizeof(f32) },
{ "file", LVT_COBJECT_P, offsetof(struct ModAudio, file), false, LOT_MODFILE, 1, sizeof(struct ModFile*) },
{ "isStream", LVT_BOOL, offsetof(struct ModAudio, isStream), true, LOT_NONE, 1, sizeof(bool) },
{ "loaded", LVT_BOOL, offsetof(struct ModAudio, loaded), true, LOT_NONE, 1, sizeof(bool) },
{ "baseVolume", LVT_F32, offsetof(struct ModAudio, baseVolume), false, LOT_NONE, 1, sizeof(f32) },
{ "filepath", LVT_STRING_P, offsetof(struct ModAudio, filepath), true, LOT_NONE, 1, sizeof(const char*) },
{ "isStream", LVT_BOOL, offsetof(struct ModAudio, isStream), true, LOT_NONE, 1, sizeof(bool) },
{ "loaded", LVT_BOOL, offsetof(struct ModAudio, loaded), true, LOT_NONE, 1, sizeof(bool) },
};
#define LUA_MOD_AUDIO_SAMPLE_COPIES_FIELD_COUNT 3
@ -1634,23 +1634,83 @@ static struct LuaObjectField sModFileFields[LUA_MOD_FILE_FIELD_COUNT] = {
{ "wroteBytes", LVT_U64, offsetof(struct ModFile, wroteBytes), true, LOT_NONE, 1, sizeof(u64) },
};
#define LUA_MOD_FS_FIELD_COUNT 5
static const char FUNCTION__mod_fs_clear[] = "mod_fs_clear";
static const char FUNCTION__mod_fs_copy_file[] = "mod_fs_copy_file";
static const char FUNCTION__mod_fs_create_file[] = "mod_fs_create_file";
static const char FUNCTION__mod_fs_delete[] = "mod_fs_delete";
static const char FUNCTION__mod_fs_delete_file[] = "mod_fs_delete_file";
static const char FUNCTION__mod_fs_get_file[] = "mod_fs_get_file";
static const char FUNCTION__mod_fs_get_filename[] = "mod_fs_get_filename";
static const char FUNCTION__mod_fs_move_file[] = "mod_fs_move_file";
static const char FUNCTION__mod_fs_save[] = "mod_fs_save";
static const char FUNCTION__mod_fs_set_public[] = "mod_fs_set_public";
#define LUA_MOD_FS_FIELD_COUNT 15
static struct LuaObjectField sModFsFields[LUA_MOD_FS_FIELD_COUNT] = {
{ "isPublic", LVT_BOOL, offsetof(struct ModFs, isPublic), true, LOT_NONE, 1, sizeof(bool) },
{ "mod", LVT_COBJECT_P, offsetof(struct ModFs, mod), true, LOT_MOD, 1, sizeof(struct Mod*) },
{ "modPath", LVT_STRING, offsetof(struct ModFs, modPath), true, LOT_NONE, 1, sizeof(char) },
{ "numFiles", LVT_U16, offsetof(struct ModFs, numFiles), true, LOT_NONE, 1, sizeof(u16) },
{ "totalSize", LVT_U32, offsetof(struct ModFs, totalSize), true, LOT_NONE, 1, sizeof(u32) },
{ "clear", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_clear, true, LOT_NONE, 1, sizeof(const char *) },
{ "copy_file", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_copy_file, true, LOT_NONE, 1, sizeof(const char *) },
{ "create_file", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_create_file, true, LOT_NONE, 1, sizeof(const char *) },
{ "delete", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_delete, true, LOT_NONE, 1, sizeof(const char *) },
{ "delete_file", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_delete_file, true, LOT_NONE, 1, sizeof(const char *) },
{ "get_file", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_get_file, true, LOT_NONE, 1, sizeof(const char *) },
{ "get_filename", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_get_filename, true, LOT_NONE, 1, sizeof(const char *) },
{ "isPublic", LVT_BOOL, offsetof(struct ModFs, isPublic), true, LOT_NONE, 1, sizeof(bool) },
{ "mod", LVT_COBJECT_P, offsetof(struct ModFs, mod), true, LOT_MOD, 1, sizeof(struct Mod*) },
{ "modPath", LVT_STRING, offsetof(struct ModFs, modPath), true, LOT_NONE, 1, sizeof(char) },
{ "move_file", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_move_file, true, LOT_NONE, 1, sizeof(const char *) },
{ "numFiles", LVT_U16, offsetof(struct ModFs, numFiles), true, LOT_NONE, 1, sizeof(u16) },
{ "save", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_save, true, LOT_NONE, 1, sizeof(const char *) },
{ "set_public", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_set_public, true, LOT_NONE, 1, sizeof(const char *) },
{ "totalSize", LVT_U32, offsetof(struct ModFs, totalSize), true, LOT_NONE, 1, sizeof(u32) },
};
#define LUA_MOD_FS_FILE_FIELD_COUNT 6
static const char FUNCTION__mod_fs_file_erase[] = "mod_fs_file_erase";
static const char FUNCTION__mod_fs_file_fill[] = "mod_fs_file_fill";
static const char FUNCTION__mod_fs_file_is_eof[] = "mod_fs_file_is_eof";
static const char FUNCTION__mod_fs_file_read_bool[] = "mod_fs_file_read_bool";
static const char FUNCTION__mod_fs_file_read_bytes[] = "mod_fs_file_read_bytes";
static const char FUNCTION__mod_fs_file_read_integer[] = "mod_fs_file_read_integer";
static const char FUNCTION__mod_fs_file_read_line[] = "mod_fs_file_read_line";
static const char FUNCTION__mod_fs_file_read_number[] = "mod_fs_file_read_number";
static const char FUNCTION__mod_fs_file_read_string[] = "mod_fs_file_read_string";
static const char FUNCTION__mod_fs_file_rewind[] = "mod_fs_file_rewind";
static const char FUNCTION__mod_fs_file_seek[] = "mod_fs_file_seek";
static const char FUNCTION__mod_fs_file_set_public[] = "mod_fs_file_set_public";
static const char FUNCTION__mod_fs_file_set_text_mode[] = "mod_fs_file_set_text_mode";
static const char FUNCTION__mod_fs_file_write_bool[] = "mod_fs_file_write_bool";
static const char FUNCTION__mod_fs_file_write_bytes[] = "mod_fs_file_write_bytes";
static const char FUNCTION__mod_fs_file_write_integer[] = "mod_fs_file_write_integer";
static const char FUNCTION__mod_fs_file_write_line[] = "mod_fs_file_write_line";
static const char FUNCTION__mod_fs_file_write_number[] = "mod_fs_file_write_number";
static const char FUNCTION__mod_fs_file_write_string[] = "mod_fs_file_write_string";
#define LUA_MOD_FS_FILE_FIELD_COUNT 25
static struct LuaObjectField sModFsFileFields[LUA_MOD_FS_FILE_FIELD_COUNT] = {
{ "filepath", LVT_STRING, offsetof(struct ModFsFile, filepath), true, LOT_NONE, 1, sizeof(char) },
{ "isPublic", LVT_BOOL, offsetof(struct ModFsFile, isPublic), true, LOT_NONE, 1, sizeof(bool) },
{ "isText", LVT_BOOL, offsetof(struct ModFsFile, isText), true, LOT_NONE, 1, sizeof(bool) },
{ "modFs", LVT_COBJECT_P, offsetof(struct ModFsFile, modFs), true, LOT_MODFS, 1, sizeof(struct ModFs*) },
{ "offset", LVT_U32, offsetof(struct ModFsFile, offset), true, LOT_NONE, 1, sizeof(u32) },
{ "size", LVT_U32, offsetof(struct ModFsFile, size), true, LOT_NONE, 1, sizeof(u32) },
{ "erase", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_erase, true, LOT_NONE, 1, sizeof(const char *) },
{ "filepath", LVT_STRING, offsetof(struct ModFsFile, filepath), true, LOT_NONE, 1, sizeof(char) },
{ "fill", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_fill, true, LOT_NONE, 1, sizeof(const char *) },
{ "isPublic", LVT_BOOL, offsetof(struct ModFsFile, isPublic), true, LOT_NONE, 1, sizeof(bool) },
{ "isText", LVT_BOOL, offsetof(struct ModFsFile, isText), true, LOT_NONE, 1, sizeof(bool) },
{ "is_eof", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_is_eof, true, LOT_NONE, 1, sizeof(const char *) },
{ "modFs", LVT_COBJECT_P, offsetof(struct ModFsFile, modFs), true, LOT_MODFS, 1, sizeof(struct ModFs*) },
{ "offset", LVT_U32, offsetof(struct ModFsFile, offset), true, LOT_NONE, 1, sizeof(u32) },
{ "read_bool", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_read_bool, true, LOT_NONE, 1, sizeof(const char *) },
{ "read_bytes", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_read_bytes, true, LOT_NONE, 1, sizeof(const char *) },
{ "read_integer", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_read_integer, true, LOT_NONE, 1, sizeof(const char *) },
{ "read_line", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_read_line, true, LOT_NONE, 1, sizeof(const char *) },
{ "read_number", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_read_number, true, LOT_NONE, 1, sizeof(const char *) },
{ "read_string", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_read_string, true, LOT_NONE, 1, sizeof(const char *) },
{ "rewind", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_rewind, true, LOT_NONE, 1, sizeof(const char *) },
{ "seek", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_seek, true, LOT_NONE, 1, sizeof(const char *) },
{ "set_public", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_set_public, true, LOT_NONE, 1, sizeof(const char *) },
{ "set_text_mode", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_set_text_mode, true, LOT_NONE, 1, sizeof(const char *) },
{ "size", LVT_U32, offsetof(struct ModFsFile, size), true, LOT_NONE, 1, sizeof(u32) },
{ "write_bool", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_write_bool, true, LOT_NONE, 1, sizeof(const char *) },
{ "write_bytes", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_write_bytes, true, LOT_NONE, 1, sizeof(const char *) },
{ "write_integer", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_write_integer, true, LOT_NONE, 1, sizeof(const char *) },
{ "write_line", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_write_line, true, LOT_NONE, 1, sizeof(const char *) },
{ "write_number", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_write_number, true, LOT_NONE, 1, sizeof(const char *) },
{ "write_string", LVT_FUNCTION, (size_t) FUNCTION__mod_fs_file_write_string, true, LOT_NONE, 1, sizeof(const char *) },
};
#define LUA_MODE_TRANSITION_INFO_FIELD_COUNT 6

View file

@ -2239,9 +2239,11 @@ char gSmluaConstants[] = ""
"GRAB_POS_LIGHT_OBJ=1\n"
"GRAB_POS_HEAVY_OBJ=2\n"
"GRAB_POS_BOWSER=3\n"
"MOD_FS_MAX_SIZE=0x1000000\n"
"MOD_FS_MAX_FILES=0x100\n"
"MOD_FS_MAX_SIZE=0x2000000\n"
"MOD_FS_MAX_FILES=0x200\n"
"MOD_FS_MAX_PATH=0x100\n"
"MOD_FS_URI_PREFIX='modfs:/'\n"
"MOD_FS_URI_FORMAT='modfs:/%s/%s'\n"
"INT_TYPE_U8=0\n"
"INT_TYPE_U16=1\n"
"INT_TYPE_U32=2\n"
@ -2978,6 +2980,7 @@ char gSmluaConstants[] = ""
"L_CBUTTONS=CONT_C\n"
"R_CBUTTONS=CONT_F\n"
"D_CBUTTONS=CONT_D\n"
"PALETTES_DIRECTORY='palettes'\n"
"MAX_PRESET_PALETTES=128\n"
"PANTS=0\n"
"SHIRT=1\n"
@ -4621,5 +4624,7 @@ char gSmluaConstants[] = ""
"VERSION_TEXT='v'\n"
"VERSION_NUMBER=41\n"
"MINOR_VERSION_NUMBER=0\n"
"GAME_NAME='sm64coopdx'\n"
"WINDOW_NAME='Super Mario 64 Coop Deluxe'\n"
"MAX_VERSION_LENGTH=128\n"
;

View file

@ -22481,53 +22481,6 @@ int smlua_func_mod_fs_create(UNUSED lua_State* L) {
return 1;
}
int smlua_func_mod_fs_delete(UNUSED lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 0) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_fs_delete", 0, top);
return 0;
}
lua_pushboolean(L, mod_fs_delete());
return 1;
}
int smlua_func_mod_fs_save(UNUSED lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 0) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_fs_save", 0, top);
return 0;
}
lua_pushboolean(L, mod_fs_save());
return 1;
}
int smlua_func_mod_fs_set_public(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 1) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_fs_set_public", 1, top);
return 0;
}
bool pub = smlua_to_boolean(L, 1);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "mod_fs_set_public"); return 0; }
lua_pushboolean(L, mod_fs_set_public(pub));
return 1;
}
int smlua_func_mod_fs_get_filename(lua_State* L) {
if (L == NULL) { return 0; }
@ -22669,6 +22622,59 @@ int smlua_func_mod_fs_clear(lua_State* L) {
return 1;
}
int smlua_func_mod_fs_save(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 1) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_fs_save", 1, top);
return 0;
}
struct ModFs* modFs = (struct ModFs*)smlua_to_cobject(L, 1, LOT_MODFS);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "mod_fs_save"); return 0; }
lua_pushboolean(L, mod_fs_save(modFs));
return 1;
}
int smlua_func_mod_fs_delete(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 1) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_fs_delete", 1, top);
return 0;
}
struct ModFs* modFs = (struct ModFs*)smlua_to_cobject(L, 1, LOT_MODFS);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "mod_fs_delete"); return 0; }
lua_pushboolean(L, mod_fs_delete(modFs));
return 1;
}
int smlua_func_mod_fs_set_public(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 2) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_fs_set_public", 2, top);
return 0;
}
struct ModFs* modFs = (struct ModFs*)smlua_to_cobject(L, 1, LOT_MODFS);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "mod_fs_set_public"); return 0; }
bool pub = smlua_to_boolean(L, 2);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "mod_fs_set_public"); return 0; }
lua_pushboolean(L, mod_fs_set_public(modFs, pub));
return 1;
}
int smlua_func_mod_fs_file_read_bool(lua_State* L) {
if (L == NULL) { return 0; }
@ -22916,6 +22922,23 @@ int smlua_func_mod_fs_file_seek(lua_State* L) {
return 1;
}
int smlua_func_mod_fs_file_rewind(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 1) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_fs_file_rewind", 1, top);
return 0;
}
struct ModFsFile* file = (struct ModFsFile*)smlua_to_cobject(L, 1, LOT_MODFSFILE);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "mod_fs_file_rewind"); return 0; }
lua_pushboolean(L, mod_fs_file_rewind(file));
return 1;
}
int smlua_func_mod_fs_file_is_eof(lua_State* L) {
if (L == NULL) { return 0; }
@ -22973,6 +22996,25 @@ int smlua_func_mod_fs_file_erase(lua_State* L) {
return 1;
}
int smlua_func_mod_fs_file_set_text_mode(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 2) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_fs_file_set_text_mode", 2, top);
return 0;
}
struct ModFsFile* file = (struct ModFsFile*)smlua_to_cobject(L, 1, LOT_MODFSFILE);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "mod_fs_file_set_text_mode"); return 0; }
bool text = smlua_to_boolean(L, 2);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "mod_fs_file_set_text_mode"); return 0; }
lua_pushboolean(L, mod_fs_file_set_text_mode(file, text));
return 1;
}
int smlua_func_mod_fs_file_set_public(lua_State* L) {
if (L == NULL) { return 0; }
@ -23136,6 +23178,21 @@ int smlua_func_mod_storage_load_bool(lua_State* L) {
return 1;
}
int smlua_func_mod_storage_load_all(UNUSED lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
if (top != 0) {
LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "mod_storage_load_all", 0, top);
return 0;
}
smlua_push_lua_table(L, mod_storage_load_all());
return 1;
}
int smlua_func_mod_storage_exists(lua_State* L) {
if (L == NULL) { return 0; }
@ -37904,9 +37961,6 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "mod_fs_get", smlua_func_mod_fs_get);
smlua_bind_function(L, "mod_fs_reload", smlua_func_mod_fs_reload);
smlua_bind_function(L, "mod_fs_create", smlua_func_mod_fs_create);
smlua_bind_function(L, "mod_fs_delete", smlua_func_mod_fs_delete);
smlua_bind_function(L, "mod_fs_save", smlua_func_mod_fs_save);
smlua_bind_function(L, "mod_fs_set_public", smlua_func_mod_fs_set_public);
smlua_bind_function(L, "mod_fs_get_filename", smlua_func_mod_fs_get_filename);
smlua_bind_function(L, "mod_fs_get_file", smlua_func_mod_fs_get_file);
smlua_bind_function(L, "mod_fs_create_file", smlua_func_mod_fs_create_file);
@ -37914,6 +37968,9 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "mod_fs_copy_file", smlua_func_mod_fs_copy_file);
smlua_bind_function(L, "mod_fs_delete_file", smlua_func_mod_fs_delete_file);
smlua_bind_function(L, "mod_fs_clear", smlua_func_mod_fs_clear);
smlua_bind_function(L, "mod_fs_save", smlua_func_mod_fs_save);
smlua_bind_function(L, "mod_fs_delete", smlua_func_mod_fs_delete);
smlua_bind_function(L, "mod_fs_set_public", smlua_func_mod_fs_set_public);
smlua_bind_function(L, "mod_fs_file_read_bool", smlua_func_mod_fs_file_read_bool);
smlua_bind_function(L, "mod_fs_file_read_integer", smlua_func_mod_fs_file_read_integer);
smlua_bind_function(L, "mod_fs_file_read_number", smlua_func_mod_fs_file_read_number);
@ -37927,9 +37984,11 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "mod_fs_file_write_string", smlua_func_mod_fs_file_write_string);
smlua_bind_function(L, "mod_fs_file_write_line", smlua_func_mod_fs_file_write_line);
smlua_bind_function(L, "mod_fs_file_seek", smlua_func_mod_fs_file_seek);
smlua_bind_function(L, "mod_fs_file_rewind", smlua_func_mod_fs_file_rewind);
smlua_bind_function(L, "mod_fs_file_is_eof", smlua_func_mod_fs_file_is_eof);
smlua_bind_function(L, "mod_fs_file_fill", smlua_func_mod_fs_file_fill);
smlua_bind_function(L, "mod_fs_file_erase", smlua_func_mod_fs_file_erase);
smlua_bind_function(L, "mod_fs_file_set_text_mode", smlua_func_mod_fs_file_set_text_mode);
smlua_bind_function(L, "mod_fs_file_set_public", smlua_func_mod_fs_file_set_public);
smlua_bind_function(L, "mod_fs_hide_errors", smlua_func_mod_fs_hide_errors);
smlua_bind_function(L, "mod_fs_get_last_error", smlua_func_mod_fs_get_last_error);
@ -37941,6 +38000,7 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "mod_storage_load", smlua_func_mod_storage_load);
smlua_bind_function(L, "mod_storage_load_number", smlua_func_mod_storage_load_number);
smlua_bind_function(L, "mod_storage_load_bool", smlua_func_mod_storage_load_bool);
smlua_bind_function(L, "mod_storage_load_all", smlua_func_mod_storage_load_all);
smlua_bind_function(L, "mod_storage_exists", smlua_func_mod_storage_exists);
smlua_bind_function(L, "mod_storage_remove", smlua_func_mod_storage_remove);
smlua_bind_function(L, "mod_storage_clear", smlua_func_mod_storage_clear);

View file

@ -96,7 +96,7 @@ static struct ModFile* smlua_find_mod_file(const char* moduleName) {
}
// only consider lua files
if (!str_ends_with(file->relativePath, ".lua") && !str_ends_with(file->relativePath, ".luac")) {
if (!path_ends_with(file->relativePath, ".lua") && !path_ends_with(file->relativePath, ".luac")) {
continue;
}
@ -118,7 +118,7 @@ static int smlua_custom_require(lua_State* L) {
return 0;
}
if (str_ends_with(moduleName, "/") || str_ends_with(moduleName, "\\")) {
if (path_ends_with(moduleName, "/") || path_ends_with(moduleName, "\\")) {
LOG_LUA_LINE("cannot require a directory");
return 0;
}

View file

@ -156,6 +156,22 @@ LuaFunction smlua_to_lua_function(lua_State* L, int index) {
return luaL_ref(L, LUA_REGISTRYINDEX);
}
LuaTable smlua_to_lua_table(lua_State* L, int index) {
if (lua_type(L, index) == LUA_TNIL) {
return 0;
}
if (lua_type(L, index) != LUA_TTABLE) {
LOG_LUA_LINE("smlua_to_lua_table received improper type '%s'", luaL_typename(L, index));
gSmLuaConvertSuccess = false;
return 0;
}
gSmLuaConvertSuccess = true;
lua_pushvalue(L, index);
return luaL_ref(L, LUA_REGISTRYINDEX);
}
bool smlua_is_cobject(lua_State* L, int index, UNUSED u16 lot) {
return lua_isuserdata(L, index);
}
@ -497,6 +513,14 @@ void smlua_push_table_field(int index, const char* name) {
///////////////////////////////////////////////////////////////////////////////////////////
void smlua_push_lua_table(lua_State* L, LuaTable table) {
if (table != 0) {
lua_rawgeti(L, LUA_REGISTRYINDEX, table);
} else {
lua_pushnil(L);
}
}
void smlua_push_bytestring(lua_State* L, ByteString bytestring) {
if (bytestring.bytes) {
lua_pushlstring(L, bytestring.bytes, bytestring.length);

View file

@ -6,6 +6,7 @@
extern u8 gSmLuaConvertSuccess;
typedef int LuaFunction;
typedef int LuaTable;
typedef struct ByteString {
const char *bytes;
@ -27,6 +28,7 @@ lua_Number smlua_to_number(lua_State* L, int index);
const char* smlua_to_string(lua_State* L, int index);
ByteString smlua_to_bytestring(lua_State* L, int index);
LuaFunction smlua_to_lua_function(lua_State* L, int index);
LuaTable smlua_to_lua_table(lua_State* L, int index);
bool smlua_is_cobject(lua_State* L, int index, u16 lot);
void* smlua_to_cobject(lua_State* L, int index, u16 lot);
void* smlua_to_cpointer(lua_State* L, int index, u16 lvt);
@ -44,6 +46,7 @@ void smlua_push_string_field(int index, const char* name, const char* val);
void smlua_push_nil_field(int index, const char* name);
void smlua_push_table_field(int index, const char* name);
void smlua_push_lua_table(lua_State* L, LuaTable table);
void smlua_push_bytestring(lua_State* L, ByteString bytestring);
void smlua_push_lnt(struct LSTNetworkType* lnt);

View file

@ -10,6 +10,7 @@
#include "game/camera.h"
#include "engine/math_util.h"
#include "pc/mods/mods.h"
#include "pc/mods/mod_fs.h"
#include "pc/lua/smlua.h"
#include "pc/lua/utils/smlua_audio_utils.h"
#include "pc/mods/mods_utils.h"
@ -78,27 +79,37 @@ bool smlua_audio_utils_override(u8 sequenceId, s32* bankId, void** seqData) {
return true;
}
static u8* buffer = NULL;
static long int length = 0;
u8* buffer = NULL;
u32 length = 0;
FILE* fp = f_open_r(override->filename);
if (!fp) { return false; }
f_seek(fp, 0L, SEEK_END);
length = f_tell(fp);
if (is_mod_fs_file(override->filename)) {
if (!mod_fs_read_file_from_uri(override->filename, (void **) &buffer, &length)) {
return false;
}
} else {
FILE* fp = f_open_r(override->filename);
if (!fp) { return false; }
f_seek(fp, 0L, SEEK_END);
length = f_tell(fp);
buffer = malloc(length+1);
if (buffer == NULL) {
LOG_ERROR("Failed to malloc m64 sound file");
f_close(fp);
f_delete(fp);
return false;
}
f_seek(fp, 0L, SEEK_SET);
f_read(buffer, length, 1, fp);
buffer = malloc(length+1);
if (buffer == NULL) {
LOG_ERROR("Failed to malloc m64 sound file");
f_close(fp);
f_delete(fp);
return false;
}
f_seek(fp, 0L, SEEK_SET);
f_read(buffer, length, 1, fp);
f_close(fp);
f_delete(fp);
if (!buffer || !length) {
return false;
}
// cache
override->loaded = true;
@ -110,6 +121,17 @@ bool smlua_audio_utils_override(u8 sequenceId, s32* bankId, void** seqData) {
return true;
}
static void smlua_audio_utils_create_audio_override(u8 sequenceId, u8 bankId, u8 defaultVolume, const char *filepath) {
struct AudioOverride* override = &sAudioOverrides[sequenceId];
if (override->enabled) { audio_init(); }
smlua_audio_utils_reset(override);
LOG_INFO("Loading audio: %s", filepath);
override->filename = strdup(filepath);
override->enabled = true;
override->bank = bankId;
sound_set_background_music_default_volume(sequenceId, defaultVolume);
}
void smlua_audio_utils_replace_sequence(u8 sequenceId, u8 bankId, u8 defaultVolume, const char* m64Name) {
if (gLuaActiveMod == NULL) { return; }
if (sequenceId >= MAX_AUDIO_OVERRIDE) {
@ -122,6 +144,11 @@ void smlua_audio_utils_replace_sequence(u8 sequenceId, u8 bankId, u8 defaultVolu
return;
}
if (is_mod_fs_file(m64Name)) {
smlua_audio_utils_create_audio_override(sequenceId, bankId, defaultVolume, m64Name);
return;
}
char m64path[SYS_MAX_PATH] = { 0 };
if (snprintf(m64path, SYS_MAX_PATH-1, "sound/%s.m64", m64Name) < 0) {
LOG_LUA_LINE("Could not concat m64path: %s", m64path);
@ -134,19 +161,8 @@ void smlua_audio_utils_replace_sequence(u8 sequenceId, u8 bankId, u8 defaultVolu
char relPath[SYS_MAX_PATH] = { 0 };
snprintf(relPath, SYS_MAX_PATH-1, "%s", file->relativePath);
normalize_path(relPath);
if (str_ends_with(relPath, m64path)) {
struct AudioOverride* override = &sAudioOverrides[sequenceId];
if (override->enabled) { audio_init(); }
smlua_audio_utils_reset(override);
LOG_INFO("Loading audio: %s", file->cachedPath);
override->filename = strdup(file->cachedPath);
override->enabled = true;
override->bank = bankId;
#ifdef VERSION_EU
//sBackgroundMusicDefaultVolume[sequenceId] = defaultVolume;
#else
sound_set_background_music_default_volume(sequenceId, defaultVolume);
#endif
if (path_ends_with(relPath, m64path)) {
smlua_audio_utils_create_audio_override(sequenceId, bankId, defaultVolume, file->cachedPath);
return;
}
}
@ -174,12 +190,12 @@ static void smlua_audio_custom_init(void) {
}
}
static struct ModAudio* find_mod_audio(struct ModFile* file) {
static struct ModAudio* find_mod_audio(const char *filepath) {
struct DynamicPoolNode* node = sModAudioPool->tail;
while (node) {
struct DynamicPoolNode* prev = node->prev;
struct ModAudio* audio = node->ptr;
if (audio->file == file) { return audio; }
if (strcmp(filepath, audio->filepath) == 0) { return audio; }
node = prev;
}
return NULL;
@ -209,7 +225,7 @@ struct ModAudio* audio_load_internal(const char* filename, bool isStream) {
const char* fileTypes[] = { ".mp3", ".aiff", ".ogg", NULL };
const char** ft = fileTypes;
while (*ft != NULL) {
if (str_ends_with((char*)filename, (char*)*ft)) {
if (path_ends_with(filename, *ft)) {
validFileType = true;
break;
}
@ -220,25 +236,30 @@ struct ModAudio* audio_load_internal(const char* filename, bool isStream) {
return NULL;
}
// find mod file in mod list
bool foundModFile = false;
struct ModFile* modFile = NULL;
u16 fileCount = gLuaActiveMod->fileCount;
for (u16 i = 0; i < fileCount; i++) {
struct ModFile* file = &gLuaActiveMod->files[i];
if(str_ends_with(file->relativePath, (char*)filename)) {
foundModFile = true;
modFile = file;
break;
const char *filepath = filename;
if (!is_mod_fs_file(filename)) {
// find mod file in mod list
bool foundModFile = false;
struct ModFile* modFile = NULL;
u16 fileCount = gLuaActiveMod->fileCount;
for (u16 i = 0; i < fileCount; i++) {
struct ModFile* file = &gLuaActiveMod->files[i];
if(path_ends_with(file->relativePath, filename)) {
foundModFile = true;
modFile = file;
break;
}
}
}
if (!foundModFile) {
LOG_LUA_LINE("Could not find audio file: '%s'", filename);
return NULL;
if (!foundModFile) {
LOG_LUA_LINE("Could not find audio file: '%s'", filename);
return NULL;
}
filepath = modFile->cachedPath;
}
// find stream in ModAudio list
struct ModAudio* audio = find_mod_audio(modFile);
struct ModAudio* audio = find_mod_audio(filepath);
if (audio) {
if (isStream == audio->isStream) {
return audio;
@ -261,36 +282,52 @@ struct ModAudio* audio_load_internal(const char* filename, bool isStream) {
}
// remember file
audio->file = modFile;
audio->filepath = strdup(filepath);
// load audio
FILE *f = f_open_r(modFile->cachedPath);
if (!f) {
LOG_ERROR("failed to load audio file '%s': file not found", filename);
return NULL;
}
void *buffer = NULL;
u32 size = 0;
f_seek(f, 0, SEEK_END);
u32 size = f_tell(f);
f_rewind(f);
void *buffer = calloc(size, 1);
if (!buffer) {
if (is_mod_fs_file(filepath)) {
if (!mod_fs_read_file_from_uri(filepath, &buffer, &size)) {
LOG_ERROR("failed to load audio file '%s': an error occurred with modfs", filename);
return NULL;
}
} else {
// load audio
FILE *f = f_open_r(filepath);
if (!f) {
LOG_ERROR("failed to load audio file '%s': file not found", filename);
return NULL;
}
f_seek(f, 0, SEEK_END);
size = f_tell(f);
f_rewind(f);
buffer = calloc(size, 1);
if (!buffer) {
f_close(f);
f_delete(f);
LOG_ERROR("failed to load audio file '%s': cannot allocate buffer of size: %d", filename, size);
return NULL;
}
// read the audio buffer
if (f_read(buffer, 1, size, f) < size) {
free(buffer);
f_close(f);
f_delete(f);
LOG_ERROR("failed to load audio file '%s': cannot read audio buffer of size: %d", filename, size);
return NULL;
}
f_close(f);
f_delete(f);
LOG_ERROR("failed to load audio file '%s': cannot allocate buffer of size: %d", filename, size);
return NULL;
}
// read the audio buffer
if (f_read(buffer, 1, size, f) < size) {
free(buffer);
f_close(f);
f_delete(f);
LOG_ERROR("failed to load audio file '%s': cannot read audio buffer of size: %d", filename, size);
if (!buffer || !size) {
LOG_ERROR("failed to load audio file '%s': failed to read audio data", filename);
return NULL;
}
f_close(f);
f_delete(f);
// decode the audio buffer
ma_result result = ma_decoder_init_memory(buffer, size, NULL, &audio->decoder);
@ -604,6 +641,7 @@ void audio_custom_shutdown(void) {
audio_sample_destroy_copies(audio);
}
ma_sound_uninit(&audio->sound);
free((void *) audio->filepath);
}
dynamic_pool_free(sModAudioPool, audio);
node = prev;

View file

@ -22,7 +22,7 @@ struct ModAudioSampleCopies {
};
struct ModAudio {
struct ModFile* file;
const char *filepath;
ma_sound sound;
ma_decoder decoder;
void *buffer;

View file

@ -23,7 +23,7 @@ size_t mod_get_lua_size(struct Mod* mod) {
for (int i = 0; i < mod->fileCount; i++) {
struct ModFile* file = &mod->files[i];
if (!(str_ends_with(file->relativePath, ".lua") || str_ends_with(file->relativePath, ".luac"))) { continue; }
if (!(path_ends_with(file->relativePath, ".lua") || path_ends_with(file->relativePath, ".luac"))) { continue; }
size += file->size;
}
@ -161,19 +161,19 @@ void mod_activate(struct Mod* mod) {
mod_cache_update(mod, file);
}
if (str_ends_with(file->relativePath, ".bin")) {
if (path_ends_with(file->relativePath, ".bin")) {
mod_activate_bin(mod, file);
}
if (str_ends_with(file->relativePath, ".col")) {
if (path_ends_with(file->relativePath, ".col")) {
mod_activate_col(file);
}
if (str_ends_with(file->relativePath, ".lvl")) {
if (path_ends_with(file->relativePath, ".lvl")) {
mod_activate_lvl(mod, file);
}
if (str_ends_with(file->relativePath, ".bhv")) {
if (path_ends_with(file->relativePath, ".bhv")) {
mod_activate_bhv(mod, file);
}
if (str_ends_with(file->relativePath, ".tex")) {
if (path_ends_with(file->relativePath, ".tex")) {
mod_activate_tex(file);
}
}
@ -337,7 +337,7 @@ static bool mod_load_files_dir(struct Mod* mod, char* fullPath, const char* subD
bool fileTypeMatch = false;
const char** ft = fileTypes;
while (*ft != NULL) {
if (str_ends_with(path, (char*)*ft)) {
if (path_ends_with(path, (char*)*ft)) {
fileTypeMatch = true;
}
ft++;
@ -562,7 +562,7 @@ bool mod_load(struct Mods* mods, char* basePath, char* modName) {
bool isDirectory = fs_sys_dir_exists(fullPath);
// make sure mod is valid
if (str_ends_with(modName, ".lua")) {
if (path_ends_with(modName, ".lua")) {
valid = true;
} else if (fs_sys_dir_exists(fullPath)) {
char tmpPath[SYS_MAX_PATH] = { 0 };

File diff suppressed because it is too large Load diff

View file

@ -4,12 +4,13 @@
#include "types.h"
#include "src/pc/lua/smlua.h"
#define MOD_FS_MAX_SIZE 0x1000000 // 16 MB
#define MOD_FS_MAX_FILES 0x100
#define MOD_FS_MAX_SIZE 0x2000000 // 32 MB
#define MOD_FS_MAX_FILES 0x200
#define MOD_FS_MAX_PATH 0x100
#define MOD_FS_DIRECTORY "sav"
#define MOD_FS_EXTENSION ".modfs"
#define MOD_FS_VERSION 1
#define MOD_FS_URI_PREFIX "modfs:/"
#define MOD_FS_URI_FORMAT "modfs:/%s/%s" // modPath, filepath
#define is_mod_fs_file(filepath) (memcmp(filepath, MOD_FS_URI_PREFIX, sizeof(MOD_FS_URI_PREFIX) - 1) == 0)
enum ModFsFileIntType {
INT_TYPE_U8,
@ -53,6 +54,26 @@ struct ModFsFile {
u32 offset;
bool isText;
bool isPublic;
FUNCTION(read_bool, mod_fs_file_read_bool);
FUNCTION(read_integer, mod_fs_file_read_integer);
FUNCTION(read_number, mod_fs_file_read_number);
FUNCTION(read_bytes, mod_fs_file_read_bytes);
FUNCTION(read_string, mod_fs_file_read_string);
FUNCTION(read_line, mod_fs_file_read_line);
FUNCTION(write_bool, mod_fs_file_write_bool);
FUNCTION(write_integer, mod_fs_file_write_integer);
FUNCTION(write_number, mod_fs_file_write_number);
FUNCTION(write_bytes, mod_fs_file_write_bytes);
FUNCTION(write_string, mod_fs_file_write_string);
FUNCTION(write_line, mod_fs_file_write_line);
FUNCTION(seek, mod_fs_file_seek);
FUNCTION(rewind, mod_fs_file_rewind);
FUNCTION(is_eof, mod_fs_file_is_eof);
FUNCTION(fill, mod_fs_file_fill);
FUNCTION(erase, mod_fs_file_erase);
FUNCTION(set_text_mode, mod_fs_file_set_text_mode);
FUNCTION(set_public, mod_fs_file_set_public);
};
struct ModFs {
@ -62,6 +83,17 @@ struct ModFs {
u16 numFiles;
u32 totalSize;
bool isPublic;
FUNCTION(get_filename, mod_fs_get_filename);
FUNCTION(get_file, mod_fs_get_file);
FUNCTION(create_file, mod_fs_create_file);
FUNCTION(move_file, mod_fs_move_file);
FUNCTION(copy_file, mod_fs_copy_file);
FUNCTION(delete_file, mod_fs_delete_file);
FUNCTION(clear, mod_fs_clear);
FUNCTION(save, mod_fs_save);
FUNCTION(delete, mod_fs_delete);
FUNCTION(set_public, mod_fs_set_public);
};
/* |description|
@ -84,21 +116,6 @@ Creates a modfs object for the active mod if it doesn't exist. Returns the modfs
|descriptionEnd| */
struct ModFs *mod_fs_create();
/* |description|
Deletes the modfs object of the active mod if it exists. Returns true on success
|descriptionEnd| */
bool mod_fs_delete();
/* |description|
Saves the modfs object of the active mod if it exists. Returns true on success
|descriptionEnd| */
bool mod_fs_save();
/* |description|
Marks the modfs object of the active mod as public (i.e. readable by other mods) if it exists. Returns true on success
|descriptionEnd| */
bool mod_fs_set_public(bool pub);
/* |description|
Gets the filename at position `index` of the provided `modFs`
|descriptionEnd| */
@ -134,6 +151,21 @@ Deletes all files of the provided `modFs`. Returns true on success
|descriptionEnd| */
bool mod_fs_clear(struct ModFs *modFs);
/* |description|
Saves the provided `modFs` to persistent storage. Returns true on success
|descriptionEnd| */
bool mod_fs_save(struct ModFs *modFs);
/* |description|
Removes the provided `modFs` from persistent storage and deletes its object. Returns true on success
|descriptionEnd| */
bool mod_fs_delete(struct ModFs *modFs);
/* |description|
Marks the provided `modFs` as public (i.e. readable by other mods). Returns true on success
|descriptionEnd| */
bool mod_fs_set_public(struct ModFs *modFs, bool pub);
/* |description|
Reads a boolean from a binary modfs `file`
|descriptionEnd| */
@ -203,6 +235,12 @@ Returns true on success
|descriptionEnd| */
bool mod_fs_file_seek(struct ModFsFile *file, s32 offset, enum ModFsFileSeek origin);
/* |description|
Sets the current position of a modfs `file` to its beginning.
Returns true on success
|descriptionEnd| */
bool mod_fs_file_rewind(struct ModFsFile *file);
/* |description|
Returns true if the provided modfs `file` has reached its end of file
|descriptionEnd| */
@ -218,6 +256,11 @@ Erases `length` bytes or characters from a modfs `file`. Returns true on success
|descriptionEnd| */
bool mod_fs_file_erase(struct ModFsFile *file, u32 length);
/* |description|
Marks the provided modfs `file` as text. Returns true on success
|descriptionEnd| */
bool mod_fs_file_set_text_mode(struct ModFsFile *file, bool text);
/* |description|
Marks the provided modfs `file` as public (i.e. readable by other mods). Returns true on success
|descriptionEnd| */
@ -233,4 +276,9 @@ Returns the last error message generated by `mod_fs` functions or nil if no erro
|descriptionEnd| */
const char *mod_fs_get_last_error();
// Functions used by other C modules, not API
bool mod_fs_read_file_from_uri(const char *uri, void **buffer, u32 *length);
void mod_fs_shutdown();
#endif // MOD_FS_H

View file

@ -124,15 +124,15 @@ static bool mod_import_zip(char* path, bool* isLua, bool* isDynos) {
return false;
}
if (str_ends_with(file_stat.m_filename, ".lua") || str_ends_with(file_stat.m_filename, ".luac")) {
if (path_ends_with(file_stat.m_filename, ".lua") || path_ends_with(file_stat.m_filename, ".luac")) {
path_get_folder(file_stat.m_filename, luaPath);
*isLua = true;
break;
} else if (str_ends_with(file_stat.m_filename, ".tex")) {
} else if (path_ends_with(file_stat.m_filename, ".tex")) {
*isDynos = true;
} else if (str_ends_with(file_stat.m_filename, ".png")) {
} else if (path_ends_with(file_stat.m_filename, ".png")) {
*isDynos = true;
} else if (str_ends_with(file_stat.m_filename, ".bin")) {
} else if (path_ends_with(file_stat.m_filename, ".bin")) {
*isDynos = true;
}
}
@ -258,18 +258,18 @@ bool mod_import_file(char* path) {
bool isPalette = false;
bool ret = false;
if (gNetworkType != NT_NONE && !str_ends_with(path, ".ini")) {
if (gNetworkType != NT_NONE && !path_ends_with(path, ".ini")) {
djui_popup_create(DLANG(NOTIF, IMPORT_FAIL_INGAME), 2);
return false;
}
if (str_ends_with(path, ".lua") || str_ends_with(path, ".luac")) {
if (path_ends_with(path, ".lua") || path_ends_with(path, ".luac")) {
isLua = true;
ret = mod_import_lua(path);
} else if (str_ends_with(path, ".ini")) {
} else if (path_ends_with(path, ".ini")) {
isPalette = true;
ret = mod_import_palette(path);
} else if (str_ends_with(path, ".zip")) {
} else if (path_ends_with(path, ".zip")) {
ret = mod_import_zip(path, &isLua, &isDynos);
}

View file

@ -3,6 +3,7 @@
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include "pc/mini.h"
extern "C" {
@ -16,6 +17,8 @@ extern "C" {
#define C_FIELD extern "C"
static std::map<std::string, mINI::INIStructure> sModStorageFiles;
static void strdelete(char* string, const char* substr) {
// i is used to loop through the string
u16 i = 0;
@ -43,7 +46,7 @@ static void strdelete(char* string, const char* substr) {
string[i] = '\0';
}
bool char_valid(const char* buffer, bool isKey) {
static bool char_valid(const char* buffer, bool isKey) {
if (buffer[0] == '\0') { return false; }
while (*buffer != '\0') {
@ -57,7 +60,7 @@ bool char_valid(const char* buffer, bool isKey) {
return true;
}
void mod_storage_get_filename(char* dest) {
static void mod_storage_get_filename(char* dest) {
const char* path = fs_get_write_path(SAVE_DIRECTORY); // get user path
snprintf(dest, SYS_MAX_PATH - 1, "%s/%s", path, gLuaActiveMod->relativePath); // append sav folder
strdelete(dest, ".lua"); // delete ".lua" from sav name
@ -65,63 +68,63 @@ void mod_storage_get_filename(char* dest) {
normalize_path(dest); // fix any out of place slashes
}
C_FIELD bool mod_storage_save(const char* key, const char* value) {
if (gLuaActiveMod == NULL) { return false; }
if (strlen(key) > MAX_KEY_VALUE_LENGTH || strlen(value) > MAX_KEY_VALUE_LENGTH) { return false; }
if (!char_valid(key, true) || !char_valid(value, false)) { return false; }
static bool mod_storage_check_inputs(const char *key, const char *value, char *filename) {
char filename[SYS_MAX_PATH] = { 0 };
// check active mod
if (gLuaActiveMod == NULL) { return false; }
// retrieve filename
mod_storage_get_filename(filename);
// ensure savPath exists
const char* savPath = fs_get_write_path(SAVE_DIRECTORY);
if (!fs_sys_dir_exists(savPath)) { fs_sys_mkdir(savPath); }
// check key validity
if (key != NULL) {
if (strlen(key) > MAX_KEY_VALUE_LENGTH) { return false; }
if (!char_valid(key, true)) { return false; }
}
mINI::INIFile file(filename);
mINI::INIStructure ini;
file.read(ini);
// check value validity
if (value != NULL) {
if (strlen(value) > MAX_KEY_VALUE_LENGTH) { return false; }
if (!char_valid(value, false)) { return false; }
if (ini["storage"].size() > MAX_KEYS) { return false; }
// write: ensure savPath exists
const char* savPath = fs_get_write_path(SAVE_DIRECTORY);
if (!fs_sys_dir_exists(savPath)) { fs_sys_mkdir(savPath); }
}
ini["storage"][key] = value;
file.write(ini);
file.generate(ini);
// No value == read or delete, check if filename exists
else {
if (!fs_sys_path_exists(filename)) { return false; }
}
return true;
}
C_FIELD bool mod_storage_save_number(const char* key, f32 value) {
// Store string results in a temporary buffer
// this assumes mod_storage_load will only ever be called by Lua
static char str[MAX_KEY_VALUE_LENGTH];
if (floor(value) == value) {
snprintf(str, MAX_KEY_VALUE_LENGTH, "%lld", (s64)value);
} else {
snprintf(str, MAX_KEY_VALUE_LENGTH, "%f", value);
//////////
// read //
//////////
static mINI::INIStructure &mod_storage_read_file(const char *filename) {
const auto &it = sModStorageFiles.find(filename);
if (it != sModStorageFiles.end()) {
return it->second;
}
return mod_storage_save(key, str);
}
C_FIELD bool mod_storage_save_bool(const char* key, bool value) {
return mod_storage_save(key, value ? "true" : "false");
}
C_FIELD const char* mod_storage_load(const char* key) {
if (gLuaActiveMod == NULL) { return NULL; }
if (strlen(key) > MAX_KEY_VALUE_LENGTH) { return NULL; }
if (!char_valid(key, true)) { return NULL; }
char filename[SYS_MAX_PATH] = { 0 };
mod_storage_get_filename(filename);
if (!fs_sys_path_exists(filename)) { return NULL; }
mINI::INIFile file(filename);
mINI::INIStructure ini;
file.read(ini);
sModStorageFiles[filename] = ini;
return sModStorageFiles[filename];
}
std::string str = ini["storage"][key];
C_FIELD const char* mod_storage_load(const char* key) {
char filename[SYS_MAX_PATH] = { 0 };
if (!mod_storage_check_inputs(key, NULL, filename)) {
return NULL;
}
const mINI::INIStructure &ini = mod_storage_read_file(filename);
std::string str = ini.get("storage").get(key);
if (str.empty()) { return NULL; }
// Store string results in a temporary buffer
@ -146,35 +149,83 @@ C_FIELD bool mod_storage_load_bool(const char* key) {
}
C_FIELD bool mod_storage_exists(const char* key) {
if (gLuaActiveMod == NULL) { return false; }
if (strlen(key) > MAX_KEY_VALUE_LENGTH) { return false; }
if (!char_valid(key, true)) { return false; }
return mod_storage_load(key) != NULL;
}
C_FIELD LuaTable mod_storage_load_all(void) {
struct lua_State *L = gLuaState;
if (!L) { return 0; }
char filename[SYS_MAX_PATH] = { 0 };
mod_storage_get_filename(filename);
if (!fs_sys_path_exists(filename)) { return false; }
if (!mod_storage_check_inputs(NULL, NULL, filename)) {
lua_pushnil(L);
return 0;
}
const mINI::INIStructure &ini = mod_storage_read_file(filename);
LUA_STACK_CHECK_BEGIN_NUM(L, 1);
lua_newtable(L);
for (const auto &kv : ini.get("storage")) {
lua_pushstring(L, kv.first.c_str());
lua_pushstring(L, kv.second.c_str());
lua_settable(L, -3);
}
LUA_STACK_CHECK_END(L);
return smlua_to_lua_table(L, -1);
}
///////////
// write //
///////////
C_FIELD bool mod_storage_save(const char* key, const char* value) {
char filename[SYS_MAX_PATH] = { 0 };
if (!mod_storage_check_inputs(key, value, filename)) {
return false;
}
mINI::INIStructure &ini = mod_storage_read_file(filename);
if (!ini["storage"].has(key) && ini["storage"].size() >= MAX_KEYS) { return false; }
ini["storage"][key] = value;
mINI::INIFile file(filename);
mINI::INIStructure ini;
file.read(ini);
file.write(ini);
file.generate(ini);
return ini["storage"].has(key);
return true;
}
C_FIELD bool mod_storage_save_number(const char* key, f32 value) {
// Store string results in a temporary buffer
// this assumes mod_storage_load will only ever be called by Lua
static char str[MAX_KEY_VALUE_LENGTH];
if (floor(value) == value) {
snprintf(str, MAX_KEY_VALUE_LENGTH, "%lld", (s64)value);
} else {
snprintf(str, MAX_KEY_VALUE_LENGTH, "%f", value);
}
return mod_storage_save(key, str);
}
C_FIELD bool mod_storage_save_bool(const char* key, bool value) {
return mod_storage_save(key, value ? "true" : "false");
}
C_FIELD bool mod_storage_remove(const char* key) {
if (gLuaActiveMod == NULL) { return false; }
if (strlen(key) > MAX_KEY_VALUE_LENGTH) { return false; }
if (!char_valid(key, true)) { return false; }
char filename[SYS_MAX_PATH] = { 0 };
mod_storage_get_filename(filename);
if (!fs_sys_path_exists(filename)) { return false; }
if (!mod_storage_check_inputs(key, NULL, filename)) {
return false;
}
mINI::INIFile file(filename);
mINI::INIStructure ini;
file.read(ini);
mINI::INIStructure &ini = mod_storage_read_file(filename);
if (ini["storage"].remove(key)) {
mINI::INIFile file(filename);
file.write(ini);
file.generate(ini);
return true;
@ -184,22 +235,26 @@ C_FIELD bool mod_storage_remove(const char* key) {
}
C_FIELD bool mod_storage_clear(void) {
if (gLuaActiveMod == NULL) { return false; }
char filename[SYS_MAX_PATH] = { 0 };
mod_storage_get_filename(filename);
if (!fs_sys_path_exists(filename)) { return false; }
mINI::INIFile file(filename);
mINI::INIStructure ini;
file.read(ini);
if (!mod_storage_check_inputs(NULL, NULL, filename)) {
return false;
}
mINI::INIStructure &ini = mod_storage_read_file(filename);
if (ini["storage"].size() == 0) { return false; }
ini["storage"].clear();
mINI::INIFile file(filename);
file.write(ini);
file.generate(ini);
return true;
}
C_FIELD void mod_storage_shutdown(void) {
for (auto &file : sModStorageFiles) {
file.second.clear();
}
sModStorageFiles.clear();
}

View file

@ -6,6 +6,7 @@
#ifdef __cplusplus
extern "C" {
#endif
#include "pc/lua/smlua_utils.h"
#define MAX_KEYS 4096
#define MAX_KEY_VALUE_LENGTH 1024
@ -25,6 +26,8 @@ const char *mod_storage_load(const char* key);
f32 mod_storage_load_number(const char* key);
/* |description|Loads a bool `value` from a `key` in mod storage|descriptionEnd| */
bool mod_storage_load_bool(const char* key);
/* |description|Loads all keys and values in mod storage as strings and returns them as a table|descriptionEnd| */
LuaTable mod_storage_load_all(void);
/* |description|Checks if a `key` is in mod storage|descriptionEnd| */
bool mod_storage_exists(const char* key);
@ -33,6 +36,8 @@ bool mod_storage_remove(const char* key);
/* |description|Clears the mod's data from mod storage|descriptionEnd| */
bool mod_storage_clear(void);
void mod_storage_shutdown(void);
#ifdef __cplusplus
}
#endif

View file

@ -160,21 +160,35 @@ bool mod_file_create_directories(struct Mod* mod, struct ModFile* modFile) {
//////////////////////////////////////////////////////////////////////////////////////////
bool str_ends_with(const char* string, const char* suffix) {
bool str_starts_with(const char *string, const char *prefix) {
if (string == NULL || prefix == NULL) { return false; }
return strncmp(string, prefix, strlen(prefix)) == 0;
}
bool str_ends_with(const char *string, const char *suffix) {
if (string == NULL || suffix == NULL) { return false; }
size_t stringLength = strlen(string);
size_t suffixLength = strlen(suffix);
return stringLength >= suffixLength && strncmp(string + stringLength - suffixLength, suffix, suffixLength) == 0;
}
if (suffixLength > stringLength) { return false; }
bool path_ends_with(const char* path, const char* suffix) {
if (path == NULL || suffix == NULL) { return false; }
size_t pathLength = strlen(path);
size_t suffixLength = strlen(suffix);
if (suffixLength > pathLength) { return false; }
#ifdef _WIN32
// Paths on Windows are case-insensitive and might have
// upper-case or mixed-case endings.
return (0 == _stricmp(&(string[stringLength - suffixLength]), suffix));
return (0 == _stricmp(&(path[pathLength - suffixLength]), suffix));
#else
// Always expecting lower-case file paths and extensions
return (0 == strcmp(&(string[stringLength - suffixLength]), suffix));
return (0 == strcmp(&(path[pathLength - suffixLength]), suffix));
#endif
}

View file

@ -12,7 +12,10 @@ void mods_delete_tmp(void);
bool mod_file_full_path(char* destination, struct Mod* mod, struct ModFile* modFile);
bool mod_file_create_directories(struct Mod* mod, struct ModFile* modFile);
bool str_ends_with(const char* string, const char* suffix);
bool str_starts_with(const char *string, const char *prefix);
bool str_ends_with(const char *string, const char *suffix);
bool path_ends_with(const char* path, const char* suffix);
char* extract_lua_field(char* fieldName, char* buffer);

View file

@ -19,7 +19,7 @@
#endif
#ifdef DEVELOPMENT
#define GAME_NAME "sm64coopdx-dev"
#define GAME_NAME "sm64coopdx-dev"
#define WINDOW_NAME "Super Mario 64 Coop Deluxe (DEV)"
#elif !defined(VERSION_US)
#define GAME_NAME "sm64coopdx-intl"

View file

@ -10,7 +10,7 @@
extern "C" {
#include "platform.h"
#include "mods/mods_utils.h" // for str_ends_with
#include "mods/mods_utils.h" // for path_ends_with
#include "mods/mod_cache.h" // for md5 hashing
#include "mods/mods.h"
#include "loading.h"
@ -81,7 +81,7 @@ static bool is_rom_valid(const std::string romPath) {
inline static bool scan_path_for_rom(const char *dir) {
for (const auto &entry: std::filesystem::directory_iterator(dir)) {
std::string path = entry.path().generic_string();
if (str_ends_with(path.c_str(), ".z64")) {
if (path_ends_with(path.c_str(), ".z64")) {
if (is_rom_valid(path)) { return true; }
}
}

25677
src/pc/utils/json.hpp Normal file

File diff suppressed because it is too large Load diff