GodMode9/arm9/source/lua/gm9internalfs.c

801 lines
23 KiB
C

#ifndef NO_LUA
#include "gm9internalfs.h"
#include "fs.h"
#include "ui.h"
#include "utils.h"
#include "sha.h"
#include "nand.h"
#include "language.h"
#include "hid.h"
#include "game.h"
#include "gamecart.h"
#define _MAX_FOR_DEPTH 16
static u8 no_data_hash_256[32] = { SHA256_EMPTY_HASH };
static u8 no_data_hash_1[32] = { SHA1_EMPTY_HASH };
static bool PathIsDirectory(const char* path) {
FRESULT res;
FILINFO fno;
res = fvx_stat(path, &fno);
if (res != FR_OK) return false;
return fno.fattrib & AM_DIR;
}
static void CreateStatTable(lua_State* L, FILINFO* fno) {
lua_createtable(L, 0, 4); // create nested table
lua_pushstring(L, fno->fname);
lua_setfield(L, -2, "name");
lua_pushstring(L, (fno->fattrib & AM_DIR) ? "dir" : "file");
lua_setfield(L, -2, "type");
lua_pushinteger(L, fno->fsize);
lua_setfield(L, -2, "size");
lua_pushboolean(L, fno->fattrib & AM_RDO);
lua_setfield(L, -2, "read_only");
// ... and leave this table on the stack for the caller to deal with
}
static int internalfs_move(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.move");
const char* path_src = luaL_checkstring(L, 1);
const char* path_dst = luaL_checkstring(L, 2);
FILINFO fno;
CheckWritePermissionsLuaError(L, path_src);
CheckWritePermissionsLuaError(L, path_dst);
u32 flags = BUILD_PATH;
if (extra) {
flags = GetFlagsFromTable(L, 3, flags, NO_CANCEL | SILENT | OVERWRITE_ALL | SKIP_ALL);
}
if (!(flags & OVERWRITE_ALL) && (fvx_stat(path_dst, &fno) == FR_OK)) {
return luaL_error(L, "destination already exists on %s -> %s and {overwrite=true} was not used", path_src, path_dst);
}
if (!(PathMoveCopy(path_dst, path_src, &flags, true))) {
return luaL_error(L, "PathMoveCopy failed on %s -> %s", path_src, path_dst);
}
return 0;
}
static int internalfs_remove(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.remove");
const char* path = luaL_checkstring(L, 1);
CheckWritePermissionsLuaError(L, path);
u32 flags = 0;
if (extra) {
flags = GetFlagsFromTable(L, 2, flags, RECURSIVE);
}
if (!(flags & RECURSIVE)) {
if (PathIsDirectory(path)) {
return luaL_error(L, "requested directory remove without {recursive=true} on %s", path);
}
}
if (!(PathDelete(path))) {
return luaL_error(L, "PathDelete failed on %s", path);
}
return 0;
}
static int internalfs_copy(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.copy");
const char* path_src = luaL_checkstring(L, 1);
const char* path_dst = luaL_checkstring(L, 2);
FILINFO fno;
CheckWritePermissionsLuaError(L, path_dst);
u32 flags = BUILD_PATH;
if (extra) {
flags = GetFlagsFromTable(L, 3, flags, CALC_SHA | USE_SHA1 | NO_CANCEL | SILENT | OVERWRITE_ALL | SKIP_ALL | APPEND_ALL | RECURSIVE);
}
if (!(flags & RECURSIVE)) {
if (PathIsDirectory(path_src)) {
return luaL_error(L, "requested directory copy without {recursive=true} on %s -> %s", path_src, path_dst);
}
}
if (!(flags & OVERWRITE_ALL) && (fvx_stat(path_dst, &fno) == FR_OK)) {
return luaL_error(L, "destination already exists on %s -> %s and {overwrite=true} was not used", path_src, path_dst);
}
if (!(PathMoveCopy(path_dst, path_src, &flags, false))) {
return luaL_error(L, "PathMoveCopy failed on %s -> %s", path_src, path_dst);
}
return 0;
}
static int internalfs_mkdir(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.mkdir");
const char* path = luaL_checkstring(L, 1);
CheckWritePermissionsLuaError(L, path);
FRESULT res = fvx_rmkdir(path);
if (res != FR_OK) {
return luaL_error(L, "could not mkdir %s (%d)", path, res);
}
return 0;
}
static int internalfs_list_dir(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.list_dir");
const char* path = luaL_checkstring(L, 1);
lua_newtable(L);
DIR dir;
FILINFO fno;
FRESULT res = fvx_opendir(&dir, path);
if (res != FR_OK) {
lua_pop(L, 1); // remove final table from stack
return luaL_error(L, "could not opendir %s (%d)", path, res);
}
for (int i = 1; true; i++) {
res = fvx_readdir(&dir, &fno);
if (res != FR_OK) {
fvx_closedir(&dir);
lua_pop(L, 1); // remove final table from stack
return luaL_error(L, "could not readdir %s (%d)", path, res);
}
if (fno.fname[0] == 0) break;
CreateStatTable(L, &fno);
lua_seti(L, -2, i); // add nested table to final table
}
fvx_closedir(&dir);
return 1;
}
static int internalfs_stat(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.stat");
const char* path = luaL_checkstring(L, 1);
FILINFO fno;
FRESULT res = fvx_stat(path, &fno);
if (res != FR_OK) {
return luaL_error(L, "could not stat %s (%d)", path, res);
}
CreateStatTable(L, &fno);
return 1;
}
// TODO: make this manually check for permissions recursively
static int internalfs_fix_cmacs(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.fix_cmacs");
const char* path = luaL_checkstring(L, 1);
ShowString("%s", STR_FIXING_CMACS_PLEASE_WAIT);
if (RecursiveFixFileCmac(path) != 0) {
return luaL_error(L, "fixcmac failed");
}
return 0;
}
static int internalfs_stat_fs(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.stat_fs");
const char* path = luaL_checkstring(L, 1);
u64 freespace = GetFreeSpace(path);
u64 totalspace = GetTotalSpace(path);
u64 usedspace = totalspace - freespace;
lua_createtable(L, 0, 3);
lua_pushinteger(L, freespace);
lua_setfield(L, -2, "free");
lua_pushinteger(L, totalspace);
lua_setfield(L, -2, "total");
lua_pushinteger(L, usedspace);
lua_setfield(L, -2, "used");
return 1;
}
static int internalfs_dir_info(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.dir_info");
const char* path = luaL_checkstring(L, 1);
u64 tsize = 0;
u32 tdirs = 0;
u32 tfiles = 0;
if (!DirInfo(path, &tsize, &tdirs, &tfiles)) {
return luaL_error(L, "error when running DirInfo");
}
lua_createtable(L, 0, 3);
lua_pushinteger(L, tsize);
lua_setfield(L, -2, "size");
lua_pushinteger(L, tdirs);
lua_setfield(L, -2, "dirs");
lua_pushinteger(L, tfiles);
lua_setfield(L, -2, "files");
return 1;
}
static int FileDirSelector(lua_State* L, const char* path_orig, const char* prompt, bool is_dir, bool include_dirs, bool explorer) {
bool ret;
char path[_VAR_CNT_LEN] = { 0 };
char choice[_VAR_CNT_LEN] = { 0 };
strncpy(path, path_orig, _VAR_CNT_LEN);
if (strncmp(path, "Z:", 2) == 0) {
return luaL_error(L, "forbidden drive");
} else if (!is_dir) {
u32 flags_ext = include_dirs ? 0 : NO_DIRS;
char *npattern = strrchr(path, '/');
if (!npattern) {
return luaL_error(L, "invalid path");
}
*(npattern++) = '\0';
ret = FileSelector(choice, prompt, path, npattern, flags_ext, explorer);
} else {
ret = FileSelector(choice, prompt, path, NULL, NO_FILES | SELECT_DIRS, explorer);
}
if (ret) {
lua_pushstring(L, choice);
} else {
lua_pushnil(L);
}
return 1;
}
static int internalfs_ask_select_file(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.ask_select_file");
const char* prompt = luaL_checkstring(L, 1);
const char* path = luaL_checkstring(L, 2);
u32 flags = 0;
if (extra) {
flags = GetFlagsFromTable(L, 3, flags, INCLUDE_DIRS | EXPLORER);
};
return FileDirSelector(L, path, prompt, false, (flags & INCLUDE_DIRS), (flags & EXPLORER));
}
static int internalfs_ask_select_dir(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.ask_select_dir");
const char* prompt = luaL_checkstring(L, 1);
const char* path = luaL_checkstring(L, 2);
u32 flags = 0;
if (extra) {
flags = GetFlagsFromTable(L, 3, flags, EXPLORER);
};
return FileDirSelector(L, path, prompt, true, true, (flags & EXPLORER));
}
static int internalfs_find(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.find");
const char* pattern = luaL_checkstring(L, 1);
char path[_VAR_CNT_LEN] = { 0 };
u32 flags = 0;
if (extra) {
flags = GetFlagsFromTable(L, 2, flags, FIND_FIRST);
}
u8 mode = (flags & FIND_FIRST) ? FN_LOWEST : FN_HIGHEST;
FRESULT res = fvx_findpath(path, pattern, mode);
if (res == FR_NO_PATH) {
lua_pushnil(L);
} else if (res != FR_OK) {
return luaL_error(L, "failed to find %s (%d)", pattern, res);
} else {
lua_pushstring(L, path);
}
return 1;
}
// this should probably be rewritten
static int internalfs_find_all(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.find_all");
const char* dir = luaL_checkstring(L, 1);
const char* pattern = luaL_checkstring(L, 2);
u32 flags = 0;
if (extra) {
flags = GetFlagsFromTable(L, 3, flags, RECURSIVE);
}
char forpath[_VAR_CNT_LEN] = { 0 };
// without re-implementing for_handler, i need to give it a "*" pattern
// and then manually compare each filename to see if it matches
// so that a recursive search actually works
bool forstatus = for_handler(NULL, dir, "*", flags & RECURSIVE);
if (!forstatus) {
return luaL_error(L, "could not open directory");
}
lua_newtable(L);
int i = 1;
char* slash;
while (true) {
forstatus = for_handler(forpath, NULL, NULL, false);
if (!forstatus) {
forpath[0] = '\0';
}
if (!forpath[0]) {
// finish for_handler
for_handler(NULL, NULL, NULL, false);
break;
} else {
slash = strrchr(forpath, '/');
if (!slash) bkpt; // this should never, ever happen
if (fvx_match_name(slash+1, pattern) == FR_OK) {
lua_pushstring(L, forpath);
lua_seti(L, -2, i++);
}
}
}
return 1;
}
static int internalfs_find_not(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.find_not");
const char* pattern = luaL_checkstring(L, 1);
char path[_VAR_CNT_LEN] = { 0 };
FRESULT res = fvx_findnopath(path, pattern);
if (res != FR_OK) {
return luaL_error(L, "failed to find %s (%d)", pattern, res);
}
lua_pushstring(L, path);
return 1;
}
static int internalfs_exists(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.exists");
const char* path = luaL_checkstring(L, 1);
FILINFO fno;
lua_pushboolean(L, (fvx_stat(path, &fno) == FR_OK));
return 1;
}
static int internalfs_is_dir(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.is_dir");
const char* path = luaL_checkstring(L, 1);
lua_pushboolean(L, PathIsDirectory(path));
return 1;
}
static int internalfs_is_file(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.is_file");
const char* path = luaL_checkstring(L, 1);
FILINFO fno;
FRESULT res = fvx_stat(path, &fno);
if (res != FR_OK) {
lua_pushboolean(L, false);
} else {
lua_pushboolean(L, !(fno.fattrib & AM_DIR));
}
return 1;
}
static int internalfs_read_file(lua_State* L) {
CheckLuaArgCount(L, 3, "_fs.read_file");
const char* path = luaL_checkstring(L, 1);
lua_Integer offset = luaL_checkinteger(L, 2);
lua_Integer size = luaL_checkinteger(L, 3);
char *buf = malloc(size);
if (!buf) {
return luaL_error(L, "could not allocate memory to read file");
}
UINT bytes_read = 0;
FRESULT res = fvx_qread(path, buf, offset, size, &bytes_read);
if (res != FR_OK) {
free(buf);
return luaL_error(L, "could not read %s (%d)", path, res);
}
lua_pushlstring(L, buf, bytes_read);
free(buf);
return 1;
}
static int internalfs_write_file(lua_State* L) {
CheckLuaArgCount(L, 3, "_fs.write_file");
const char* path = luaL_checkstring(L, 1);
lua_Integer offset = luaL_checkinteger(L, 2);
size_t data_length = 0;
const char* data = luaL_checklstring(L, 3, &data_length);
CheckWritePermissionsLuaError(L, path);
UINT bytes_written = 0;
FRESULT res = fvx_qwrite(path, data, offset, data_length, &bytes_written);
if (res != FR_OK) {
return luaL_error(L, "error writing %s (%d)", path, res);
}
lua_pushinteger(L, bytes_written);
return 1;
}
static int internalfs_fill_file(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 4, "_fs.fill_file");
const char* path = luaL_checkstring(L, 1);
lua_Integer offset = luaL_checkinteger(L, 2);
lua_Integer size = luaL_checkinteger(L, 3);
lua_Integer byte = luaL_checkinteger(L, 4);
u8 real_byte = byte & 0xFF;
u32 flags = ALLOW_EXPAND;
if (extra) {
flags = GetFlagsFromTable(L, 4, flags, NO_CANCEL);
};
if ((byte < 0) || (byte > 0xFF)) {
return luaL_error(L, "byte is not between 0x00 and 0xFF (got: %I)", byte);
}
CheckWritePermissionsLuaError(L, path);
if (!(FileSetByte(path, offset, size, real_byte, &flags))) {
return luaL_error(L, "FileSetByte failed on %s", path);
}
return 0;
}
static int internalfs_make_dummy_file(lua_State* L) {
CheckLuaArgCount(L, 2, "_fs.make_dummy_file");
const char* path = luaL_checkstring(L, 1);
lua_Integer size = luaL_checkinteger(L, 2);
CheckWritePermissionsLuaError(L, path);
if (!(FileCreateDummy(path, NULL, size))) {
return luaL_error(L, "FileCreateDummy failed on %s", path);
}
return 0;
}
static int internalfs_truncate(lua_State* L) {
CheckLuaArgCount(L, 2, "_fs.truncate");
const char* path = luaL_checkstring(L, 1);
lua_Integer size = luaL_checkinteger(L, 2);
FIL fp;
FRESULT res;
CheckWritePermissionsLuaError(L, path);
res = f_open(&fp, path, FA_READ | FA_WRITE);
if (res != FR_OK) {
return luaL_error(L, "failed to open %s (note: this only works on FAT filesystems, not virtual)", path);
}
res = f_lseek(&fp, size);
if (res != FR_OK) {
f_close(&fp);
return luaL_error(L, "failed to seek on %s", path);
}
res = f_truncate(&fp);
if (res != FR_OK) {
f_close(&fp);
return luaL_error(L, "failed to truncate %s", path);
}
f_close(&fp);
return 0;
}
static int internalfs_img_mount(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.img_mount");
const char* path = luaL_checkstring(L, 1);
bool res = InitImgFS(path);
if (!res) {
return luaL_error(L, "failed to mount %s", path);
}
return 0;
}
static int internalfs_img_umount(lua_State* L) {
CheckLuaArgCount(L, 0, "_fs.img_umount");
InitImgFS(NULL);
return 0;
}
static int internalfs_get_img_mount(lua_State* L) {
CheckLuaArgCount(L, 0, "_fs.get_img_mount");
char path[256] = { 0 };
strncpy(path, GetMountPath(), 256);
if (path[0] == 0) {
// since lua treats "" as true, return a nil to make if/else easier
lua_pushnil(L);
} else {
lua_pushstring(L, path);
}
return 1;
}
// TODO: what if someone does offset != 0 but size = 0 (end of file)?
static int internalfs_hash_file(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 3, "_fs.hash_file");
const char* path = luaL_checkstring(L, 1);
lua_Integer offset = luaL_checkinteger(L, 2);
lua_Integer size = luaL_checkinteger(L, 3);
FRESULT res;
FILINFO fno;
if (size == 0) {
res = fvx_stat(path, &fno);
if (res != FR_OK) {
return luaL_error(L, "failed to stat %s", path);
}
size = fno.fsize;
}
u32 flags = 0;
if (extra) {
flags = GetFlagsFromTable(L, 4, flags, USE_SHA1);
}
const u8 hashlen = (flags & USE_SHA1) ? 20 : 32;
u8 hash_fil[0x20];
if (size == 0) {
// shortcut by just returning the hash of empty data
memcpy(hash_fil, (flags & USE_SHA1) ? no_data_hash_1 : no_data_hash_256, hashlen);
} else if (!(FileGetSha(path, hash_fil, offset, size, (flags & USE_SHA1)))) {
return luaL_error(L, "FileGetSha failed on %s", path);
}
lua_pushlstring(L, (char*)hash_fil, hashlen);
return 1;
}
static int internalfs_hash_data(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.hash_data");
size_t data_length = 0;
const char* data = luaL_checklstring(L, 1, &data_length);
u32 flags = 0;
if (extra) {
flags = GetFlagsFromTable(L, 2, flags, USE_SHA1);
}
const u8 hashlen = (flags & USE_SHA1) ? 20 : 32;
u8 hash_fil[0x20];
if (data_length == 0) {
// shortcut by just returning the hash of empty data
memcpy(hash_fil, (flags & USE_SHA1) ? no_data_hash_1 : no_data_hash_256, hashlen);
} else {
sha_quick(hash_fil, data, data_length, (flags & USE_SHA1) ? SHA1_MODE : SHA256_MODE);
}
lua_pushlstring(L, (char*)hash_fil, hashlen);
return 1;
}
static int internalfs_allow(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.allow");
const char* path = luaL_checkstring(L, 1);
u32 flags = 0;
bool allowed;
if (extra) {
flags = GetFlagsFromTable(L, 2, 0, ASK_ALL);
}
if (flags & ASK_ALL) {
allowed = CheckDirWritePermissions(path);
} else {
allowed = CheckWritePermissions(path);
}
lua_pushboolean(L, allowed);
return 1;
};
static int internalfs_verify(lua_State* L) {
CheckLuaArgCount(L, 1, "_fs.verify");
const char* path = luaL_checkstring(L, 1);
bool res;
u64 filetype = IdentifyFileType(path);
if (filetype & IMG_NAND) res = (ValidateNandDump(path) == 0);
else res = (VerifyGameFile(path) == 0);
lua_pushboolean(L, res);
return 1;
}
static int internalfs_sd_is_mounted(lua_State* L) {
CheckLuaArgCount(L, 0, "_fs.sd_is_mounted");
lua_pushboolean(L, CheckSDMountState());
return 1;
}
static int internalfs_sd_switch(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 0, "_fs.sd_switch");
const char* message;
if (extra) {
message = luaL_checkstring(L, 1);
} else {
message = "Please switch the SD card now.";
}
bool ret;
DeinitExtFS();
if (!(ret = CheckSDMountState())) {
return luaL_error(L, "%s", STR_SCRIPTERR_SD_NOT_MOUNTED);
}
u32 pad_state;
DeinitSDCardFS();
ShowString("%s\n \n%s", message, STR_EJECT_SD_CARD);
while (!((pad_state = InputWait(0)) & (BUTTON_B|SD_EJECT)));
if (pad_state & SD_EJECT) {
ShowString("%s\n \n%s", message, STR_INSERT_SD_CARD);
while (!((pad_state = InputWait(0)) & (BUTTON_B|SD_INSERT)));
}
if (pad_state & BUTTON_B) {
return luaL_error(L, "user canceled");
}
InitSDCardFS();
AutoEmuNandBase(true);
InitExtFS();
return 0;
}
static int internalfs_key_dump(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 1, "_fs.key_dump");
const char* opts[] = {SEEDINFO_NAME, TIKDB_NAME_ENC, TIKDB_NAME_DEC, NULL};
int opt = luaL_checkoption(L, 1, NULL, opts);
bool ret = false;
u32 flags = 0;
if (extra) {
flags = GetFlagsFromTable(L, 2, 0, OVERWRITE_ALL);
}
if (opt == 1 || opt == 2) {
bool tik_dec = opt == 2;
if (flags & OVERWRITE_ALL) fvx_unlink(tik_dec ? OUTPUT_PATH "/" TIKDB_NAME_DEC : OUTPUT_PATH "/" TIKDB_NAME_ENC);
if (BuildTitleKeyInfo(NULL, tik_dec, false) == 0) {
ShowString(STR_BUILDING_TO_OUT_ARG, OUTPUT_PATH, opts[opt]);
if (((BuildTitleKeyInfo("1:/dbs/ticket.db", tik_dec, false) == 0) ||
(BuildTitleKeyInfo("4:/dbs/ticket.db", tik_dec, false) == 0)) &&
(BuildTitleKeyInfo(NULL, tik_dec, true) == 0))
ret = true;
}
} else if (opt == 0) {
if (flags & OVERWRITE_ALL) fvx_unlink(OUTPUT_PATH "/" SEEDINFO_NAME);
if (BuildSeedInfo(NULL, false) == 0) {
ShowString(STR_BUILDING_TO_OUT_ARG, OUTPUT_PATH, opts[opt]);
if (((BuildSeedInfo("1:", false) == 0) ||
(BuildSeedInfo("4:", false) == 0)) &&
(BuildSeedInfo(NULL, true) == 0))
ret = true;
}
}
if (!ret) {
return luaL_error(L, "building %s failed", opts[opt]);
}
return 0;
}
static int internalfs_cart_dump(lua_State* L) {
bool extra = CheckLuaArgCountPlusExtra(L, 2, "_fs.cart_dump");
const char* path = luaL_checkstring(L, 1);
u64 fsize = (u64)luaL_checkinteger(L, 2);
bool ret = false;
const char* errstr = "";
u32 flags = 0;
if (extra) {
flags = GetFlagsFromTable(L, 3, flags, ENCRYPTED);
}
CartData* cdata = (CartData*) malloc(sizeof(CartData));
u8* buf = (u8*) malloc(STD_BUFFER_SIZE);
ret = false;
if (!cdata || !buf) {
errstr = "out of memory";
} else if (InitCartRead(cdata) != 0){
errstr = "cart init fail";
} else {
SetSecureAreaEncryption(flags & ENCRYPTED);
fvx_unlink(path);
ret = true;
errstr = "cart dump failed or canceled";
for (u64 p = 0; p < fsize; p += STD_BUFFER_SIZE) {
u64 len = min((fsize - p), STD_BUFFER_SIZE);
ShowProgress(p, fsize, path);
if (!ShowProgress(p, fsize, path) ||
(ReadCartBytes(buf, p, len, cdata, false) != 0) ||
(fvx_qwrite(path, buf, p, len, NULL) != FR_OK)) {
ret = false;
break;
}
}
}
free(buf);
free(cdata);
if (!ret) {
return luaL_error(L, "%s", errstr);
}
return 0;
}
static const luaL_Reg internalfs_lib[] = {
{"move", internalfs_move},
{"remove", internalfs_remove},
{"copy", internalfs_copy},
{"mkdir", internalfs_mkdir},
{"list_dir", internalfs_list_dir},
{"stat", internalfs_stat},
{"stat_fs", internalfs_stat_fs},
{"dir_info", internalfs_dir_info},
{"ask_select_file", internalfs_ask_select_file},
{"ask_select_dir", internalfs_ask_select_dir},
{"find", internalfs_find},
{"find_all", internalfs_find_all},
{"find_not", internalfs_find_not},
{"exists", internalfs_exists},
{"is_dir", internalfs_is_dir},
{"is_file", internalfs_is_file},
{"read_file", internalfs_read_file},
{"write_file", internalfs_write_file},
{"fill_file", internalfs_fill_file},
{"make_dummy_file", internalfs_make_dummy_file},
{"truncate", internalfs_truncate},
{"img_mount", internalfs_img_mount},
{"img_umount", internalfs_img_umount},
{"get_img_mount", internalfs_get_img_mount},
{"hash_file", internalfs_hash_file},
{"hash_data", internalfs_hash_data},
{"verify", internalfs_verify},
{"allow", internalfs_allow},
{"sd_is_mounted", internalfs_sd_is_mounted},
{"sd_switch", internalfs_sd_switch},
{"fix_cmacs", internalfs_fix_cmacs},
{"key_dump", internalfs_key_dump},
{"cart_dump", internalfs_cart_dump},
{NULL, NULL}
};
int gm9lua_open_internalfs(lua_State* L) {
luaL_newlib(L, internalfs_lib);
return 1;
}
#endif