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 }