Newer
Older
uwuboot / source / arm9 / config.c
@profi200 profi200 on 28 May 2017 12 KB Little fixes.

/* This is a parser/writer for the loadercfg.txt file. It's not vulnerable, I swear. */ 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "types.h"
#include "mem_map.h"
#include "fatfs/ff.h"
#include "arm9/console.h"
#include "arm9/main.h"
#include "arm9/interrupt.h"
#include "util.h"
#include "hid.h"
#include "arm9/debug.h"
#include "arm9/ui.h"
#include "arm9/config.h"

#define MAX_FILE_SIZE	0x4000 - 1

static const char *SdmcFilepath = "sdmc:/fastbootcfg.txt";
// static const char *NandFilepath = "nand:/loader/fastbootcfg.txt";

static const char *filepath;

static FIL 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 parseBootMode(AttributeEntryType *attr);
static bool writeBootMode(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"
};

static FunctionsEntryType keyFunctions[] = {
	{ parseBootOption,		writeBootOption },
	{ parseBootOption,		writeBootOption },
	{ parseBootOption,		writeBootOption },
	/* use the same functions for nand image option */
	{ parseBootOption,		writeBootOption },
	{ parseBootOption,		writeBootOption },
	{ parseBootOption,		writeBootOption },
	{ parseBootOptionPad,	NULL },
	{ parseBootOptionPad,	NULL },
	{ parseBootOptionPad,	NULL },
	{ parseBootMode,		writeBootMode }
};

static AttributeEntryType attributes[numKeys];

static char *filebuf = NULL;

static bool configLoaded = false;

/* This loads the config file from SD card and parses it */
bool loadConfigFile()
{
	FILINFO fileStat;
	u32 fileSize;
	unsigned bytesRead;
	
	if(configLoaded)
		unloadConfigFile();
	
	filepath = SdmcFilepath;
	
	// does the config file exist?
	if(f_stat(filepath, &fileStat) != FR_OK)
	{
		// try to create a file
		if(!createConfigFile())
			return false;
		// does it work now?
		if(f_stat(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(f_open(&file, filepath, FA_READ) != FR_OK)
	{
		uiPrintIfVerbose("Failed to open config-file for reading!\n");
		goto fail;
	}
	
	if(f_read(&file, filebuf, fileSize, &bytesRead) != FR_OK || bytesRead != fileSize)
	{
		uiPrintIfVerbose("Failed to read from config-file!\n");
		f_close(&file);
		goto fail;
	}
	
	f_close(&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; i<numKeys; i++)
	{
		curAttr = &attributes[i];
		
		curAttr->textData = 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);
	unsigned int bytesWritten;
	
	if(fileSize > MAX_FILE_SIZE)
		panicMsg("fileSize too large!");
	
	if(f_open(&file, filepath, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK)
	{
		goto fail;
	}
	
	if(fileSize)
	{
		if((f_write(&file, filebuf, fileSize, &bytesWritten) != FR_OK) || (bytesWritten != fileSize))
		{
			f_close(&file);
			goto fail;
		}
	}

	// Make sure changes are written to disk in case
	// the SD card is removed later.
	if(f_sync(&file) != FR_OK)
	{
		f_close(&file);
		goto fail;
	}

	f_close(&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; i<numKeys; i++)
	{
		curAttr = &attributes[i];
		text = findDefinition(keyStrings[i]);
		curAttr->textData = 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; i<numKeys; i++)
	{
		curAttr = &attributes[i];
		if(!curAttr->textData)
		{
			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;
	}
	
	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<numKeys; key++)
		{
			if(attributes[key].textData > 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(!isascii(c))
			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 bool parseBootOptionPad(AttributeEntryType *attr)
{
	char c;
	char *textData = attr->textData;
	u32 padValue = 0;
	u32 i;
	
	static const char * convTable[] = {
		"A", "B", "SELECT", "START", "RIGHT", "LEFT",
		"UP", "DOWN", "R", "L", "X", "Y"
	};
	
	for(; (c = *textData) != '\0' && textData < attr->textData + attr->textLength;)
	{
		for(i=0; i<arrayEntries(convTable); i++)
		{
			if(strnicmp(textData, convTable[i], strlen(convTable[i])) == 0)
			{
				padValue |= (u32) 1 << i;
				break;
			}
		}
		if(i == arrayEntries(convTable))
			textData ++;
		else textData += strlen(convTable[i]);
	}
	
	attr->data = (u32 *) malloc(sizeof(u32));
	if(!attr->data)
		return false;
		
	*(u32 *)attr->data = padValue;
	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 *data = modeTable[mode];
	
	writeAttributeText(attr, data, 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<numKeys; key++)
	{
		if(key == KBootMode)
			configSetKeyData(KBootMode, "Normal");
		else
			configSetKeyData(key, "");
	}
}