// Copyright (c) 2012- PPSSPP Project.

// 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, version 2.0 or later versions.

// 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 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.

#include "Core/Reporting.h"
#include "Core/Dialog/SavedataParam.h"
#include "Core/Dialog/PSPSaveDialog.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/HLE/sceChnnlsv.h"
#include "Core/ELF/ParamSFO.h"
#include "Core/HW/MemoryStick.h"

#include "image/png_load.h"

static const std::string ICON0_FILENAME = "ICON0.PNG";
static const std::string ICON1_FILENAME = "ICON1.PMF";
static const std::string PIC1_FILENAME = "PIC1.PNG";
static const std::string SND0_FILENAME = "SND0.AT3";
static const std::string SFO_FILENAME = "PARAM.SFO";

std::string savePath = "ms0:/PSP/SAVEDATA/";

namespace
{
	int getSizeNormalized(int size)
	{
		int sizeCluster = (int)MemoryStick_SectorSize();
		return ((int)((size + sizeCluster - 1) / sizeCluster)) * sizeCluster;
	}

	void SetStringFromSFO(ParamSFOData &sfoFile, const char *name, char *str, int strLength)
	{
		std::string value = sfoFile.GetValueString(name);
		strncpy(str, value.c_str(), strLength - 1);
		str[strLength - 1] = 0;
	}

	bool ReadPSPFile(std::string filename, u8 **data, s64 dataSize, s64 *readSize)
	{
		u32 handle = pspFileSystem.OpenFile(filename, FILEACCESS_READ);
		if (handle == 0)
			return false;

		if(dataSize == -1)
		{
			dataSize = pspFileSystem.GetFileInfo(filename).size;
			*data = new u8[(size_t)dataSize];
		}

		size_t result = pspFileSystem.ReadFile(handle, *data, dataSize);
		pspFileSystem.CloseFile(handle);
		if(readSize)
			*readSize = result;

		return result != 0;
	}

	bool WritePSPFile(std::string filename, u8 *data, SceSize dataSize)
	{
		u32 handle = pspFileSystem.OpenFile(filename, (FileAccess)(FILEACCESS_WRITE | FILEACCESS_CREATE));
		if (handle == 0)
			return false;

		size_t result = pspFileSystem.WriteFile(handle, data, dataSize);
		pspFileSystem.CloseFile(handle);

		return result != 0;
	}

	bool PSPMatch(std::string text, std::string regexp)
	{
		if(text.empty() && regexp.empty())
			return true;
		else if(regexp == "*")
			return true;
		else if(text.empty())
			return false;
		else if(regexp.empty())
			return false;
		else if(regexp == "?" && text.length() == 1)
			return true;
		else if(text == regexp)
			return true;
		else if(regexp.data()[0] == '*')
		{
			bool res = PSPMatch(text.substr(1),regexp.substr(1));
			if(!res)
				res = PSPMatch(text.substr(1),regexp);
			return res;
		}
		else if(regexp.data()[0] == '?')
		{
			return PSPMatch(text.substr(1),regexp.substr(1));
		}
		else if(regexp.data()[0] == text.data()[0])
		{
			return PSPMatch(text.substr(1),regexp.substr(1));
		}

		return false;
	}

	int align16(int address)
	{
		return ((address + 0xF) >> 4) << 4;
	}

	int GetSDKMainVersion(int sdkVersion)
	{
		if(sdkVersion > 0x307FFFF)
			return 6;
		if(sdkVersion > 0x300FFFF)
			return 5;
		if(sdkVersion > 0x206FFFF)
			return 4;
		if(sdkVersion > 0x205FFFF)
			return 3;
		if(sdkVersion >= 0x2000000)
			return 2;
		if(sdkVersion >= 0x1000000)
			return 1;
		return 0;
	};
}

SavedataParam::SavedataParam()
	: pspParam(0)
	, selectedSave(0)
	, saveDataList(0)
	, noSaveIcon(0)
	, saveDataListCount(0)
	, saveNameListDataCount(0)
{

}

void SavedataParam::Init()
{
	if (!pspFileSystem.GetFileInfo(savePath).exists)
	{
		pspFileSystem.MkDir(savePath);
	}
}

std::string SavedataParam::GetSaveDirName(SceUtilitySavedataParam* param, int saveId)
{
	if (!param) {
		return "";
	}

	std::string dirName = GetSaveName(param);
	if (saveId >= 0 && saveNameListDataCount > 0) // if user selection, use it
		dirName = GetFilename(saveId);

	return dirName;
}

std::string SavedataParam::GetSaveDir(SceUtilitySavedataParam* param, const std::string &saveDirName)
{
	if (!param) {
		return "";
	}

	return GetGameName(param) + saveDirName;
}

std::string SavedataParam::GetSaveDir(SceUtilitySavedataParam* param, int saveId)
{
	return GetSaveDir(param, GetSaveDirName(param, saveId));
}

std::string SavedataParam::GetSaveFilePath(SceUtilitySavedataParam* param, const std::string &saveDir)
{
	if (!param) {
		return "";
	}

	return savePath + saveDir;
}

std::string SavedataParam::GetSaveFilePath(SceUtilitySavedataParam* param, int saveId)
{
	return GetSaveFilePath(param, GetSaveDir(param, saveId));
}

std::string SavedataParam::GetGameName(SceUtilitySavedataParam* param)
{
	char gameName[14];
	memcpy(gameName,param->gameName,13);
	gameName[13] = 0;
	return gameName;
}

std::string SavedataParam::GetSaveName(SceUtilitySavedataParam* param)
{
	char saveName[21];
	memcpy(saveName,param->saveName,20);
	saveName[20] = 0;
	if(strcmp(saveName,"<>") == 0)
		return "";
	return saveName;
}

std::string SavedataParam::GetFileName(SceUtilitySavedataParam* param)
{
	char fileName[14];
	memcpy(fileName,param->fileName,13);
	fileName[13] = 0;
	return fileName;
}

bool SavedataParam::Delete(SceUtilitySavedataParam* param, int saveId)
{
	if (!param)
	{
		return false;
	}

	std::string dirPath = GetSaveFilePath(param,saveId);
	if (saveId >= 0 && saveNameListDataCount > 0) // if user selection, use it
	{
		if (saveDataList[saveId].size == 0) // don't delete no existing file
		{
			return false;
		}
	}

	pspFileSystem.RmDir(dirPath);
	return true;
}

bool SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveDirName, bool secureMode)
{
	if (!param) {
		return false;
	}

	std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));

	if (!pspFileSystem.GetFileInfo(dirPath).exists)
		pspFileSystem.MkDir(dirPath);

	u8* cryptedData = 0;
	int cryptedSize = 0;
	u8 cryptedHash[0x10];
	memset(cryptedHash,0,0x10);
	// Encrypt save.
	// TODO: Is this the correct difference between MAKEDATA and MAKEDATASECURE?
	if (param->dataBuf != 0 && g_Config.bEncryptSave && secureMode)
	{
		cryptedSize = param->dataSize;
		if(cryptedSize == 0 || (SceSize)cryptedSize > param->dataBufSize)
			cryptedSize = param->dataBufSize; // fallback, should never use this
		u8 *data_ = param->dataBuf;

		int aligned_len = align16(cryptedSize);
		cryptedData = new u8[aligned_len + 0x10];
		memcpy(cryptedData, data_, cryptedSize);

		int decryptMode = 1;
		if(param->key[0] != 0)
		{
			decryptMode = (GetSDKMainVersion(sceKernelGetCompiledSdkVersion()) >= 4 ? 5 : 3);
		}

		if(EncryptData(decryptMode, cryptedData, &cryptedSize, &aligned_len, cryptedHash, ((param->key[0] != 0)?param->key:0)) == 0)
		{
		}
		else
		{
			ERROR_LOG(HLE,"Save encryption failed. This save won't work on real PSP");
			delete[] cryptedData;
			cryptedData = 0;
		}
	}

	// SAVE PARAM.SFO
	ParamSFOData sfoFile;
	std::string sfopath = dirPath+"/" + SFO_FILENAME;
	PSPFileInfo sfoInfo = pspFileSystem.GetFileInfo(sfopath);
	if(sfoInfo.exists) // Read old sfo if exist
	{
		u8 *sfoData = new u8[(size_t)sfoInfo.size];
		size_t sfoSize = (size_t)sfoInfo.size;
		if(ReadPSPFile(sfopath,&sfoData,sfoSize, NULL))
		{
			sfoFile.ReadSFO(sfoData,sfoSize);
			delete[] sfoData;
		}
	}

	// Update values
	sfoFile.SetValue("TITLE",param->sfoParam.title,128);
	sfoFile.SetValue("SAVEDATA_TITLE",param->sfoParam.savedataTitle,128);
	sfoFile.SetValue("SAVEDATA_DETAIL",param->sfoParam.detail,1024);
	sfoFile.SetValue("PARENTAL_LEVEL",param->sfoParam.parentalLevel,4);
	sfoFile.SetValue("CATEGORY","MS",4);
	sfoFile.SetValue("SAVEDATA_DIRECTORY", GetSaveDir(param, saveDirName), 64);

	// For each file, 13 bytes for filename, 16 bytes for file hash (0 in PPSSPP), 3 byte for padding
	if (secureMode)
	{
		const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3;
		const int FILE_LIST_COUNT_MAX = 99;
		const int FILE_LIST_TOTAL_SIZE = FILE_LIST_ITEM_SIZE * FILE_LIST_COUNT_MAX;
		u32 tmpDataSize = 0;
		u8 *tmpDataOrig = sfoFile.GetValueData("SAVEDATA_FILE_LIST", &tmpDataSize);
		u8 *tmpData = new u8[FILE_LIST_TOTAL_SIZE];

		if (tmpDataOrig != NULL)
			memcpy(tmpData, tmpDataOrig, tmpDataSize > FILE_LIST_TOTAL_SIZE ? FILE_LIST_TOTAL_SIZE : tmpDataSize);
		else
			memset(tmpData, 0, FILE_LIST_TOTAL_SIZE);

		if (param->dataBuf != 0)
		{
			char *fName = (char*)tmpData;
			for(int i = 0; i < FILE_LIST_COUNT_MAX; i++)
			{
				if(fName[0] == 0)
					break; // End of list
				if(strncmp(fName,GetFileName(param).c_str(),20) == 0)
					break;
				fName += FILE_LIST_ITEM_SIZE;
			}

			if (fName + 13 <= (char*)tmpData + FILE_LIST_TOTAL_SIZE)
				snprintf(fName, 13, "%s",GetFileName(param).c_str());
			if (fName + 13 + 16 <= (char*)tmpData + FILE_LIST_TOTAL_SIZE)
				memcpy(fName+13, cryptedHash, 16);
		}
		sfoFile.SetValue("SAVEDATA_FILE_LIST", tmpData, FILE_LIST_TOTAL_SIZE, FILE_LIST_TOTAL_SIZE);
		delete[] tmpData;
	}

	// Init param with 0. This will be used to detect crypted save or not on loading
	u8 *tmpData = new u8[128];
	memset(tmpData, 0, 128);
	sfoFile.SetValue("SAVEDATA_PARAMS", tmpData, 128, 128);
	delete[] tmpData;

	u8 *sfoData;
	size_t sfoSize;
	sfoFile.WriteSFO(&sfoData,&sfoSize);

	// Calc SFO hash for PSP.
	if(cryptedData != 0)
	{
		int offset = sfoFile.GetDataOffset(sfoData,"SAVEDATA_PARAMS");
		if(offset >= 0)
			UpdateHash(sfoData, (int)sfoSize, offset, (param->key[0] ? 3 : 1));
	}
	WritePSPFile(sfopath, sfoData, (SceSize)sfoSize);
	delete[] sfoData;

	if(param->dataBuf != 0)	// Can launch save without save data in mode 13
	{
		std::string filePath = dirPath+"/"+GetFileName(param);
		u8 *data_ = 0;
		SceSize saveSize = 0;
		if(cryptedData == 0) // Save decrypted data
		{
			saveSize = param->dataSize;
			if(saveSize == 0 || saveSize > param->dataBufSize)
				saveSize = param->dataBufSize; // fallback, should never use this

			data_ = param->dataBuf;
		}
		else
		{
			data_ = cryptedData;
			saveSize = cryptedSize;
		}

		INFO_LOG(HLE,"Saving file with size %u in %s",saveSize,filePath.c_str());

		// copy back save name in request
		strncpy(param->saveName, saveDirName.c_str(), 20);

		if (!WritePSPFile(filePath, data_, saveSize))
		{
			ERROR_LOG(HLE,"Error writing file %s",filePath.c_str());
			if(cryptedData != 0)
			{
				delete[] cryptedData;
			}
			return false;
		}
		delete[] cryptedData;
	}


	// SAVE ICON0
	if (param->icon0FileData.buf.Valid())
	{
		std::string icon0path = dirPath + "/" + ICON0_FILENAME;
		WritePSPFile(icon0path, param->icon0FileData.buf, param->icon0FileData.bufSize);
	}
	// SAVE ICON1
	if (param->icon1FileData.buf.Valid())
	{
		std::string icon1path = dirPath + "/" + ICON1_FILENAME;
		WritePSPFile(icon1path, param->icon1FileData.buf, param->icon1FileData.bufSize);
	}
	// SAVE PIC1
	if (param->pic1FileData.buf.Valid())
	{
		std::string pic1path = dirPath + "/" + PIC1_FILENAME;
		WritePSPFile(pic1path, param->pic1FileData.buf, param->pic1FileData.bufSize);
	}

	// Save SND
	if (param->snd0FileData.buf.Valid())
	{
		std::string snd0path = dirPath + "/" + SND0_FILENAME;
		WritePSPFile(snd0path, param->snd0FileData.buf, param->snd0FileData.bufSize);
	}

	return true;
}

bool SavedataParam::Load(SceUtilitySavedataParam *param, const std::string &saveDirName, int saveId, bool secureMode)
{
	if (!param) {
		return false;
	}

	u8 *data_ = param->dataBuf;

	std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));
	if (saveId >= 0 && saveNameListDataCount > 0) // if user selection, use it
	{
		if (saveDataList[saveId].size == 0) // don't read no existing file
		{
			return false;
		}
	}

	std::string filePath = dirPath+"/"+GetFileName(param);
	s64 readSize;
	INFO_LOG(HLE,"Loading file with size %u in %s",param->dataBufSize,filePath.c_str());
	u8* saveData = 0;
	int saveSize = -1;
	if (!ReadPSPFile(filePath, &saveData, saveSize, &readSize))
	{
		ERROR_LOG(HLE,"Error reading file %s",filePath.c_str());
		return false;
	}
	saveSize = (int)readSize;

	// copy back save name in request
	strncpy(param->saveName, saveDirName.c_str(), 20);

	ParamSFOData sfoFile;
	std::string sfopath = dirPath+"/" + SFO_FILENAME;
	PSPFileInfo sfoInfo = pspFileSystem.GetFileInfo(sfopath);
	if(sfoInfo.exists) // Read sfo
	{
		u8 *sfoData = new u8[(size_t)sfoInfo.size];
		size_t sfoSize = (size_t)sfoInfo.size;
		if(ReadPSPFile(sfopath,&sfoData,sfoSize, NULL))
		{
			sfoFile.ReadSFO(sfoData,sfoSize);

			// copy back info in request
			strncpy(param->sfoParam.title,sfoFile.GetValueString("TITLE").c_str(),128);
			strncpy(param->sfoParam.savedataTitle,sfoFile.GetValueString("SAVEDATA_TITLE").c_str(),128);
			strncpy(param->sfoParam.detail,sfoFile.GetValueString("SAVEDATA_DETAIL").c_str(),1024);
			param->sfoParam.parentalLevel = sfoFile.GetValueInt("PARENTAL_LEVEL");
		}
		delete[] sfoData;
	}
	// Don't know what it is, but PSP always respond this and this unlock some game
	param->bind = 1021;

	bool isCrypted = IsSaveEncrypted(param, saveDirName) && secureMode;
	bool saveDone = false;
	if(isCrypted)// Try to decrypt
	{
		int align_len = align16(saveSize);
		u8* data_base = new u8[align_len];
		u8* cryptKey = new u8[0x10];
		memset(cryptKey,0,0x10);

		if(param->key[0] != 0)
		{
			memcpy(cryptKey, param->key, 0x10);
		}
		memset(data_base + saveSize, 0, align_len - saveSize);
		memcpy(data_base, saveData, saveSize);

		int decryptMode = 1;
		if(param->key[0] != 0)
		{
			decryptMode = (GetSDKMainVersion(sceKernelGetCompiledSdkVersion()) >= 4 ? 5 : 3);
		}

		if(DecryptSave(decryptMode, data_base, &saveSize, &align_len, ((param->key[0] != 0)?cryptKey:0)) == 0)
		{
			memcpy(data_, data_base, saveSize);
			saveDone = true;
		}
		delete[] data_base;
		delete[] cryptKey;
	}
	if(!saveDone) // not crypted or decrypt fail
	{
		memcpy(data_, saveData, saveSize);
	}
	param->dataSize = (SceSize)saveSize;
	delete[] saveData;

	return true;
}

int SavedataParam::EncryptData(unsigned int mode,
		 unsigned char *data,
		 int *dataLen,
		 int *alignedLen,
		 unsigned char *hash,
		 unsigned char *cryptkey)
{
	pspChnnlsvContext1 ctx1;
	pspChnnlsvContext2 ctx2;

	/* Make room for the IV in front of the data. */
	memmove(data + 0x10, data, *alignedLen);

	/* Set up buffers */
	memset(&ctx1, 0, sizeof(pspChnnlsvContext1));
	memset(&ctx2, 0, sizeof(pspChnnlsvContext2));
	memset(hash, 0, 0x10);
	memset(data, 0, 0x10);

	/* Build the 0x10-byte IV and setup encryption */
	if (sceSdCreateList_(ctx2, mode, 1, data, cryptkey) < 0)
		return -1;
	if (sceSdSetIndex_(ctx1, mode) < 0)
		return -2;
	if (sceSdRemoveValue_(ctx1, data, 0x10) < 0)
		return -3;
	if (sceSdSetMember_(ctx2, data + 0x10, *alignedLen) < 0)
		return -4;

	/* Clear any extra bytes left from the previous steps */
	memset(data + 0x10 + *dataLen, 0, *alignedLen - *dataLen);

	/* Encrypt the data */
	if (sceSdRemoveValue_(ctx1, data + 0x10, *alignedLen) < 0)
		return -5;

	/* Verify encryption */
	if (sceChnnlsv_21BE78B4_(ctx2) < 0)
		return -6;

	/* Build the file hash from this PSP */
	if (sceSdGetLastIndex_(ctx1, hash, cryptkey) < 0)
		return -7;

	/* Adjust sizes to account for IV */
	*alignedLen += 0x10;
	*dataLen += 0x10;

	/* All done */
	return 0;
}

int SavedataParam::DecryptSave(unsigned int mode,
		 unsigned char *data,
		 int *dataLen,
		 int *alignedLen,
		 unsigned char *cryptkey)
{

	pspChnnlsvContext1 ctx1;
	pspChnnlsvContext2 ctx2;

	/* Need a 16-byte IV plus some data */
	if (*alignedLen <= 0x10)
		return -1;
	*dataLen -= 0x10;
	*alignedLen -= 0x10;

	/* Set up buffers */
	memset(&ctx1, 0, sizeof(pspChnnlsvContext1));
	memset(&ctx2, 0, sizeof(pspChnnlsvContext2));

	/* Perform the magic */
	if (sceSdSetIndex_(ctx1, mode) < 0)
		return -2;
	if (sceSdCreateList_(ctx2, mode, 2, data, cryptkey) < 0)
		return -3;
	if (sceSdRemoveValue_(ctx1, data, 0x10) < 0)
		return -4;
	if (sceSdRemoveValue_(ctx1, data + 0x10, *alignedLen) < 0)
		return -5;
	if (sceSdSetMember_(ctx2, data + 0x10, *alignedLen) < 0)
		return -6;

	/* Verify that it decrypted correctly */
	if (sceChnnlsv_21BE78B4_(ctx2) < 0)
		return -7;

	/* The decrypted data starts at data + 0x10, so shift it back. */
	memmove(data, data + 0x10, *dataLen);
	return 0;
}

int SavedataParam::UpdateHash(u8* sfoData, int sfoSize, int sfoDataParamsOffset, int encryptmode)
{
	int alignedLen = align16(sfoSize);
	memset(sfoData+sfoDataParamsOffset, 0, 128);
	u8 filehash[16];
	int ret = 0;

	/* Compute 11D0 hash over entire file */
	if ((ret = BuildHash(filehash, sfoData, sfoSize, alignedLen, (encryptmode & 2) ? 4 : 2, NULL)) < 0)
	{	// Not sure about "2"
		return ret - 400;
	}

	/* Copy 11D0 hash to param.sfo and set flag indicating it's there */
	memcpy(sfoData+sfoDataParamsOffset + 0x20, filehash, 0x10);
	*(sfoData+sfoDataParamsOffset) |= 0x01;

	/* If new encryption mode, compute and insert the 1220 hash. */
	if (encryptmode & 2)
	{

		/* Enable the hash bit first */
		*(sfoData+sfoDataParamsOffset) |= 0x20;

		if ((ret = BuildHash(filehash, sfoData, sfoSize, alignedLen, 3, 0)) < 0)
		{
			return ret - 500;
		}
		memcpy(sfoData+sfoDataParamsOffset + 0x70, filehash, 0x10);
	}

	/* Compute and insert the 11C0 hash. */
	if ((ret = BuildHash(filehash, sfoData, sfoSize, alignedLen, 1, 0)) < 0)
	{
		return ret - 600;
	}
	memcpy(sfoData+sfoDataParamsOffset + 0x10, filehash, 0x10);

	/* All done. */
	return 0;
}

int SavedataParam::BuildHash(unsigned char *output,
		unsigned char *data,
		unsigned int len,
		unsigned int alignedLen,
		int mode,
		unsigned char *cryptkey)
{
	pspChnnlsvContext1 ctx1;

	/* Set up buffers */
	memset(&ctx1, 0, sizeof(pspChnnlsvContext1));
	memset(output, 0, 0x10);
	memset(data + len, 0, alignedLen - len);

	/* Perform the magic */
	if (sceSdSetIndex_(ctx1, mode & 0xFF) < 0)
		return -1;
	if (sceSdRemoveValue_(ctx1, data, alignedLen) < 0)
		return -2;
	if (sceSdGetLastIndex_(ctx1, output, cryptkey) < 0)
	{
		// Got here since Kirk CMD5 missing, return random value;
		memset(output,0x1,0x10);
		return 0;
	}
	/* All done. */
	return 0;
}

std::string SavedataParam::GetSpaceText(int size)
{
	char text[50];

	if(size < 1024)
	{
		sprintf(text,"%d B",size);
		return std::string(text);
	}

	size /= 1024;

	if(size < 1024)
	{
		sprintf(text,"%d KB",size);
		return std::string(text);
	}

	size /= 1024;

	if(size < 1024)
	{
		sprintf(text,"%d MB",size);
		return std::string(text);
	}

	size /= 1024;
	sprintf(text,"%d GB",size);
	return std::string(text);
}

bool SavedataParam::GetSizes(SceUtilitySavedataParam *param)
{
	if (!param) {
		return false;
	}

	bool ret = true;

	if (Memory::IsValidAddress(param->msFree))
	{
		Memory::Write_U32((u32)MemoryStick_SectorSize(),param->msFree); // cluster Size
		Memory::Write_U32((u32)(MemoryStick_FreeSpace() / MemoryStick_SectorSize()),param->msFree+4);	// Free cluster
		Memory::Write_U32((u32)(MemoryStick_FreeSpace() / 0x400),param->msFree+8); // Free space (in KB)
		std::string spaceTxt = SavedataParam::GetSpaceText((int)MemoryStick_FreeSpace());
		Memory::Memset(param->msFree+12,0,(u32)spaceTxt.size()+1);
		Memory::Memcpy(param->msFree+12,spaceTxt.c_str(),(u32)spaceTxt.size()); // Text representing free space
	}
	if (Memory::IsValidAddress(param->msData))
	{
		std::string path = GetSaveFilePath(param,0);
		PSPFileInfo finfo = pspFileSystem.GetFileInfo(path);
		if(finfo.exists)
		{
			// TODO : fill correctly with the total save size, be aware of crypted file size
			Memory::Write_U32(1,param->msData+36);	//1
			Memory::Write_U32(0x20,param->msData+40);	// 0x20
			Memory::Write_U8(0,param->msData+44);	// "32 KB" // 8 u8
			Memory::Write_U32(0x20,param->msData+52);	//  0x20
			Memory::Write_U8(0,param->msData+56);	// "32 KB" // 8 u8
		}
		else
		{
			Memory::Write_U32(0,param->msData+36);
			Memory::Write_U32(0,param->msData+40);
			Memory::Write_U8(0,param->msData+44);
			Memory::Write_U32(0,param->msData+52);
			Memory::Write_U8(0,param->msData+56);
			ret = false;
			// this should return SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_DATA
		}
	}
	if (Memory::IsValidAddress(param->utilityData))
	{
		int total_size = 0;
		total_size += getSizeNormalized(1); // SFO;
		total_size += getSizeNormalized(param->dataSize); // Save Data
		total_size += getSizeNormalized(param->icon0FileData.size);
		total_size += getSizeNormalized(param->icon1FileData.size);
		total_size += getSizeNormalized(param->pic1FileData.size);
		total_size += getSizeNormalized(param->snd0FileData.size);

		Memory::Write_U32(total_size / (u32)MemoryStick_SectorSize(),param->utilityData);	// num cluster
		Memory::Write_U32(total_size / 0x400,param->utilityData+4);	// save size in KB
		std::string spaceTxt = SavedataParam::GetSpaceText(total_size);
		Memory::Memset(param->utilityData+8,0,(u32)spaceTxt.size()+1);
		Memory::Memcpy(param->utilityData+8,spaceTxt.c_str(),(u32)spaceTxt.size()); // save size in text
		Memory::Write_U32(total_size / 0x400,param->utilityData+16);	// save size in KB
		spaceTxt = SavedataParam::GetSpaceText(total_size);
		Memory::Memset(param->utilityData+20,0,(u32)spaceTxt.size()+1);
		Memory::Memcpy(param->utilityData+20,spaceTxt.c_str(),(u32)spaceTxt.size()); // save size in text
	}
	return ret;

}

bool SavedataParam::GetList(SceUtilitySavedataParam *param)
{
	if (!param) {
		return false;
	}

	if (Memory::IsValidAddress(param->idListAddr))
	{
		u32 outputBuffer = Memory::Read_U32(param->idListAddr + 8);
		u32 maxFile = Memory::Read_U32(param->idListAddr + 0);

		std::vector<PSPFileInfo> validDir;
		std::vector<PSPFileInfo> allDir = pspFileSystem.GetDirListing(savePath);

		if (Memory::IsValidAddress(outputBuffer))
		{
			std::string searchString = GetGameName(param)+GetSaveName(param);
			for (size_t i = 0; i < allDir.size() && validDir.size() < maxFile; i++)
			{
				std::string dirName = allDir[i].name;
				if(PSPMatch(dirName, searchString))
				{
					validDir.push_back(allDir[i]);
				}
			}

			for (u32 i = 0; i < (u32)validDir.size(); i++)
			{
				u32 baseAddr = outputBuffer + (i*72);
				Memory::Write_U32(0x11FF,baseAddr + 0); // mode
				Memory::Write_U64(0,baseAddr + 4); // TODO ctime
				Memory::Write_U64(0,baseAddr + 12); // TODO unknow
				Memory::Write_U64(0,baseAddr + 20); // TODO atime
				Memory::Write_U64(0,baseAddr + 28); // TODO unknow
				Memory::Write_U64(0,baseAddr + 36); // TODO mtime
				Memory::Write_U64(0,baseAddr + 44); // TODO unknow
				// folder name without gamename (max 20 u8)
				std::string outName = validDir[i].name.substr(GetGameName(param).size());
				Memory::Memset(baseAddr + 52,0,20);
				Memory::Memcpy(baseAddr + 52, outName.c_str(), (u32)outName.size());
			}
		}
		// Save num of folder found
		Memory::Write_U32((u32)validDir.size(), param->idListAddr + 4);
	}
	return true;
}

int SavedataParam::GetFilesList(SceUtilitySavedataParam *param)
{
	if (!param)	{
		return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_STATUS;
	}

	if (!param->fileList.Valid()) {
		ERROR_LOG_REPORT(HLE, "SavedataParam::GetFilesList(): bad fileList address %08x", param->fileList.ptr);
		// Should crash.
		return -1;
	}

	auto &fileList = param->fileList;
	if (fileList->secureEntries.Valid() && fileList->maxSecureEntries > 99) {
		ERROR_LOG_REPORT(HLE, "SavedataParam::GetFilesList(): too many secure entries, %d", fileList->maxSecureEntries);
		return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;
	}
	if (fileList->normalEntries.Valid() && fileList->maxNormalEntries > 8192) {
		ERROR_LOG_REPORT(HLE, "SavedataParam::GetFilesList(): too many normal entries, %d", fileList->maxNormalEntries);
		return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;
	}
	if (fileList->systemEntries.Valid() && fileList->maxSystemEntries > 5) {
		ERROR_LOG_REPORT(HLE, "SavedataParam::GetFilesList(): too many system entries, %d", fileList->maxSystemEntries);
		return SCE_UTILITY_SAVEDATA_ERROR_RW_BAD_PARAMS;
	}

	std::string dirPath = savePath + GetGameName(param) + GetSaveName(param);
	if (!pspFileSystem.GetFileInfo(dirPath).exists) {
		DEBUG_LOG(HLE, "SavedataParam::GetFilesList(): directory %s does not exist", dirPath.c_str());
		return SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA;
	}

	// Even if there are no files, initialize to 0.
	fileList->resultNumSecureEntries = 0;
	fileList->resultNumNormalEntries = 0;
	fileList->resultNumSystemEntries = 0;

	// We need PARAMS.SFO's SAVEDATA_FILE_LIST to determine which entries are secure.
	PSPFileInfo sfoFileInfo = pspFileSystem.GetFileInfo(dirPath + "/" + SFO_FILENAME);
	std::set<std::string> secureFilenames;
	// TODO: Error code if not?
	if (sfoFileInfo.exists) {
		ParamSFOData sfoFile;
		size_t sfoSize = (size_t)sfoFileInfo.size;
		u8 *sfoData = new u8[sfoSize];
		if (ReadPSPFile(dirPath + "/" + SFO_FILENAME, &sfoData, sfoSize, NULL)){
			sfoFile.ReadSFO(sfoData, sfoSize);
		}
		delete[] sfoData;

		u32 sfoFileListSize = 0;
		char *sfoFileList = (char *)sfoFile.GetValueData("SAVEDATA_FILE_LIST", &sfoFileListSize);
		const int FILE_LIST_ITEM_SIZE = 13 + 16 + 3;
		const int FILE_LIST_COUNT_MAX = 99;

		// Filenames are 13 bytes long at most.  Add a NULL so there's no surprises.
		char temp[14];
		temp[13] = '\0';

		for (u32 i = 0; i < FILE_LIST_COUNT_MAX; ++i) {
			// Ends at a NULL filename.
			if (i * FILE_LIST_ITEM_SIZE >= sfoFileListSize || sfoFileList[i * FILE_LIST_ITEM_SIZE] == '\0') {
				break;
			}

			strncpy(temp, &sfoFileList[i * FILE_LIST_ITEM_SIZE], 13);
			secureFilenames.insert(temp);
		}
	}

	// Does not list directories, nor recurse into them, and ignores files not ALL UPPERCASE.
	auto files = pspFileSystem.GetDirListing(dirPath);
	for (auto file = files.begin(), end = files.end(); file != end; ++file) {
		if (file->type == FILETYPE_DIRECTORY) {
			continue;
		}
		// TODO: What are the exact rules?  It definitely skips lowercase, and allows FILE or FILE.EXT.
		if (file->name.find_first_of("abcdefghijklmnopqrstuvwxyz") != file->name.npos) {
			DEBUG_LOG(HLE, "SavedataParam::GetFilesList(): skipping file %s with lowercase", file->name.c_str());
			continue;
		}

		bool isSystemFile = file->name == ICON0_FILENAME || file->name == ICON1_FILENAME || file->name == PIC1_FILENAME;
		isSystemFile = isSystemFile || file->name == SND0_FILENAME || file->name == SFO_FILENAME;

		SceUtilitySavedataFileListEntry *entry = NULL;
		int sizeOffset = 0;
		if (isSystemFile) {
			if (fileList->systemEntries.Valid() && fileList->resultNumSystemEntries < fileList->maxSystemEntries) {
				entry = &fileList->systemEntries[fileList->resultNumSystemEntries++];
			}
		} else if (secureFilenames.find(file->name) != secureFilenames.end()) {
			if (fileList->secureEntries.Valid() && fileList->resultNumSecureEntries < fileList->maxSecureEntries) {
				entry = &fileList->secureEntries[fileList->resultNumSecureEntries++];
			}
			// Secure files are slightly bigger.
			bool isCrypted = IsSaveEncrypted(param, GetSaveDirName(param, 0));
			if (isCrypted) {
				sizeOffset = -0x10;
			}
		} else {
			if (fileList->normalEntries.Valid() && fileList->resultNumNormalEntries < fileList->maxNormalEntries) {
				entry = &fileList->normalEntries[fileList->resultNumNormalEntries++];
			}
		}

		// Out of space for this file in the list.
		if (entry == NULL) {
			continue;
		}

		entry->st_mode = 0x21FF;
		entry->st_size = file->size + sizeOffset;
		// TODO: ctime, atime, mtime
		// TODO: Probably actually 13 + 3 pad...
		strncpy(entry->name, file->name.c_str(), 16);
		entry->name[15] = '\0';
	}

	return 0;
}

bool SavedataParam::GetSize(SceUtilitySavedataParam *param)
{
	if (!param)
	{
		return false;
	}

	std::string saveDir = savePath + GetGameName(param) + GetSaveName(param);
	PSPFileInfo info = pspFileSystem.GetFileInfo(saveDir);
	bool exists = info.exists;

	if (Memory::IsValidAddress(param->sizeAddr))
	{
		PspUtilitySavedataSizeInfo sizeInfo;
		Memory::ReadStruct(param->sizeAddr, &sizeInfo);

		// TODO: Read the entries and count up the size vs. existing size?

		sizeInfo.sectorSize = (int)MemoryStick_SectorSize();
		sizeInfo.freeSectors = (int)(MemoryStick_FreeSpace() / MemoryStick_SectorSize());

		// TODO: Is this after the specified files?  Before?
		sizeInfo.freeKB = (int)(MemoryStick_FreeSpace() / 1024);
		std::string spaceTxt = SavedataParam::GetSpaceText((int)MemoryStick_FreeSpace());
		strncpy(sizeInfo.freeString, spaceTxt.c_str(), 8);
		sizeInfo.freeString[7] = '\0';

		// TODO.
		sizeInfo.neededKB = 0;
		strcpy(sizeInfo.neededString, "0 KB");
		sizeInfo.overwriteKB = 0;
		strcpy(sizeInfo.overwriteString, "0 KB");

		Memory::WriteStruct(param->sizeAddr, &sizeInfo);
	}

	return exists;
}

void SavedataParam::Clear()
{
	if (saveDataList)
	{
		for (int i = 0; i < saveNameListDataCount; i++)
		{
			if (saveDataList[i].textureData != 0 && saveDataList[i].size != 0)
				kernelMemory.Free(saveDataList[i].textureData);
			saveDataList[i].textureData = 0;
		}

		delete[] saveDataList;
		saveDataList = 0;
		saveDataListCount = 0;
	}
	if (noSaveIcon)
	{
		if(noSaveIcon->textureData != 0)
			kernelMemory.Free(noSaveIcon->textureData);
		noSaveIcon->textureData = 0;
		delete noSaveIcon;
		noSaveIcon = 0;
	}
}

int SavedataParam::SetPspParam(SceUtilitySavedataParam *param)
{
	pspParam = param;
	if (!pspParam)
	{
		Clear();
		return 0;
	}

	bool listEmptyFile = true;
	if (param->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTLOAD ||
			param->mode == SCE_UTILITY_SAVEDATA_TYPE_LISTDELETE)
	{
		listEmptyFile = false;
	}

	SceUtilitySavedataSaveName *saveNameListData;
	bool hasMultipleFileName = false;
	if (param->saveNameList.Valid())
	{
		Clear();

		saveNameListData = param->saveNameList;

		// Get number of fileName in array
		saveDataListCount = 0;
		while (saveNameListData[saveDataListCount][0] != 0)
		{
			saveDataListCount++;
		}

		if (saveDataListCount > 0)
		{
			hasMultipleFileName = true;
			saveDataList = new SaveFileInfo[saveDataListCount];

			// get and stock file info for each file
			int realCount = 0;
			for (int i = 0; i < saveDataListCount; i++)
			{
				// TODO: Maybe we should fill the list with existing files instead?
				if (strcmp(saveNameListData[i], "<>") == 0)
					continue;

				DEBUG_LOG(HLE,"Name : %s",saveNameListData[i]);

				std::string fileDataPath = savePath+GetGameName(param) + saveNameListData[i] + "/" + param->fileName;
				PSPFileInfo info = pspFileSystem.GetFileInfo(fileDataPath);
				if (info.exists)
				{
					SetFileInfo(realCount, info, saveNameListData[i]);

					DEBUG_LOG(HLE,"%s Exist",fileDataPath.c_str());
					realCount++;
				}
				else
				{
					if (listEmptyFile)
					{
						ClearFileInfo(saveDataList[realCount], saveNameListData[i]);
						DEBUG_LOG(HLE,"Don't Exist");
						realCount++;
					}
				}
			}
			saveNameListDataCount = realCount;
		}
	}
	if (!hasMultipleFileName) // Load info on only save
	{
		saveNameListData = 0;

		Clear();
		saveDataList = new SaveFileInfo[1];
		saveDataListCount = 1;

		// get and stock file info for each file
		DEBUG_LOG(HLE,"Name : %s",GetSaveName(param).c_str());

		std::string fileDataPath = savePath + GetGameName(param) + GetSaveName(param) + "/" + param->fileName;
		PSPFileInfo info = pspFileSystem.GetFileInfo(fileDataPath);
		if (info.exists)
		{
			SetFileInfo(0, info, GetSaveName(param));

			DEBUG_LOG(HLE,"%s Exist",fileDataPath.c_str());
			saveNameListDataCount = 1;
		}
		else
		{
			if (listEmptyFile)
			{
				ClearFileInfo(saveDataList[0], GetSaveName(param));
				DEBUG_LOG(HLE,"Don't Exist");
			}
			saveNameListDataCount = 0;
			return 0;
		}
	}
	return 0;
}

bool SavedataParam::CreatePNGIcon(u8* pngData, int pngSize, SaveFileInfo& info)
{
	unsigned char *textureData;
	int w,h;

	int success = pngLoadPtr(pngData, (int)pngSize, &w, &h, &textureData, false);

	u32 texSize = w*h*4;
	u32 atlasPtr;
	if (success)
		atlasPtr = kernelMemory.Alloc(texSize, true, "SaveData Icon");
	if (success && atlasPtr != (u32)-1)
	{
		info.textureData = atlasPtr;
		Memory::Memcpy(atlasPtr, textureData, texSize);
		free(textureData);
		info.textureWidth = w;
		info.textureHeight = h;
	}
	else
	{
		WARN_LOG(HLE, "Unable to load PNG data for savedata.");
		return false;
	}
	return true;
}

void SavedataParam::SetFileInfo(SaveFileInfo &saveInfo, PSPFileInfo &info, std::string saveName)
{
	saveInfo.size = info.size;
	saveInfo.saveName = saveName;
	saveInfo.idx = 0;
	saveInfo.modif_time = info.mtime;

	// Start with a blank slate.
	saveInfo.textureData = 0;
	saveInfo.title[0] = 0;
	saveInfo.saveTitle[0] = 0;
	saveInfo.saveDetail[0] = 0;

	// Search save image icon0
	// TODO : If icon0 don't exist, need to use icon1 which is a moving icon. Also play sound
	std::string fileDataPath2 = savePath + GetGameName(pspParam) + saveName + "/" + ICON0_FILENAME;
	PSPFileInfo info2 = pspFileSystem.GetFileInfo(fileDataPath2);
	if (info2.exists)
	{
		u8 *textureDataPNG = new u8[(size_t)info2.size];
		ReadPSPFile(fileDataPath2, &textureDataPNG, info2.size, NULL);
		CreatePNGIcon(textureDataPNG, (int)info2.size, saveInfo);
		delete[] textureDataPNG;
	}

	// Load info in PARAM.SFO
	fileDataPath2 = savePath + GetGameName(pspParam) + saveName + "/" + SFO_FILENAME;
	info2 = pspFileSystem.GetFileInfo(fileDataPath2);
	if (info2.exists)
	{
		u8 *sfoParam = new u8[(size_t)info2.size];
		ReadPSPFile(fileDataPath2, &sfoParam, info2.size, NULL);
		ParamSFOData sfoFile;
		if (sfoFile.ReadSFO(sfoParam,(size_t)info2.size))
		{
			SetStringFromSFO(sfoFile, "TITLE", saveInfo.title, sizeof(saveInfo.title));
			SetStringFromSFO(sfoFile, "SAVEDATA_TITLE", saveInfo.saveTitle, sizeof(saveInfo.saveTitle));
			SetStringFromSFO(sfoFile, "SAVEDATA_DETAIL", saveInfo.saveDetail, sizeof(saveInfo.saveDetail));
		}
		delete [] sfoParam;
	}
}

void SavedataParam::SetFileInfo(int idx, PSPFileInfo &info, std::string saveName)
{
	SetFileInfo(saveDataList[idx], info, saveName);
	saveDataList[idx].idx = idx;
}

void SavedataParam::ClearFileInfo(SaveFileInfo &saveInfo, std::string saveName)
{
	saveInfo.size = 0;
	saveInfo.saveName = saveName;
	saveInfo.idx = 0;
	saveInfo.textureData = 0;

	if (GetPspParam()->newData.Valid() && GetPspParam()->newData->buf.Valid())
	{
		// We have a png to show
		if (!noSaveIcon)
		{
			noSaveIcon = new SaveFileInfo();
			PspUtilitySavedataFileData *newData = GetPspParam()->newData;
			CreatePNGIcon(newData->buf, (int)newData->size, *noSaveIcon);
		}
		saveInfo.textureData = noSaveIcon->textureData;
		saveInfo.textureWidth = noSaveIcon->textureWidth;
		saveInfo.textureHeight = noSaveIcon->textureHeight;
	}
}

SceUtilitySavedataParam* SavedataParam::GetPspParam()
{
	return pspParam;
}

int SavedataParam::GetFilenameCount()
{
	return saveNameListDataCount;
}

const SaveFileInfo& SavedataParam::GetFileInfo(int idx)
{
	return saveDataList[idx];
}
std::string SavedataParam::GetFilename(int idx)
{
	return saveDataList[idx].saveName;
}

int SavedataParam::GetSelectedSave()
{
	// The slot # of the same save on LOAD/SAVE lists can dismatch so this isn't right anyhow
	return selectedSave < saveNameListDataCount ? selectedSave : 0;
}

void SavedataParam::SetSelectedSave(int idx)
{
	selectedSave = idx;
}

int SavedataParam::GetFirstListSave()
{
	return 0;
}

int SavedataParam::GetLastListSave()
{
	return saveDataListCount - 1;
}

int SavedataParam::GetLatestSave()
{
	int idx = 0;
	time_t idxTime = 0;
	for (int i = 0; i < saveDataListCount; ++i)
	{
		time_t thisTime = mktime(&saveDataList[i].modif_time);
		if (idxTime < thisTime)
		{
			idx = i;
			idxTime = thisTime;
		}
	}
	return idx;
}

int SavedataParam::GetOldestSave()
{
	int idx = 0;
	time_t idxTime = 0;
	for (int i = 0; i < saveDataListCount; ++i)
	{
		time_t thisTime = mktime(&saveDataList[i].modif_time);
		if (idxTime > thisTime)
		{
			idx = i;
			idxTime = thisTime;
		}
	}
	return idx;
}

int SavedataParam::GetFirstDataSave()
{
	int idx = 0;
	for (int i = 0; i < saveDataListCount; ++i)
	{
		if (saveDataList[i].size != 0)
		{
			idx = i;
			break;
		}
	}
	return idx;
}

int SavedataParam::GetLastDataSave()
{
	int idx = 0;
	for (int i = saveDataListCount; i > 0; )
	{
		--i;
		if (saveDataList[i].size != 0)
		{
			idx = i;
			break;
		}
	}
	return idx;
}

int SavedataParam::GetFirstEmptySave()
{
	int idx = 0;
	for (int i = 0; i < saveDataListCount; ++i)
	{
		if (saveDataList[i].size == 0)
		{
			idx = i;
			break;
		}
	}
	return idx;
}

int SavedataParam::GetLastEmptySave()
{
	int idx = 0;
	for (int i = saveDataListCount; i > 0; )
	{
		--i;
		if (saveDataList[i].size == 0)
		{
			idx = i;
			break;
		}
	}
	return idx;
}

void SavedataParam::DoState(PointerWrap &p)
{
	// pspParam is handled in PSPSaveDialog.
	p.Do(selectedSave);
	p.Do(saveDataListCount);
	p.Do(saveNameListDataCount);
	if (p.mode == p.MODE_READ)
	{
		if (saveDataList != NULL)
			delete [] saveDataList;
		if (saveDataListCount != 0)
		{
			saveDataList = new SaveFileInfo[saveDataListCount];
			p.DoArray(saveDataList, saveDataListCount);
		}
		else
			saveDataList = NULL;
	}
	else
		p.DoArray(saveDataList, saveDataListCount);
	p.DoMarker("SavedataParam");
}

bool SavedataParam::IsSaveEncrypted(SceUtilitySavedataParam* param, const std::string &saveDirName)
{
	bool isCrypted = false;

	ParamSFOData sfoFile;
	std::string dirPath = GetSaveFilePath(param, GetSaveDir(param, saveDirName));
	std::string sfopath = dirPath + "/" + SFO_FILENAME;
	PSPFileInfo sfoInfo = pspFileSystem.GetFileInfo(sfopath);
	if(sfoInfo.exists) // Read sfo
	{
		u8 *sfoData = new u8[(size_t)sfoInfo.size];
		size_t sfoSize = (size_t)sfoInfo.size;
		if(ReadPSPFile(sfopath,&sfoData,sfoSize, NULL))
		{
			sfoFile.ReadSFO(sfoData,sfoSize);

			// save created in PPSSPP and not encrypted has '0' in SAVEDATA_PARAMS
			u32 tmpDataSize = 0;
			u8* tmpDataOrig = sfoFile.GetValueData("SAVEDATA_PARAMS", &tmpDataSize);
			for(u32 i = 0; i < tmpDataSize; i++)
			{
				if(tmpDataOrig[i] != 0)
				{
					isCrypted = true;
					break;
				}
			}
		}
		delete[] sfoData;
	}
	return isCrypted;
}

