Pastebin

New pastes are no longer accepted · Stats

Latest Pastes

epatch


#include <ctime>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <deque>
#include <fstream>
#include <map>
#include <stdexcept>
#include <sstream>
#include <string>
#include <vector>

#include <windows.h>
#include <process.h>
#include <commctrl.h>

#include <zlib.h>

#include "nanohttp.hpp"
#include "util.hpp"

#undef ERROR

void fail(const char *error);

struct Update
{
	enum Action
	{
		Info,
		Add,
		Del,
		Mod,
		Comment,
		Version
	};

	enum Type
	{
		GFX,
		SFX,
		SFXGuitar,
		SFXHarp,
		MFX,
		JBox,
		Help,
		Data,
		File
	};

	Action action;
	int download_size;
	std::string name;
	Type type;
	std::string file;
	std::vector<int> files;

	std::string text;

	union
	{
		// For UPDATE info
		struct
		{
			int from;
			int to;
		} u;

		// For VERSION info
		struct
		{
			int version;
			int entries;
		} v;
	};

	static Action CharToAction(char c)
	{
		switch (c)
		{
			case '@': return Info;
			case '+': return Add;
			case '-': return Del;
			case '%': return Mod;
			case '#': return Comment;
			default: throw std::runtime_error("Invalid action character");
		}
	}

	static Type StringToType(std::string str)
	{
		     if (str == "gfx") return GFX;
		else if (str == "sfx") return SFX;
		else if (str == "sgui") return SFXGuitar;
		else if (str == "shar") return SFXHarp;
		else if (str == "mfx") return MFX;
		else if (str == "jbox") return JBox;
		else if (str == "help") return Help;
		else if (str == "data") return Data;
		else if (str == "file") return File;
		else throw std::runtime_error("Invalid file type");
	}

	static std::vector<int> ParseRange(std::string str)
	{
		std::vector<int> numbers;

		std::stringstream ss(str);

		int n;
		int n2;
		char c;

		ss >> n;

		while (ss)
		{
			ss >> c;

			if (!ss)
			{
				numbers.push_back(n);
				break;
			}

			if (c == ',')
			{
				numbers.push_back(n);
				ss >> n;
				continue;
			}

			ss >> n2;

			switch (c)
			{
				case ':':
					if (n >= n2)
						throw std::runtime_error("Malformed range string (invalid range)");

					while (n < n2)
						numbers.push_back(n++);

					break;

				default:
					throw std::runtime_error("Malformed range string (unknown separator)");
			}

			n = n2;
		}

		return numbers;
	}

	Update(const char *str)
	{
		Load(str);
	}

	Update(std::string str)
	{
		Load(str.c_str());
	}

	void Load(const char *str)
	{
		download_size = 0;

		std::istringstream ss(str);

		char action_char;
		ss >> action_char;
		action = CharToAction(action_char);

		if (action == Info)
		{
			ss >> text;

			if (text == "UPDATE")
			{
				ss >> u.from;
				ss >> u.to;
			}
			else if (text == "VERSION")
			{
				ss >> v.version;
				ss >> v.entries;
				ss >> download_size;
				name = "Version " + util::to_string(v.version);
				action = Version;
			}

			return;
		}
		else if (action == Comment)
		{
			std::getline(ss, text);
			return;
		}

		ss >> name;

		std::string type_string;
		ss >> type_string;
		type = StringToType(type_string);

		ss >> file;

		ss >> download_size;

		if (type == GFX)
		{
			std::string range_string;
			ss >> range_string;
			files = ParseRange(range_string);
		}
	}
};

struct UpdateInfo
{
	enum State
	{
		ERROR,
		OK,
		UPDATE
	};

	State state;
	std::deque<Update> updates;
	int download_total;
	int total_files;

	union
	{
		// For UPDATE info
		struct
		{
			int from;
			int to;
		} u;

		// For VERSION info
		struct
		{
			int version;
			int entries;
			int size;
		} v;
	};

	UpdateInfo() { }

	UpdateInfo(std::string str)
	{
		Load(str.c_str());
	}

	UpdateInfo(const char *str)
	{
		Load(str);
	}

	void Load(const char *str)
	{
		this->download_total = 0;
		this->total_files = 0;

		updates.clear();

		std::istringstream ss(str);

		std::string line;

		while (std::getline(ss, line))
		{
			Update new_update = Update(line);

			if (new_update.action != Update::Comment)
				updates.push_back(new_update);
		}

		if (!updates.empty())
		{
			if (updates[0].action == Update::Info)
			{
				Update front = updates.front();
				updates.pop_front();

				if (front.text == "ERROR")
				{
					state = ERROR;
				}
				else if (front.text == "OK")
				{
					state = OK;
				}
				else if (front.text == "UPDATE")
				{
					state = UPDATE;
					u.from = front.u.from;
					u.to = front.u.to;
				}

				bool had_version = false;

				for (auto it = updates.begin(); it != updates.end(); ++it)
				{
					if (it->action == Update::Version)
					{
						++total_files;
						download_total += it->download_size;
						had_version = true;
					}
					else if (it->download_size > 0)
					{
						if (!had_version)
						{
							++total_files;
							download_total += it->download_size;
						}
					}
				}
			}
			else
			{
				state = ERROR;
			}
		}
		else
		{
			state = ERROR;
		}
	}
};

struct DataFile
{
	Update::Type type;
	std::vector<std::string> entries;

	DataFile(const std::string &data)
	{
		typedef unsigned char uchar;

		if (data.substr(0, 4) != "EDGE")
		{
			throw std::runtime_error("Corrupt data file");
		}

		std::string buffer;
		std::size_t pos = 0;

		z_stream z = {};
		z.zalloc = 0;
		z.zfree = 0;
		z.opaque = 0;

		std::string type_string = util::rtrim(data.substr(4, 4));
		type = Update::StringToType(type_string);

		// 8 - 11 reserved

		std::size_t payload_size = (uchar(data[12]) << 24) | (uchar(data[13]) << 16) | (uchar(data[14]) << 8) | uchar(data[15]);

		std::size_t file = (uchar(data[16]) << 8) | uchar(data[17]);
		std::string file_string;

		std::size_t entry_count = (uchar(data[18]) << 8) | uchar(data[19]);

		if (entry_count == 0) // Single-file archive
			entries.resize(1);
		else
			entries.resize(entry_count);

		std::size_t off = 20;

		std::vector<std::size_t> entry_sizes(entries.size());

		if (type == Update::Help)
		{
			file_string = data.substr(off, file);
			off += file;
		}

		std::size_t buffer_max = 0;

		for (std::size_t i = 0; i < entry_count; ++i)
		{
			entry_sizes[i] = (uchar(data[off]) << 24) | (uchar(data[off+1]) << 16) | (uchar(data[off+2]) << 8) | uchar(data[off+3]);
			off += 4;

			if (entry_sizes[i] > buffer_max)
				buffer_max = entry_sizes[i];
		}

		if (entry_count == 0)
		{
			buffer_max = entry_sizes[0] = payload_size;
		}

		std::size_t entry_end = 0;
		z.next_in = (Bytef*)&data[off];
		z.avail_in = data.size() - off;

		inflateInit(&z);
		while (pos < entries.size())
		{
			buffer.resize(buffer_max);
			entry_end += entry_sizes[0];
			z.next_out = (Bytef*)&buffer[0];
			z.avail_out = entry_sizes[pos];

			int result = inflate(&z, Z_FINISH);

			if (result < 0)
			{
				std::string error = std::string("Decompression error: ") + (z.msg ? z.msg : util::to_string(result));
				inflateEnd(&z);
				throw std::runtime_error(error);
			}

			inflateReset(&z);

			entries[pos] = buffer;
			++pos;
		}

		inflateEnd(&z);
	}
};

struct DataFilePack
{
	std::deque<DataFile*> datafiles;

	DataFilePack(const std::string &data)
	{
		typedef unsigned char uchar;

		if (data.substr(0, 4) != "EPAK")
		{
			throw std::runtime_error("Corrupt data file pack");
		}

		std::size_t files = (uchar(data[4]) << 24) | (uchar(data[5]) << 16) | (uchar(data[6]) << 8) | uchar(data[7]);

		std::deque<std::size_t> sizes;

		std::size_t off = 8;

		for (std::size_t i = 0; i < files; ++i)
		{
			sizes.push_back((uchar(data[off]) << 24) | (uchar(data[off + 1]) << 16) | (uchar(data[off + 2]) << 8) | uchar(data[off + 3]));
			off += 4;
		}

		for (std::size_t i = 0; i < files; ++i)
		{
			datafiles.push_back(new DataFile(data.substr(off, sizes[i])));
			off += sizes[i];
		}
	}

	~DataFilePack()
	{
		for (auto it = datafiles.begin(); it != datafiles.end(); ++it)
			delete *it;

		datafiles.clear();
	}
};

struct GFXFile
{
	HANDLE handle;

	GFXFile(std::string filename)
	{
		handle = BeginUpdateResource(filename.c_str(), FALSE);

		if (!handle)
			throw std::runtime_error(std::string("Failed to open GFX file: ") + OSErrorString());
	}

	GFXFile(const GFXFile&) = delete;

	void Update(int id, const std::string &data)
	{
		if (!UpdateResource(handle, RT_BITMAP, MAKEINTRESOURCE(id), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), (void*)&data[0], data.length()))
			throw std::runtime_error(std::string("Failed to update resource: ") + OSErrorString());
	}

	void Delete(int id)
	{
		if (!UpdateResource(handle, RT_BITMAP,  MAKEINTRESOURCE(id), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), 0, 0))
			throw std::runtime_error(std::string("Failed to update resource: ") + OSErrorString());
	}

	void Commit()
	{
		if (handle)
		{
			if (!EndUpdateResource(handle, FALSE))
			throw std::runtime_error(std::string("Failed to save GFX file: ") + OSErrorString());
			handle = 0;
		}
	}

	~GFXFile()
	{
		if (handle)
			EndUpdateResource(handle, FALSE);
	}
};

std::string update_url;
std::string files_url;
std::string exe_file;
int version_offset;

void load_config()
{
	std::ifstream f("epatch_config.txt");

	if (!f.good())
		throw std::runtime_error("Could not load epatch_config.txt\nMake sure Endless Edge Patcher is run from the Endless Edge directory");

	getline(f, update_url);
	getline(f, files_url);
	getline(f, exe_file);
	f >> version_offset;
}

int get_version()
{
	FILE* fh = fopen(exe_file.c_str(), "rb");

	if (!fh)
		return -1;

	int version = 0;

	fseek(fh, version_offset, SEEK_SET);

	unsigned char c1 = (fgetc(fh) & 0xFF);
	unsigned char c2 = (fgetc(fh) & 0xFF);
	unsigned char c3 = (fgetc(fh) & 0xFF);
	unsigned char c4 = (fgetc(fh) & 0xFF);

	version = c1 | (c2 << 8) | (c3 << 16) | (c4 << 24);

	fclose(fh);

	return version;
}

int set_version(int version)
{
	FILE* fh = fopen(exe_file.c_str(), "r+b");

	if (!fh)
		return -1;

	fseek(fh, version_offset, SEEK_SET);
	fputc(version & 0xFF, fh);
	fputc((version >> 8) & 0xFF, fh);
	fputc((version >> 16) & 0xFF, fh);
	fputc((version >> 24) & 0xFF, fh);

	fclose(fh);

	return version;
}

HINSTANCE instance;
HWND window;
HWND button_play;
HWND progress_bar;
HFONT font;
HBITMAP splash;
std::string text1, text2, text3;
int update_progress;
int cur_file;
bool running = true;
bool launch_eo;
UpdateInfo updates;
int state = 0;

void paint_window()
{
	PAINTSTRUCT ps;
	HDC hdc = BeginPaint(window, &ps);
	HDC hdc_mem = CreateCompatibleDC(hdc);
	RECT text1_rect = {8, 320, 292, 336};
	RECT text2_rect = {8, 364, 228, 380};
	RECT text3_rect = {8, 380, 228, 396};
	BITMAP bm;

	SelectObject(hdc, font);
	SetBkMode(hdc, TRANSPARENT);

	DrawText(hdc, text1.c_str(), text1.length(), &text1_rect, DT_CENTER);
	DrawText(hdc, text2.c_str(), text2.length(), &text2_rect, DT_CENTER);
	DrawText(hdc, text3.c_str(), text3.length(), &text3_rect, DT_CENTER);

	SelectObject(hdc_mem, splash);
	GetObject(splash, sizeof(bm), &bm);
	BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdc_mem, 0, 0, SRCCOPY);

	SelectObject(hdc_mem, hdc);
	DeleteDC(hdc_mem);

	EndPaint(window, &ps);
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
	switch (message)
	{
		case WM_PAINT:
			paint_window();
			break;

		case WM_COMMAND:
			// if (... there's only one button
			{
				running = false;
				launch_eo = true;
			}
			break;

		case WM_CLOSE:
			running = false;
			break;
	}

	return DefWindowProc(hwnd, message, wparam, lparam);
}

int progress_div;

void set_progress_bar_width(int size)
{
	progress_div = 1;

	while (size > 65000)
	{
		size /= 2;
		progress_div *= 2;
	}

	Sleep(1000);
	SendMessage(progress_bar, PBM_SETRANGE, 0, MAKELPARAM(0, size));
}

void set_progress_bar(int pos)
{
	SendMessage(progress_bar, PBM_SETPOS, pos / progress_div, 0);
}

void marquee_progress_bar(bool startstop)
{
//#ifdef COMPAT
	SetWindowLongPtr(progress_bar, GWL_STYLE, WS_CHILD | WS_VISIBLE | PBS_SMOOTH);
	//SendMessage(progress_bar, PBM_SETMARQUEE, startstop, 0);
//#else
//	SetWindowLongPtr(progress_bar, GWL_STYLE, WS_CHILD | WS_VISIBLE | (startstop ? PBS_MARQUEE : PBS_SMOOTH));
//	SendMessage(progress_bar, PBM_SETMARQUEE, startstop, 0);
//#endif
}

void enable_play(bool enabled)
{
	SetWindowLongPtr(button_play, GWL_STYLE, WS_CHILD | WS_VISIBLE | (enabled ? 0 : WS_DISABLED));
	SendMessage(button_play, WM_PAINT, 0, 0);
}

void redraw_window(bool full = false)
{
	if (full)
	{
		RedrawWindow(window, 0, 0, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
	}
	else
	{
		RECT lower_rect = {0, 320, 300, 400};
		RedrawWindow(window, &lower_rect, 0, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
	}
}

void create_window()
{
	instance = GetModuleHandle(0);

	font = CreateFont(-11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Tahoma");

	WNDCLASSEX wc = {};
	wc.cbSize = sizeof(wc);
	wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wc.lpfnWndProc = WindowProc;
	wc.hInstance = instance;
	wc.hIcon = LoadIcon(GetModuleHandle(NULL), "MAINICON");
	wc.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL), "MAINICON", IMAGE_ICON, 16, 16, 0);
	wc.hCursor = LoadCursor(GetModuleHandle(NULL), IDC_ARROW);
	wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
	wc.lpszClassName = "EPATCH";

	if (!RegisterClassEx(&wc))
		fail("Could not register window class");

	RECT rect = {};

	rect.right = 300;
	rect.bottom = 400;

	int screen_width = GetSystemMetrics(SM_CXSCREEN);
	int screen_height = GetSystemMetrics(SM_CYSCREEN);

	AdjustWindowRectEx(&rect, WS_POPUPWINDOW | WS_MINIMIZEBOX | WS_CAPTION, false, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE | WS_EX_TOPMOST);

	char title[] = "Endless Edge";

	window = CreateWindowEx(WS_EX_APPWINDOW | WS_EX_WINDOWEDGE | WS_EX_TOPMOST,
							wc.lpszClassName, title,
							WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUPWINDOW | WS_MINIMIZEBOX | WS_CAPTION,
							screen_width / 2 - (rect.right - rect.left) / 2, screen_height / 2 - (rect.bottom - rect.top) / 2,
							rect.right - rect.left,
							rect.bottom - rect.top,
							0, 0, instance, 0);

	if (!window)
		fail("Could not create window");

	button_play = CreateWindow("BUTTON", "Play", WS_CHILD | WS_VISIBLE | WS_DISABLED, 212, 368, 80, 24, window, 0, instance, 0);

	if (!button_play)
		fail("Could not create button");

	SendMessage(button_play, WM_SETFONT, (WPARAM)font, MAKELPARAM(TRUE, 0));

	progress_bar = CreateWindow(PROGRESS_CLASS, "", WS_CHILD | WS_VISIBLE, 6, 338, 288, 24, window, 0, instance, 0);

	if (!progress_bar)
		fail("Could not create progress bar");

	splash = LoadBitmap(instance, MAKEINTRESOURCE(2));

	if (!splash)
		fail("Could not load splash image");

	ShowWindow(window, SW_SHOW);
	SetForegroundWindow(window);
	SetFocus(window);
}

void destroy_window()
{
	DeleteObject(splash);
	DestroyWindow(progress_bar);
	DestroyWindow(button_play);
	DestroyWindow(window);
}

void fail(const char *error)
{
	MessageBox(window, error, "Error", MB_OK | MB_ICONERROR);

	if (state != 0)
	{
		text1 = "Failed to retrieve updates";
		marquee_progress_bar(false);
		enable_play(true);
		redraw_window();
		state = 0;
	}
	else
	{
		ShellExecute(0, "open", exe_file.c_str(), "", 0, SW_SHOWDEFAULT);
		exit(EXIT_FAILURE);
	}
}

void tick_window()
{
	MSG message;

	if (GetMessage(&message, window, 0, 0))
		DispatchMessage(&message);
}

HANDLE window_created_event;
HANDLE thread_winevent_event;

void thread_winevent(void *)
{
	create_window();
	SetEvent(window_created_event);

	while (WaitForSingleObject(thread_winevent_event, 1) != WAIT_OBJECT_0)
	{
		tick_window();
	}

	destroy_window();
	window = 0;

	_endthread();
}

HANDLE thread_network_event;
CRITICAL_SECTION request_mutex;
HTTP * volatile request;

void thread_network(void *)
{
	while (WaitForSingleObject(thread_network_event, 1) != WAIT_OBJECT_0)
	{
		EnterCriticalSection(&request_mutex);
		bool request_exists = request;

		if (request_exists)
		{
			try
			{
				request->Tick(0.001);
			}
			catch (std::exception &e)
			{
				fail(e.what());
				request = 0;
			}
		}

		LeaveCriticalSection(&request_mutex);

		if (request_exists)
			Sleep(0);
		else
			Sleep(50);
	}

	_endthread();
}

std::string make_filename(Update::Type type, std::string file)
{
	char filename_buf[18];

	switch (type)
	{
		case Update::SFX: sprintf(filename_buf, "sfx/sfx%03i.wav", util::to_int(file)); break;
		case Update::SFXGuitar: sprintf(filename_buf, "sfx/gui%03i.wav", util::to_int(file)); break;
		case Update::SFXHarp: sprintf(filename_buf, "sfx/har%03i.wav", util::to_int(file)); break;
		case Update::MFX: sprintf(filename_buf, "mfx/mfx%03i.mid", util::to_int(file)); break;
		case Update::JBox: sprintf(filename_buf, "jbox/jbox%03i.mid", util::to_int(file)); break;
		case Update::Help: return "help/" + file + ".ehf";
		case Update::Data: sprintf(filename_buf, "data/dat%03i.edf", util::to_int(file)); break;
		case Update::File: return file;

		default:
			throw std::runtime_error("Unknown entry type");
	}

	return filename_buf;
}

#ifdef ELEVATED
	const std::string image_name = "epatch2.exe";
#else
	const std::string image_name = "epatch.exe";
#endif

const int epatch_version = 2;

int updated_patcher = 0;
int downloaded = 0;

static void update_progress_bar()
{
	static int last_progress_update = 0;
	static bool last_progress_done = false;

	if (last_progress_update != clock() * 2 / CLOCKS_PER_SEC || last_progress_done != request->Done())
	{
		Update update = updates.updates[cur_file - 1];

		if (!request->Done())
			text2 = update.name + " (" + util::to_string(request->Progress() / 1024) + " / " + util::to_string(update.download_size / 1024) + " KB)";
		else
			text2 = update.name + " (Installing)";

		redraw_window();
		last_progress_update = clock() * 2 / CLOCKS_PER_SEC;
	}

	set_progress_bar(downloaded + request->Progress());
}

int main()
{
	try
	{
		InitializeCriticalSection(&request_mutex);

		window_created_event = CreateEvent(0, FALSE, FALSE, 0);
		thread_winevent_event = CreateEvent(0, FALSE, FALSE, 0);
		thread_network_event = CreateEvent(0, FALSE, FALSE, 0);

		uintptr_t thread_winevent_handle = _beginthread(thread_winevent, 0, 0);
		uintptr_t thread_network_handle = _beginthread(thread_network, 0, 0);

		WaitForSingleObject(window_created_event, INFINITE);

		state = 1;

		load_config();

		text1 = "Checking for updates";
		marquee_progress_bar(true);
		redraw_window();

		int version = get_version();

		if (version == -1)
			fail("Could not retrieve version information.\nMake sure Endless Edge Patcher is run from the Endless Edge directory");

		EnterCriticalSection(&request_mutex);
		try
		{
			request = HTTP::RequestURL(update_url + util::to_string(version) + ',' + util::to_string(epatch_version));
		}
		catch (std::exception &e)
		{
			fail(e.what());
		}
		LeaveCriticalSection(&request_mutex);

		time_t started = time(0);

		std::map<std::string, GFXFile*> gfxfile_cache;

		while (running)
		{
			try
			{
				Sleep(50);

				if (state == 1) // Downloading update list
				{
					EnterCriticalSection(&request_mutex);

					if (!request)
					{
						state = 0;
						continue;
					}

					if (request->Done())
					{
						updates.Load(request->Response().c_str());

						delete request;
						request = 0;

						LeaveCriticalSection(&request_mutex);

						if (updates.state == UpdateInfo::OK)
						{
							text1 = "No updates found";
							marquee_progress_bar(false);
							set_progress_bar_width(1);
							set_progress_bar(1);
							enable_play(true);
							redraw_window();
							state = 0;
						}
						else if (updates.state == UpdateInfo::ERROR)
						{
							text1 = "Failed to retrieve updates";
							marquee_progress_bar(false);
							enable_play(true);
							redraw_window();
							state = 0;
						}
						else if (updates.state == UpdateInfo::UPDATE)
						{
#if defined(ELEVATED) || defined(COMPAT)
							int set_version_reuslt = set_version(updates.u.from);

							while (set_version_reuslt == -1)
							{
								if (MessageBox(window, "Could not open EndlessEdge.exe. Please make sure the game is not running and try again.", "Error", MB_RETRYCANCEL | MB_ICONERROR) != IDRETRY)
									break;

								set_version_reuslt = set_version(updates.u.from);
							}

							if (set_version_reuslt == -1)
							{
								fail("Could not update version information.\nMake sure you're running as Administrator.");
							}
#else
							if (set_version(updates.u.from) == -1)
							{
								text1 = "Requesting UAC elevation.";
								marquee_progress_bar(true);
								redraw_window();

								ShellExecute(0, "open", "epatch2.exe", "", 0, SW_SHOWDEFAULT);
								running = false;
								state = 0;
							}
#endif
							else
							{
								text1 = "Downloading updates...";
								marquee_progress_bar(false);
								set_progress_bar_width(updates.download_total);
								cur_file = 0;
								update_progress = 0;
								text3 = util::to_string(update_progress) + " / " + util::to_string((int)updates.total_files);
								redraw_window();
								state = 2;
							}
						}
					}
					else
					{
						if (started + 20 < time(0))
						{
							text1 = "Could not connect to update server";
							marquee_progress_bar(false);
							enable_play(true);
							redraw_window();
							state = 0;
						}

						LeaveCriticalSection(&request_mutex);
					}
				}
				else if (state == 2) // Downloading single update
				{
					EnterCriticalSection(&request_mutex);
					if (request == 0)
					{
						next_entry:

						if (update_progress == updates.total_files)
						{
							text1 = "Update complete!";
							text2 = "";
							text3 = "";
							state = 0;

							if (set_version(updates.u.to) == -1)
							{
								fail("Could not update version information.\nMake sure you're running as Administrator.");
							}

							if (updated_patcher == 1)
							{
								text1 = "Updating patcher";
								marquee_progress_bar(true);
								redraw_window();
								updated_patcher = 2;
								running = false;
							}
							else
							{
								enable_play(true);
								set_progress_bar(downloaded);
								redraw_window();
							}
						}
						else
						{
							Update update = updates.updates[cur_file];

							text2 = update.name + " (0 / " + util::to_string(update.download_size / 1024) + " KB)";
							redraw_window();

							if (update.action == Update::Del)
							{
								++cur_file;
								++update_progress;
								text2 = "Delete " + update.name;
								text3 = util::to_string(update_progress) + " / " + util::to_string((int)updates.total_files);
								redraw_window();

								if (update.type == Update::GFX)
								{
									char filename_buf[16];
									sprintf(filename_buf, "gfx/gfx%03i.egf", util::to_int(update.file));
									std::string filename(filename_buf);

									auto it = gfxfile_cache.find(filename);

									if (it == gfxfile_cache.end())
									{
										it = gfxfile_cache.insert(std::make_pair(filename, new GFXFile(filename))).first;
									}

									GFXFile& file = *it->second;

									for (std::size_t i = 0; i < update.files.size(); ++i)
									{
										file.Delete(update.files[i]);
									}
								}
								else
								{
									std::string filename = make_filename(update.type, update.file);
									unlink(filename.c_str());
								}

								goto next_entry;
							}

							Sleep(50);

							text2 = update.name + " (0 / " + util::to_string(update.download_size / 1024) + " KB)";

							if (update.action == Update::Version)
							{
								std::string url = files_url + "pack/" + util::to_string(update.v.version) + ".bin";
								request = HTTP::RequestURL(url);
								state = 3;
								++cur_file;
								++update_progress;
								text3 = util::to_string(update_progress) + " / " + util::to_string((int)updates.total_files);
							}
							else
							{
								std::string url = files_url + update.name + ".bin";
								request = HTTP::RequestURL(url);

								++cur_file;
								++update_progress;
								text3 = util::to_string(update_progress) + " / " + util::to_string((int)updates.total_files);
								set_progress_bar(downloaded);
								redraw_window();
							}
						}
					}
					else if (request->Done())
					{
						update_progress_bar();
						Update update = updates.updates[cur_file - 1];

						downloaded += update.download_size;

						if (update.type == Update::GFX)
						{
							char filename_buf[16];
							sprintf(filename_buf, "gfx/gfx%03i.egf", util::to_int(update.file));
							std::string filename(filename_buf);

							DataFile data_file(request->Response());

							auto it = gfxfile_cache.find(filename);

							if (it == gfxfile_cache.end())
							{
								it = gfxfile_cache.insert(std::make_pair(filename, new GFXFile(filename))).first;
							}

							GFXFile& file = *it->second;

							for (std::size_t i = 0; i < update.files.size(); ++i)
							{
								int file_id = update.files[i];

								if (update.action == Update::Add || update.action == Update::Mod)
								{
									file.Update(file_id, data_file.entries.at(i).substr(14));
								}
							}
						}
						else
						{
							std::string filename = make_filename(update.type, update.file);

							DataFile data_file(request->Response());

							if (filename == image_name)
							{
								DeleteFile(("old_" + image_name).c_str());

								if (!MoveFile(image_name.c_str(), ("old_" + image_name).c_str()))
									throw std::runtime_error("Failed to rename file: " + image_name);

								updated_patcher = 1;
							}

							std::ofstream f(filename, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc);

							if (!f.good())
								throw std::runtime_error("Failed to open file: " + filename);

							f << data_file.entries[0];
						}

						delete request;
						request = 0;
					}
					else
					{
						update_progress_bar();
					}
					LeaveCriticalSection(&request_mutex);
				}
				else if (state == 3) // Downloading package
				{
					EnterCriticalSection(&request_mutex);

					if (!request)
						throw std::runtime_error("Unknown error");

					if (request->Done())
					{
						update_progress_bar();
						Update pack_update = updates.updates[cur_file - 1];

						downloaded += pack_update.download_size;

						DataFilePack pack(request->Response());
						int file_pack_pos = 0;

						for (int i = 0; i < pack_update.v.entries; ++i)
						{
							++cur_file;
							Update update = updates.updates[cur_file - 1];

							if (update.action == Update::Del)
							{
								if (update.type == Update::GFX)
								{
									char filename_buf[16];
									sprintf(filename_buf, "gfx/gfx%03i.egf", util::to_int(update.file));
									std::string filename(filename_buf);

									auto it = gfxfile_cache.find(filename);

									if (it == gfxfile_cache.end())
									{
										it = gfxfile_cache.insert(std::make_pair(filename, new GFXFile(filename))).first;
									}

									GFXFile& file = *it->second;

									for (std::size_t i = 0; i < update.files.size(); ++i)
									{
										file.Delete(update.files[i]);
									}
								}
								else
								{
									std::string filename = make_filename(update.type, update.file);
									unlink(filename.c_str());
								}
							}
							else
							{
								if (update.type == Update::GFX)
								{
									char filename_buf[16];
									sprintf(filename_buf, "gfx/gfx%03i.egf", util::to_int(update.file));
									std::string filename(filename_buf);

									DataFile& data_file = *pack.datafiles[file_pack_pos++];

									auto it = gfxfile_cache.find(filename);

									if (it == gfxfile_cache.end())
									{
										it = gfxfile_cache.insert(std::make_pair(filename, new GFXFile(filename))).first;
									}

									GFXFile& file = *it->second;

									for (std::size_t i = 0; i < update.files.size(); ++i)
									{
										int file_id = update.files[i];

										if (update.action == Update::Add || update.action == Update::Mod)
										{
											file.Update(file_id, data_file.entries.at(i).substr(14));
										}
									}
								}
								else
								{
									std::string filename = make_filename(update.type, update.file);

									DataFile& data_file = *pack.datafiles[file_pack_pos++];

									if (filename == image_name)
									{
										DeleteFile(("old_" + image_name).c_str());

										if (!MoveFile(image_name.c_str(), ("old_" + image_name).c_str()))
											throw std::runtime_error("Failed to rename file: " + image_name);

										updated_patcher = 1;
									}

									std::ofstream f(filename, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc);

									if (!f.good())
										throw std::runtime_error("Failed to open file: " + filename);

									f << data_file.entries[0];
								}
							}
						}

						{
							int i = 0;

							set_progress_bar_width(int(gfxfile_cache.size()));

							for (auto it = gfxfile_cache.begin(); it != gfxfile_cache.end(); ++it)
							{
								set_progress_bar(i++);
								text2 = "Saving file " + it->first + "...";
								redraw_window();
								it->second->Commit();
								delete it->second;
							}

							text2 = "Updating version...";
							set_progress_bar_width(updates.download_total);
							set_progress_bar(downloaded);
							redraw_window();

							gfxfile_cache.clear();
						}

						delete request;
						request = 0;
						state = 2;

						int set_version_reuslt = set_version(pack_update.v.version);

						while (set_version_reuslt == -1)
						{
							if (MessageBox(window, "Could not open EndlessEdge.exe. Please make sure the game is not running and try again.", "Error", MB_RETRYCANCEL | MB_ICONERROR) != IDRETRY)
								break;

							set_version_reuslt = set_version(pack_update.v.version);
						}

						if (set_version_reuslt == -1)
						{
							fail("Could not update version information.");
						}
					}
					else
					{
						update_progress_bar();
					}
					LeaveCriticalSection(&request_mutex);
				}
				else if (state == 4)
				{
					int i = 0;

					set_progress_bar_width(int(gfxfile_cache.size()));

					for (auto it = gfxfile_cache.begin(); it != gfxfile_cache.end(); ++it)
					{
						set_progress_bar(i++);
						text2 = "Saving file " + it->first + "...";
						redraw_window();
						it->second->Commit();
						delete it->second;
					}

					gfxfile_cache.clear();
				}
			}
			catch (std::exception &e)
			{
				fail(e.what());
			}
		}

		SetEvent(thread_network_event);
		SetEvent(thread_winevent_event);
		SendMessage(window, WM_PAINT, 0, 0); // Need a window message before winevent thread checks the event

		// Not infinite. No use hanging around forever.
		WaitForSingleObject((void *)thread_network_handle, 800);
		WaitForSingleObject((void *)thread_winevent_handle, 400);

		CloseHandle(thread_network_event);
		CloseHandle(thread_winevent_event);
		CloseHandle(window_created_event);

		DeleteCriticalSection(&request_mutex);

		if (updated_patcher)
		{
			ShellExecute(0, "open", image_name.c_str(), "", 0, SW_SHOWDEFAULT);
		}
		else if (launch_eo)
		{
			ShellExecute(0, "open", exe_file.c_str(), "", 0, SW_SHOWDEFAULT);
		}
	}
	catch (std::exception &e)
	{
		fail(e.what());
	}
}