#include "../bbnet/room/room_service.h" #include "bbport_write_log.h" #include #include "bbport_gnm_hooks.h" #include "bbport_overlay.h" #include "bbport_settings.h" #include "bbport_copy.h" #ifndef _WIN32 #include #endif #include "bbport_free_check.h" #include "bbport_sampler.h" #include "bbport_freeze.h" #include "bbport_platform.h" #include "bbport_toggles.h" #include "engine/engine_hooks.h" #include "engine/batch.h" #include #include #include #include #include #include "common/polyfill_thread.h" #include "video_core/renderdoc.h" #include #include #include #include #include #include #include #include #include #include "../bbgpu.h" #include "common/elf_info.h" #include "shader_recompiler/native/table_plan.h" #include "common/logging/log.h" #include "common/rdtsc.h" #include "common/signal_context.h" #include "core/libraries/kernel/orbis_error.h" #include "core/libraries/libs.h" #include "core/emulator_settings.h" #include "core/memory.h" #include "core/signals.h" #include "sdl_window.h" #include "video_core/renderer_vulkan/vk_presenter.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" extern "C" { int runtime_memory_is_mapped(uintptr_t address, uint64_t size); int runtime_memory_write_backing(uintptr_t address, const void* data, uint64_t size); void runtime_memory_read_backing(uintptr_t address, void* data, uint64_t size); uint64_t runtime_memory_clamp(uintptr_t address, uint64_t size); int runtime_memory_region(uintptr_t address, uintptr_t* start, uintptr_t* end, int* mapped); const uint64_t* runtime_memory_generation(void); void runtime_memory_gpu_protect(uintptr_t address, uint64_t size, int read, int write); typedef void (*RuntimeGpuRange)(uintptr_t address, uint64_t size); uint64_t runtime_process_time_us(void); uint64_t runtime_process_time_counter(void); uint64_t runtime_tsc_frequency(void); int32_t* runtime_errno(void); void runtime_memory_set_gpu_hooks(RuntimeGpuRange map, RuntimeGpuRange unmap, RuntimeGpuRange invalidate); void runtime_memory_set_note_write_hook(RuntimeGpuRange note); void runtime_memory_set_cpu_write_hook(RuntimeGpuRange hook); int runtime_memory_restore_access(void* context, uintptr_t address, int repeated); } namespace Shader { void RegisterSrtFaultHandler(); } Frontend::WindowSDL* g_window = nullptr; extern std::unique_ptr presenter; namespace Libraries::GnmDriver { void RegisterLib(Core::Loader::SymbolsResolver* sym); } extern "C" void lantern_av_decode_start(void); namespace Libraries::VideoOut { void Start(); } namespace { struct Symbol { std::string nid, library, module; u64 address; }; std::vector g_symbols; u32 g_sdk_version; } namespace Core::Loader { void SymbolsResolver::AddSymbol(const char* nid, const char* library, const char* module, SymbolType, u64 address) { g_symbols.push_back({nid, library, module, address}); } } namespace Core { class Emulator { public: static void FillElfInfo(const BbGpuConfig& config) { auto& info = Common::ElfInfo::Instance(); info.initialized = true; info.game_serial = config.serial ? config.serial : "UNKNOWN"; info.title = config.title ? config.title : ""; info.sdk_ver = config.sdk_version; info.psf_attributes.raw = config.psf_attributes; const char* app0 = std::getenv("BB_GAME_DIR"); if (app0 && *app0) { info.game_folder = std::filesystem::path(app0); info.app_ver = "01.09"; std::ifstream file(info.game_folder / "sce_sys/npbind.dat", std::ios::binary); if (file) { file.seekg(0, std::ios::end); const auto length = file.tellg(); if (length >= 0x80 && length <= 1024 * 1024) { file.seekg(0); std::vector bytes(static_cast(length)); if (file.read(reinterpret_cast(bytes.data()), bytes.size())) { info.npCommIds.clear(); for (size_t at = 0x80; at + 4 <= bytes.size();) { const u16 tag = u16(bytes[at] << 8 | bytes[at + 1]); const u16 size = u16(bytes[at + 2] << 8 | bytes[at + 3]); at += 4; if (size > bytes.size() - at) break; if (tag == 0x10 && size) { const auto end = std::find(bytes.begin() + at, bytes.begin() + at + size, 0); info.npCommIds.emplace_back(bytes.begin() + at, end); } at += size; } } } } } } }; void MemoryManager::SetRasterizer(Vulkan::Rasterizer* rasterizer_) { rasterizer = rasterizer_; Shader::Native::TablePlan::guest_readable = [](u64 address, u64 bytes) { return Memory::Instance()->ClampRangeSize(address, bytes) >= bytes; }; runtime_memory_set_gpu_hooks( [](uintptr_t address, uint64_t size) { auto* r = Memory::Instance()->GetRasterizer(); r->RegisterMemory(address, size); r->MapMemory(address, size); }, [](uintptr_t address, uint64_t size) { Memory::Instance()->GetRasterizer()->UnmapMemory(address, size); }, [](uintptr_t address, uint64_t size) { Memory::Instance()->GetRasterizer()->InvalidateMemory(address, size); }); runtime_memory_set_note_write_hook([](uintptr_t address, uint64_t size) { Memory::Instance()->GetRasterizer()->NoteAssetWrite(address, size); }); runtime_memory_set_cpu_write_hook([](uintptr_t address, uint64_t size) { Memory::Instance()->GetRasterizer()->OnCpuWrite(address, size); }); } void MemoryManager::InvalidateMemory(VAddr address, u64 size) { if (rasterizer) rasterizer->InvalidateMemory(address, size); } namespace { struct RegionCache { u64 generation = 1; uintptr_t start = 0, end = 0; }; thread_local RegionCache region_cache; bool CachedMapped(VAddr address, u64 size) { static const uint64_t* const generation_ptr = runtime_memory_generation(); const u64 generation = __atomic_load_n(generation_ptr, __ATOMIC_ACQUIRE); if (generation & 1) { return false; } auto& cache = region_cache; if (cache.generation == generation && address >= cache.start && address + size <= cache.end) { return true; } uintptr_t start = 0, end = 0; int mapped = 0; if (!runtime_memory_region(address, &start, &end, &mapped) || !mapped) { return false; } cache = {generation, start, end}; return address + size <= end; } } u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) { if (size && CachedMapped(virtual_addr, size)) { return size; } return runtime_memory_clamp(virtual_addr, size); } bool MemoryManager::IsValidMapping(VAddr virtual_addr, u64 size) { const u64 wanted = size ? size : 1; return virtual_addr && runtime_memory_clamp(virtual_addr, wanted) >= wanted; } static void CopySparseSerial(VAddr source, u8* dest, u64 size) { static const bool precise = EmulatorSettings.GetReadbacksMode() == GpuReadbacksMode::Precise; if (precise) { runtime_memory_read_backing(source, dest, size); return; } if (size && CachedMapped(source, size)) { std::memcpy(dest, reinterpret_cast(source), size); return; } while (size) { uintptr_t start = 0, end = 0; int mapped = 0; if (!runtime_memory_region(source, &start, &end, &mapped)) { end = source + size; } const u64 n = std::min(size, end - source); if (mapped) std::memcpy(dest, reinterpret_cast(source), n); else std::memset(dest, 0, n); source += n; dest += n; size -= n; } } void MemoryManager::CopySparseMemory(VAddr source, u8* dest, u64 size) { constexpr u64 Chunk = 512 * 1024; if (size < 4 * Chunk || !BbCopy::Enabled()) { return CopySparseSerial(source, dest, size); } BbCopy::ParallelFor((size + Chunk - 1) / Chunk, [&](std::size_t i) { const u64 offset = i * Chunk; CopySparseSerial(source + offset, dest + offset, std::min(Chunk, size - offset)); }); } bool MemoryManager::TryWriteBacking(void* address, const void* data, u64 size) { BbWriteLog::Note(reinterpret_cast(address), data, size, BbWriteLog::Backing); return runtime_memory_write_backing(reinterpret_cast(address), data, size) != 0; } void AddressSpace::Protect(VAddr virtual_addr, u64 size, MemoryPermission perms) { BbStats::Timer timer{BbStats::t_protect}; const u64 pages = (size + 4095) / 4096; BbStats::protect_calls.fetch_add(1, std::memory_order_relaxed); BbStats::protect_pages.fetch_add(pages, std::memory_order_relaxed); if (!True(perms & MemoryPermission::Write)) { BbStats::protect_revoke_calls.fetch_add(1, std::memory_order_relaxed); BbStats::protect_revoke_pages.fetch_add(pages, std::memory_order_relaxed); } runtime_memory_gpu_protect(virtual_addr, size, True(perms & MemoryPermission::Read), True(perms & MemoryPermission::Write)); } boost::icl::interval_set AddressSpace::GetUsableRegions() { boost::icl::interval_set set; set += boost::icl::interval::right_open(0x10'0000'0000ULL, 0xfc'0000'0000ULL); return set; } } namespace Libraries::Kernel { u64 PS4_SYSV_ABI sceKernelGetTscFrequency() { return runtime_tsc_frequency(); } u64 PS4_SYSV_ABI sceKernelReadTsc() { return Common::FencedRDTSC(); } u64 PS4_SYSV_ABI sceKernelGetProcessTime() { return runtime_process_time_us(); } u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter() { return runtime_process_time_counter(); } u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency() { return 1'000'000'000; } s32 PS4_SYSV_ABI sceKernelIsNeoMode() { return 0; } s32 PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(s32* ver) { if (!ver) return ORBIS_KERNEL_ERROR_EINVAL; *ver = s32(g_sdk_version); return ORBIS_OK; } s32 PS4_SYSV_ABI sceKernelUsleep(u32 microseconds) { std::this_thread::sleep_for(std::chrono::microseconds(microseconds)); return ORBIS_OK; } } namespace { std::thread g_window_thread; std::mutex g_window_mutex; std::condition_variable g_window_cv; bool g_window_ready; long EnvMs(const char* name, long fallback) { const char* value = std::getenv(name); if (!value || !*value) { return fallback; } char* end = nullptr; const long ms = std::strtol(value, &end, 10); return (end && *end == '\0' && ms >= 0 && ms <= 60'000) ? ms : fallback; } [[noreturn]] void TerminateNow(int code) { if (BbRoom::Active()) { auto done = std::make_shared>(false); std::thread([done] { BbRoom::Shutdown(); done->store(true); }).detach(); for (int i = 0; i < 150 && !done->load(); ++i) std::this_thread::sleep_for(std::chrono::milliseconds(10)); } std::fflush(stdout); const char* mode = std::getenv("BB_EXIT_MODE"); if (mode && std::strcmp(mode, "exit") == 0) { std::_Exit(code); } BbPlatform::TerminateProcessNow(static_cast(code)); } } #ifdef BB_PGO_GENERATE #ifdef __clang__ extern "C" int __llvm_profile_write_file(void); extern "C" void __llvm_profile_reset_counters(void); static void ProfileDump() { __llvm_profile_write_file(); } static void ProfileReset() { __llvm_profile_reset_counters(); } #else extern "C" void __gcov_dump(void); extern "C" void __gcov_reset(void); static void ProfileDump() { __gcov_dump(); } static void ProfileReset() { __gcov_reset(); } #endif static void StartProfileWriter() { std::thread([] { Common::SetCurrentThreadName("bb:pgo"); for (;;) { std::this_thread::sleep_for(std::chrono::seconds(30)); ProfileDump(); ProfileReset(); std::printf("PGO: profile written\n"); } }).detach(); } #endif extern "C" int bbgpu_init(const BbGpuConfig* config) { BbSettings::Load(); Shader::RegisterSrtFaultHandler(); #ifdef BB_PGO_GENERATE StartProfileWriter(); #endif BbSampler::Start(); BbFreeze::SetStateReporter([] { if (presenter) { presenter->ReportState(); } }); g_sdk_version = config->sdk_version; #ifdef _WIN32 if (config->user_dir && !std::getenv("BB_GPU_USER_DIR")) _putenv_s("BB_GPU_USER_DIR", config->user_dir); #else if (config->user_dir) setenv("BB_GPU_USER_DIR", config->user_dir, 0); #endif Core::Emulator::FillElfInfo(*config); const std::string title = config->title ? config->title : "Bloodborne"; const s32 width = config->width, height = config->height; g_window_thread = std::thread([title, width, height] { Common::SetCurrentThreadName("bb:window"); auto* window = new Frontend::WindowSDL(width, height, title.c_str()); { std::scoped_lock lock{g_window_mutex}; g_window = window; g_window_ready = true; } g_window_cv.notify_all(); while (window->PollEvents()) { SDL_WaitEventTimeout(nullptr, 2); } LOG_INFO(Frontend, "Window closed by user"); const auto exit_started = std::chrono::steady_clock::now(); const long watchdog_ms = EnvMs("BB_EXIT_WATCHDOG_MS", 4000); std::thread([watchdog_ms] { std::this_thread::sleep_for(std::chrono::milliseconds(watchdog_ms)); std::printf("Exit: watchdog ended the process after %ld ms\n", watchdog_ms); TerminateNow(0); }).detach(); std::atomic cache_saved{false}; std::thread([&cache_saved] { Vulkan::PipelineCache::SaveAllForShutdown(); cache_saved.store(true, std::memory_order_release); cache_saved.notify_all(); }).detach(); if (SDL_Window* sdl_window = window->GetSDLWindow()) { SDL_SetWindowFullscreen(sdl_window, false); SDL_HideWindow(sdl_window); } if (presenter) { presenter->WaitForSubmittedWork(u64(EnvMs("BB_EXIT_GPU_WAIT_MS", 1500)) * 1'000'000ull); } bool saved = cache_saved.load(std::memory_order_acquire); while (!saved) { cache_saved.wait(false, std::memory_order_acquire); saved = cache_saved.load(std::memory_order_acquire); } BbEngine::Batch::Report("exit"); std::printf("Exit: clean shutdown in %lld ms\n", static_cast(std::chrono::duration_cast( std::chrono::steady_clock::now() - exit_started) .count())); Common::Log::Flush(); TerminateNow(0); }); g_window_thread.detach(); { std::unique_lock lock{g_window_mutex}; g_window_cv.wait(lock, [] { return g_window_ready; }); } Core::Loader::SymbolsResolver resolver; Libraries::GnmDriver::RegisterLib(&resolver); Libraries::VideoOut::Start(); return 0; } extern "C" void bbnet_register(void); extern "C" void bbnet_initialize(void); extern "C" void bbgpu_start_online(void) { const char* online = std::getenv("BB_ONLINE"); if (online && online[0] == '1') bbnet_initialize(); } extern "C" void bbgpu_register_kernel(void) { Core::Loader::SymbolsResolver resolver; lantern_av_decode_start(); const char* online = std::getenv("BB_ONLINE"); if (online && online[0] == '1') bbnet_register(); } extern "C" uintptr_t bbgpu_resolve(const char* scoped_nid) { const char* hash = std::strchr(scoped_nid, '#'); const size_t length = hash ? size_t(hash - scoped_nid) : std::strlen(scoped_nid); for (const auto& symbol : g_symbols) { if (symbol.nid.size() == length && !std::memcmp(symbol.nid.data(), scoped_nid, length)) { return uintptr_t(symbol.address); } } return 0; } namespace { struct RepeatedFault { u64 rip = 0, address = 0, last_tsc = 0; u32 count = 0; }; thread_local RepeatedFault repeated_fault; bool FaultRepeats(void* ucontext, void* address) { constexpr u64 RepeatCycles = 4'000'000; constexpr u32 RepeatLimit = 64; auto& r = repeated_fault; const u64 rip = reinterpret_cast(Common::GetRip(ucontext)); const u64 now = Common::FencedRDTSC(); if (rip != r.rip || reinterpret_cast(address) != r.address || now - r.last_tsc > RepeatCycles) { r = {rip, reinterpret_cast(address), now, 0}; return false; } r.last_tsc = now; if (++r.count < RepeatLimit) { return false; } r.count = 0; return true; } } extern "C" int bbgpu_handle_fault(void* ucontext, void* address) { if (BbFreeCheck::OnTrapFault(ucontext, reinterpret_cast(address))) { return 1; } if (bb_fault_recovers_only && runtime_fault_recover) { return 0; } if (Core::Signals::Instance()->DispatchAccessViolation(ucontext, address)) { if (FaultRepeats(ucontext, address)) { runtime_memory_restore_access(ucontext, reinterpret_cast(address), 1); } return 1; } if (BbFreeCheck::OnStaleTrapFault(reinterpret_cast(address))) { return 1; } return runtime_memory_restore_access(ucontext, reinterpret_cast(address), 0); } extern "C" void bbnet_set_image(unsigned char* image, uint64_t size); extern "C" void bbgpu_patch_image(unsigned char* image, uint64_t size) { bbnet_set_image(image, size); BbGnmHooks::PatchImage(image, size); BbEngine::Install(image, size); } extern "C" unsigned bbgpu_symbol_count(void) { return unsigned(g_symbols.size()); } namespace Libraries::Kernel { int* PS4_SYSV_ABI __Error() { return runtime_errno(); } } namespace VideoCore { void LoadRenderDoc() {} void StartCapture() {} void EndCapture() {} void TriggerCapture() {} void SetOutputDir(const std::filesystem::path&, const std::string&) {} bool IsRenderDocLoaded() { return false; } void RequestScreenshot(ScreenshotRequest) {} u32 ConsumeGameOnlyScreenshotRequests() { return 0; } u32 ConsumeWithOverlaysScreenshotRequests() { return 0; } ScreenshotRequests ConsumeScreenshotRequests() { return {}; } } extern "C" int bbgpu_overlay_captures_input(void) { return BbOverlay::CapturesInput() ? 1 : 0; } extern "C" int bbgpu_window_in_background(void) { return Frontend::WindowInBackground() ? 1 : 0; } extern "C" int bbgpu_text_input_begin(const char* initial, const char* prompt) { if (!g_window) return 0; g_window->BeginTextInput(initial ? initial : "", prompt ? prompt : "Text"); return 1; } extern "C" int bbgpu_text_input_poll(char* out, uint64_t size) { if (!g_window) return 2; std::string text; const int state = g_window->PollTextInput(text); if (size) { const size_t n = std::min(text.size(), size - 1); std::memcpy(out, text.data(), n); out[n] = 0; } return state; }