medfall

A super great game engine
Log | Files | Refs

main.cc (23894B)


      1 #include <stdlib.h>
      2 
      3 #include <string>
      4 #include <vector>
      5 #include <map>
      6 #include <set>
      7 
      8 #include "intrinsics.h"
      9 #include "int_conversions.h"
     10 #include "game.h"
     11 #include "log.h"
     12 #include "gl.h"
     13 #include "http.h"
     14 #include "locked.h"
     15 #include "work_queue.h"
     16 #include "ggformat.h"
     17 #include "str.h"
     18 #include "strlcpy.h"
     19 #include "strtonum.h"
     20 #include "patterns.h"
     21 #include "platform.h"
     22 #include "platform_exec.h"
     23 #include "platform_io.h"
     24 #include "platform_network.h"
     25 #include "platform_thread.h"
     26 #include "platform_time.h"
     27 #include "liberation.h"
     28 
     29 #define GLFW_INCLUDE_NONE
     30 #include "libs/glfw/include/GLFW/glfw3.h"
     31 
     32 #include "libs/imgui/imgui.h"
     33 #include "libs/imgui/imgui_impl_glfw_gl3.h"
     34 
     35 #include "libs/monocypher/monocypher.h"
     36 
     37 #include "libs/whereami/whereami.h"
     38 
     39 #if PLATFORM_WINDOWS
     40 #define GAME_BINARY "medfall.exe"
     41 #else
     42 #define GAME_BINARY "medfall"
     43 #endif
     44 
     45 #define HEX256_PATTERN "%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x"
     46 
     47 #define BLAKE2B256_DIGEST_LENGTH 32
     48 #define BLAKE2B256_PATTERN HEX256_PATTERN
     49 
     50 #define SIGNATURE_PATTERN HEX256_PATTERN HEX256_PATTERN
     51 
     52 const u8 PUBLIC_KEY[] = {
     53 	0x3b, 0x0a, 0xc7, 0xa1, 0xd1, 0x9e, 0xbd, 0x0c,
     54 	0x71, 0x75, 0x4f, 0xfc, 0x2a, 0xef, 0xcf, 0x91,
     55 	0x93, 0xd0, 0x58, 0x94, 0x79, 0xc2, 0xeb, 0x16,
     56 	0x30, 0x74, 0x62, 0x88, 0xe8, 0x18, 0x03, 0xb6,
     57 };
     58 
     59 template< size_t N >
     60 struct GenericHash {
     61 	StaticArray< u8, N > digest;
     62 };
     63 
     64 template< size_t N >
     65 static bool operator==( const GenericHash< N > & a, const GenericHash< N > & b ) {
     66 	return memcmp( a.digest.ptr(), b.digest.ptr(), a.digest.num_bytes() ) == 0;
     67 }
     68 
     69 template< size_t N >
     70 static bool operator!=( const GenericHash< N > & a, const GenericHash< N > & b ) {
     71 	return !( a == b );
     72 }
     73 
     74 template< size_t N >
     75 static void format( FormatBuffer * fb, const GenericHash< N > & h, const FormatOpts & opts ) {
     76 	str< N * 2 + 1 > s;
     77 	static const char hex[] = "0123456789abcdef";
     78 	for( size_t i = 0; i < N; i++ ) {
     79 		s += hex[ h.digest[ i ] >> 4 ];
     80 		s += hex[ h.digest[ i ] & 0x0f ];
     81 	}
     82 	format( fb, s, opts );
     83 }
     84 
     85 struct Blake2b256 : public GenericHash< BLAKE2B256_DIGEST_LENGTH > {
     86 	Blake2b256() { }
     87 
     88 	explicit Blake2b256( const void * data, size_t len ) {
     89 		crypto_blake2b_general( digest.ptr(), digest.num_bytes(), NULL, 0, ( const u8 * ) data, len );
     90 	}
     91 };
     92 
     93 static u8 hex2dec( char hex ) {
     94 	if( hex == '0' ) return 0;
     95 	if( hex == '1' ) return 1;
     96 	if( hex == '2' ) return 2;
     97 	if( hex == '3' ) return 3;
     98 	if( hex == '4' ) return 4;
     99 	if( hex == '5' ) return 5;
    100 	if( hex == '6' ) return 6;
    101 	if( hex == '7' ) return 7;
    102 	if( hex == '8' ) return 8;
    103 	if( hex == '9' ) return 9;
    104 
    105 	if( hex == 'a' || hex == 'A' ) return 10;
    106 	if( hex == 'b' || hex == 'B' ) return 11;
    107 	if( hex == 'c' || hex == 'C' ) return 12;
    108 	if( hex == 'd' || hex == 'D' ) return 13;
    109 	if( hex == 'e' || hex == 'E' ) return 14;
    110 	if( hex == 'f' || hex == 'F' ) return 15;
    111 
    112 	return 255;
    113 }
    114 
    115 static bool parse_hex( const array< const char > hex, u8 * bytes ) {
    116 	if( hex.n % 2 != 0 ) {
    117 		return false;
    118 	}
    119 
    120 	for( size_t i = 0; i < hex.n / 2; i++ ) {
    121 		u8 a = hex2dec( hex[ i * 2 ] );
    122 		u8 b = hex2dec( hex[ i * 2 + 1 ] );
    123 
    124 		if( a == 255 || b == 255 ) {
    125 			return false;
    126 		}
    127 
    128 		bytes[ i ] = a * 16 + b;
    129 	}
    130 
    131 	return true;
    132 }
    133 
    134 template< size_t N >
    135 static bool parse_digest( GenericHash< N > * h, const array< const char > hex ) {
    136 	if( hex.n != N * 2 )
    137 		return false;
    138 	return parse_hex( hex, h->digest.ptr() );
    139 }
    140 
    141 struct ManifestEntry {
    142 	Blake2b256 checksum;
    143 	u64 file_size;
    144 	bool platform_specific;
    145 };
    146 
    147 struct Version {
    148 	u32 a, b, c, d;
    149 };
    150 
    151 static bool operator==( const Version & a, const Version & b ) {
    152 	return a.a == b.a && a.b == b.b && a.c == b.c && a.d == b.d;
    153 }
    154 
    155 static bool operator!=( const Version & a, const Version & b ) {
    156 	return !( a == b );
    157 }
    158 
    159 static bool parse_version( Version * v, const char * str ) {
    160 	int fields = sscanf( str, "%u.%u.%u.%u", &v->a, &v->b, &v->c, &v->d );
    161 	if( fields != 4 ) {
    162 		v = { };
    163 		return false;
    164 	}
    165 	return true;
    166 }
    167 
    168 static bool parse_manifest( std::map< std::string, ManifestEntry > & manifest, const char * data ) {
    169 	for( array< array< const char > > line : gmatch( data, "([^\n]+)" ) ) {
    170 		if( line.n != 1 )
    171 			return false;
    172 
    173 		Matches matches;
    174 		bool ok = match( &matches, line[ 0 ], "^(%S+) (" BLAKE2B256_PATTERN ") (%d+)%s*(%w*)$" );
    175 		if( !ok )
    176 			return false;
    177 
    178 		if( matches[ 1 ].n != BLAKE2B256_DIGEST_LENGTH * 2 )
    179 			return false;
    180 
    181 		const str< 256 > file_name( "{}", matches[ 0 ] );
    182 		const str< 16 > file_size( "{}", matches[ 2 ] );
    183 		const str< 32 > file_platform( "{}", matches[ 3 ] );
    184 		u64 size = u64( strtonum( file_size.c_str(), 1, S64_MAX, NULL ) );
    185 
    186 		ManifestEntry entry;
    187 		bool ok_parse = parse_digest( &entry.checksum, matches[ 1 ] );
    188 
    189 		if( matches[ 0 ].n > file_name.len() || matches[ 2 ].n > file_size.len() || matches[ 3 ].n > file_platform.len() || size == 0 || !ok_parse ) {
    190 			manifest.clear();
    191 			return false;
    192 		}
    193 
    194 		if( file_platform != "" && file_platform != PLATFORM_NAME ) {
    195 			continue;
    196 		}
    197 
    198 		entry.file_size = size;
    199 		entry.platform_specific = file_platform != "";
    200 
    201 		manifest[ file_name.c_str() ] = entry;
    202 	}
    203 
    204 	return true;
    205 }
    206 
    207 static bool already_downloaded( const std::string & path, const ManifestEntry & entry ) {
    208 	FILE * file = fopen( path.c_str(), "rb" );
    209 	if( file == NULL )
    210 		return false;
    211 	defer { fclose( file ); };
    212 
    213 	fseek( file, 0, SEEK_END );
    214 	u32 file_size = checked_cast< u32 >( ftell( file ) );
    215 	fseek( file, 0, SEEK_SET );
    216 
    217 	if( file_size != entry.file_size )
    218 		return false;
    219 
    220 	void * contents = malloc( file_size );
    221 	defer { free( contents ); };
    222 
    223 	size_t bytes_read = fread( contents, 1, file_size, file );
    224 	if( bytes_read != file_size )
    225 		return false;
    226 
    227 	Blake2b256 digest( contents, file_size );
    228 	return digest == entry.checksum;
    229 }
    230 
    231 static const char * file_get_contents_or_empty( const char * path, size_t * out_len = NULL ) {
    232 	FILE * file = fopen( path, "rb" );
    233 	if( file == NULL ) return "";
    234 
    235 	fseek( file, 0, SEEK_END );
    236 	size_t len = checked_cast< size_t >( ftell( file ) );
    237 	ASSERT( len < SIZE_MAX );
    238 	fseek( file, 0, SEEK_SET );
    239 
    240 	char * contents = ( char * ) malloc( len + 1 );
    241 	if( contents == NULL ) FATAL( "malloc" );
    242 	size_t bytes_read = fread( contents, 1, len, file );
    243 	contents[ len ] = '\0';
    244 	ASSERT( bytes_read == len );
    245 
    246 	if( out_len ) *out_len = len;
    247 
    248 	fclose( file );
    249 
    250 	return contents;
    251 }
    252 
    253 static void recursive_mkdir(
    254 	std::vector< std::string > * directories_to_remove,
    255 	const std::string & path
    256 ) {
    257 	std::string cur;
    258 	// TODO: might need to look for \ too
    259 	// TODO: this assumes you pass in a full file name...
    260 	// TODO: error checking
    261 	for( array< array< const char > > matches : gmatch( path.c_str(), "([^/]+)/" ) ) {
    262 		cur.append( matches[ 0 ].ptr(), matches[ 0 ].n );
    263 		cur += '/';
    264 		mkdir( cur.c_str(), 0755 );
    265 		if( directories_to_remove != NULL ) {
    266 			directories_to_remove->push_back( cur );
    267 		}
    268 	}
    269 }
    270 
    271 struct FileUpdate {
    272 	std::string file_name;
    273 	u64 bytes_downloaded;
    274 	bool done;
    275 
    276 	explicit FileUpdate( const std::string & n ) {
    277 		file_name = n;
    278 		bytes_downloaded = 0;
    279 		done = false;
    280 	}
    281 };
    282 
    283 enum UpdaterState {
    284 	UPDATER_DNS,
    285 	UPDATER_DNS_FAILED,
    286 	UPDATER_DNS_RETRY,
    287 
    288 	UPDATER_VERSION,
    289 	UPDATER_VERSION_FAILED,
    290 	UPDATER_VERSION_RETRY,
    291 
    292 	UPDATER_MANIFEST,
    293 	UPDATER_MANIFEST_FAILED,
    294 	UPDATER_MANIFEST_RETRY,
    295 
    296 	UPDATER_NEED_UPDATE,
    297 
    298 	UPDATER_DOWNLOADING_PRE,
    299 	UPDATER_DOWNLOADING,
    300 	UPDATER_INSTALLING,
    301 
    302 	UPDATER_READY,
    303 };
    304 
    305 struct Updater {
    306 	UpdaterState state = UPDATER_DNS_RETRY;
    307 	double retry_at;
    308 
    309 	NetAddress address;
    310 
    311 	Version local_version, remote_version;
    312 	std::map< std::string, ManifestEntry > local_manifest, remote_manifest;
    313 
    314 	std::vector< FileUpdate > files_to_update;
    315 	std::vector< std::string > files_to_remove;
    316 
    317 	u64 update_size = 0;
    318 	u64 game_size = 0;
    319 };
    320 
    321 #define DOWNLOAD_THREADS 16
    322 static Locked< Updater > locked_updater;
    323 static WorkQueue download_queue;
    324 static bool autostart_update = false;
    325 
    326 #define HOST "medfall.mikejsavage.co.uk"
    327 
    328 static WORK_QUEUE_CALLBACK( lookup_update_server ) {
    329 	NetAddress address;
    330 	bool ok = dns_first( HOST, &address );
    331 
    332 	Updater * updater = SCOPED_ACQUIRE( locked_updater );
    333 	if( ok ) {
    334 		updater->address = address;
    335 		updater->state = UPDATER_VERSION_RETRY;
    336 		updater->retry_at = get_time();
    337 	}
    338 	else {
    339 		updater->state = UPDATER_DNS_FAILED;
    340 	}
    341 	glfwPostEmptyEvent();
    342 }
    343 
    344 static WORK_QUEUE_CALLBACK( download_remote_version ) {
    345 	NetAddress address;
    346 	{
    347 		Updater * updater = SCOPED_ACQUIRE( locked_updater );
    348 		address = updater->address;
    349 	}
    350 
    351 	std::string remote_version_str;
    352 	bool ok = http_get( address, HOST, "/version.txt", &remote_version_str ) == GET_OK;
    353 	Updater * updater = SCOPED_ACQUIRE( locked_updater );
    354 	if( ok ) {
    355 		ok = parse_version( &updater->remote_version, remote_version_str.c_str() );
    356 	}
    357 	if( ok ) {
    358 		if( updater->local_version == updater->remote_version ) {
    359 			updater->state = UPDATER_READY;
    360 		}
    361 		else {
    362 			updater->state = UPDATER_MANIFEST_RETRY;
    363 			updater->retry_at = get_time();
    364 		}
    365 	}
    366 	else {
    367 		updater->state = UPDATER_VERSION_FAILED;
    368 	}
    369 	glfwPostEmptyEvent();
    370 }
    371 
    372 static WORK_QUEUE_CALLBACK( download_manifest ) {
    373 	NetAddress address;
    374 	Version version;
    375 	{
    376 		Updater * updater = SCOPED_ACQUIRE( locked_updater );
    377 		address = updater->address;
    378 		version = updater->remote_version;
    379 	}
    380 
    381 	str< 256 > path( "/{}.{}.{}.{}.txt", version.a, version.b, version.c, version.d );
    382 	std::string body;
    383 	bool download_ok = http_get( address, HOST, path.c_str(), &body ) == GET_OK;
    384 
    385 	Updater * updater = SCOPED_ACQUIRE( locked_updater );
    386 	// we haven't failed yet, but it makes bail-out code simpler. advance
    387 	// state after everything succeeds
    388 	updater->state = UPDATER_MANIFEST_FAILED;
    389 
    390 	if( !download_ok )
    391 		return;
    392 
    393 	// check the manifest signature is ok
    394 	Matches matches;
    395 	bool ok = match( &matches, body.c_str(), "(" SIGNATURE_PATTERN ")\n(.*)" );
    396 	if( !ok )
    397 		return;
    398 
    399 	u8 signature[ 64 ];
    400 	bool ok_hex = parse_hex( matches[ 0 ], signature );
    401 	ASSERT( ok_hex );
    402 
    403 	int signature_ok = crypto_check( signature, PUBLIC_KEY, ( const u8 * ) matches[ 1 ].ptr(), matches[ 1 ].n );
    404 	if( signature_ok != 0 )
    405 		return;
    406 
    407 	// parse manifest
    408 	bool parse_ok = parse_manifest( updater->remote_manifest, matches[ 1 ].ptr() );
    409 	if( !parse_ok )
    410 		return;
    411 
    412 	// look for files that have changed and files that should be removed
    413 	for( const auto & kv : updater->local_manifest ) {
    414 		const auto & it = updater->remote_manifest.find( kv.first );
    415 		if( it == updater->remote_manifest.end() ) {
    416 			updater->files_to_remove.push_back( kv.first );
    417 		}
    418 		else if( it->second.checksum != kv.second.checksum ) {
    419 			updater->files_to_update.push_back( FileUpdate( kv.first ) );
    420 			updater->update_size += it->second.file_size;
    421 		}
    422 	}
    423 
    424 	// look for new files
    425 	for( const auto & kv : updater->remote_manifest ) {
    426 		const auto & it = updater->local_manifest.find( kv.first );
    427 		if( it == updater->local_manifest.end() ) {
    428 			updater->files_to_update.push_back( FileUpdate( kv.first ) );
    429 			updater->update_size += kv.second.file_size;
    430 		}
    431 		updater->game_size += kv.second.file_size;
    432 	}
    433 
    434 	if( autostart_update ) {
    435 		updater->state = UPDATER_DOWNLOADING_PRE;
    436 	}
    437 	else {
    438 		updater->state = UPDATER_NEED_UPDATE;
    439 	}
    440 
    441 	glfwPostEmptyEvent();
    442 }
    443 
    444 static HTTP_PROGRESS_CALLBACK( download_progress ) {
    445 	SCOPED_ACQUIRE( locked_updater );
    446 	u64 * entry_bytes_downloaded = ( u64 * ) data;
    447 	*entry_bytes_downloaded = bytes_downloaded;
    448 
    449 	glfwPostEmptyEvent();
    450 }
    451 
    452 static WORK_QUEUE_CALLBACK( download_file ) {
    453 	FileUpdate * file = ( FileUpdate * ) data;
    454 
    455 	std::string file_name;
    456 	ManifestEntry entry;
    457 
    458 	NetAddress address;
    459 	Version version;
    460 	{
    461 		Updater * updater = SCOPED_ACQUIRE( locked_updater );
    462 		address = updater->address;
    463 		version = updater->remote_version;
    464 
    465 		file_name = file->file_name;
    466 		entry = updater->remote_manifest[ file_name ];
    467 	}
    468 
    469 	std::string body;
    470 	str< 128 > path( "/{}", entry.checksum );
    471 	GetResult ok = http_get( address, HOST, path.c_str(), &body, download_progress, &file->bytes_downloaded );
    472 	// TODO: real error handling
    473 	ASSERT( ok == GET_OK );
    474 
    475 	std::string local_path = "update/" + file->file_name;
    476 	recursive_mkdir( NULL, local_path.c_str() );
    477 
    478 	FILE * f = fopen( local_path.c_str(), "wb" );
    479 	if( f == NULL ) {
    480 		FATAL( "couldn't open {} for writing", local_path.c_str() );
    481 	}
    482 	fwrite( body.c_str(), 1, body.size(), f );
    483 
    484 #if PLATFORM_UNIX
    485 	if( entry.platform_specific ) {
    486 		// mark executable
    487 		int fd = fileno( f );
    488 		fchmod( fd, 0755 );
    489 	}
    490 #endif
    491 
    492 	fclose( f );
    493 
    494 	file->done = true;
    495 
    496 	glfwPostEmptyEvent();
    497 }
    498 
    499 #if PLATFORM_WINDOWS
    500 static void set_registry_key( HKEY hkey, const char * path, const char * value, const char * data ) {
    501 	HKEY key;
    502 	LONG ok_open = RegOpenKeyExA( hkey, path, 0, KEY_ALL_ACCESS | KEY_WOW64_64KEY, &key );
    503 	if( ok_open != ERROR_SUCCESS ) {
    504 		WARN( "couldn't open registry key {} {} ({})", path, value, ok_open );
    505 		return;
    506 	}
    507 	defer { RegCloseKey( key ); };
    508 
    509 	LONG ok_set = RegSetValueExA( key, value, 0, REG_SZ, ( const BYTE * ) data, checked_cast< DWORD >( strlen( data ) + 1 ) );
    510 	if( ok_set != ERROR_SUCCESS ) {
    511 		WARN( "couldn't write registry key ({})", ok_set );
    512 	}
    513 }
    514 
    515 static void set_registry_key( HKEY hkey, const char * path, const char * value, u32 data ) {
    516 	HKEY key;
    517 	LONG ok_open = RegOpenKeyExA( hkey, path, 0, KEY_ALL_ACCESS | KEY_WOW64_64KEY, &key );
    518 	if( ok_open != ERROR_SUCCESS ) {
    519 		WARN( "couldn't open registry key {} {} ({})", path, value, ok_open );
    520 		return;
    521 	}
    522 	defer { RegCloseKey( key ); };
    523 
    524 	LONG ok_set = RegSetValueExA( key, value, 0, REG_DWORD, ( const BYTE * ) &data, sizeof( data ) );
    525 	if( ok_set != ERROR_SUCCESS ) {
    526 		WARN( "couldn't write registry key ({})", ok_set );
    527 	}
    528 }
    529 #endif
    530 
    531 static WORK_QUEUE_CALLBACK( install_update ) {
    532 	std::vector< std::string > updated_files;
    533 	Version version;
    534 	std::map< std::string, ManifestEntry > manifest;
    535 
    536 	{
    537 		Updater * updater = SCOPED_ACQUIRE( locked_updater );
    538 
    539 		for( const FileUpdate & file : updater->files_to_update ) {
    540 			updated_files.push_back( file.file_name );
    541 		}
    542 
    543 		version = updater->remote_version;
    544 		manifest = updater->remote_manifest;
    545 	}
    546 
    547 	char launcher_path[ 1024 ];
    548 	int dirname_length;
    549 	int path_length = wai_getExecutablePath( launcher_path, sizeof( launcher_path ), &dirname_length );
    550 	launcher_path[ path_length ] = '\0';
    551 	const char * launcher_exe = launcher_path + dirname_length + 1;
    552 
    553 	for( const std::string & file_name : updated_files ) {
    554 		if( file_name == launcher_exe ) {
    555 			str< 1024 > old( "{}.old", launcher_path );
    556 			rename( launcher_path, old.c_str() );
    557 		}
    558 		recursive_mkdir( NULL, file_name.c_str() );
    559 		// TODO: put this somewhere proper
    560 #if PLATFORM_WINDOWS
    561 		MoveFileEx( ( "update/" + file_name ).c_str(), file_name.c_str(), MOVEFILE_REPLACE_EXISTING );
    562 #elif PLATFORM_UNIX
    563 		rename( ( "update/" + file_name ).c_str(), file_name.c_str() );
    564 #else
    565 #error new platform
    566 #endif
    567 	}
    568 
    569 
    570 	// TODO: remove update directory tree
    571 	// TODO: remove game files that are no longer needed
    572 
    573 	str< 64 > version_str( "{}.{}.{}.{}", version.a, version.b, version.c, version.d );
    574 
    575 	FILE * version_txt = fopen( "version.txt", "w" );
    576 	ggprint_to_file( version_txt, "{}\n", version_str );
    577 	fclose( version_txt );
    578 
    579 	FILE * manifest_txt = fopen( "manifest.txt", "w" );
    580 	for( const auto & kv : manifest ) {
    581 		ggprint_to_file( manifest_txt, "{} {} {}\n", kv.first.c_str(), kv.second.checksum, kv.second.file_size );
    582 	}
    583 	fclose( manifest_txt );
    584 
    585 	Updater * updater = SCOPED_ACQUIRE( locked_updater );
    586 
    587 #if PLATFORM_WINDOWS
    588 	set_registry_key( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Medfall", "DisplayVersion", version_str.c_str() );
    589 	set_registry_key( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Medfall", "EstimatedSize", clamp_u32( updater->game_size / 1024 ) );
    590 #endif
    591 
    592 	updater->local_version = version;
    593 	updater->state = UPDATER_READY;
    594 
    595 	glfwPostEmptyEvent();
    596 }
    597 
    598 static void asdf() {
    599 	double now = get_time();
    600 
    601 	Updater * updater = SCOPED_ACQUIRE( locked_updater );
    602 	bool retry = now >= updater->retry_at;
    603 	switch( updater->state ) {
    604 		case UPDATER_DNS_FAILED:
    605 			updater->retry_at = now + 5;
    606 			updater->state = UPDATER_DNS_RETRY;
    607 			break;
    608 
    609 		case UPDATER_DNS_RETRY:
    610 			if( retry ) {
    611 				updater->state = UPDATER_DNS;
    612 				workqueue_enqueue( &download_queue, lookup_update_server );
    613 			}
    614 			break;
    615 
    616 		case UPDATER_VERSION_FAILED:
    617 			updater->retry_at = now + 5;
    618 			updater->state = UPDATER_VERSION_RETRY;
    619 			break;
    620 
    621 		case UPDATER_VERSION_RETRY:
    622 			if( retry ) {
    623 				updater->state = UPDATER_VERSION;
    624 				workqueue_enqueue( &download_queue, download_remote_version );
    625 			}
    626 			break;
    627 
    628 		case UPDATER_MANIFEST_FAILED:
    629 			updater->retry_at = now + 5;
    630 			updater->state = UPDATER_MANIFEST_RETRY;
    631 			break;
    632 
    633 		case UPDATER_MANIFEST_RETRY:
    634 			if( retry ) {
    635 				updater->state = UPDATER_MANIFEST;
    636 				workqueue_enqueue( &download_queue, download_manifest );
    637 			}
    638 			break;
    639 
    640 		default:
    641 			break;
    642 	}
    643 }
    644 
    645 int main( int argc, char ** argv ) {
    646 	logger_init();
    647 	logger_thread_name( "main" );
    648 	net_init();
    649 
    650 	if( argc == 2 && strcmp( argv[ 1 ], "--start-update" ) == 0 ) {
    651 		autostart_update = true;
    652 	}
    653 
    654 	static u8 memory[ megabytes( 512 ) ];
    655 	MemoryArena arena;
    656 	memarena_init( &arena, memory, sizeof( memory ) );
    657 	workqueue_init( &download_queue, &arena, DOWNLOAD_THREADS );
    658 
    659 	GLFWwindow * window = gl_init( WINDOW_LAUNCHER );
    660 
    661 	ImGui_ImplGlfwGL3_Init( window, true );
    662 
    663 	// ImFont * small;
    664 	ImFont * large;
    665 	ImFont * verylarge;
    666 
    667 	{
    668 		u8 * data = LiberationSans_Regular_ttf;
    669 		int size = LiberationSans_Regular_ttf_len;
    670 		ImGuiIO & io = ImGui::GetIO();
    671 		ImFontConfig config;
    672 		config.FontDataOwnedByAtlas = false;
    673 		io.IniFilename = NULL;
    674 		io.Fonts->AddFontFromMemoryTTF( data, size, 16.0f, &config );
    675 		// small = io.Fonts->AddFontFromMemoryCompressedTTF( data, size, 14.0f );
    676 		large = io.Fonts->AddFontFromMemoryTTF( data, size, 32.0f, &config );
    677 		verylarge = io.Fonts->AddFontFromMemoryTTF( data, size, 128.0f, &config );
    678 	}
    679 
    680 	{
    681 		ImGuiStyle & style = ImGui::GetStyle();
    682 		style.WindowPadding = ImVec2( 32, 16 );
    683 		style.WindowRounding = 0;
    684 		style.FramePadding = ImVec2( 8, 8 );
    685 		style.Colors[ ImGuiCol_WindowBg ] = ImColor( 0x26, 0x39, 0x78 );
    686 		style.ItemSpacing.y = 8;
    687 	}
    688 
    689 	{
    690 		const char * local_version_str = file_get_contents_or_empty( "version.txt" );
    691 		const char * local_manifest_str = file_get_contents_or_empty( "manifest.txt" );
    692 
    693 		Updater * updater = SCOPED_ACQUIRE( locked_updater );
    694 		updater->retry_at = get_time();
    695 		parse_version( &updater->local_version, local_version_str );
    696 		parse_manifest( updater->local_manifest, local_manifest_str );
    697 	}
    698 
    699 	// char name[ 64 ] = "travis_rizzle";
    700 
    701 	double last_frame_time = get_time();
    702 	while( !glfwWindowShouldClose( window ) )
    703 	{
    704 		asdf();
    705 
    706 		glfwWaitEvents();
    707 		ImGui_ImplGlfwGL3_NewFrame();
    708 
    709 		double now = get_time();
    710 		double dt = now - last_frame_time;
    711 
    712 		Updater * updater = SCOPED_ACQUIRE( locked_updater );
    713 
    714 		v2u32 window_size = get_window_size();
    715 		ImGui::SetNextWindowPos( ImVec2() );
    716 		ImGui::SetNextWindowSize( ImVec2( float( window_size.x ), float( window_size.y ) ) );
    717 		ImGui::Begin( "", NULL, 0
    718 			| ImGuiWindowFlags_NoTitleBar
    719 			| ImGuiWindowFlags_NoResize
    720 			| ImGuiWindowFlags_NoMove
    721 			| ImGuiWindowFlags_NoCollapse
    722 			| ImGuiWindowFlags_NoScrollbar
    723 			| ImGuiWindowFlags_NoScrollWithMouse
    724 		);
    725 
    726 		ImGui::PushItemWidth( 900 );
    727 		ImGui::PushStyleColor( ImGuiCol_Text, ImColor( 255, 255, 255 ) );
    728 		ImGui::PushFont( verylarge );
    729 		ImGui::Text( "Medfall!!!!!!!" );
    730 		ImGui::PopFont();
    731 		ImGui::PopStyleColor();
    732 		ImGui::PopItemWidth();
    733 
    734 		ImGui::Text( "v%u.%u.%u.%u",
    735 			updater->local_version.a,
    736 			updater->local_version.b,
    737 			updater->local_version.c,
    738 			updater->local_version.d
    739 		);
    740 
    741 		// ImGui::AlignFirstTextHeightToWidgets();
    742 		// ImGui::Text( "Name:" );
    743 		// ImGui::SameLine();
    744 		// ImGui::PushItemWidth( 200 );
    745 		// ImGui::InputText( "##", name, sizeof( name ), 0
    746 		// 	| ImGuiInputTextFlags_AutoSelectAll
    747 		// );
    748 		// ImGui::PopItemWidth();
    749 
    750 		ImGui::PushFont( large );
    751 		if( updater->state == UPDATER_READY ) {
    752 			ImGui::PushStyleColor( ImGuiCol_Button, ImColor( 0x29, 0x8a, 0x67 ) );
    753 			ImGui::PushStyleColor( ImGuiCol_ButtonHovered, ImColor( 0x30, 0xa1, 0x78 ) );
    754 			ImGui::PushStyleColor( ImGuiCol_ButtonActive, ImColor( 0x38, 0xb9, 0x8a ) );
    755 
    756 			bool launch = ImGui::Button( "Play", ImVec2( -1, 50 ) );
    757 			ImGui::PopStyleColor( 3 );
    758 
    759 			if( launch ) {
    760 				exec_and_quit( GAME_BINARY );
    761 			}
    762 		}
    763 		else if( updater->state == UPDATER_NEED_UPDATE ) {
    764 			ImGui::PushStyleColor( ImGuiCol_Button, ImColor( 0x29, 0x8a, 0x67 ) );
    765 			ImGui::PushStyleColor( ImGuiCol_ButtonHovered, ImColor( 0x30, 0xa1, 0x78 ) );
    766 			ImGui::PushStyleColor( ImGuiCol_ButtonActive, ImColor( 0x38, 0xb9, 0x8a ) );
    767 
    768 			str< 256 > button_text( "Update to v{}.{}.{}.{} - {.2}Mb",
    769 				updater->remote_version.a,
    770 				updater->remote_version.b,
    771 				updater->remote_version.c,
    772 				updater->remote_version.d,
    773 				updater->update_size / 1024.0 / 1024.0
    774 			);
    775 			bool update = ImGui::Button( button_text.c_str(), ImVec2( -1, 50 ) );
    776 			ImGui::PopStyleColor( 3 );
    777 
    778 			if( update ) {
    779 #if PLATFORM_WINDOWS
    780 				exec_and_quit( "elevate_for_update.exe" );
    781 #else
    782 				updater->state = UPDATER_DOWNLOADING_PRE;
    783 #endif
    784 			}
    785 		}
    786 		else if( updater->state == UPDATER_DOWNLOADING_PRE || updater->state == UPDATER_DOWNLOADING ) {
    787 			if( updater->state == UPDATER_DOWNLOADING_PRE ) {
    788 				for( size_t i = 0; i < updater->files_to_update.size(); i++ ) {
    789 					workqueue_enqueue( &download_queue, download_file, &updater->files_to_update[ i ] );
    790 				}
    791 				updater->state = UPDATER_DOWNLOADING;
    792 			}
    793 
    794 			static u64 last_total_downloaded = 0;
    795 			u64 total_downloaded = 0;
    796 			bool all_done = true;
    797 			for( const FileUpdate & file : updater->files_to_update ) {
    798 				total_downloaded += file.bytes_downloaded;
    799 				if( !file.done ) {
    800 					all_done = false;
    801 				}
    802 			}
    803 			float frac = float( double( total_downloaded ) / double( updater->update_size ) );
    804 
    805 			static double smooth_download_speed;
    806 			static bool download_speed_set = false;
    807 
    808 			if( download_speed_set ) {
    809 				double speed = ( total_downloaded - last_total_downloaded ) / dt;
    810 				smooth_download_speed += ( speed - smooth_download_speed ) / ( 0.5 / dt );
    811 			}
    812 			else {
    813 				smooth_download_speed = total_downloaded * 60.0;
    814 				download_speed_set = true;
    815 			}
    816 
    817 			ImGui::PushStyleColor( ImGuiCol_PlotHistogram, ImColor( 0x29, 0x8a, 0x67 ) );
    818 			str< 256 > progress_text( "{.2}/{.2}Mb. {.2}% {.2}Kb/s",
    819 				total_downloaded / 1024.0 / 1024.0,
    820 				updater->update_size / 1024.0 / 1024.0,
    821 				frac * 100,
    822 				smooth_download_speed / 1024.0
    823 				);
    824 			ImGui::ProgressBar( frac, ImVec2( -1, 50 ), progress_text.c_str() );
    825 			ImGui::PopStyleColor();
    826 
    827 			last_total_downloaded = total_downloaded;
    828 
    829 			if( all_done ) {
    830 				updater->state = UPDATER_INSTALLING;
    831 				workqueue_enqueue( &download_queue, install_update );
    832 			}
    833 		}
    834 		else if( updater->state == UPDATER_INSTALLING ) {
    835 			ImGui::PushStyleColor( ImGuiCol_Button, ImColor( 0x1c, 0x2a, 0x59 ) );
    836 			ImGui::PushStyleColor( ImGuiCol_ButtonHovered, ImColor( 0x1c, 0x2a, 0x59 ) );
    837 			ImGui::PushStyleColor( ImGuiCol_ButtonActive, ImColor( 0x1c, 0x2a, 0x59 ) );
    838 			ImGui::Button( "Installing update...", ImVec2( -1, 50 ) );
    839 			ImGui::PopStyleColor( 3 );
    840 		}
    841 		else {
    842 			ImGui::PushStyleColor( ImGuiCol_Button, ImColor( 0x1c, 0x2a, 0x59 ) );
    843 			ImGui::PushStyleColor( ImGuiCol_ButtonHovered, ImColor( 0x1c, 0x2a, 0x59 ) );
    844 			ImGui::PushStyleColor( ImGuiCol_ButtonActive, ImColor( 0x1c, 0x2a, 0x59 ) );
    845 			ImGui::Button( "Checking for updates...", ImVec2( -1, 50 ) );
    846 			ImGui::PopStyleColor( 3 );
    847 		}
    848 
    849 		ImGui::PopFont();
    850 
    851 		// ImGui::BeginChildFrame( 1337, ImVec2(), 0
    852 		// 	| ImGuiWindowFlags_AlwaysVerticalScrollbar
    853 		// );
    854 		// ImGui::PushFont( small );
    855 		// for( int i = 0; i < 100; i++ ) ImGui::TextWrapped( "lots of text" );
    856 		// ImGui::PopFont();
    857 		// ImGui::EndChildFrame();
    858 
    859 		ImGui::End();
    860 
    861 		ImGui::Render();
    862 		glfwSwapBuffers( window );
    863 
    864 		last_frame_time = now;
    865 	}
    866 
    867 	ImGui_ImplGlfwGL3_Shutdown();
    868 	glfwTerminate();
    869 
    870 	logger_term();
    871 
    872 	return 0;
    873 }