game_database.cpp (50254B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "game_database.h" 5 #include "controller.h" 6 #include "host.h" 7 #include "system.h" 8 9 #include "util/cd_image.h" 10 #include "util/imgui_manager.h" 11 12 #include "common/assert.h" 13 #include "common/binary_reader_writer.h" 14 #include "common/error.h" 15 #include "common/file_system.h" 16 #include "common/heterogeneous_containers.h" 17 #include "common/log.h" 18 #include "common/path.h" 19 #include "common/string_util.h" 20 #include "common/timer.h" 21 22 #include "ryml.hpp" 23 24 #include <iomanip> 25 #include <memory> 26 #include <optional> 27 #include <sstream> 28 #include <type_traits> 29 30 #include "IconsEmoji.h" 31 #include "IconsFontAwesome5.h" 32 33 Log_SetChannel(GameDatabase); 34 35 namespace GameDatabase { 36 37 enum : u32 38 { 39 GAME_DATABASE_CACHE_SIGNATURE = 0x45434C48, 40 GAME_DATABASE_CACHE_VERSION = 14, 41 }; 42 43 static Entry* GetMutableEntry(std::string_view serial); 44 static const Entry* GetEntryForId(std::string_view code); 45 46 static bool LoadFromCache(); 47 static bool SaveToCache(); 48 49 static void SetRymlCallbacks(); 50 static bool LoadGameDBYaml(); 51 static bool ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value); 52 static bool ParseYamlCodes(u32 index, const ryml::ConstNodeRef& value, std::string_view serial); 53 static bool LoadTrackHashes(); 54 55 static constexpr const std::array<const char*, static_cast<int>(CompatibilityRating::Count)> 56 s_compatibility_rating_names = { 57 {"Unknown", "DoesntBoot", "CrashesInIntro", "CrashesInGame", "GraphicalAudioIssues", "NoIssues"}}; 58 59 static constexpr const std::array<const char*, static_cast<size_t>(CompatibilityRating::Count)> 60 s_compatibility_rating_display_names = { 61 {TRANSLATE_NOOP("GameDatabase", "Unknown"), TRANSLATE_NOOP("GameDatabase", "Doesn't Boot"), 62 TRANSLATE_NOOP("GameDatabase", "Crashes In Intro"), TRANSLATE_NOOP("GameDatabase", "Crashes In-Game"), 63 TRANSLATE_NOOP("GameDatabase", "Graphical/Audio Issues"), TRANSLATE_NOOP("GameDatabase", "No Issues")}}; 64 65 static constexpr const std::array<const char*, static_cast<u32>(GameDatabase::Trait::Count)> s_trait_names = {{ 66 "ForceInterpreter", 67 "ForceSoftwareRenderer", 68 "ForceSoftwareRendererForReadbacks", 69 "ForceRoundTextureCoordinates", 70 "ForceAccurateBlending", 71 "ForceInterlacing", 72 "DisableAutoAnalogMode", 73 "DisableTrueColor", 74 "DisableUpscaling", 75 "DisableTextureFiltering", 76 "DisableSpriteTextureFiltering", 77 "DisableScaledDithering", 78 "DisableForceNTSCTimings", 79 "DisableWidescreen", 80 "DisablePGXP", 81 "DisablePGXPCulling", 82 "DisablePGXPTextureCorrection", 83 "DisablePGXPColorCorrection", 84 "DisablePGXPDepthBuffer", 85 "DisablePGXPPreserveProjFP", 86 "DisablePGXPOn2DPolygons", 87 "ForcePGXPVertexCache", 88 "ForcePGXPCPUMode", 89 "ForceRecompilerMemoryExceptions", 90 "ForceRecompilerICache", 91 "ForceRecompilerLUTFastmem", 92 "IsLibCryptProtected", 93 }}; 94 95 static constexpr const std::array<const char*, static_cast<u32>(GameDatabase::Trait::Count)> s_trait_display_names = {{ 96 TRANSLATE_NOOP("GameDatabase", "Force Interpreter"), 97 TRANSLATE_NOOP("GameDatabase", "Force Software Renderer"), 98 TRANSLATE_NOOP("GameDatabase", "Force Software Renderer For Readbacks"), 99 TRANSLATE_NOOP("GameDatabase", "Force Round Texture Coordinates"), 100 TRANSLATE_NOOP("GameDatabase", "Force Accurate Blending"), 101 TRANSLATE_NOOP("GameDatabase", "Force Interlacing"), 102 TRANSLATE_NOOP("GameDatabase", "Disable Automatic Analog Mode"), 103 TRANSLATE_NOOP("GameDatabase", "Disable True Color"), 104 TRANSLATE_NOOP("GameDatabase", "Disable Upscaling"), 105 TRANSLATE_NOOP("GameDatabase", "Disable Texture Filtering"), 106 TRANSLATE_NOOP("GameDatabase", "Disable Sprite Texture Filtering"), 107 TRANSLATE_NOOP("GameDatabase", "Disable Scaled Dithering"), 108 TRANSLATE_NOOP("GameDatabase", "Disable Force NTSC Timings"), 109 TRANSLATE_NOOP("GameDatabase", "Disable Widescreen"), 110 TRANSLATE_NOOP("GameDatabase", "Disable PGXP"), 111 TRANSLATE_NOOP("GameDatabase", "Disable PGXP Culling"), 112 TRANSLATE_NOOP("GameDatabase", "Disable PGXP Texture Correction"), 113 TRANSLATE_NOOP("GameDatabase", "Disable PGXP Color Correction"), 114 TRANSLATE_NOOP("GameDatabase", "Disable PGXP Depth Buffer"), 115 TRANSLATE_NOOP("GameDatabase", "Disable PGXP Preserve Projection Floating Point"), 116 TRANSLATE_NOOP("GameDatabase", "Disable PGXP on 2D Polygons"), 117 TRANSLATE_NOOP("GameDatabase", "Force PGXP Vertex Cache"), 118 TRANSLATE_NOOP("GameDatabase", "Force PGXP CPU Mode"), 119 TRANSLATE_NOOP("GameDatabase", "Force Recompiler Memory Exceptions"), 120 TRANSLATE_NOOP("GameDatabase", "Force Recompiler ICache"), 121 TRANSLATE_NOOP("GameDatabase", "Force Recompiler LUT Fastmem"), 122 TRANSLATE_NOOP("GameDatabase", "Is LibCrypt Protected"), 123 }}; 124 125 static constexpr const char* GAMEDB_YAML_FILENAME = "gamedb.yaml"; 126 static constexpr const char* DISCDB_YAML_FILENAME = "discdb.yaml"; 127 128 static bool s_loaded = false; 129 static bool s_track_hashes_loaded = false; 130 131 static std::vector<GameDatabase::Entry> s_entries; 132 static PreferUnorderedStringMap<u32> s_code_lookup; 133 134 static TrackHashesMap s_track_hashes_map; 135 } // namespace GameDatabase 136 137 // RapidYAML utility routines. 138 139 ALWAYS_INLINE std::string_view to_stringview(const c4::csubstr& s) 140 { 141 return std::string_view(s.data(), s.size()); 142 } 143 144 ALWAYS_INLINE std::string_view to_stringview(const c4::substr& s) 145 { 146 return std::string_view(s.data(), s.size()); 147 } 148 149 ALWAYS_INLINE c4::csubstr to_csubstr(std::string_view sv) 150 { 151 return c4::csubstr(sv.data(), sv.length()); 152 } 153 154 static bool GetStringFromObject(const ryml::ConstNodeRef& object, std::string_view key, std::string* dest) 155 { 156 dest->clear(); 157 158 const ryml::ConstNodeRef member = object.find_child(to_csubstr(key)); 159 if (!member.valid()) 160 return false; 161 162 const c4::csubstr val = member.val(); 163 if (!val.empty()) 164 dest->assign(val.data(), val.size()); 165 166 return true; 167 } 168 169 template<typename T> 170 static bool GetUIntFromObject(const ryml::ConstNodeRef& object, std::string_view key, T* dest) 171 { 172 *dest = 0; 173 174 const ryml::ConstNodeRef member = object.find_child(to_csubstr(key)); 175 if (!member.valid()) 176 return false; 177 178 const c4::csubstr val = member.val(); 179 if (val.empty()) 180 { 181 ERROR_LOG("Unexpected empty value in {}", key); 182 return false; 183 } 184 185 const std::optional<T> opt_value = StringUtil::FromChars<T>(to_stringview(val)); 186 if (!opt_value.has_value()) 187 { 188 ERROR_LOG("Unexpected non-uint value in {}", key); 189 return false; 190 } 191 192 *dest = opt_value.value(); 193 return true; 194 } 195 196 template<typename T> 197 static std::optional<T> GetOptionalTFromObject(const ryml::ConstNodeRef& object, std::string_view key) 198 { 199 std::optional<T> ret; 200 201 const ryml::ConstNodeRef member = object.find_child(to_csubstr(key)); 202 if (member.valid()) 203 { 204 const c4::csubstr val = member.val(); 205 if (!val.empty()) 206 { 207 ret = StringUtil::FromChars<T>(to_stringview(val)); 208 if (!ret.has_value()) 209 { 210 if constexpr (std::is_floating_point_v<T>) 211 ERROR_LOG("Unexpected non-float value in {}", key); 212 else if constexpr (std::is_integral_v<T>) 213 ERROR_LOG("Unexpected non-int value in {}", key); 214 } 215 } 216 else 217 { 218 ERROR_LOG("Unexpected empty value in {}", key); 219 } 220 } 221 222 return ret; 223 } 224 225 template<typename T> 226 static std::optional<T> ParseOptionalTFromObject(const ryml::ConstNodeRef& object, std::string_view key, 227 std::optional<T> (*from_string_function)(const char* str)) 228 { 229 std::optional<T> ret; 230 231 const ryml::ConstNodeRef member = object.find_child(to_csubstr(key)); 232 if (member.valid()) 233 { 234 const c4::csubstr val = member.val(); 235 if (!val.empty()) 236 { 237 ret = from_string_function(TinyString(to_stringview(val))); 238 if (!ret.has_value()) 239 ERROR_LOG("Unknown value for {}: {}", key, to_stringview(val)); 240 } 241 else 242 { 243 ERROR_LOG("Unexpected empty value in {}", key); 244 } 245 } 246 247 return ret; 248 } 249 250 void GameDatabase::EnsureLoaded() 251 { 252 if (s_loaded) 253 return; 254 255 Common::Timer timer; 256 257 s_loaded = true; 258 259 if (!LoadFromCache()) 260 { 261 s_entries = {}; 262 s_code_lookup = {}; 263 264 LoadGameDBYaml(); 265 SaveToCache(); 266 } 267 268 INFO_LOG("Database load of {} entries took {:.0f}ms.", s_entries.size(), timer.GetTimeMilliseconds()); 269 } 270 271 void GameDatabase::Unload() 272 { 273 s_entries = {}; 274 s_code_lookup = {}; 275 s_loaded = false; 276 } 277 278 const GameDatabase::Entry* GameDatabase::GetEntryForId(std::string_view code) 279 { 280 if (code.empty()) 281 return nullptr; 282 283 EnsureLoaded(); 284 285 auto iter = s_code_lookup.find(code); 286 return (iter != s_code_lookup.end()) ? &s_entries[iter->second] : nullptr; 287 } 288 289 std::string GameDatabase::GetSerialForDisc(CDImage* image) 290 { 291 std::string ret; 292 293 const GameDatabase::Entry* entry = GetEntryForDisc(image); 294 if (entry) 295 ret = entry->serial; 296 297 return ret; 298 } 299 300 std::string GameDatabase::GetSerialForPath(const char* path) 301 { 302 std::string ret; 303 304 if (System::IsLoadableFilename(path) && !System::IsExeFileName(path) && !System::IsPsfFileName(path)) 305 { 306 std::unique_ptr<CDImage> image(CDImage::Open(path, false, nullptr)); 307 if (image) 308 ret = GetSerialForDisc(image.get()); 309 } 310 311 return ret; 312 } 313 314 const GameDatabase::Entry* GameDatabase::GetEntryForDisc(CDImage* image) 315 { 316 std::string id; 317 System::GameHash hash; 318 System::GetGameDetailsFromImage(image, &id, &hash); 319 const Entry* entry = GetEntryForGameDetails(id, hash); 320 if (entry) 321 return entry; 322 323 WARNING_LOG("No entry found for disc '{}'", id); 324 return nullptr; 325 } 326 327 const GameDatabase::Entry* GameDatabase::GetEntryForGameDetails(const std::string& id, u64 hash) 328 { 329 const Entry* entry; 330 331 if (!id.empty()) 332 { 333 entry = GetEntryForId(id); 334 if (entry) 335 return entry; 336 } 337 338 // some games with invalid serials use the hash 339 entry = GetEntryForId(System::GetGameHashId(hash)); 340 if (entry) 341 return entry; 342 343 return nullptr; 344 } 345 346 const GameDatabase::Entry* GameDatabase::GetEntryForSerial(std::string_view serial) 347 { 348 EnsureLoaded(); 349 350 return GetMutableEntry(serial); 351 } 352 353 GameDatabase::Entry* GameDatabase::GetMutableEntry(std::string_view serial) 354 { 355 for (Entry& entry : s_entries) 356 { 357 if (entry.serial == serial) 358 return &entry; 359 } 360 361 return nullptr; 362 } 363 364 const char* GameDatabase::GetTraitName(Trait trait) 365 { 366 return s_trait_names[static_cast<size_t>(trait)]; 367 } 368 369 const char* GameDatabase::GetTraitDisplayName(Trait trait) 370 { 371 return Host::TranslateToCString("GameDatabase", s_trait_display_names[static_cast<size_t>(trait)]); 372 } 373 374 const char* GameDatabase::GetCompatibilityRatingName(CompatibilityRating rating) 375 { 376 return s_compatibility_rating_names[static_cast<int>(rating)]; 377 } 378 379 const char* GameDatabase::GetCompatibilityRatingDisplayName(CompatibilityRating rating) 380 { 381 return (rating >= CompatibilityRating::Unknown && rating < CompatibilityRating::Count) ? 382 Host::TranslateToCString("GameDatabase", s_compatibility_rating_display_names[static_cast<size_t>(rating)]) : 383 ""; 384 } 385 386 void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_messages) const 387 { 388 if (display_active_start_offset.has_value()) 389 { 390 settings.display_active_start_offset = display_active_start_offset.value(); 391 if (display_osd_messages) 392 INFO_LOG("GameDB: Display active start offset set to {}.", settings.display_active_start_offset); 393 } 394 if (display_active_end_offset.has_value()) 395 { 396 settings.display_active_end_offset = display_active_end_offset.value(); 397 if (display_osd_messages) 398 INFO_LOG("GameDB: Display active end offset set to {}.", settings.display_active_end_offset); 399 } 400 if (display_line_start_offset.has_value()) 401 { 402 settings.display_line_start_offset = display_line_start_offset.value(); 403 if (display_osd_messages) 404 INFO_LOG("GameDB: Display line start offset set to {}.", settings.display_line_start_offset); 405 } 406 if (display_line_end_offset.has_value()) 407 { 408 settings.display_line_end_offset = display_line_end_offset.value(); 409 if (display_osd_messages) 410 INFO_LOG("GameDB: Display line end offset set to {}.", settings.display_line_start_offset); 411 } 412 if (dma_max_slice_ticks.has_value()) 413 { 414 settings.dma_max_slice_ticks = dma_max_slice_ticks.value(); 415 if (display_osd_messages) 416 INFO_LOG("GameDB: DMA max slice ticks set to {}.", settings.dma_max_slice_ticks); 417 } 418 if (dma_halt_ticks.has_value()) 419 { 420 settings.dma_halt_ticks = dma_halt_ticks.value(); 421 if (display_osd_messages) 422 INFO_LOG("GameDB: DMA halt ticks set to {}.", settings.dma_halt_ticks); 423 } 424 if (gpu_fifo_size.has_value()) 425 { 426 settings.gpu_fifo_size = gpu_fifo_size.value(); 427 if (display_osd_messages) 428 INFO_LOG("GameDB: GPU FIFO size set to {}.", settings.gpu_fifo_size); 429 } 430 if (gpu_max_run_ahead.has_value()) 431 { 432 settings.gpu_max_run_ahead = gpu_max_run_ahead.value(); 433 if (display_osd_messages) 434 INFO_LOG("GameDB: GPU max runahead set to {}.", settings.gpu_max_run_ahead); 435 } 436 if (gpu_pgxp_tolerance.has_value()) 437 { 438 settings.gpu_pgxp_tolerance = gpu_pgxp_tolerance.value(); 439 if (display_osd_messages) 440 INFO_LOG("GameDB: GPU PGXP tolerance set to {}.", settings.gpu_pgxp_tolerance); 441 } 442 if (gpu_pgxp_depth_threshold.has_value()) 443 { 444 settings.SetPGXPDepthClearThreshold(gpu_pgxp_depth_threshold.value()); 445 if (display_osd_messages) 446 INFO_LOG("GameDB: GPU depth clear threshold set to {}.", settings.GetPGXPDepthClearThreshold()); 447 } 448 if (gpu_line_detect_mode.has_value()) 449 { 450 settings.gpu_line_detect_mode = gpu_line_detect_mode.value(); 451 if (display_osd_messages) 452 { 453 INFO_LOG("GameDB: GPU line detect mode set to {}.", 454 Settings::GetLineDetectModeName(settings.gpu_line_detect_mode)); 455 } 456 } 457 458 SmallStackString<512> messages; 459 #define APPEND_MESSAGE(msg) \ 460 do \ 461 { \ 462 messages.append("\n \u2022 "); \ 463 messages.append(msg); \ 464 } while (0) 465 #define APPEND_MESSAGE_FMT(...) \ 466 do \ 467 { \ 468 messages.append("\n \u2022 "); \ 469 messages.append_format(__VA_ARGS__); \ 470 } while (0) 471 472 if (display_crop_mode.has_value()) 473 { 474 if (display_osd_messages && settings.display_crop_mode != display_crop_mode.value()) 475 { 476 APPEND_MESSAGE_FMT(TRANSLATE_FS("GameDatabase", "Display cropping set to {}."), 477 Settings::GetDisplayCropModeDisplayName(display_crop_mode.value())); 478 } 479 480 settings.display_crop_mode = display_crop_mode.value(); 481 } 482 483 if (display_deinterlacing_mode.has_value()) 484 { 485 if (display_osd_messages && settings.display_deinterlacing_mode != display_deinterlacing_mode.value()) 486 { 487 APPEND_MESSAGE_FMT(TRANSLATE_FS("GameDatabase", "Deinterlacing set to {}."), 488 Settings::GetDisplayDeinterlacingModeDisplayName(display_deinterlacing_mode.value())); 489 } 490 491 settings.display_deinterlacing_mode = display_deinterlacing_mode.value(); 492 } 493 494 if (HasTrait(Trait::ForceInterpreter)) 495 { 496 if (display_osd_messages && settings.cpu_execution_mode != CPUExecutionMode::Interpreter) 497 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "CPU recompiler disabled.")); 498 499 settings.cpu_execution_mode = CPUExecutionMode::Interpreter; 500 } 501 502 if (HasTrait(Trait::ForceSoftwareRenderer)) 503 { 504 if (display_osd_messages && settings.gpu_renderer != GPURenderer::Software) 505 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Hardware rendering disabled.")); 506 507 settings.gpu_renderer = GPURenderer::Software; 508 } 509 510 if (HasTrait(Trait::ForceSoftwareRendererForReadbacks)) 511 { 512 if (display_osd_messages && settings.gpu_renderer != GPURenderer::Software) 513 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Software renderer readbacks enabled.")); 514 515 settings.gpu_use_software_renderer_for_readbacks = true; 516 } 517 518 if (HasTrait(Trait::ForceRoundUpscaledTextureCoordinates)) 519 { 520 settings.gpu_force_round_texcoords = true; 521 } 522 523 if (HasTrait(Trait::ForceAccurateBlending)) 524 { 525 if (display_osd_messages && !settings.IsUsingSoftwareRenderer() && !settings.gpu_accurate_blending) 526 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Accurate blending enabled.")); 527 528 settings.gpu_accurate_blending = true; 529 } 530 531 if (HasTrait(Trait::ForceInterlacing)) 532 { 533 if (display_osd_messages && settings.gpu_disable_interlacing) 534 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Interlaced rendering enabled.")); 535 536 settings.gpu_disable_interlacing = false; 537 } 538 539 if (HasTrait(Trait::DisableTrueColor)) 540 { 541 if (display_osd_messages && settings.gpu_true_color) 542 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "True color disabled.")); 543 544 settings.gpu_true_color = false; 545 } 546 547 if (HasTrait(Trait::DisableUpscaling)) 548 { 549 if (display_osd_messages && settings.gpu_resolution_scale > 1) 550 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Upscaling disabled.")); 551 552 settings.gpu_resolution_scale = 1; 553 } 554 555 if (HasTrait(Trait::DisableTextureFiltering)) 556 { 557 if (display_osd_messages && (settings.gpu_texture_filter != GPUTextureFilter::Nearest || 558 g_settings.gpu_sprite_texture_filter != GPUTextureFilter::Nearest)) 559 { 560 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Texture filtering disabled.")); 561 } 562 563 settings.gpu_texture_filter = GPUTextureFilter::Nearest; 564 settings.gpu_sprite_texture_filter = GPUTextureFilter::Nearest; 565 } 566 567 if (HasTrait(Trait::DisableSpriteTextureFiltering)) 568 { 569 if (display_osd_messages && g_settings.gpu_sprite_texture_filter != GPUTextureFilter::Nearest) 570 { 571 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Sprite texture filtering disabled.")); 572 } 573 574 settings.gpu_sprite_texture_filter = GPUTextureFilter::Nearest; 575 } 576 577 if (HasTrait(Trait::DisableScaledDithering)) 578 { 579 if (display_osd_messages && settings.gpu_scaled_dithering) 580 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Scaled dithering.")); 581 582 settings.gpu_scaled_dithering = false; 583 } 584 585 if (HasTrait(Trait::DisableWidescreen)) 586 { 587 if (display_osd_messages && settings.gpu_widescreen_hack) 588 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Widescreen rendering disabled.")); 589 590 settings.gpu_widescreen_hack = false; 591 } 592 593 if (HasTrait(Trait::DisableForceNTSCTimings)) 594 { 595 if (display_osd_messages && settings.gpu_force_ntsc_timings) 596 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Force NTSC timings disabled.")); 597 598 settings.gpu_force_ntsc_timings = false; 599 } 600 601 if (HasTrait(Trait::DisablePGXP)) 602 { 603 if (display_osd_messages && settings.gpu_pgxp_enable) 604 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "PGXP geometry correction disabled.")); 605 606 settings.gpu_pgxp_enable = false; 607 } 608 609 if (HasTrait(Trait::DisablePGXPCulling)) 610 { 611 if (display_osd_messages && settings.gpu_pgxp_enable && settings.gpu_pgxp_culling) 612 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "PGXP culling correction disabled.")); 613 614 settings.gpu_pgxp_culling = false; 615 } 616 617 if (HasTrait(Trait::DisablePGXPTextureCorrection)) 618 { 619 if (display_osd_messages && settings.gpu_pgxp_enable && settings.gpu_pgxp_texture_correction) 620 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "PGXP perspective correct textures disabled.")); 621 622 settings.gpu_pgxp_texture_correction = false; 623 } 624 625 if (HasTrait(Trait::DisablePGXPColorCorrection)) 626 { 627 if (display_osd_messages && settings.gpu_pgxp_enable && settings.gpu_pgxp_texture_correction && 628 settings.gpu_pgxp_color_correction) 629 { 630 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "PGXP perspective correct colors disabled.")); 631 } 632 633 settings.gpu_pgxp_color_correction = false; 634 } 635 636 if (HasTrait(Trait::DisablePGXPPreserveProjFP)) 637 { 638 if (display_osd_messages && settings.gpu_pgxp_enable && settings.gpu_pgxp_preserve_proj_fp) 639 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "PGXP preserve projection precision disabled.")); 640 641 settings.gpu_pgxp_preserve_proj_fp = false; 642 } 643 644 if (HasTrait(Trait::ForcePGXPVertexCache)) 645 { 646 if (display_osd_messages && settings.gpu_pgxp_enable && !settings.gpu_pgxp_vertex_cache) 647 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "PGXP vertex cache enabled.")); 648 649 settings.gpu_pgxp_vertex_cache = settings.gpu_pgxp_enable; 650 } 651 else if (settings.gpu_pgxp_enable && settings.gpu_pgxp_vertex_cache) 652 { 653 Host::AddIconOSDMessage( 654 "gamedb_force_pgxp_vertex_cache", ICON_EMOJI_WARNING, 655 TRANSLATE_STR( 656 "GameDatabase", 657 "PGXP Vertex Cache is enabled, but it is not required for this game. This may cause rendering errors."), 658 Host::OSD_WARNING_DURATION); 659 } 660 661 if (HasTrait(Trait::ForcePGXPCPUMode)) 662 { 663 if (display_osd_messages && settings.gpu_pgxp_enable && !settings.gpu_pgxp_cpu) 664 { 665 #ifndef __ANDROID__ 666 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "PGXP CPU mode enabled.")); 667 #else 668 Host::AddIconOSDMessage("gamedb_force_pgxp_cpu", ICON_EMOJI_WARNING, 669 "This game requires PGXP CPU mode, which increases system requirements.\n" 670 " If the game runs too slow, disable PGXP for this game.", 671 Host::OSD_WARNING_DURATION); 672 #endif 673 } 674 675 settings.gpu_pgxp_cpu = settings.gpu_pgxp_enable; 676 } 677 else if (settings.UsingPGXPCPUMode()) 678 { 679 Host::AddIconOSDMessage( 680 "gamedb_force_pgxp_cpu", ICON_EMOJI_WARNING, 681 TRANSLATE_STR("GameDatabase", 682 "PGXP CPU mode is enabled, but it is not required for this game. This may cause rendering errors."), 683 Host::OSD_WARNING_DURATION); 684 } 685 686 if (HasTrait(Trait::DisablePGXPDepthBuffer)) 687 { 688 if (display_osd_messages && settings.gpu_pgxp_enable && settings.gpu_pgxp_depth_buffer) 689 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "PGXP depth buffer disabled.")); 690 691 settings.gpu_pgxp_depth_buffer = false; 692 } 693 694 if (HasTrait(Trait::DisablePGXPOn2DPolygons)) 695 { 696 if (display_osd_messages && settings.gpu_pgxp_enable && !settings.gpu_pgxp_disable_2d) 697 APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "PGXP disabled on 2D polygons.")); 698 699 g_settings.gpu_pgxp_disable_2d = true; 700 } 701 702 if (HasTrait(Trait::ForceRecompilerMemoryExceptions)) 703 { 704 WARNING_LOG("Memory exceptions for recompiler forced by compatibility settings."); 705 settings.cpu_recompiler_memory_exceptions = true; 706 } 707 708 if (HasTrait(Trait::ForceRecompilerICache)) 709 { 710 WARNING_LOG("ICache for recompiler forced by compatibility settings."); 711 settings.cpu_recompiler_icache = true; 712 } 713 714 if (settings.cpu_fastmem_mode == CPUFastmemMode::MMap && HasTrait(Trait::ForceRecompilerLUTFastmem)) 715 { 716 WARNING_LOG("LUT fastmem for recompiler forced by compatibility settings."); 717 settings.cpu_fastmem_mode = CPUFastmemMode::LUT; 718 } 719 720 if (!messages.empty()) 721 { 722 Host::AddIconOSDMessage( 723 "GameDBCompatibility", ICON_EMOJI_INFORMATION, 724 fmt::format("{}{}", TRANSLATE_SV("GameDatabase", "Compatibility settings for this game have been applied."), 725 messages.view()), 726 Host::OSD_WARNING_DURATION); 727 } 728 729 #undef APPEND_MESSAGE_FMT 730 #undef APPEND_MESSAGE 731 732 #define BIT_FOR(ctype) (static_cast<u16>(1) << static_cast<u32>(ctype)) 733 734 if (supported_controllers != 0 && supported_controllers != static_cast<u16>(-1)) 735 { 736 for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) 737 { 738 const ControllerType ctype = settings.controller_types[i]; 739 if (ctype == ControllerType::None) 740 continue; 741 742 if (supported_controllers & BIT_FOR(ctype)) 743 continue; 744 745 // Special case: Dualshock is permitted when not supported as long as it's in digital mode. 746 if (ctype == ControllerType::AnalogController && 747 (supported_controllers & BIT_FOR(ControllerType::DigitalController)) != 0) 748 { 749 continue; 750 } 751 752 if (display_osd_messages) 753 { 754 SmallString supported_controller_string; 755 for (u32 j = 0; j < static_cast<u32>(ControllerType::Count); j++) 756 { 757 const ControllerType supported_ctype = static_cast<ControllerType>(j); 758 if ((supported_controllers & BIT_FOR(supported_ctype)) == 0) 759 continue; 760 761 if (!supported_controller_string.empty()) 762 supported_controller_string.append(", "); 763 764 supported_controller_string.append(Controller::GetControllerInfo(supported_ctype)->GetDisplayName()); 765 } 766 767 Host::AddKeyedOSDMessage( 768 "gamedb_controller_unsupported", 769 fmt::format(TRANSLATE_FS("GameDatabase", 770 "Controller in port {0} ({1}) is not supported for {2}.\nSupported controllers: " 771 "{3}\nPlease configure a supported controller from the list above."), 772 i + 1u, Controller::GetControllerInfo(ctype)->GetDisplayName(), System::GetGameTitle(), 773 supported_controller_string), 774 Host::OSD_CRITICAL_ERROR_DURATION); 775 } 776 } 777 } 778 779 #undef BIT_FOR 780 } 781 782 template<typename T> 783 static inline void AppendIntegerSetting(SmallStringBase& str, bool& heading, std::string_view title, 784 const std::optional<T>& value) 785 { 786 if (!value.has_value()) 787 return; 788 789 if (!heading) 790 { 791 heading = true; 792 str.append_format("**{}**\n\n", TRANSLATE_SV("GameDatabase", "Settings")); 793 } 794 795 str.append_format(" - {}: {}\n", title, value.value()); 796 } 797 798 static inline void AppendFloatSetting(SmallStringBase& str, bool& heading, std::string_view title, 799 const std::optional<float>& value) 800 { 801 if (!value.has_value()) 802 return; 803 804 if (!heading) 805 { 806 heading = true; 807 str.append_format("**{}**\n\n", TRANSLATE_SV("GameDatabase", "Settings")); 808 } 809 810 str.append_format(" - {}: {:.2f}\n", title, value.value()); 811 } 812 813 template<typename T> 814 static inline void AppendEnumSetting(SmallStringBase& str, bool& heading, std::string_view title, 815 const char* (*get_display_name_func)(T), const std::optional<T>& value) 816 { 817 if (!value.has_value()) 818 return; 819 820 if (!heading) 821 { 822 heading = true; 823 str.append_format("**{}**\n\n", TRANSLATE_SV("GameDatabase", "Settings")); 824 } 825 826 str.append_format(" - {}: {}\n", title, get_display_name_func(value.value())); 827 } 828 829 std::string GameDatabase::Entry::GenerateCompatibilityReport() const 830 { 831 LargeString ret; 832 ret.append_format("**{}:** {}\n\n", TRANSLATE_SV("GameDatabase", "Title"), title); 833 ret.append_format("**{}:** {}\n\n", TRANSLATE_SV("GameDatabase", "Serial"), serial); 834 ret.append_format("**{}:** {}\n\n", TRANSLATE_SV("GameDatabase", "Rating"), 835 GetCompatibilityRatingDisplayName(compatibility)); 836 837 if (!compatibility_version_tested.empty()) 838 ret.append_format("**{}:**\n{}\n\n", TRANSLATE_SV("GameDatabase", "Version Tested"), compatibility_version_tested); 839 840 if (!compatibility_comments.empty()) 841 ret.append_format("**{}**\n\n{}\n\n", TRANSLATE_SV("GameDatabase", "Comments"), compatibility_comments); 842 843 if (supported_controllers != 0) 844 { 845 ret.append_format("**{}**\n\n", TRANSLATE_SV("GameDatabase", "Supported Controllers")); 846 847 for (u32 j = 0; j < static_cast<u32>(ControllerType::Count); j++) 848 { 849 if ((supported_controllers & (static_cast<u16>(1) << j)) == 0) 850 continue; 851 852 ret.append_format(" - {}\n", Controller::GetControllerInfo(static_cast<ControllerType>(j))->GetDisplayName()); 853 } 854 855 ret.append("\n"); 856 } 857 858 if (traits.any()) 859 { 860 ret.append_format("**{}**\n\n", TRANSLATE_SV("GameDatabase", "Traits")); 861 for (u32 i = 0; i < static_cast<u32>(Trait::Count); i++) 862 { 863 if (traits.test(i)) 864 ret.append_format(" - {}\n", GetTraitDisplayName(static_cast<Trait>(i))); 865 } 866 ret.append("\n"); 867 } 868 869 bool settings_heading = false; 870 AppendIntegerSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "Display Active Start Offset"), 871 display_active_start_offset); 872 AppendIntegerSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "Display Active End Offset"), 873 display_active_end_offset); 874 AppendIntegerSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "Display Line Start Offset"), 875 display_line_start_offset); 876 AppendIntegerSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "Display Line End Offset"), 877 display_line_end_offset); 878 AppendEnumSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "Display Crop Mode"), 879 &Settings::GetDisplayCropModeDisplayName, display_crop_mode); 880 AppendEnumSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "Display Deinterlacing Mode"), 881 &Settings::GetDisplayDeinterlacingModeDisplayName, display_deinterlacing_mode); 882 AppendIntegerSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "DMA Max Slice Ticks"), dma_max_slice_ticks); 883 AppendIntegerSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "DMA Halt Ticks"), dma_halt_ticks); 884 AppendIntegerSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "GPU FIFO Size"), gpu_fifo_size); 885 AppendIntegerSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "GPU Max Runahead"), gpu_max_run_ahead); 886 AppendFloatSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "GPU PGXP Tolerance"), gpu_pgxp_tolerance); 887 AppendFloatSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "GPU PGXP Depth Threshold"), 888 gpu_pgxp_depth_threshold); 889 AppendEnumSetting(ret, settings_heading, TRANSLATE_SV("GameDatabase", "GPU Line Detect Mode"), 890 &Settings::GetLineDetectModeDisplayName, gpu_line_detect_mode); 891 892 if (!disc_set_name.empty()) 893 { 894 ret.append_format("**{}:** {}\n", TRANSLATE_SV("GameDatabase", "Disc Set"), disc_set_name); 895 for (const std::string& ds_serial : disc_set_serials) 896 ret.append_format(" - {}\n", ds_serial); 897 } 898 899 return std::string(ret.view()); 900 } 901 902 static std::string GetCacheFile() 903 { 904 return Path::Combine(EmuFolders::Cache, "gamedb.cache"); 905 } 906 907 bool GameDatabase::LoadFromCache() 908 { 909 auto fp = FileSystem::OpenManagedCFile(GetCacheFile().c_str(), "rb"); 910 if (!fp) 911 { 912 DEV_LOG("Cache does not exist, loading full database."); 913 return false; 914 } 915 916 BinaryFileReader reader(fp.get()); 917 const u64 gamedb_ts = Host::GetResourceFileTimestamp("gamedb.yaml", false).value_or(0); 918 919 u32 signature, version, num_entries, num_codes; 920 u64 file_gamedb_ts; 921 if (!reader.ReadU32(&signature) || !reader.ReadU32(&version) || !reader.ReadU64(&file_gamedb_ts) || 922 !reader.ReadU32(&num_entries) || !reader.ReadU32(&num_codes) || signature != GAME_DATABASE_CACHE_SIGNATURE || 923 version != GAME_DATABASE_CACHE_VERSION) 924 { 925 DEV_LOG("Cache header is corrupted or version mismatch."); 926 return false; 927 } 928 929 if (gamedb_ts != file_gamedb_ts) 930 { 931 DEV_LOG("Cache is out of date, recreating."); 932 return false; 933 } 934 935 s_entries.reserve(num_entries); 936 937 for (u32 i = 0; i < num_entries; i++) 938 { 939 Entry& entry = s_entries.emplace_back(); 940 941 constexpr u32 num_bytes = (static_cast<u32>(Trait::Count) + 7) / 8; 942 std::array<u8, num_bytes> bits; 943 u8 compatibility; 944 u32 num_disc_set_serials; 945 946 if (!reader.ReadSizePrefixedString(&entry.serial) || !reader.ReadSizePrefixedString(&entry.title) || 947 !reader.ReadSizePrefixedString(&entry.genre) || !reader.ReadSizePrefixedString(&entry.developer) || 948 !reader.ReadSizePrefixedString(&entry.publisher) || 949 !reader.ReadSizePrefixedString(&entry.compatibility_version_tested) || 950 !reader.ReadSizePrefixedString(&entry.compatibility_comments) || !reader.ReadU64(&entry.release_date) || 951 !reader.ReadU8(&entry.min_players) || !reader.ReadU8(&entry.max_players) || !reader.ReadU8(&entry.min_blocks) || 952 !reader.ReadU8(&entry.max_blocks) || !reader.ReadU16(&entry.supported_controllers) || 953 !reader.ReadU8(&compatibility) || compatibility >= static_cast<u8>(GameDatabase::CompatibilityRating::Count) || 954 !reader.Read(bits.data(), num_bytes) || !reader.ReadOptionalT(&entry.display_active_start_offset) || 955 !reader.ReadOptionalT(&entry.display_active_end_offset) || 956 !reader.ReadOptionalT(&entry.display_line_start_offset) || 957 !reader.ReadOptionalT(&entry.display_line_end_offset) || !reader.ReadOptionalT(&entry.display_crop_mode) || 958 !reader.ReadOptionalT(&entry.display_deinterlacing_mode) || !reader.ReadOptionalT(&entry.dma_max_slice_ticks) || 959 !reader.ReadOptionalT(&entry.dma_halt_ticks) || !reader.ReadOptionalT(&entry.gpu_fifo_size) || 960 !reader.ReadOptionalT(&entry.gpu_max_run_ahead) || !reader.ReadOptionalT(&entry.gpu_pgxp_tolerance) || 961 !reader.ReadOptionalT(&entry.gpu_pgxp_depth_threshold) || !reader.ReadOptionalT(&entry.gpu_line_detect_mode) || 962 !reader.ReadSizePrefixedString(&entry.disc_set_name) || !reader.ReadU32(&num_disc_set_serials)) 963 { 964 DEV_LOG("Cache entry is corrupted."); 965 return false; 966 } 967 968 if (num_disc_set_serials > 0) 969 { 970 entry.disc_set_serials.reserve(num_disc_set_serials); 971 for (u32 j = 0; j < num_disc_set_serials; j++) 972 { 973 if (!reader.ReadSizePrefixedString(&entry.disc_set_serials.emplace_back())) 974 { 975 DEV_LOG("Cache entry is corrupted."); 976 return false; 977 } 978 } 979 } 980 981 entry.compatibility = static_cast<GameDatabase::CompatibilityRating>(compatibility); 982 entry.traits.reset(); 983 for (u32 j = 0; j < static_cast<int>(Trait::Count); j++) 984 { 985 if ((bits[j / 8] & (1u << (j % 8))) != 0) 986 entry.traits[j] = true; 987 } 988 } 989 990 for (u32 i = 0; i < num_codes; i++) 991 { 992 std::string code; 993 u32 index; 994 if (!reader.ReadSizePrefixedString(&code) || !reader.ReadU32(&index) || index >= static_cast<u32>(s_entries.size())) 995 { 996 DEV_LOG("Cache code entry is corrupted."); 997 return false; 998 } 999 1000 s_code_lookup.emplace(std::move(code), index); 1001 } 1002 1003 return true; 1004 } 1005 1006 bool GameDatabase::SaveToCache() 1007 { 1008 const u64 gamedb_ts = Host::GetResourceFileTimestamp("gamedb.yaml", false).value_or(0); 1009 1010 Error error; 1011 FileSystem::AtomicRenamedFile file = FileSystem::CreateAtomicRenamedFile(GetCacheFile(), "wb", &error); 1012 if (!file) 1013 { 1014 ERROR_LOG("Failed to open cache file for writing: {}", error.GetDescription()); 1015 return false; 1016 } 1017 1018 BinaryFileWriter writer(file.get()); 1019 writer.WriteU32(GAME_DATABASE_CACHE_SIGNATURE); 1020 writer.WriteU32(GAME_DATABASE_CACHE_VERSION); 1021 writer.WriteU64(static_cast<u64>(gamedb_ts)); 1022 1023 writer.WriteU32(static_cast<u32>(s_entries.size())); 1024 writer.WriteU32(static_cast<u32>(s_code_lookup.size())); 1025 1026 for (const Entry& entry : s_entries) 1027 { 1028 writer.WriteSizePrefixedString(entry.serial); 1029 writer.WriteSizePrefixedString(entry.title); 1030 writer.WriteSizePrefixedString(entry.genre); 1031 writer.WriteSizePrefixedString(entry.developer); 1032 writer.WriteSizePrefixedString(entry.publisher); 1033 writer.WriteSizePrefixedString(entry.compatibility_version_tested); 1034 writer.WriteSizePrefixedString(entry.compatibility_comments); 1035 writer.WriteU64(entry.release_date); 1036 writer.WriteU8(entry.min_players); 1037 writer.WriteU8(entry.max_players); 1038 writer.WriteU8(entry.min_blocks); 1039 writer.WriteU8(entry.max_blocks); 1040 writer.WriteU16(entry.supported_controllers); 1041 writer.WriteU8(static_cast<u8>(entry.compatibility)); 1042 1043 constexpr u32 num_bytes = (static_cast<u32>(Trait::Count) + 7) / 8; 1044 std::array<u8, num_bytes> bits; 1045 bits.fill(0); 1046 for (u32 j = 0; j < static_cast<int>(Trait::Count); j++) 1047 { 1048 if (entry.traits[j]) 1049 bits[j / 8] |= (1u << (j % 8)); 1050 } 1051 1052 writer.Write(bits.data(), num_bytes); 1053 1054 writer.WriteOptionalT(entry.display_active_start_offset); 1055 writer.WriteOptionalT(entry.display_active_end_offset); 1056 writer.WriteOptionalT(entry.display_line_start_offset); 1057 writer.WriteOptionalT(entry.display_line_end_offset); 1058 writer.WriteOptionalT(entry.display_crop_mode); 1059 writer.WriteOptionalT(entry.display_deinterlacing_mode); 1060 writer.WriteOptionalT(entry.dma_max_slice_ticks); 1061 writer.WriteOptionalT(entry.dma_halt_ticks); 1062 writer.WriteOptionalT(entry.gpu_fifo_size); 1063 writer.WriteOptionalT(entry.gpu_max_run_ahead); 1064 writer.WriteOptionalT(entry.gpu_pgxp_tolerance); 1065 writer.WriteOptionalT(entry.gpu_pgxp_depth_threshold); 1066 writer.WriteOptionalT(entry.gpu_line_detect_mode); 1067 1068 writer.WriteSizePrefixedString(entry.disc_set_name); 1069 writer.WriteU32(static_cast<u32>(entry.disc_set_serials.size())); 1070 for (const std::string& serial : entry.disc_set_serials) 1071 writer.WriteSizePrefixedString(serial); 1072 } 1073 1074 for (const auto& it : s_code_lookup) 1075 { 1076 writer.WriteSizePrefixedString(it.first); 1077 writer.WriteU32(it.second); 1078 } 1079 1080 return true; 1081 } 1082 1083 void GameDatabase::SetRymlCallbacks() 1084 { 1085 ryml::Callbacks callbacks = ryml::get_callbacks(); 1086 callbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location loc, void* userdata) { 1087 ERROR_LOG("Parse error at {}:{} (bufpos={}): {}", loc.line, loc.col, loc.offset, std::string_view(msg, msg_len)); 1088 }; 1089 ryml::set_callbacks(callbacks); 1090 c4::set_error_callback( 1091 [](const char* msg, size_t msg_size) { ERROR_LOG("C4 error: {}", std::string_view(msg, msg_size)); }); 1092 } 1093 1094 bool GameDatabase::LoadGameDBYaml() 1095 { 1096 const std::optional<std::string> gamedb_data = Host::ReadResourceFileToString(GAMEDB_YAML_FILENAME, false); 1097 if (!gamedb_data.has_value()) 1098 { 1099 ERROR_LOG("Failed to read game database"); 1100 return false; 1101 } 1102 1103 SetRymlCallbacks(); 1104 1105 const ryml::Tree tree = ryml::parse_in_arena(to_csubstr(GAMEDB_YAML_FILENAME), to_csubstr(gamedb_data.value())); 1106 const ryml::ConstNodeRef root = tree.rootref(); 1107 s_entries.reserve(root.num_children()); 1108 1109 for (const ryml::ConstNodeRef& current : root.children()) 1110 { 1111 // TODO: binary sort 1112 const u32 index = static_cast<u32>(s_entries.size()); 1113 Entry& entry = s_entries.emplace_back(); 1114 if (!ParseYamlEntry(&entry, current)) 1115 { 1116 s_entries.pop_back(); 1117 continue; 1118 } 1119 1120 ParseYamlCodes(index, current, entry.serial); 1121 } 1122 1123 ryml::reset_callbacks(); 1124 return !s_entries.empty(); 1125 } 1126 1127 bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value) 1128 { 1129 entry->serial = to_stringview(value.key()); 1130 if (entry->serial.empty()) 1131 { 1132 ERROR_LOG("Missing serial for entry."); 1133 return false; 1134 } 1135 1136 GetStringFromObject(value, "name", &entry->title); 1137 1138 if (const ryml::ConstNodeRef metadata = value.find_child(to_csubstr("metadata")); metadata.valid()) 1139 { 1140 GetStringFromObject(metadata, "genre", &entry->genre); 1141 GetStringFromObject(metadata, "developer", &entry->developer); 1142 GetStringFromObject(metadata, "publisher", &entry->publisher); 1143 1144 GetUIntFromObject(metadata, "minPlayers", &entry->min_players); 1145 GetUIntFromObject(metadata, "maxPlayers", &entry->max_players); 1146 GetUIntFromObject(metadata, "minBlocks", &entry->min_blocks); 1147 GetUIntFromObject(metadata, "maxBlocks", &entry->max_blocks); 1148 1149 entry->release_date = 0; 1150 { 1151 std::string release_date; 1152 if (GetStringFromObject(metadata, "releaseDate", &release_date)) 1153 { 1154 std::istringstream iss(release_date); 1155 struct tm parsed_time = {}; 1156 iss >> std::get_time(&parsed_time, "%Y-%m-%d"); 1157 if (!iss.fail()) 1158 { 1159 parsed_time.tm_isdst = 0; 1160 #ifdef _WIN32 1161 entry->release_date = _mkgmtime(&parsed_time); 1162 #else 1163 entry->release_date = timegm(&parsed_time); 1164 #endif 1165 } 1166 } 1167 } 1168 } 1169 1170 entry->supported_controllers = static_cast<u16>(~0u); 1171 1172 if (const ryml::ConstNodeRef controllers = value.find_child(to_csubstr("controllers")); 1173 controllers.valid() && controllers.has_children()) 1174 { 1175 bool first = true; 1176 for (const ryml::ConstNodeRef& controller : controllers.children()) 1177 { 1178 const std::string_view controller_str = to_stringview(controller.val()); 1179 if (controller_str.empty()) 1180 { 1181 WARNING_LOG("controller is not a string in {}", entry->serial); 1182 return false; 1183 } 1184 1185 const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(controller_str); 1186 if (!cinfo) 1187 { 1188 WARNING_LOG("Invalid controller type {} in {}", controller_str, entry->serial); 1189 continue; 1190 } 1191 1192 if (first) 1193 { 1194 entry->supported_controllers = 0; 1195 first = false; 1196 } 1197 1198 entry->supported_controllers |= (1u << static_cast<u16>(cinfo->type)); 1199 } 1200 } 1201 1202 if (const ryml::ConstNodeRef compatibility = value.find_child(to_csubstr("compatibility")); 1203 compatibility.valid() && compatibility.has_children()) 1204 { 1205 const ryml::ConstNodeRef rating = compatibility.find_child(to_csubstr("rating")); 1206 if (rating.valid()) 1207 { 1208 const std::string_view rating_str = to_stringview(rating.val()); 1209 1210 const auto iter = std::find(s_compatibility_rating_names.begin(), s_compatibility_rating_names.end(), rating_str); 1211 if (iter != s_compatibility_rating_names.end()) 1212 { 1213 const size_t rating_idx = static_cast<size_t>(std::distance(s_compatibility_rating_names.begin(), iter)); 1214 DebugAssert(rating_idx < static_cast<size_t>(CompatibilityRating::Count)); 1215 entry->compatibility = static_cast<CompatibilityRating>(rating_idx); 1216 } 1217 else 1218 { 1219 WARNING_LOG("Unknown compatibility rating {} in {}", rating_str, entry->serial); 1220 } 1221 } 1222 1223 GetStringFromObject(compatibility, "versionTested", &entry->compatibility_version_tested); 1224 GetStringFromObject(compatibility, "comments", &entry->compatibility_comments); 1225 } 1226 1227 if (const ryml::ConstNodeRef traits = value.find_child(to_csubstr("traits")); traits.valid() && traits.has_children()) 1228 { 1229 for (const ryml::ConstNodeRef& trait : traits.children()) 1230 { 1231 const std::string_view trait_str = to_stringview(trait.val()); 1232 if (trait_str.empty()) 1233 { 1234 WARNING_LOG("Empty trait in {}", entry->serial); 1235 continue; 1236 } 1237 1238 const auto iter = std::find(s_trait_names.begin(), s_trait_names.end(), trait_str); 1239 if (iter == s_trait_names.end()) 1240 { 1241 WARNING_LOG("Unknown trait {} in {}", trait_str, entry->serial); 1242 continue; 1243 } 1244 1245 const size_t trait_idx = static_cast<size_t>(std::distance(s_trait_names.begin(), iter)); 1246 DebugAssert(trait_idx < static_cast<size_t>(Trait::Count)); 1247 entry->traits[trait_idx] = true; 1248 } 1249 } 1250 1251 if (const ryml::ConstNodeRef& libcrypt = value.find_child(to_csubstr("libcrypt")); libcrypt.valid()) 1252 { 1253 if (const std::optional libcrypt_val = StringUtil::FromChars<bool>(to_stringview(libcrypt.val())); 1254 libcrypt_val.has_value()) 1255 { 1256 entry->traits[static_cast<size_t>(Trait::IsLibCryptProtected)] = true; 1257 } 1258 else 1259 { 1260 WARNING_LOG("Invalid libcrypt value in {}", entry->serial); 1261 } 1262 } 1263 1264 if (const ryml::ConstNodeRef settings = value.find_child(to_csubstr("settings")); 1265 settings.valid() && settings.has_children()) 1266 { 1267 entry->display_active_start_offset = GetOptionalTFromObject<s16>(settings, "displayActiveStartOffset"); 1268 entry->display_active_end_offset = GetOptionalTFromObject<s16>(settings, "displayActiveEndOffset"); 1269 entry->display_line_start_offset = GetOptionalTFromObject<s8>(settings, "displayLineStartOffset"); 1270 entry->display_line_end_offset = GetOptionalTFromObject<s8>(settings, "displayLineEndOffset"); 1271 entry->display_crop_mode = 1272 ParseOptionalTFromObject<DisplayCropMode>(settings, "displayCropMode", &Settings::ParseDisplayCropMode); 1273 entry->display_deinterlacing_mode = ParseOptionalTFromObject<DisplayDeinterlacingMode>( 1274 settings, "displayDeinterlacingMode", &Settings::ParseDisplayDeinterlacingMode); 1275 entry->dma_max_slice_ticks = GetOptionalTFromObject<u32>(settings, "dmaMaxSliceTicks"); 1276 entry->dma_halt_ticks = GetOptionalTFromObject<u32>(settings, "dmaHaltTicks"); 1277 entry->gpu_fifo_size = GetOptionalTFromObject<u32>(settings, "gpuFIFOSize"); 1278 entry->gpu_max_run_ahead = GetOptionalTFromObject<u32>(settings, "gpuMaxRunAhead"); 1279 entry->gpu_pgxp_tolerance = GetOptionalTFromObject<float>(settings, "gpuPGXPTolerance"); 1280 entry->gpu_pgxp_depth_threshold = GetOptionalTFromObject<float>(settings, "gpuPGXPDepthThreshold"); 1281 entry->gpu_line_detect_mode = 1282 ParseOptionalTFromObject<GPULineDetectMode>(settings, "gpuLineDetectMode", &Settings::ParseLineDetectModeName); 1283 } 1284 1285 if (const ryml::ConstNodeRef disc_set = value.find_child("discSet"); disc_set.valid() && disc_set.has_children()) 1286 { 1287 GetStringFromObject(disc_set, "name", &entry->disc_set_name); 1288 1289 if (const ryml::ConstNodeRef set_serials = disc_set.find_child("serials"); 1290 set_serials.valid() && set_serials.has_children()) 1291 { 1292 entry->disc_set_serials.reserve(set_serials.num_children()); 1293 for (const ryml::ConstNodeRef& serial : set_serials) 1294 { 1295 const std::string_view serial_str = to_stringview(serial.val()); 1296 if (serial_str.empty()) 1297 { 1298 WARNING_LOG("Empty disc set serial in {}", entry->serial); 1299 continue; 1300 } 1301 1302 if (std::find(entry->disc_set_serials.begin(), entry->disc_set_serials.end(), serial_str) != 1303 entry->disc_set_serials.end()) 1304 { 1305 WARNING_LOG("Duplicate serial {} in disc set serials for {}", serial_str, entry->serial); 1306 continue; 1307 } 1308 1309 entry->disc_set_serials.emplace_back(serial_str); 1310 } 1311 } 1312 } 1313 1314 return true; 1315 } 1316 1317 bool GameDatabase::ParseYamlCodes(u32 index, const ryml::ConstNodeRef& value, std::string_view serial) 1318 { 1319 const ryml::ConstNodeRef& codes = value.find_child(to_csubstr("codes")); 1320 if (!codes.valid() || !codes.has_children()) 1321 { 1322 // use serial instead 1323 auto iter = s_code_lookup.find(serial); 1324 if (iter != s_code_lookup.end()) 1325 { 1326 WARNING_LOG("Duplicate code '{}'", serial); 1327 return false; 1328 } 1329 1330 s_code_lookup.emplace(serial, index); 1331 return true; 1332 } 1333 1334 u32 added = 0; 1335 for (const ryml::ConstNodeRef& current_code : codes) 1336 { 1337 const std::string_view current_code_str = to_stringview(current_code.val()); 1338 if (current_code_str.empty()) 1339 { 1340 WARNING_LOG("code is not a string in {}", serial); 1341 continue; 1342 } 1343 1344 auto iter = s_code_lookup.find(current_code_str); 1345 if (iter != s_code_lookup.end()) 1346 { 1347 WARNING_LOG("Duplicate code '{}' in {}", current_code_str, serial); 1348 continue; 1349 } 1350 1351 s_code_lookup.emplace(current_code_str, index); 1352 added++; 1353 } 1354 1355 return (added > 0); 1356 } 1357 1358 void GameDatabase::EnsureTrackHashesMapLoaded() 1359 { 1360 if (s_track_hashes_loaded) 1361 return; 1362 1363 s_track_hashes_loaded = true; 1364 LoadTrackHashes(); 1365 } 1366 1367 bool GameDatabase::LoadTrackHashes() 1368 { 1369 Common::Timer load_timer; 1370 1371 std::optional<std::string> gamedb_data(Host::ReadResourceFileToString(DISCDB_YAML_FILENAME, false)); 1372 if (!gamedb_data.has_value()) 1373 { 1374 ERROR_LOG("Failed to read game database"); 1375 return false; 1376 } 1377 1378 SetRymlCallbacks(); 1379 1380 // TODO: Parse in-place, avoid string allocations. 1381 const ryml::Tree tree = ryml::parse_in_arena(to_csubstr(DISCDB_YAML_FILENAME), to_csubstr(gamedb_data.value())); 1382 const ryml::ConstNodeRef root = tree.rootref(); 1383 1384 s_track_hashes_map = {}; 1385 1386 size_t serials = 0; 1387 for (const ryml::ConstNodeRef& current : root.children()) 1388 { 1389 const std::string_view serial = to_stringview(current.key()); 1390 if (serial.empty() || !current.has_children()) 1391 { 1392 WARNING_LOG("entry is not an object"); 1393 continue; 1394 } 1395 1396 const ryml::ConstNodeRef track_data = current.find_child(to_csubstr("trackData")); 1397 if (!track_data.valid() || !track_data.has_children()) 1398 { 1399 WARNING_LOG("trackData is missing in {}", serial); 1400 continue; 1401 } 1402 1403 u32 revision = 0; 1404 for (const ryml::ConstNodeRef& track_revisions : track_data.children()) 1405 { 1406 const ryml::ConstNodeRef tracks = track_revisions.find_child(to_csubstr("tracks")); 1407 if (!tracks.valid() || !tracks.has_children()) 1408 { 1409 WARNING_LOG("tracks member is missing in {}", serial); 1410 continue; 1411 } 1412 1413 std::string revision_string; 1414 GetStringFromObject(track_revisions, "version", &revision_string); 1415 1416 for (const ryml::ConstNodeRef& track : tracks) 1417 { 1418 const ryml::ConstNodeRef md5 = track.find_child("md5"); 1419 std::string_view md5_str; 1420 if (!md5.valid() || (md5_str = to_stringview(md5.val())).empty()) 1421 { 1422 WARNING_LOG("md5 is missing in track in {}", serial); 1423 continue; 1424 } 1425 1426 const std::optional<CDImageHasher::Hash> md5o = CDImageHasher::HashFromString(md5_str); 1427 if (md5o.has_value()) 1428 { 1429 s_track_hashes_map.emplace(std::piecewise_construct, std::forward_as_tuple(md5o.value()), 1430 std::forward_as_tuple(std::string(serial), revision_string, revision)); 1431 } 1432 else 1433 { 1434 WARNING_LOG("invalid md5 in {}", serial); 1435 } 1436 } 1437 revision++; 1438 } 1439 1440 serials++; 1441 } 1442 1443 ryml::reset_callbacks(); 1444 INFO_LOG("Loaded {} track hashes from {} serials in {:.0f}ms.", s_track_hashes_map.size(), serials, 1445 load_timer.GetTimeMilliseconds()); 1446 return !s_track_hashes_map.empty(); 1447 } 1448 1449 const GameDatabase::TrackHashesMap& GameDatabase::GetTrackHashesMap() 1450 { 1451 EnsureTrackHashesMapLoaded(); 1452 return s_track_hashes_map; 1453 }