#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <string>
#include <vector>
#include <deque>
#include <list>
#include <assert.h>
#include <time.h>
#include <math.h>
#include <iostream>
#include <fstream>
#include <complex>
#include <windows.h>
//#include <udis86.h>
#include <io.h>
#include <fcntl.h>
#include <boost/scoped_array.hpp>
using namespace std;

const int BOOTSECTOR_JUMPINST = 3;

#pragma pack(push, 1)

struct FAT32_BOOTSECTOR
{
	//BYTE BS_jmpBoot[3]; // Jump Instruction to boot code
	char BS_OEMName[8]; // "MSWIN4.1" is recommended setting
	WORD BPB_BytsPerSec; // Count of bytes per Sector
	BYTE BPB_SecPerClus; // Number of Sectors per Allocation Unit which must be a power of 2
	WORD BPB_RsvdSecCnt; // Number of Reserved Sectors in the Reserved Region of the volume starting at the first sector of the volume
	BYTE BPB_NumFATs; // Count of FAT data structures on the volume
	WORD BPB_RootEntCnt; // Count of 32byte directory entries in the root directory for FAT12 and FAT16 volumes
	WORD BPB_TotSec16; // Old 16-bit total count of sectors on the volume
	BYTE BPB_Media; // 0xF8 for fixed media, 0xF0 for removable media
	WORD BPB_FATSz16; //@ FAT12/FAT16 16bit-count of sectors occupied by ONE FAT

	WORD BPB_SecPerTrk; // Sectors per Track for interrupt 0x13 (um... it is Wintel culture... x_x;)
	WORD BPB_NumHeads; // Number of Heads for interrupt 0x13 (um... it is Wintel culture... x_x;)
	DWORD BPB_HiddSec; // Count of Hidden Sectors preceding the partition that contains this FAT volume
	DWORD BPB_TotSec32; // New 32-bit total count of sectors on the volume
	// Following structure is prepared for FAT12 and FAT16 (not for FAT32)
	DWORD BPB_FATSz32; //@ FAT32 32bit-count of sectors occupied by ONE FAT←ここを追加
	WORD BPB_ExtFlags;
	WORD BPB_FSVer;
	DWORD BPB_RootClus;
	WORD BPB_FSInfo;
	WORD BPB_BkBootSec;
	BYTE BPB_Reserved[12];
	BYTE BS_DrvNum; // Int0x13 drive number (um... it is Wintel culture... x_x;)
	BYTE BS_Reserved1; // Reserved for WindowsNT (um... it is MS culture... x_x;)
	BYTE BS_BootSig; // Extended Boot Signature (0x29) which indicates following three fields in the boot sector are present
	DWORD BS_VolID; // Volume Serial Number
	BYTE BS_VolLab[11]; // Volume Label
	BYTE BS_FilSysType[8]; // Type of File System (it is no more than a hint)
} ;

struct DIRENT{
	char DIR_Name[11];
	BYTE DIR_Attr;
	BYTE DIR_NTRes;
	BYTE DIR_CrtTimeTenth;
	WORD DIR_CrtTime;
	WORD DIR_CrtDate;
	WORD DIR_LstAccDate;
	WORD DIR_FstClusHI;
	WORD DIR_WrtTime;
	WORD DIR_WrtDate;
	WORD DIR_FstClusLO;
	DWORD DIR_FileSize;
	
	DWORD getfirstclus(){
		return DIR_FstClusHI << 16 | DIR_FstClusLO;
	}
	void setfirstclus(DWORD clus){
		DIR_FstClusHI = (clus & 0xffff0000) >> 16;
		DIR_FstClusLO = clus & 0x0000ffff;
	}
};

enum FAT_ATTR{
	ATTR_READ_ONLY = 0x01,
	ATTR_HIDDEN = 0x02,
	ATTR_SYSTEM = 0x04,
	ATTR_VOLUME_ID = 0x08,
	ATTR_DIRECTORY = 0x10,
	ATTR_ARCHIVE = 0x20,
	ATTR_LONG_NAME = ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID
};

struct PAT_TABLE{
	BYTE flag;
	BYTE begin_chs[3];
	BYTE type;
	BYTE end_chs[3];
	DWORD begin_lba;
	DWORD size_lba;
};

struct FSINFO{
	DWORD LeadSig;
	BYTE Reserved1[480];
	DWORD StrucSig;
	DWORD Free_Count;
	DWORD Nxt_Free;
	BYTE Reserved2[12];
	DWORD TrailSig;
};

#pragma pack(pop)

bool islegalch(char c){
	return isalnum(c) || c > 127
			|| c == '$' || c == '\'' || c == '%'
			 || c == '-' || c == '_' || c == '@'
			 || c == '~' || c == '`' || c == '!'
			 || c == '(' || c == ')' || c == '{'
			 || c == '}' || c == '^' || c == '#'
			  || c == '&';
}

bool toshort(char *s, string l){
	memset(s, 0x20, 11);
	int r = l.find('.');
	if(r == -1){
		int np = 0;
		for(size_t i = 0; i < l.length(); i++){
			if(islegalch(l[i])){
				s[np++] = toupper(l[i]);
				if(np >= 8){
					break;
				}
			}
		}
		if(np == 0){
			return false;
		}
		return true;
	}
	string name = l.substr(0, r);
	string ext = l.substr(r + 1);
	int np = 0;
	for(size_t i = 0; i < name.length(); i++){
		if(islegalch(name[i])){
			s[np++] = toupper(name[i]);
			if(np >= 8){
				break;
			}
		}
	}
	if(np == 0){
		return false;
	}
	int ep = 8;
	for(size_t i = 0; i < ext.length(); i++){
		if(islegalch(ext[i])){
			s[ep++] = toupper(ext[i]);
			if(ep >= 11){
				break;
			}
		}
	}
	return true;
}

const char *DEFAULT_DEF_FILE = "fat.fatdef";
const DWORD sectorsize = 512;
const DWORD sectorperclus = 2;
const DWORD clussize = sectorperclus * sectorsize;
const DWORD EOC = 0x0FFFFFF8;
const DWORD fatreserved = 0x80;
const DWORD fatnum = 2;

string getelem(const string& text, int *pos){
	string ret = "";
	for(; (text[*pos] == ' ' || text[*pos] == '\t'); (*pos)++);
	if(*pos == text.length()){
		return "";
	}
	if(isalnum(text[*pos])){
		for(; isalnum(text[*pos]); (*pos)++){
			ret += text[*pos];
		}
		return ret;
	}else if(text[*pos] == ':'){
		(*pos)++;
		return ":";
	}else if(text[*pos] == '"'){
		(*pos)++;
		int n = text.find('"', *pos);
		if(n == -1){
			ret = text.substr(*pos);
			*pos = text.length();
			return ret;
		}
		ret = text.substr(*pos, n - *pos);
		*pos = n + 1;
		return ret;
	}else{
		char buf[2] = {0};
		buf[0] = text[(*pos)++];
		return buf;
	}
}

bool getfilesize(string f, DWORD *s){
	HANDLE hFile = CreateFile(f.c_str(), GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
	if( hFile != INVALID_HANDLE_VALUE ){
		DWORD size = GetFileSize( hFile, NULL );
		if( size == -1 )
		{
			if( GetLastError() == NO_ERROR )
			{
				// GetFileSize()は失敗したのではない
				CloseHandle( hFile );
				*s = size;
				return true;
			}
			else
			{
				// 本当にGetFileSize()が失敗した
				CloseHandle( hFile );
				return false;
			}
		}
		CloseHandle( hFile );
		*s = size;
		return true;
	}
	return false;
}

class DirEntWrap{
public:
	DIRENT ent;
	DirEntWrap(){
		memset(&ent, 0, sizeof(ent));
	}
	string filename;
	vector<DirEntWrap> subdir;
};

void PutDir(DWORD *const fat, DWORD *const entptr, std::vector<DirEntWrap> &dirent
	, const FAT32_BOOTSECTOR &bootsect, DWORD FirstDataSector, fstream &of){
	DWORD direntclus = *entptr;

	//ディレクトリエントリを格納するのに必要なクラスタ数を計算
	DWORD dirclus = (dirent.size() * sizeof(DIRENT) + clussize - 1) / clussize;
	for(DWORD i = 0; i < dirclus - 1; i++){
		fat[*entptr + i] = *entptr + i + 1;
	}
	fat[*entptr + dirclus - 1] = EOC;
	*entptr += dirclus;


	for(size_t i = 0; i < dirent.size(); i++){
		DWORD clus;
		if(dirent[i].ent.DIR_Attr & ATTR_DIRECTORY){
			dirent[i].ent.setfirstclus(*entptr);
			PutDir(fat, entptr, dirent[i].subdir, bootsect, FirstDataSector, of);
		}else{
			clus = (dirent[i].ent.DIR_FileSize + clussize - 1) / clussize;
		
			for(DWORD j = 0; j < clus - 1; j++){
				fat[j + *entptr] = j + *entptr + 1;
			}
			fat[clus + *entptr - 1] = EOC;
			*entptr += clus;

			dirent[i].ent.setfirstclus(*entptr - clus);

			string filename = dirent[i].filename;

			fstream entfile(filename.c_str(), ios::binary | ios::in);
			if(entfile.is_open()){
				boost::scoped_array<char> b(new char [dirent[i].ent.DIR_FileSize]);
				entfile.read(b.get(), dirent[i].ent.DIR_FileSize);
				entfile.close();

				of.seekg(bootsect.BPB_BytsPerSec * (FirstDataSector + (*entptr - clus - 2) * bootsect.BPB_SecPerClus));
				of.write(b.get(), dirent[i].ent.DIR_FileSize);
			}else{
				printf("could not open %s\n", filename.c_str());
				throw std::exception("file not found");
			}
		}
	}
	//fatのルートディレクトリへ移動
	of.seekg(bootsect.BPB_BytsPerSec * (FirstDataSector + (direntclus - 2) * bootsect.BPB_SecPerClus), ios::beg);
	//of.write((char *)&dirent[0], dirent.size() * sizeof(DIRENT));
	for(size_t i = 0; i < dirent.size(); i++){
		of.write((char *)&dirent[i].ent, sizeof(DIRENT));
	}
}

string getdir(string &dir){
	size_t p = dir.find('/', 0);
	string ret = dir.substr(0, p);
	dir.erase(0, p + 1);
	return ret;
}

int main(int argc, char *argv[]){
	const char *def = DEFAULT_DEF_FILE;
	if(argc < 2){
		printf("use default file : %s\n", def);
	}else{
		def = argv[1];
	}

	ifstream f(def);

	string bootsector, outfile, bootmgr, mbr;
	DWORD dataclus = 0;
	std::vector<std::pair<string, string> > files;
	DWORD mintotalclus = 0;
	std::vector<DirEntWrap> dirent;
	std::vector<std::vector<DirEntWrap> *> dirent_list;
	dirent_list.push_back(&dirent);

	string buf, line = "";
	int c = 0;
	while(getline(f, buf)){
		c++;
		line = buf;
		int pos = 0;
		string cmd = getelem(line, &pos);
		if(cmd == "b" || cmd == "bootsector"){
			bootsector = getelem(line, &pos);
			printf("bootsector: %s\n", bootsector.c_str());
		}else if(cmd == "r" || cmd == "mbr"){
			mbr = getelem(line, &pos);
			printf("mbr: %s\n", mbr.c_str());
		}else if(cmd == "o" || cmd == "outfile"){
			outfile = getelem(line, &pos);
			printf("outfile: %s\n", outfile.c_str());
		}else if(cmd == "s" || cmd == "size"){
			string size = getelem(line, &pos);
			DWORD size_i = atoi(size.c_str());
			printf("size: %d mb\n", size_i);
			dataclus = size_i * 1024 * 1024 / clussize;
		}else if(cmd == "m" || cmd == "bootmgr"){
			bootmgr = getelem(line, &pos);
			printf("bootmgr: %s\n", bootmgr.c_str());
			DWORD size;
			if(!getfilesize(bootmgr, &size)){
				printf("could not open %s\n", bootmgr.c_str());
				return -1;
			}
			mintotalclus += (size + clussize - 1) / clussize;
		}else if(cmd == "f" || cmd == "file"){
			string file, dir;
			file = getelem(line, &pos);
			dir = getelem(line, &pos);
			
			printf("file: %s\n", file.c_str());
			files.push_back(make_pair(file, dir));
		}else{
			printf("error unkown command %s line %d\n", cmd.c_str(), c);
			return -1;
		}
	}
	f.close();

	if(bootmgr != ""){
		files.insert(files.begin(), make_pair(bootmgr, ""));
	}
	for(size_t i = 0; i < files.size(); i++){
		std::vector<DirEntWrap> *curdirent = &dirent;
		if(files[i].second == ""){
		}else{
			string dir = files[i].second;
			size_t p = 0;
			while(1){
				p = dir.find('\\', p);
				if(p == -1){
					break;
				}
				dir.replace(p, 1, 1, '/');
			}
			char last = dir[dir.size() - 1];
			if(last != '/'){
				dir.push_back('/');
			}

			while(dir != ""){
				string name = getdir(dir);
				DirEntWrap w;
				w.ent.DIR_Attr = ATTR_DIRECTORY;
				w.filename = name;
				toshort(w.ent.DIR_Name, name);
				bool found = false;
				for(size_t j = 0; j < curdirent->size(); j++){
					if(memcmp(curdirent->operator[](j).ent.DIR_Name
						, w.ent.DIR_Name, sizeof(w.ent.DIR_Name)) == 0){
						found = true;
						curdirent = &((*curdirent)[j].subdir);
						break;
					}
				}
				if(found){
				}else{
					curdirent->push_back(w);
					curdirent = &((*curdirent)[curdirent->size() - 1].subdir);
					dirent_list.push_back(curdirent);
				}
			}
		}
		DirEntWrap w;
		w.ent.DIR_Attr = 0;
		if(!toshort(w.ent.DIR_Name, files[i].first)){
			printf("toshort failed %s\n", files[i].first.c_str());
			return -1;
		}

		DWORD size;
		if(!getfilesize(files[i].first, &size)){
			printf("could not open %s\n", files[i].first.c_str());
			return -1;
		}
		mintotalclus += (size + clussize - 1) / clussize;
		w.ent.DIR_FileSize = size;
		w.filename = files[i].first;
		curdirent->push_back(w);
	}

	//ディレクトリエントリに必要なクラスタ数
	for(size_t i = 0; i < dirent_list.size(); i++){
		mintotalclus += (dirent_list[i]->size() * sizeof(DIRENT) + clussize - 1) / clussize;
	}
	dataclus = max(dataclus, mintotalclus);


	printf("mintotalclus = 0x%X\n", mintotalclus);
	printf("dataclus = 0x%X\n", dataclus);

	//fat クラスタテーブルに必要なセクタ数
	DWORD fatsectors = (dataclus * 4 + sectorsize - 1) / sectorsize;

	fstream of;

	if(mbr != ""){
		if(!CopyFile(mbr.c_str(), outfile.c_str(), FALSE)){
			printf("could not copy '%s' to '%s'\n", mbr.c_str(), outfile.c_str());
			return -1;
		}
	}
	of.open(outfile.c_str(), ios::out | ios::binary | ios::in);
	if(!of.is_open()){
		printf("could not open %s\n", outfile.c_str());
		return -1;
	}

	//-------------------パーティションテーブルの書き込み----------------
	PAT_TABLE pat0;
	pat0.flag = 0x80;
	pat0.begin_chs[0] = 1;
	pat0.begin_chs[1] = 1;
	pat0.begin_chs[2] = 0;
	pat0.type = 0x0B;
	pat0.end_chs[0] = 0xfe;
	pat0.end_chs[1] = 0xff;
	pat0.end_chs[2] = 0xff;
	pat0.begin_lba = 0x80;
	pat0.size_lba = dataclus * sectorperclus + fatreserved + fatnum * fatsectors;

	of.seekg(0x200 - 2 - sizeof(PAT_TABLE) * 4, ios::beg);
	of.write((char *)&pat0, sizeof(PAT_TABLE));


	//--------------------ブートセクタの書き込み----------------------------
	ifstream fbootsect(bootsector.c_str(), ios::in | ios::binary);
	if(!fbootsect.is_open()){
		printf("could not open %s\n", bootsector.c_str());
		return -1;
	}
	fbootsect.seekg(0, ios::end);
	DWORD bootsectorsize = fbootsect.tellg();
	fbootsect.seekg(0, ios::beg);

	boost::scoped_array<char> bootsectbuf(new char [bootsectorsize]);
	fbootsect.read(bootsectbuf.get(), bootsectorsize);
	fbootsect.close();

	of.seekg(pat0.begin_lba * sectorsize, ios::beg);
	of.write(bootsectbuf.get(), bootsectorsize);

	bootsectbuf.reset();


	FAT32_BOOTSECTOR bootsect = {0};
	memcpy(bootsect.BS_OEMName, "MSWIN4.1", 8);
	bootsect.BPB_BytsPerSec = sectorsize;
	bootsect.BPB_SecPerClus = sectorperclus;
	bootsect.BPB_RsvdSecCnt = fatreserved;
	bootsect.BPB_NumFATs = fatnum;
	bootsect.BPB_RootEntCnt = 0;
	bootsect.BPB_TotSec16 = 0;
	bootsect.BPB_Media = 0xf8;
	bootsect.BPB_FATSz16 = 0;
	bootsect.BPB_SecPerTrk = 0x003f;
	bootsect.BPB_NumHeads = 0x00ff;
	bootsect.BPB_HiddSec = pat0.begin_lba;
	bootsect.BPB_TotSec32 = pat0.size_lba;
	bootsect.BPB_FATSz32 = fatsectors;
	bootsect.BPB_ExtFlags = 0;
	bootsect.BPB_FSVer = 0;
	bootsect.BPB_RootClus = 0x02;
	bootsect.BPB_FSInfo = 0x01;
	bootsect.BPB_BkBootSec = 0x06;
	//bootsect.BPB_Reserved;
	bootsect.BS_DrvNum = 0x80;
	bootsect.BS_Reserved1 = 0;
	bootsect.BS_BootSig = 0x29;
	bootsect.BS_VolID = 0xdeadbeef;
	memcpy(bootsect.BS_VolLab, "NO NAME    ", 11);
	memcpy(bootsect.BS_FilSysType, "FAT32   ", 8);

	of.seekg(bootsect.BPB_HiddSec * bootsect.BPB_BytsPerSec + BOOTSECTOR_JUMPINST, ios::beg);
	of.write((char *)&bootsect, sizeof(FAT32_BOOTSECTOR));

	//------------------------------FSInfo書き込み------------------------------
	FSINFO fsi = {0};
	fsi.LeadSig = 0x41615252;
	fsi.StrucSig = 0x61417272;
	fsi.Free_Count = 0xffffffff;
	fsi.Nxt_Free = 0xffffffff;
	fsi.TrailSig = 0xAA550000;

	of.seekg((bootsect.BPB_HiddSec + bootsect.BPB_FSInfo) * bootsect.BPB_BytsPerSec, ios::beg);
	of.write((char *)&fsi, sizeof(FSINFO));

	//DWORD DataSectors = 
	DWORD FirstDataSector = bootsect.BPB_HiddSec + bootsect.BPB_RsvdSecCnt + bootsect.BPB_NumFATs * bootsect.BPB_FATSz32;
	printf("FirstDataSector = 0x%X\n", FirstDataSector);
	printf("FirstDataSector * 512 = %p\n", FirstDataSector * bootsect.BPB_BytsPerSec);

	//bootmgrの設定
	//DWORD bootmgrsize = 0;
	//if(bootmgr != ""){
	//	ifstream bootmgrfile(bootmgr.c_str(), ios::out | ios::binary | ios::in);
	//	if(bootmgrfile.is_open()){
	//		bootmgrfile.seekg(0, ios::end);
	//		bootmgrsize = static_cast<DWORD>(bootmgrfile.tellg());
	//		bootmgrfile.seekg(0, ios::beg);

	//		boost::scoped_array<char> b(new char [bootmgrsize]);
	//		bootmgrfile.read(b.get(), bootmgrsize);
	//		bootmgrfile.close();
	//		//FirstSectorofCluster = ((N - 2) * BPB_SecPerClus) + FirstDataSector;
	//		//FirstDataSector = BPB_ResvdSecCnt + (BPB_NumFATs * FATSz) + RootDirSectors;

	//		//fatのルートディレクトリへ移動
	//		//of.seekg(bootsect.BPB_BytsPerSec * FirstDataSector);
	//		//of.write(b.get(), bootmgrsize);
	//	}else{
	//		printf("could not open %s\n", bootmgr.c_str());
	//		return -1;
	//	}
	//}

	//fatエントリの設定
	boost::scoped_array<DWORD> fat(new DWORD [bootsect.BPB_FATSz32 * bootsect.BPB_BytsPerSec / 4]);
	memset(fat.get(), 0, bootsect.BPB_FATSz32 * bootsect.BPB_BytsPerSec);
	fat[0] = bootsect.BPB_Media | 0x0fffff00;
	fat[1] = 0x03ffffff;
	



	//if(bootmgr != ""){
	//	DWORD bootmgrsize;
	//	if(!getfilesize(bootmgr, &bootmgrsize)){
	//		printf("could not get file size: %s\n", bootmgr.c_str());
	//		return -1;
	//	}

	//	DIRENT mgrent = {0};
	//	mgrent.DIR_Attr = ATTR_READ_ONLY | ATTR_SYSTEM;
	//	mgrent.DIR_FileSize = bootmgrsize;
	//	memcpy(mgrent.DIR_Name, "BOOTMGR    ", 11);

	//	dirent.insert(dirent.begin(), mgrent);
	//}

	DWORD entptr = 2;

	PutDir(fat.get(), &entptr, dirent, bootsect, FirstDataSector, of);


	for(int i = 0; i < bootsect.BPB_NumFATs; i++){
		of.seekg(bootsect.BPB_BytsPerSec * 
			(bootsect.BPB_HiddSec + bootsect.BPB_RsvdSecCnt + i * bootsect.BPB_FATSz32));
		of.write((char *)fat.get(), bootsect.BPB_FATSz32 * bootsect.BPB_BytsPerSec);
	}


	of.seekg(0, ios::end);
	DWORD lastsize = of.tellg();
	DWORD lastsector = (lastsize + sectorsize - 1) / sectorsize;
	//最後にあるファイルが奇数セクタ使っている場合1ずれるのでチェックはしない
	//assert(lastsector == (mintotalclus * sectorperclus + bootsect.BPB_HiddSec + bootsect.BPB_RsvdSecCnt + bootsect.BPB_NumFATs * bootsect.BPB_FATSz32));
	DWORD allsector = dataclus * sectorperclus + bootsect.BPB_HiddSec + bootsect.BPB_RsvdSecCnt + bootsect.BPB_NumFATs * bootsect.BPB_FATSz32;


	//ファイルサイズをセクタサイズ境界にあわせる
	of.seekg(0, ios::end);
	if(static_cast<DWORD>(of.tellg()) == allsector * sectorsize){
		//既に揃っている
	}else{
		of.seekg(allsector * sectorsize - 1, ios::beg);
		char dummy = 0;
		of.write(&dummy, 1);
	}

	of.close();
}


