diff --git a/include/arm11/config.h b/include/arm11/config.h
new file mode 100644
index 0000000..dd4226a
--- /dev/null
+++ b/include/arm11/config.h
@@ -0,0 +1,54 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+
+enum Keys {
+ KBootOption1 = 0,
+ KBootOption2,
+ KBootOption3,
+ KBootOption1NandImage,
+ KBootOption2NandImage,
+ KBootOption3NandImage,
+ KBootOption1Buttons,
+ KBootOption2Buttons,
+ KBootOption3Buttons,
+ KBootMode,
+ KDevMode,
+ // ...
+ KLast
+};
+
+enum BootModes {
+ BootModeNormal = 0,
+ BootModeQuick,
+ BootModeQuiet
+};
+
+#define numKeys KLast
+
+bool loadConfigFile();
+bool writeConfigFile();
+void *configCopyText(int key);
+const void *configGetData(int key);
+bool configDataExist(int key);
+const char *configGetKeyText(int key);
+bool configSetKeyData(int key, const void *data);
+void configRestoreDefaults();
+bool configDevModeEnabled();
diff --git a/include/arm11/fsutils.h b/include/arm11/fsutils.h
new file mode 100644
index 0000000..a67c8fa
--- /dev/null
+++ b/include/arm11/fsutils.h
@@ -0,0 +1,30 @@
+#pragma once
+
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "types.h"
+
+
+
+bool fsEnsureMounted(const char *path);
+void fsUnmountAll();
+u32 fsMountNandFilesystems();
+void fsUnmountNandFilesystems();
+bool fsMountSdmc();
+bool fsCreateFileWithPath(const char *filepath);
diff --git a/source/arm11/config.c b/source/arm11/config.c
new file mode 100644
index 0000000..90f93d9
--- /dev/null
+++ b/source/arm11/config.c
@@ -0,0 +1,834 @@
+/*
+ * This file is part of fastboot 3DS
+ * Copyright (C) 2017 derrek, profi200
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/* This is a parser/writer for the fastbootcfg.txt file. It's not vulnerable, I swear. */
+
+#include
+#include
+#include
+#include
+#include "types.h"
+#include "mem_map.h"
+#include "fs.h"
+#include "util.h"
+// #include "arm9/hardware/interrupt.h"
+#include "arm11/fsutils.h"
+#include "arm11/hardware/hid.h"
+#include "arm11/debug.h"
+#include "arm11/config.h"
+#include "arm11/fmt.h"
+
+#define MAX_FILE_SIZE 0x4000 - 1
+
+static const char *SdmcFilepath = "sdmc:/3ds/fastbootcfg.txt";
+static const char *NandFilepath = "nand:/fastboot3DS/fastbootcfg.txt";
+
+static const char *filepath;
+
+static s32 file;
+
+typedef struct {
+ char *textData; // ptr to the first char of an attribute's data inside filebuf
+ u32 textLength; // length of the above data, without '\0' ofc
+ void *data; // used to store the final native data
+} AttributeEntryType;
+
+typedef struct {
+ // function to convert the textData to arbitrary native data
+ bool (*parse)(AttributeEntryType *attr);
+ // opposite of parse(), used to update/create an entry in the file
+ bool (*write)(AttributeEntryType *attr, const void *newData, int key);
+} FunctionsEntryType;
+
+static void unloadConfigFile();
+static bool createConfigFile();
+static bool parseConfigFile();
+static bool parseBootOption(AttributeEntryType *attr);
+static bool writeBootOption(AttributeEntryType *attr, const void *newData, int key);
+static bool parseBootOptionPad(AttributeEntryType *attr);
+static bool writeBootOptionPad(AttributeEntryType *attr, const void *newData, int key);
+static bool parseBootMode(AttributeEntryType *attr);
+static bool writeBootMode(AttributeEntryType *attr, const void *newData, int key);
+static bool parseDevMode(AttributeEntryType *attr);
+static bool writeDevMode(AttributeEntryType *attr, const void *newData, int key);
+
+static const char *keyStrings[] = {
+ "BOOT_OPTION1",
+ "BOOT_OPTION2",
+ "BOOT_OPTION3",
+ "BOOT_OPTION1_NAND_IMAGE",
+ "BOOT_OPTION2_NAND_IMAGE",
+ "BOOT_OPTION3_NAND_IMAGE",
+ "BOOT_OPTION1_BUTTONS",
+ "BOOT_OPTION2_BUTTONS",
+ "BOOT_OPTION3_BUTTONS",
+ "BOOT_MODE",
+ "DEV_MODE"
+};
+
+static FunctionsEntryType keyFunctions[] = {
+ { parseBootOption, writeBootOption },
+ { parseBootOption, writeBootOption },
+ { parseBootOption, writeBootOption },
+ /* use the same functions for nand image option */
+ { parseBootOption, writeBootOption },
+ { parseBootOption, writeBootOption },
+ { parseBootOption, writeBootOption },
+ { parseBootOptionPad, writeBootOptionPad },
+ { parseBootOptionPad, writeBootOptionPad },
+ { parseBootOptionPad, writeBootOptionPad },
+ { parseBootMode, writeBootMode },
+ { parseDevMode, writeDevMode }
+};
+
+static AttributeEntryType attributes[numKeys];
+
+static char *filebuf = NULL;
+
+static bool configLoaded = false;
+
+/* This loads the config file from SD card or eMMC and parses it */
+bool loadConfigFile()
+{
+ FILINFO fileStat;
+ u32 fileSize;
+ bool createFile = false;
+
+ if(configLoaded)
+ unloadConfigFile();
+
+ // first, try SD card fatfs
+ if(true) // !!! if(bootInfo.sd_status == 2)
+ {
+ filepath = SdmcFilepath;
+
+ // does the config file exist?
+ if(fStat(filepath, &fileStat) != FR_OK)
+ {
+ createFile = true;
+ }
+ }
+ else
+ {
+ filepath = NandFilepath;
+
+ if(fStat(filepath, &fileStat) != FR_OK)
+ {
+ createFile = true;
+ }
+ }
+
+ if(createFile)
+ {
+ // try to create a file
+ if(!createConfigFile())
+ return false;
+
+ // does it work now?
+ if(fStat(filepath, &fileStat) != FR_OK)
+ return false;
+ }
+
+ fileSize = fileStat.fsize;
+
+ if(fileSize > MAX_FILE_SIZE)
+ {
+ //uiPrintIfVerbose("Invalid config-file size!\n");
+ goto fail;
+ }
+
+ filebuf = (char *) malloc(MAX_FILE_SIZE + 1);
+
+ if(!filebuf)
+ {
+ //uiPrintIfVerbose("Out of memory!\n");
+ goto fail;
+ }
+
+ if((file = fOpen(filepath, FS_OPEN_READ)) < 0)
+ {
+ //uiPrintIfVerbose("Failed to open config-file for reading!\n");
+ goto fail;
+ }
+
+ if(fRead(file, filebuf, fileSize) != (s32) fileSize)
+ {
+ //uiPrintIfVerbose("Failed to read from config-file!\n");
+ fClose(file);
+ goto fail;
+ }
+
+ fClose(file);
+
+ // terminate string buf
+ filebuf[fileSize] = '\0';
+
+ return parseConfigFile();
+
+fail:
+
+ // something didn't work, clean everything up
+ unloadConfigFile();
+
+ return false;
+}
+
+static void unloadConfigFile()
+{
+ configLoaded = false;
+ AttributeEntryType *curAttr;
+
+ /* Free all data */
+ for(u32 i=0; itextData = NULL;
+ curAttr->textLength = 0;
+
+ if(curAttr->data)
+ {
+ free(curAttr->data);
+ curAttr->data = NULL;
+ }
+ }
+
+ if(filebuf)
+ {
+ free(filebuf);
+ filebuf = NULL;
+ }
+}
+
+bool writeConfigFile()
+{
+ if(!filebuf)
+ goto fail;
+
+ const u32 fileSize = strlen(filebuf);
+
+ if(fileSize > MAX_FILE_SIZE)
+ panicMsg("fileSize too large!");
+
+ if(!fsCreateFileWithPath(filepath))
+ {
+ goto fail;
+ }
+
+ if((file = fOpen(filepath, FS_OPEN_WRITE)) < 0)
+ {
+ goto fail;
+ }
+
+ if(fileSize)
+ {
+ if(fWrite(file, filebuf, fileSize) != (s32) fileSize)
+ {
+ fClose(file);
+ goto fail;
+ }
+ }
+
+ // Make sure changes are written to disk in case
+ // the SD card is removed later.
+ if(fSync(file) != FR_OK)
+ {
+ fClose(file);
+ goto fail;
+ }
+
+ fClose(file);
+
+ return true;
+
+fail:
+
+ return false;
+}
+
+static bool createConfigFile()
+{
+ char temp = '\0';
+ bool ret;
+
+ if(filebuf)
+ panicMsg("config: internal error");
+
+ filebuf = &temp;
+
+ ret = writeConfigFile();
+
+ filebuf = NULL;
+
+ return ret;
+}
+
+/* returns the start of an attribute's data. */
+static char *findDefinition(const char *attrName)
+{
+ char *start;
+ char c;
+
+ start = strstr(filebuf, attrName);
+
+ if(start)
+ {
+ /* verify definition char */
+ start = start + strlen(attrName);
+
+ while(*start == ' ') start++;
+
+ c = *start;
+ if(c != '=')
+ start = NULL;
+ else
+ {
+ start ++;
+ while(*start == ' ') start++;
+ }
+ }
+
+ return start;
+}
+
+/* returns the length of an attribute's data. */
+static u32 parseDefinition(char *attrData)
+{
+ static const char LF = 0x0A, CR = 0x0D, NEL = 0x15;
+ u32 len = 0;
+ char *cur = attrData;
+ char c;
+
+ if(!attrData)
+ return 0;
+
+ // look for linebreaks that mark the end of a definition
+ for(; (c = *cur) != '\0'; cur++)
+ {
+ if(c == LF || c == CR || c == NEL)
+ break;
+ len ++;
+ c = *cur;
+ }
+
+ return len;
+}
+
+static bool parseConfigFile()
+{
+ AttributeEntryType *curAttr;
+ u32 len;
+ char *text;
+
+ // pass #1: look for definitions and populate our lookup table.
+ for(u32 i=0; itextData = text;
+ if(!text)
+ {
+ curAttr->textLength = 0;
+ continue;
+ }
+ len = parseDefinition(text);
+ curAttr->textLength = len;
+ }
+
+ // pass #2: parse text-data and convert it into a native form
+ for(u32 i=0; itextData)
+ {
+ curAttr->data = NULL;
+ continue;
+ }
+ else
+ { // TODO: what to do if ret == false ?
+ ////uiPrintIfVerbose("Parsing attr text with size %i\n%s\n", curAttr->textLength, curAttr->textData);
+ keyFunctions[i].parse(curAttr);
+ }
+ }
+
+ configLoaded = true;
+
+ return true;
+}
+
+// Add a new definition in our file
+static char *writeAddDefinitionText(const char *keyName, const char *textData)
+{
+ const char *def = " = ";
+ u32 curLen = strlen(filebuf);
+ u32 remainingLen = MAX_FILE_SIZE - curLen;
+ u32 totalLen = strlen(textData);
+ u32 defLen = strlen(def);
+ u32 keyLen = strlen(keyName);
+
+ if(totalLen > remainingLen)
+ return NULL;
+
+ totalLen += keyLen;
+ totalLen += defLen;
+
+ if(curLen != 0)
+ totalLen += 2; // new line encoding
+
+ if(totalLen > remainingLen)
+ return NULL;
+
+ // insert line break if we already have any content
+ if(curLen != 0)
+ {
+ filebuf[curLen++] = 0x0d;
+ filebuf[curLen++] = 0x0a;
+ }
+
+ ee_sprintf(&filebuf[curLen], "%s%s%s", keyName, def, textData);
+
+ return &filebuf[curLen + keyLen + defLen];
+}
+
+static u32 writeUpdateDefinitionText(char *textData, u32 curTextLen, const char *newText)
+{
+ u32 diff;
+ u32 newLen = strlen(newText);
+ u32 curLen = strlen(filebuf);
+ u32 remainingLen = MAX_FILE_SIZE - curLen;
+
+ if(newLen > remainingLen)
+ return 0;
+
+ diff = newLen - curTextLen;
+
+ if(diff)
+ {
+ if(newLen > curTextLen)
+ {
+ size_t backupLen = curLen - (textData - filebuf) - curTextLen + 1;
+ char *tempBuf = (char *) malloc(backupLen);
+ if(!tempBuf)
+ return 0;
+ memcpy(tempBuf, textData + curTextLen, backupLen);
+
+ memcpy_s(filebuf, MAX_FILE_SIZE + 1, textData - filebuf + newLen,
+ tempBuf, backupLen, 0, false);
+ }
+ else
+ {
+ memcpy_s(filebuf, MAX_FILE_SIZE + 1, textData - filebuf + newLen,
+ filebuf, MAX_FILE_SIZE + 1, textData - filebuf + curTextLen, false);
+ }
+
+ for(u32 key=0; key textData)
+ {
+ attributes[key].textData += diff;
+ }
+ }
+ }
+
+ memcpy(textData, newText, newLen);
+
+ return newLen;
+}
+
+static void writeAttributeText(AttributeEntryType *attr, const char *newText, int key)
+{
+ // text data doesn't exist yet?
+ if(!attr->textData)
+ {
+ attr->textData = writeAddDefinitionText(configGetKeyText(key), newText);
+ attr->textLength = strlen(newText);
+ }
+ else // update definition
+ {
+ attr->textLength = writeUpdateDefinitionText(attr->textData, attr->textLength, newText);
+ }
+}
+
+static bool isValidPath(const char *path)
+{
+ char c;
+ bool gotMountpoint = false;
+
+ for(; (c = *path) != '\0'; path++)
+ {
+ // no multiple mountpoints
+ if(c == ':')
+ {
+ if(gotMountpoint)
+ return false;
+ gotMountpoint = true;
+ }
+
+ // no dir-return
+ if(c == '.' && path[1] == '.')
+ return false;
+
+ // no space after dir slash
+ if((c == '\\' || c == '/') && path[1] == ' ')
+ return false;
+
+ // no non ASCII chars
+ if(!((unsigned char)c <= 127))
+ return false;
+ }
+
+ if(!gotMountpoint)
+ return false;
+
+ return true;
+}
+
+static bool parseBootOption(AttributeEntryType *attr)
+{
+ attr->data = NULL;
+
+ char *buf = (char *) malloc(attr->textLength + 1);
+ if(!buf) return false;
+
+ memcpy(buf, attr->textData, attr->textLength);
+ buf[attr->textLength] = '\0';
+
+ if(!isValidPath(buf))
+ {
+ free(buf);
+ return false;
+ }
+
+ attr->data = buf;
+
+ return true;
+}
+
+static bool writeBootOption(AttributeEntryType *attr, const void *newData, int key)
+{
+ u32 len;
+ char *buf;
+ const char *path = (const char *) newData;
+
+ if(!path)
+ return false;
+
+ if(!isValidPath(path))
+ return false;
+
+ len = strlen(path);
+
+ if(!attr->data)
+ {
+ buf = (char *) malloc(len + 1);
+ if(!buf)
+ return false;
+ attr->data = buf;
+ }
+ else if(len != attr->textLength)
+ {
+ free(attr->data);
+ buf = (char *) malloc(len + 1);
+ if(!buf)
+ return false;
+ attr->data = buf;
+ }
+ else buf = attr->data;
+
+ memcpy(buf, path, len);
+ buf[len] = '\0';
+
+ writeAttributeText(attr, path, key);
+
+ return true;
+}
+
+static const char * convTable[] = {
+ "A", "B", "SELECT", "START", "RIGHT", "LEFT",
+ "UP", "DOWN", "R", "L", "X", "Y"
+};
+
+static bool parseBootOptionPad(AttributeEntryType *attr)
+{
+ char c;
+ char *textData = attr->textData;
+ u32 padValue = 0;
+ u32 i;
+
+ for(; (c = *textData) != '\0' && textData < attr->textData + attr->textLength;)
+ {
+ for(i=0; idata = (u32 *) malloc(sizeof(u32));
+ if(!attr->data)
+ return false;
+
+ *(u32 *)attr->data = padValue;
+ return true;
+}
+
+static bool writeBootOptionPad(AttributeEntryType *attr, const void *newData, int key)
+{
+ char tempbuf[0x40] = { 0 };
+ u32 padValue;
+ u32 i;
+ bool keysAdded = false;
+
+ if(!newData)
+ return false;
+
+ padValue = *(const u32 *)newData;
+
+ padValue &= HID_KEY_MASK_ALL;
+
+ if(!attr->data)
+ {
+ attr->data = (u32 *) malloc(sizeof(u32));
+ if(!attr->data)
+ return false;
+ }
+
+ *(u32 *)attr->data = padValue;
+
+ for(i=0; i> i) & 1)
+ {
+ if(keysAdded)
+ strcat(tempbuf, " + ");
+ strcat(tempbuf, convTable[i]);
+ keysAdded = true;
+ }
+ }
+
+ writeAttributeText(attr, tempbuf, key);
+
+ return true;
+}
+
+static const char * modeTable[] = {
+ "Normal", "Quick", "Quiet"
+};
+
+static bool parseBootMode(AttributeEntryType *attr)
+{
+ char *textData = attr->textData;
+ u32 i;
+
+ for(i=0; i<3; i++)
+ {
+ if(strnicmp(textData, modeTable[i], strlen(modeTable[i])) == 0)
+ break;
+ }
+
+ if(i >= 3)
+ {
+ attr->data = NULL;
+ return false;
+ }
+
+ attr->data = (u32 *) malloc(sizeof(u32));
+ if(!attr->data)
+ return false;
+
+ *(u32 *)attr->data = i;
+ return true;
+}
+
+static bool writeBootMode(AttributeEntryType *attr, const void *newData, int key)
+{
+ u32 mode;
+
+ if(!newData)
+ return false;
+
+ mode = *(const u32 *)newData;
+
+ if(mode > BootModeQuiet)
+ return false;
+
+ if(!attr->data)
+ {
+ attr->data = (u32 *) malloc(sizeof(u32));
+ if(!attr->data)
+ return false;
+ }
+
+ *(u32 *)attr->data = mode;
+
+ const char *textData = modeTable[mode];
+
+ writeAttributeText(attr, textData, key);
+
+ return true;
+}
+
+static const char * devModeStates[] = {
+ "Enabled", "Disabled"
+};
+
+static bool parseDevMode(AttributeEntryType *attr)
+{
+ char *textData = attr->textData;
+ bool enabled;
+
+ if(strcmp(textData, devModeStates[0]) == 0)
+ enabled = true;
+ else if(strcmp(textData, devModeStates[1]) == 0)
+ enabled = false;
+ else
+ {
+ attr->data = NULL;
+ return false;
+ }
+
+ attr->data = (bool *) malloc(sizeof(bool));
+ if(!attr->data)
+ return false;
+
+ *(bool *)attr->data = enabled;
+ return true;
+}
+
+static bool writeDevMode(AttributeEntryType *attr, const void *newData, int key)
+{
+ bool enabled;
+
+ if(!newData)
+ return false;
+
+ enabled = *(const bool *)newData;
+
+ if(!attr->data)
+ {
+ attr->data = (bool *) malloc(sizeof(bool));
+ if(!attr->data)
+ return false;
+ }
+
+ *(bool *)attr->data = enabled;
+
+ const char *textData = modeTable[enabled ? 0 : 1];
+
+ writeAttributeText(attr, textData, key);
+
+ return true;
+}
+
+void *configCopyText(int key)
+{
+ if(!configLoaded)
+ return NULL;
+
+ if(key < 0 || key >= KLast)
+ return NULL;
+
+ if(!attributes[key].textData)
+ return NULL;
+
+ char *buf = (char *) malloc(attributes[key].textLength + 1);
+ if(!buf)
+ return NULL;
+
+ memcpy(buf, attributes[key].textData, attributes[key].textLength);
+ buf[attributes[key].textLength] = '\0';
+
+ return buf;
+}
+
+const void *configGetData(int key)
+{
+ if(!configLoaded)
+ return NULL;
+
+ if(key < 0 || key >= KLast)
+ return NULL;
+
+ return attributes[key].data;
+}
+
+bool configDataExist(int key)
+{
+ if(!configLoaded)
+ return false;
+
+ return configGetData(key) != NULL;
+}
+
+const char *configGetKeyText(int key)
+{
+ if(key < 0 || key >= KLast)
+ return NULL;
+
+ return keyStrings[key];
+}
+
+bool configSetKeyData(int key, const void *data)
+{
+ AttributeEntryType *attr;
+
+ if(!configLoaded)
+ return false;
+
+ if(key < 0 || key >= KLast)
+ return NULL;
+
+ attr = &attributes[key];
+
+ if(!keyFunctions[key].write)
+ panicMsg("Unimplemented key function!");
+
+ return keyFunctions[key].write(attr, data, key);
+}
+
+void configRestoreDefaults()
+{
+ if(!configLoaded)
+ return;
+
+ for(int key=0; key.
+ */
+
+#include
+#include
+#include
+// #include "arm11/dev.h"
+#include "arm11/fsutils.h"
+#include "util.h"
+#include "fs.h"
+
+bool fsEnsureMounted(const char *path)
+{
+ if(strncmp(path, "sdmc:", 5) == 0)
+ {
+ return (fMount(FS_DRIVE_SDMC) == 0);
+ }
+ else if(strncmp(path, "nand:", 5) == 0)
+ {
+ return (fMount(FS_DRIVE_NAND) == 0);
+ }
+ else if(strncmp(path, "twln:", 5) == 0)
+ {
+ return (fMount(FS_DRIVE_TWLN) == 0);
+ }
+ else if(strncmp(path, "twlp:", 5) == 0)
+ {
+ return (fMount(FS_DRIVE_TWLP) == 0);
+ }
+
+ return false;
+}
+
+void fsUnmountAll()
+{
+ fUnmount(FS_DRIVE_SDMC);
+ fUnmount(FS_DRIVE_NAND);
+ fUnmount(FS_DRIVE_TWLN);
+ fUnmount(FS_DRIVE_TWLP);
+}
+
+u32 fsMountNandFilesystems()
+{
+ u32 res = 0;
+
+ res |= (fMount(FS_DRIVE_TWLN) ? 0 : 1u);
+ res |= (fMount(FS_DRIVE_TWLP) ? 0 : 1u<<1);
+ res |= (fMount(FS_DRIVE_NAND) ? 0 : 1u<<2);
+
+ return res;
+}
+
+void fsUnmountNandFilesystems()
+{
+ fUnmount(FS_DRIVE_NAND);
+ fUnmount(FS_DRIVE_TWLN);
+ fUnmount(FS_DRIVE_TWLP);
+ // dev_decnand->close(); // ?
+}
+
+bool fsMountSdmc()
+{
+ return (fMount(FS_DRIVE_SDMC) == 0);
+}
+
+bool fsCreateFileWithPath(const char *filepath)
+{
+ const size_t maxBufSize = 0x100;
+ char *tempBuf = (char *) malloc(maxBufSize);
+ char *tempPtr = tempBuf;
+ s32 fhandle;
+ FILINFO fno;
+
+ /* create directories */
+ for (char *p = (char*) filepath; *p; p++)
+ {
+ if ((*p == '/') || (*p == '\\'))
+ {
+ *tempPtr = '\0';
+ if (fStat(tempBuf, &fno) == 0)
+ {
+ if (!(fno.fattrib & AM_DIR)) goto fail;
+ }
+ else if (fMkdir(tempBuf) != 0) goto fail;
+ }
+ *(tempPtr++) = *p;
+ }
+ *tempPtr = '\0';
+
+ /* touch file */
+ if (fStat(tempBuf, &fno) == 0)
+ {
+ if (fno.fattrib & AM_DIR) goto fail;
+ }
+
+ fhandle = fOpen(tempPtr, FS_CREATE_ALWAYS);
+ if (fhandle < 0) goto fail;
+ fClose(fhandle);
+
+ free(tempBuf);
+
+ return true;
+
+fail:
+
+ free(tempBuf);
+
+ return false;
+}
+
diff --git a/source/arm11/menu.c b/source/arm11/menu.c
index de8d324..88500e6 100644
--- a/source/arm11/menu.c
+++ b/source/arm11/menu.c
@@ -192,7 +192,7 @@
hidScanInput();
const u32 kDown = hidKeysDown();
- const u32 kHeld = hidKeysHeld();
+ // const u32 kHeld = hidKeysHeld();
if ((kDown & KEY_A) && (curr_menu->entries[index].function == NULL))
{