//
// sha1sum.cpp (modified by Hoshimin)
//
//   Copyright (c) 2010 by Hoshimin / All rights reserved.
//
//     Redistribution and use in source and binary forms, with or without modification,
//     are permitted provided that the following conditions are met:
//
//     1. Redistributions of source code must retain the above copyright notice,
//        this list of conditions and the following disclaimer. 
//
//     2. Redistributions in binary form must reproduce the above copyright notice,
//        this list of conditions and the following disclaimer in the documentation
//        and/or other materials provided with the distribution. 
//
//     3. The name of author may not be used to endorse or promote products derived
//        from this software without specific prior written permission. 
//
//     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//     IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
//     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
//     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
//     OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
//     OF THE POSSIBILITY OF SUCH DAMAGE.
//

//
// Original source code is protected by the following copyright.
//

//
// sha1sum.cpp
//
//     -- Fast sha1sum/md5sum program.
//        (This program runs on Windows NT(5.x or upper) environment)
//
//     Version 0.0.2
//
//   Copyright (c) 2004 by T.Tsujikawa / All rights reserved.
//
//     Redistribution and use in source and binary forms, with or without modification,
//     are permitted provided that the following conditions are met:
//
//     1. Redistributions of source code must retain the above copyright notice,
//        this list of conditions and the following disclaimer. 
//
//     2. Redistributions in binary form must reproduce the above copyright notice,
//        this list of conditions and the following disclaimer in the documentation
//        and/or other materials provided with the distribution. 
//
//     3. The name of author may not be used to endorse or promote products derived
//        from this software without specific prior written permission. 
//
//     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//     IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
//     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
//     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
//     OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
//     OF THE POSSIBILITY OF SUCH DAMAGE.
//

// source: TAB=4


#pragma	warning(disable:4312)

#include	<windows.h>
#include	<tchar.h>

#include	<stdio.h>
#include	<stdlib.h>
#include	<locale.h>
#include	<string.h>
#include	<process.h>
#include	<io.h>
#include	<fcntl.h>

#include	<deque>
#include	<list>
using namespace std;


//
// Basic Types
//

typedef		UINT			uint;


//
// Mutex/Signal Macros
//

/* Mutex lock/unlock */

#define		CREATE_MUTEX()			CreateMutex(NULL, FALSE, NULL)
#define		DESTROY_MUTEX(m)		CloseHandle(m)

#define		MUTEX_LOCK(m)			do { WaitForSingleObject(m, INFINITE); } while (false)
#define		MUTEX_UNLOCK(m)			do { ReleaseMutex(m); } while (false)

/* Event object */

#define		CREATE_COND()			CreateEvent(NULL, FALSE, FALSE, NULL)
#define		DESTROY_COND(e)			CloseHandle(e)

#define		COND_WAIT(e)			do { WaitForSingleObject(e, INFINITE); } while (false)
#define		COND_WAIT_TIME(e, t)	do { WaitForSingleObject(e, t); } while (false)
#define		COND_WAKE(e)			do { SetEvent(e); } while (false)


//
// Constants
//

/* Buffer size */

#define		BUFFER_SIZE				(4096*(1024*8))
#define		BUFFER_UNIT				(4096*512)

/* hash algorithm to use */

#ifdef	HASH_SHA1
/* SHA1 */
#define		HASH_LEN				(20)
#define		HASH_PROGNAME			L"sha1sum"
#define		HASH_ALGORITHM			L"SHA1"
#else
/* MD5 */
#define		HASH_LEN				(16)
#define		HASH_PROGNAME			L"md5sum"
#define		HASH_ALGORITHM			L"MD5"
#endif


// For Character Encoding.
int gTextMode[] = {_O_U16TEXT, _O_U16TEXT, _O_U16TEXT};


//
// hash calc/check job
//

class Job {

	wstring		filename;								// filename
	bool		isCheckJob;								// true: -c mode,  false: -b/-l mode
	BYTE		hash[HASH_LEN];							// hash value to compare with (-c mode)

	HCRYPTHASH	hHash;									// Handle by CryptCreateHash()  (NULL: not acquired)

public:

	Job (const wchar_t* _filename, bool _isCheckJob) {
		filename.assign(_filename); isCheckJob = _isCheckJob; hHash = NULL;
	}

	Job (const wchar_t* _filename, bool _isCheckJob, BYTE* _hash) {
		filename.assign(_filename); isCheckJob = _isCheckJob; hHash = NULL;
		memcpy(hash, _hash, HASH_LEN);
	}

	bool get_isCheckJob () { return isCheckJob; }

	const wchar_t* get_filename () { return filename.c_str(); }

	HCRYPTHASH get_hHash () { return hHash; }

	void set_hHash (HCRYPTHASH _hHash) { hHash = _hHash; }

	BYTE* get_hash () { return &hash[0]; }

};


//
// Buffer management
//

class BufferBlock {

public:

	size_t			buffer_index;							// index of BufferSpool#v
	char*			addr;									// buffer top address
	size_t			len;									// buffer length

	BufferBlock () { buffer_index = 0; addr = NULL; len = 0; }

	BufferBlock (size_t _buffer_index, char* _addr, size_t _len) {
		buffer_index = _buffer_index;
		addr = _addr; len = _len;
	}

	~BufferBlock () { }

};

class BufferSpool {

	HANDLE			mutex;									// mutex to alloc/free mutex
	HANDLE			event;									// event to wait for buffers

	size_t			blocks;									// number of (all) buffer blocks
	size_t			remainBlocks;							// number of free buffer blocks
	bool*			v;										// buffer allocation bitmap

	char*			vmem;									// VirtualAlloc()ed memory area address
	size_t			vmem_len;								// VirtualAlloc()ed memory area size

public:

	BufferSpool (size_t buf_size) {
		v = NULL; vmem = NULL;
		while (true) {
			vmem_len = buf_size;
			blocks = remainBlocks = vmem_len/BUFFER_UNIT;

			if (blocks < 2) { fwprintf(stderr, L"Not enough memory.\n"); exit(-1); }

			vmem = (char*)VirtualAlloc(NULL, vmem_len, MEM_COMMIT, PAGE_READWRITE);
			if (vmem) break;

			buf_size /= 2;
		}
		mutex = CREATE_MUTEX(); event = CREATE_COND();
		v = new bool[blocks];
		for (uint i = 0; i < blocks; i++) v[i] = false;
	}

	~BufferSpool () {
		if (v) delete [] v;
		if (vmem) VirtualFree(vmem, vmem_len, MEM_DECOMMIT | MEM_RELEASE);

		DESTROY_MUTEX(mutex); DESTROY_COND(event);
	}


	//
	// allocate buffer unit
	//

	BufferBlock* alloc_wait () {
		BufferBlock*	r = NULL;

		while (true) {
			MUTEX_LOCK(mutex); {
				if (remainBlocks) {
					for (uint i = 0; i < blocks; i++) {
						if (!v[i]) {
							v[i] = true; remainBlocks--;
							r = new BufferBlock(i, vmem+BUFFER_UNIT*i, BUFFER_UNIT);
							break;
						}
					}
					if (!r) {	// internal state error
						fwprintf(stderr, HASH_PROGNAME L": internal buffer allocation error.\n");
						exit(-1);
					}
				}
			} MUTEX_UNLOCK(mutex);

			if (r) break;

			COND_WAIT_TIME(event, 20);
		}
		return r;
	}


	//
	// free buffer unit
	//

	void free (BufferBlock* buf) {
		MUTEX_LOCK(mutex); {
			if (v[buf->buffer_index]) {
				v[buf->buffer_index] = false; remainBlocks++;
			} else {
				fwprintf(stderr, HASH_PROGNAME L": internal buffer free error.\n");
				exit(-1);
			}
		} MUTEX_UNLOCK(mutex);

		delete buf;

		COND_WAKE(event);
	}

};

BufferSpool*	bspool;


//
// job management
//

class HashCommand {

public:

	bool					quitReq;						// request for thread to quit

	bool					isFinal;						// reached file end

	BufferBlock*			buf;							// buffer block
	size_t					valid_len;						// valid data length

	Job*					job;							// processing job

	HashCommand () {
		quitReq = isFinal = false;
	}

};

class ReportCommand {

public:

	bool					quitReq;						// request for thread to quit
	bool					openFailed;						// failed to open file
	bool					readFailed;						// failed to read file
	bool					hashFailed;						// hash check failed

	BYTE					hash[HASH_LEN];					// calculated hash value

	Job*					job;							// processed job

	ReportCommand () {
		quitReq = openFailed = readFailed = hashFailed = false;
	}

	~ReportCommand () { }

};


//
// job/command queue management
//

class ProcStat {

	HANDLE					readCommand_mutex;
	deque<Job*>				readCommand_queue;

	HANDLE					hashCommand_mutex;
	HANDLE					hashCommand_event;
	list<HashCommand>		hashCommand_queue;

	HANDLE					reportCommand_mutex;
	HANDLE					reportCommand_event;
	list<ReportCommand>		reportCommand_queue;

public:

	ProcStat () {
		hashCommand_mutex = CREATE_MUTEX(); reportCommand_mutex = CREATE_MUTEX(); readCommand_mutex = CREATE_MUTEX();
		hashCommand_event = CREATE_COND();  reportCommand_event = CREATE_COND();
	}

	~ProcStat () {
		DESTROY_MUTEX(hashCommand_mutex); DESTROY_MUTEX(reportCommand_mutex); DESTROY_MUTEX(readCommand_mutex);
		DESTROY_COND(hashCommand_event);  DESTROY_COND(reportCommand_event);
	}

	//
	// read command
	//

	Job* retr_readCommand () {
		Job*	j = NULL;
		MUTEX_LOCK(readCommand_mutex); {
			if (readCommand_queue.size() == 0) {
				j = NULL;
			} else {
				j = readCommand_queue[0]; readCommand_queue.pop_front();
			}
		} MUTEX_UNLOCK(readCommand_mutex);
		return j;
	}

	void append_readCommand (Job* j) {
		MUTEX_LOCK(readCommand_mutex); {
			readCommand_queue.push_back(j);
		} MUTEX_UNLOCK(readCommand_mutex);
	}

	//
	// hash command
	//

	HashCommand retr_hashCommand () {
		HashCommand		r;
		bool			get_ok = false;
		while (true) {
			MUTEX_LOCK(hashCommand_mutex); {
				if (hashCommand_queue.size()) {
					r = hashCommand_queue.front(); hashCommand_queue.pop_front(); get_ok = true;
				}
			} MUTEX_UNLOCK(hashCommand_mutex);
			if (get_ok) break;
			COND_WAIT_TIME(hashCommand_event, 20);
		}
		return r;
	}

	void append_hashCommand (HashCommand& hc) {
		MUTEX_LOCK(hashCommand_mutex); {
			hashCommand_queue.push_back(hc);
		} MUTEX_UNLOCK(hashCommand_mutex);
		COND_WAKE(hashCommand_event);
	}

	//
	// report command
	//

	ReportCommand retr_reportCommand () {
		ReportCommand	r;
		bool			get_ok = false;
		while (true) {
			MUTEX_LOCK(reportCommand_mutex); {
				if (reportCommand_queue.size()) {
					r = reportCommand_queue.front(); reportCommand_queue.pop_front(); get_ok = true;
				}
			} MUTEX_UNLOCK(reportCommand_mutex);
			if (get_ok) break;
			COND_WAIT_TIME(reportCommand_event, 20);
		}
		return r;
	}

	void append_reportCommand (ReportCommand& rc) {
		MUTEX_LOCK(reportCommand_mutex); {
			reportCommand_queue.push_back(rc);
		} MUTEX_UNLOCK(reportCommand_mutex);
		COND_WAKE(reportCommand_event);
	}

};

ProcStat*	ps;


//
// file reading thread
//
//   invoke this thread after enqueueing jobs
//

UINT __stdcall thread_read (void* arg_read)
{
	while (true) {
		Job*	j;

		if ((j = ps->retr_readCommand()) == NULL) {		// all jobs completed
			HashCommand		hc;
			hc.quitReq = true;
			ps->append_hashCommand(hc);
			break;
		}

		const wchar_t*	fname = j->get_filename();

		HANDLE	fh;

		fh = CreateFile(fname, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, NULL);
		if (fh == INVALID_HANDLE_VALUE) {
			ReportCommand	rc;
			rc.job = j; rc.openFailed = true;
			ps->append_reportCommand(rc);
			continue;
		}

		while (true) {
			BufferBlock*	buf = bspool->alloc_wait();
			DWORD	readBytes = 0;

			BOOL	r = ReadFile(fh, (BYTE*)buf->addr, (DWORD)buf->len, &readBytes, NULL);
			if (!r) {
				ReportCommand	rc;
				rc.job = j; rc.readFailed = true;
				ps->append_reportCommand(rc); break;
			} else {
				HashCommand		hc;
				hc.job = j; hc.isFinal = readBytes < BUFFER_UNIT;
				hc.buf = buf; hc.valid_len = readBytes;
				ps->append_hashCommand(hc);
				if (hc.isFinal) break;
			}
		}

		CloseHandle(fh);
	}

	return 0;
}


//
// hash calculating thread
//

UINT __stdcall thread_hash (void* arg_hash)
{
	HCRYPTPROV	hProv = 0;

	if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
		fwprintf(stderr, HASH_PROGNAME L": CryptAcquireContext() failed\n");
		exit(-1);
	}

	while (true) {
		HashCommand		hc = ps->retr_hashCommand();

		if (hc.quitReq) {
			ReportCommand	rc;
			rc.quitReq = true;
			ps->append_reportCommand(rc);
			break;
		}

		Job*	j = hc.job;

		HCRYPTHASH		hHash = j->get_hHash();

		if (!hHash) {
#ifdef	HASH_SHA1
			if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
#else
			if (!CryptCreateHash(hProv, CALG_MD5,  0, 0, &hHash))
#endif
			{
				fwprintf(stderr, HASH_PROGNAME L": CryptCreateHash() failed\n");
				exit(-1);
			}
			j->set_hHash(hHash);
		}

		BufferBlock*	buf = hc.buf;

		if (hc.valid_len > 0) {
			if (!CryptHashData(hHash, (BYTE*)buf->addr, (DWORD)hc.valid_len, 0)) {
				fwprintf(stderr, HASH_PROGNAME L": CryptHashData() failed\n");
				exit(-1);
			}
		}

		bspool->free(buf);

		if (hc.isFinal) {
			ReportCommand	rc;
			rc.job = hc.job;
			DWORD		cbHash = HASH_LEN;
			if (CryptGetHashParam(hHash, HP_HASHVAL, rc.hash, &cbHash, 0)) {
				if (j->get_isCheckJob()) rc.hashFailed = !(memcmp(rc.hash, j->get_hash(), cbHash) == 0);
			} else {
				fwprintf(stderr, HASH_PROGNAME L": CryptGetHashParam() failed\n");
			}
			CryptDestroyHash(hHash);
			ps->append_reportCommand(rc);
		}
	}

	CryptReleaseContext(hProv, 0);

	return 0;
}


//
// console output thread
//

int thread_report (void* arg_report)
{
	static wchar_t	hex_table[] = L"0123456789abcdef";
	int				files = 0, files_processed = 0, files_openfail = 0, files_readfail = 0, files_hashfail = 0;
	bool			checkMode = false;

	while (true) {
		ReportCommand	rc = ps->retr_reportCommand();

		if (rc.quitReq) break;			// requested quitting

		Job*	j = rc.job;
		files++;

		if        (rc.openFailed) {
			if (j->get_isCheckJob()) {
				wprintf(L"%s: FAILED open\n", j->get_filename());
			} else {
				fwprintf(stderr, HASH_PROGNAME L": %s: No such file or directory\n", j->get_filename());
			}
			files_openfail++;
		} else if (rc.readFailed) {
			if (j->get_isCheckJob()) {
				wprintf(L"%s: FAILED read\n", j->get_filename());
			} else {
				fwprintf(stderr, HASH_PROGNAME L": %s: File read error\n", j->get_filename());
			}
			files_readfail++;
		} else if (j->get_isCheckJob()) {
			wprintf(L"%s: %s\n", j->get_filename(), (rc.hashFailed ? L"FAILED" : L"OK"));
			if (rc.hashFailed) files_hashfail++;
			files_processed++;
			checkMode = true;
		} else {
			wchar_t	buf[HASH_LEN*2+1];

			for (int i = 0; i < HASH_LEN; i++) {
				buf[i*2]   = hex_table[(rc.hash[i] >> 4) & 0x0F];
				buf[i*2+1] = hex_table[rc.hash[i] & 0x0F];
			}
			buf[HASH_LEN*2] = 0;
			wprintf(L"%s *%s\n", buf, j->get_filename());
			files_processed++;
		}

		delete j;
	}

	if (checkMode && (files_openfail || files_readfail || files_hashfail)) {
		if (files_openfail) {
			fwprintf(stderr, HASH_PROGNAME L": WARNING: %d of %d listed files could not be opened\n",
					files_openfail, files);
		}
		if (files_readfail) {
			fwprintf(stderr, HASH_PROGNAME L": WARNING: %d of %d listed files could not be read\n",
					files_openfail + files_readfail, files);
		}
		if (files_hashfail) {
			fwprintf(stderr, HASH_PROGNAME L": WARNING: %d of %d computed checksum did NOT match\n",
					files_hashfail, files_processed);
		}

		return 1;
	}

	return 0;
}


//
// Print copyright and usage message
//

void hmsg ()
{
	wprintf(L"[ "HASH_PROGNAME L" ] Version 0.0.2 - print or check "HASH_ALGORITHM L" checksums.\n"
		   L"Copyright (c) 2004 by T.Tsujikawa / All rights reserved.\n"
		   L"\n"
		   L"To calculate and print "HASH_ALGORITHM L" value of files:\n"
		   L"\n"
		   L"  %% "HASH_PROGNAME L" [--encoding ENCODING] [-wbom|-wbom-] [-b] file [file2 ...]\n"
		   L"\n"
		   L"   Reading from stdin is not supported.\n"
		   L"   Each input file is processed as a binary file. (GNU md5sum's -t option is\n"
		   L"   not supported, and -b option is always specified.)\n"
		   L"   \"--encoding\" option must come first.\n"
		   L"   ENCODING is UTF-16(default), UTF-8, and ANSI. ANSI indicates system default code page.\n"
		   L"   If \"-wbom\" is specified and specified encoding is UTF-16 or UTF-8, write byte order mark.\n"
		   L"   If \"-wbom-\" is specified and specified encoding is UTF-16 or UTF-8, don't write byte order mark.\n"
		   L"\n"
		   L"Same as previous case, but to feed filename list with a text file:\n"
		   L"\n"
		   L"  %% "HASH_PROGNAME L" [--encoding ENCODING] [-wbom|-wbom-] [-b] -l [list-file list-file2 ...]\n"
		   L"\n"
		   L"   The list-files must be one-filename-per-one-line style.\n"
		   L"   If no list-file specified or '-' specified, stdin is used for input.\n"
		   L"\n"
		   L"To check hash file:\n"
		   L"\n"
		   L"  %% "HASH_PROGNAME L" [--encoding ENCODING] [-wbom|-wbom-] [-b] -c [checksum-file checksum-file2 ...]\n"
		   L"\n"
		   L"   The checksum-files must be GNU style.\n"
		   L"   If no checksum-file specified or '-' specified, stdin is used for input.\n"
		   L"\n"
		   L"To print this message:\n"
		   L"\n"
		   L"  %% "HASH_PROGNAME L" --help\n"
		   L"\n"
		   L"Exit with status 0      => no error occurred.\n"
		   L"                 1      => some open/read/hash-check failed.\n"
		   L"                 others => some error occurred.\n");

	exit(0);
}


//
// enqueue hash calculation job (single filename)
//

void enq_clc_hash_filename (const wchar_t* filename)
{
	Job*	j = new Job(filename, false);
	ps->append_readCommand(j);
}


//
// enqueue hash calculation job (list filename)
//

void enq_clc_hash_listfile (const wchar_t* filename)
{
	FILE*	fin = NULL;
	if (wcscmp(filename, L"-") == 0) {
		fin = stdin;
	} else {
		fin = _wfopen(filename, L"r");
		if (fin != NULL) {
			if (_setmode(_fileno(fin), gTextMode[0]) == -1) {
				fclose(fin);
				fin = NULL;
			}
		}
	}
	if (!fin) {
		fwprintf(stderr, HASH_PROGNAME L": %s: No such file or directory\n", filename);
		return;
	}
	/* check BOM. */
	wint_t wc = fgetwc(fin);
	if ((wc != WEOF) && (wc != (wint_t)0xFEFF)) {
		ungetwc(wc, fin);
	}

	wchar_t	buf[MAX_PATH+3];

	while (fgetws(buf, sizeof(buf)/sizeof(buf[0]), fin)) {
		size_t	l = wcslen(buf);
		if (buf[l-1] == L'\n') { buf[l-1] = 0; l--; }
		if (buf[l-1] == L'\r') { buf[l-1] = 0; l--; }
		if (buf[0] == 0) continue;

		Job*	j = new Job(buf, false);
		ps->append_readCommand(j);
	}

	if (fin != stdin) fclose(fin);
}


//
// enqueue hash checking job (list filename)
//

void inv_line (const wchar_t* filename, const wchar_t* line)
{
	fwprintf(stderr, HASH_PROGNAME L": unknown format line in %s: %s",
			(wcscmp(filename, L"-") == 0 ? L"stdin" : filename), line);
}

int hex2bin (wchar_t c)
{
	if (c >= L'0' && c <= L'9') return (c-L'0');
	c |= 0x0020;
	if (c >= L'a' && c <= L'f') return (c-L'a'+10);
	return -1;
}

void enq_chk_hash_listfile (const wchar_t* filename)
{
	FILE*	fin = NULL;
	if (wcscmp(filename, L"-") == 0) {
		fin = stdin;
	} else {
		fin = _wfopen(filename, L"r");
		if (fin != NULL) {
			if (_setmode(_fileno(fin), gTextMode[0]) == -1) {
				fclose(fin);
				fin = NULL;
			}
		}
	}
	if (!fin) {
		fwprintf(stderr, HASH_PROGNAME L": %s: No such file or directory\n", filename);
		return;
	}
	/* check BOM. */
	wint_t wc = fgetwc(fin);
	if ((wc != WEOF) && (wc != (wint_t)0xFEFF)) {
		ungetwc(wc, fin);
	}

	wchar_t	buf[MAX_PATH+HASH_LEN*2+5];
	BYTE	hash[HASH_LEN];

	while (fgetws(buf, sizeof(buf)/sizeof(buf[0]), fin)) {
		size_t	l = wcslen(buf);
		if (l < HASH_LEN*2+2) { inv_line(filename, buf); continue; }
		memset(hash, 0, HASH_LEN);
		for (int i = 0; i < HASH_LEN; i++) {
			int		n = hex2bin(buf[i*2]);
			if (n >= 0) {
				hash[i] = n << 4;
			} else {
				inv_line(filename, buf); continue;
			}
			n = hex2bin(buf[i*2+1]);
			if (n >= 0) {
				hash[i] |= n;
			} else {
				inv_line(filename, buf); continue;
			}
		}

		if (buf[l-1] == L'\n') { buf[l-1] = 0; l--; }
		if (buf[l-1] == L'\r') { buf[l-1] = 0; l--; }

		wchar_t*	fn = NULL;

		for (uint i = HASH_LEN*2; i < l; i++) {
			if (buf[i] != L' ' && buf[i] != L'\t') { fn = &buf[i]; break; }
		}

		if (!fn) { inv_line(filename, buf); continue; }

		if (*fn == L'*') fn++;

		Job*	j = new Job(fn, true, hash);
		ps->append_readCommand(j);
	}

	if (fin != stdin) fclose(fin);
}


//
// main ()
//

void writebom(bool writebomflag1, bool writebomflag2)
{
	if (writebomflag1) {
		/* output BOM, if redirected or piped */
		if (!_isatty(_fileno(stdout))) {
			fwprintf(stdout, L"\xFEFF");
		}
	}
	if (writebomflag2) {
		if (!_isatty(_fileno(stderr))) {
			fwprintf(stderr, L"\xFEFF");
		}
	}
}

int wmain (int argc, wchar_t* argv[])
{
	setlocale(LC_ALL, "");

	if ((_setmode(_fileno(stdin), gTextMode[0]) == -1) ||
		(_setmode(_fileno(stdout), gTextMode[1]) == -1) ||
		(_setmode(_fileno(stderr), gTextMode[2]) == -1)) {
			// nothing to do...
	}

	if (argc < 2) {
		writebom(true, true);
		hmsg();
	}

	ps = new ProcStat();
	bspool = new BufferSpool(BUFFER_SIZE);

	/* enqueue hash jobs */
	bool checkInputFlag = false;
	bool writebomflag[] = { true, true };
	for (int i = 1; i < argc; i++) {
		if (_wcsicmp(argv[i], L"--encoding") == 0) {
			if ((i != 1) || ((i + 1) >= argc)) {
				writebom(writebomflag[0], writebomflag[1]);
				hmsg();
			}
			i++;

			wstring encstrBase = argv[i]; // i == 1.
			for (wstring::size_type pos = 0, size = encstrBase.size(), cnt = 0; (cnt < (sizeof(gTextMode)/sizeof(gTextMode[0]))) && (pos < size); cnt++) {
				wstring::size_type findPos = encstrBase.find(L',', pos);
				wstring tmp;
				if (findPos != wstring::npos) {
					tmp = encstrBase.substr(pos, findPos - pos);
				} else {
					tmp = encstrBase.substr(pos);
				}
				pos += (tmp.size() + 1);

				const wchar_t *encstr = tmp.c_str();
				if ((_wcsicmp(encstr, L"UTF16") == 0) || (_wcsicmp(encstr, L"UTF-16") == 0)) {
					gTextMode[cnt] = _O_U16TEXT;
				} else if ((_wcsicmp(encstr, L"UTF8") == 0) || (_wcsicmp(encstr, L"UTF-8") == 0)) {
					gTextMode[cnt] = _O_U8TEXT;
				} else if (_wcsicmp(encstr, L"ANSI") == 0) {
					gTextMode[cnt] = _O_TEXT;
				} else {
					// Text mode is not modified. Text mode is default mode.
					writebom(true, true);
					hmsg();
				}
				// overwrite trail element.
				for(size_t idx = cnt + 1; idx < (sizeof(gTextMode)/sizeof(gTextMode[0])); idx++) {
					gTextMode[idx] = gTextMode[cnt];
				}
			}
			for(size_t idx = 1; idx < (sizeof(gTextMode)/sizeof(gTextMode[0])); idx++) {
				if (gTextMode[idx] == _O_U16TEXT) {
					writebomflag[idx - 1] = true;
				} else {
					writebomflag[idx - 1] = false;
				}
			}

			if ((_setmode(_fileno(stdin), gTextMode[0]) == -1) ||
				(_setmode(_fileno(stdout), gTextMode[1]) == -1) ||
				(_setmode(_fileno(stderr), gTextMode[2]) == -1)) {
					// nothing to do...
			}
		} else if (_wcsicmp(argv[i], L"-wbom") == 0) {
			for(size_t idx = 1; idx < (sizeof(gTextMode)/sizeof(gTextMode[0])); idx++) {
				if (gTextMode[idx] != _O_TEXT) {
					writebomflag[idx - 1] = true;
				}
			}
		} else if (_wcsicmp(argv[i], L"-wbom-") == 0) {
			for(size_t idx = 1; idx < (sizeof(gTextMode)/sizeof(gTextMode[0])); idx++) {
				if (gTextMode[idx] != _O_TEXT) {
					writebomflag[idx - 1] = false;
				}
			}
		} else if (_wcsicmp(argv[i], L"-c") == 0) {
			writebom(writebomflag[0], writebomflag[1]);
			if (argc == (i + 1)) {
				enq_chk_hash_listfile(L"-");
			} else {
				i++;
				for (; i < argc; i++) enq_chk_hash_listfile(argv[i]);
			}
			checkInputFlag = true;
		} else if (_wcsicmp(argv[i], L"-l") == 0) {
			writebom(writebomflag[0], writebomflag[1]);
			if (argc == (i + 1)) {
				enq_clc_hash_listfile(L"-");
			} else {
				i++;
				for (; i < argc; i++) enq_clc_hash_listfile(argv[i]);
			}
			checkInputFlag = true;
		} else if (_wcsicmp(argv[i], L"--help") == 0) {
			writebom(writebomflag[0], writebomflag[1]);
			hmsg();
		} else if (_wcsicmp(argv[i], L"-b") == 0) {
			/* no-op */
		} else {
			writebom(writebomflag[0], writebomflag[1]);
			for (; i < argc; i++) enq_clc_hash_filename(argv[i]);
			checkInputFlag = true;
		}
	}
	if (!checkInputFlag) {
		writebom(writebomflag[0], writebomflag[1]);
		hmsg();
	}

	/* launch worker threads */

	uint	thrdaddr_hash, thrdaddr_read;
	if (_beginthreadex(NULL, 0, thread_hash, NULL, 0, &thrdaddr_hash) == 0) return -1;
	if (_beginthreadex(NULL, 0, thread_read, NULL, 0, &thrdaddr_read) == 0) return -1;

	int	r = thread_report(NULL);

	delete ps;
	delete bspool;

	return r;
}
