#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());
}
}