std::string GetInfoStringOfSlot(int slot, bool translate) { std::string filename = MakeStateFilename(slot); if (!File::Exists(filename)) return translate ? GetStringT("Empty") : "Empty"; State::StateHeader header; if (!ReadHeader(filename, header)) return translate ? GetStringT("Unknown") : "Unknown"; return Common::Timer::GetDateTimeFormatted(header.time); }
void VertexShaderCache::WaitForBackgroundCompilesToComplete() { g_async_compiler->WaitUntilCompletion([](size_t completed, size_t total) { Host_UpdateProgressDialog(GetStringT("Compiling shaders...").c_str(), static_cast<int>(completed), static_cast<int>(total)); }); g_async_compiler->RetrieveWorkItems(); Host_UpdateProgressDialog("", -1, -1); // Switch from precompile -> runtime compiler threads. g_async_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderCompilerThreads()); }
std::string FormatSize(u64 bytes) { // i18n: The symbol for the unit "bytes" const char* const unit_symbols[] = {_trans("B"), _trans("KiB"), _trans("MiB"), _trans("GiB"), _trans("TiB"), _trans("PiB"), _trans("EiB")}; // Find largest power of 2 less than size. // div 10 to get largest named unit less than size // 10 == log2(1024) (number of B in a KiB, KiB in a MiB, etc) // Max value is 63 / 10 = 6 const int unit = IntLog2(std::max<u64>(bytes, 1)) / 10; // Don't need exact values, only 5 most significant digits const double unit_size = std::pow(2, unit * 10); return StringFromFormat("%.2f %s", bytes / unit_size, GetStringT(unit_symbols[unit]).c_str()); }
void HiresTexture::Prefetch() { Common::SetCurrentThreadName("Prefetcher"); const size_t total = s_textureMap.size(); size_t count = 0; size_t notification = 10; u32 starttime = Common::Timer::GetTimeMs(); for (const auto& entry : s_textureMap) { const std::string& base_filename = entry.first; std::unique_lock<std::mutex> lk(s_textureCacheMutex); auto iter = s_textureCache.find(base_filename); if (iter == s_textureCache.end()) { lk.unlock(); HiresTexture* ptr = Load(base_filename, [](size_t requested_size) { return new u8[requested_size]; }, true); lk.lock(); if (ptr != nullptr) { size_sum.fetch_add(ptr->m_cached_data_size); iter = s_textureCache.insert( iter, std::make_pair(base_filename, std::shared_ptr<HiresTexture>(ptr))); } } if (s_textureCacheAbortLoading.IsSet()) { if (g_ActiveConfig.bWaitForCacheHiresTextures) { Host_UpdateProgressDialog("", -1, -1); } return; } if (size_sum.load() > max_mem) { Config::SetCurrent(Config::GFX_HIRES_TEXTURES, false); OSD::AddMessage( StringFromFormat( "Custom Textures prefetching after %.1f MB aborted, not enough RAM available", size_sum / (1024.0 * 1024.0)), 10000); if (g_ActiveConfig.bWaitForCacheHiresTextures) { Host_UpdateProgressDialog("", -1, -1); } return; } count++; size_t percent = (count * 100) / total; if (percent >= notification) { if (g_ActiveConfig.bWaitForCacheHiresTextures) { Host_UpdateProgressDialog(GetStringT("Prefetching Custom Textures...").c_str(), static_cast<int>(count), static_cast<int>(total)); } else { OSD::AddMessage(StringFromFormat("Custom Textures prefetching %.1f MB %zu %% finished", size_sum / (1024.0 * 1024.0), percent), 2000); } notification += 10; } } if (g_ActiveConfig.bWaitForCacheHiresTextures) { Host_UpdateProgressDialog("", -1, -1); } u32 stoptime = Common::Timer::GetTimeMs(); OSD::AddMessage(StringFromFormat("Custom Textures loaded, %.1f MB in %.1f s", size_sum / (1024.0 * 1024.0), (stoptime - starttime) / 1000.0), 10000); }
bool DecompressBlobToFile(const std::string& infile_path, const std::string& outfile_path, CompressCB callback, void* arg) { std::unique_ptr<CompressedBlobReader> reader; { File::IOFile infile(infile_path, "rb"); if (!IsGCZBlob(infile)) { PanicAlertT("File not compressed"); return false; } reader = CompressedBlobReader::Create(std::move(infile), infile_path); } if (!reader) { PanicAlertT("Failed to open the input file \"%s\".", infile_path.c_str()); return false; } File::IOFile outfile(outfile_path, "wb"); if (!outfile) { PanicAlertT("Failed to open the output file \"%s\".\n" "Check that you have permissions to write the target folder and that the media can " "be written.", outfile_path.c_str()); return false; } const CompressedBlobHeader& header = reader->GetHeader(); static const size_t BUFFER_BLOCKS = 32; size_t buffer_size = header.block_size * BUFFER_BLOCKS; size_t last_buffer_size = header.block_size * (header.num_blocks % BUFFER_BLOCKS); std::vector<u8> buffer(buffer_size); u32 num_buffers = (header.num_blocks + BUFFER_BLOCKS - 1) / BUFFER_BLOCKS; int progress_monitor = std::max<int>(1, num_buffers / 100); bool success = true; for (u64 i = 0; i < num_buffers; i++) { if (i % progress_monitor == 0) { bool was_cancelled = !callback(GetStringT("Unpacking"), (float)i / (float)num_buffers, arg); if (was_cancelled) { success = false; break; } } const size_t sz = i == num_buffers - 1 ? last_buffer_size : buffer_size; reader->Read(i * buffer_size, sz, buffer.data()); if (!outfile.WriteBytes(buffer.data(), sz)) { PanicAlertT("Failed to write the output file \"%s\".\n" "Check that you have enough space available on the target drive.", outfile_path.c_str()); success = false; break; } } if (!success) { // Remove the incomplete output file. outfile.Close(); File::Delete(outfile_path); } else { outfile.Resize(header.data_size); } return success; }
bool CompressFileToBlob(const std::string& infile_path, const std::string& outfile_path, u32 sub_type, int block_size, CompressCB callback, void* arg) { bool scrubbing = false; File::IOFile infile(infile_path, "rb"); if (IsGCZBlob(infile)) { PanicAlertT("\"%s\" is already compressed! Cannot compress it further.", infile_path.c_str()); return false; } if (!infile) { PanicAlertT("Failed to open the input file \"%s\".", infile_path.c_str()); return false; } File::IOFile outfile(outfile_path, "wb"); if (!outfile) { PanicAlertT("Failed to open the output file \"%s\".\n" "Check that you have permissions to write the target folder and that the media can " "be written.", outfile_path.c_str()); return false; } DiscScrubber disc_scrubber; if (sub_type == 1) { if (!disc_scrubber.SetupScrub(infile_path, block_size)) { PanicAlertT("\"%s\" failed to be scrubbed. Probably the image is corrupt.", infile_path.c_str()); return false; } scrubbing = true; } z_stream z = {}; if (deflateInit(&z, 9) != Z_OK) return false; callback(GetStringT("Files opened, ready to compress."), 0, arg); CompressedBlobHeader header; header.magic_cookie = GCZ_MAGIC; header.sub_type = sub_type; header.block_size = block_size; header.data_size = infile.GetSize(); // round upwards! header.num_blocks = (u32)((header.data_size + (block_size - 1)) / block_size); std::vector<u64> offsets(header.num_blocks); std::vector<u32> hashes(header.num_blocks); std::vector<u8> out_buf(block_size); std::vector<u8> in_buf(block_size); // seek past the header (we will write it at the end) outfile.Seek(sizeof(CompressedBlobHeader), SEEK_CUR); // seek past the offset and hash tables (we will write them at the end) outfile.Seek((sizeof(u64) + sizeof(u32)) * header.num_blocks, SEEK_CUR); // seek to the start of the input file to make sure we get everything infile.Seek(0, SEEK_SET); // Now we are ready to write compressed data! u64 position = 0; int num_compressed = 0; int num_stored = 0; int progress_monitor = std::max<int>(1, header.num_blocks / 1000); bool success = true; for (u32 i = 0; i < header.num_blocks; i++) { if (i % progress_monitor == 0) { const u64 inpos = infile.Tell(); int ratio = 0; if (inpos != 0) ratio = (int)(100 * position / inpos); std::string temp = StringFromFormat(GetStringT("%i of %i blocks. Compression ratio %i%%").c_str(), i, header.num_blocks, ratio); bool was_cancelled = !callback(temp, (float)i / (float)header.num_blocks, arg); if (was_cancelled) { success = false; break; } } offsets[i] = position; size_t read_bytes; if (scrubbing) read_bytes = disc_scrubber.GetNextBlock(infile, in_buf.data()); else infile.ReadArray(in_buf.data(), header.block_size, &read_bytes); if (read_bytes < header.block_size) std::fill(in_buf.begin() + read_bytes, in_buf.begin() + header.block_size, 0); int retval = deflateReset(&z); z.next_in = in_buf.data(); z.avail_in = header.block_size; z.next_out = out_buf.data(); z.avail_out = block_size; if (retval != Z_OK) { ERROR_LOG(DISCIO, "Deflate failed"); success = false; break; } int status = deflate(&z, Z_FINISH); int comp_size = block_size - z.avail_out; u8* write_buf; int write_size; if ((status != Z_STREAM_END) || (z.avail_out < 10)) { // PanicAlert("%i %i Store %i", i*block_size, position, comp_size); // let's store uncompressed write_buf = in_buf.data(); offsets[i] |= 0x8000000000000000ULL; write_size = block_size; num_stored++; } else { // let's store compressed // PanicAlert("Comp %i to %i", block_size, comp_size); write_buf = out_buf.data(); write_size = comp_size; num_compressed++; } if (!outfile.WriteBytes(write_buf, write_size)) { PanicAlertT("Failed to write the output file \"%s\".\n" "Check that you have enough space available on the target drive.", outfile_path.c_str()); success = false; break; } position += write_size; hashes[i] = HashAdler32(write_buf, write_size); } header.compressed_data_size = position; if (!success) { // Remove the incomplete output file. outfile.Close(); File::Delete(outfile_path); } else { // Okay, go back and fill in headers outfile.Seek(0, SEEK_SET); outfile.WriteArray(&header, 1); outfile.WriteArray(offsets.data(), header.num_blocks); outfile.WriteArray(hashes.data(), header.num_blocks); } // Cleanup deflateEnd(&z); if (success) { callback(GetStringT("Done compressing disc image."), 1.0f, arg); } return success; }
// called from ---NETPLAY--- thread unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) { MessageId mid; packet >> mid; INFO_LOG(NETPLAY, "Got client message: %x", mid); // don't need lock because this is the only thread that modifies the players // only need locks for writes to m_players in this thread switch (mid) { case NP_MSG_CHAT_MESSAGE: { std::string msg; packet >> msg; // send msg to other clients sf::Packet spac; spac << (MessageId)NP_MSG_CHAT_MESSAGE; spac << player.pid; spac << msg; SendToClients(spac, player.pid); } break; case NP_MSG_PAD_DATA: { // if this is pad data from the last game still being received, ignore it if (player.current_game != m_current_game) break; sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_PAD_DATA); while (!packet.endOfPacket()) { PadMapping map; packet >> map; // If the data is not from the correct player, // then disconnect them. if (m_pad_map.at(map) != player.pid) { return 1; } GCPadStatus pad; packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >> pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected; if (m_host_input_authority) { m_last_pad_status[map] = pad; if (!m_first_pad_status_received[map]) { m_first_pad_status_received[map] = true; SendFirstReceivedToHost(map, true); } } else { spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; } } if (!m_host_input_authority) SendToClients(spac, player.pid); } break; case NP_MSG_PAD_HOST_POLL: { PadMapping pad_num; packet >> pad_num; sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_PAD_DATA); if (pad_num < 0) { for (size_t i = 0; i < m_pad_map.size(); i++) { if (m_pad_map[i] == -1) continue; const GCPadStatus& pad = m_last_pad_status[i]; spac << static_cast<PadMapping>(i) << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; } } else if (m_pad_map.at(pad_num) != -1) { const GCPadStatus& pad = m_last_pad_status[pad_num]; spac << pad_num << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight << pad.isConnected; } SendToClients(spac); } break; case NP_MSG_WIIMOTE_DATA: { // if this is Wiimote data from the last game still being received, ignore it if (player.current_game != m_current_game) break; PadMapping map = 0; u8 size; packet >> map >> size; std::vector<u8> data(size); for (size_t i = 0; i < data.size(); ++i) packet >> data[i]; // If the data is not from the correct player, // then disconnect them. if (m_wiimote_map.at(map) != player.pid) { return 1; } // relay to clients sf::Packet spac; spac << (MessageId)NP_MSG_WIIMOTE_DATA; spac << map; spac << size; for (const u8& byte : data) spac << byte; SendToClients(spac, player.pid); } break; case NP_MSG_PONG: { const u32 ping = (u32)m_ping_timer.GetTimeElapsed(); u32 ping_key = 0; packet >> ping_key; if (m_ping_key == ping_key) { player.ping = ping; } sf::Packet spac; spac << (MessageId)NP_MSG_PLAYER_PING_DATA; spac << player.pid; spac << player.ping; SendToClients(spac); } break; case NP_MSG_START_GAME: { packet >> player.current_game; } break; case NP_MSG_STOP_GAME: { if (!m_is_running) break; m_is_running = false; // tell clients to stop game sf::Packet spac; spac << (MessageId)NP_MSG_STOP_GAME; std::lock_guard<std::recursive_mutex> lkp(m_crit.players); SendToClients(spac); } break; case NP_MSG_GAME_STATUS: { u32 status; packet >> status; m_players[player.pid].game_status = static_cast<PlayerGameStatus>(status); // send msg to other clients sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_GAME_STATUS); spac << player.pid; spac << status; SendToClients(spac); } break; case NP_MSG_IPL_STATUS: { bool status; packet >> status; m_players[player.pid].has_ipl_dump = status; } break; case NP_MSG_TIMEBASE: { u64 timebase = Common::PacketReadU64(packet); u32 frame; packet >> frame; if (m_desync_detected) break; std::vector<std::pair<PlayerId, u64>>& timebases = m_timebase_by_frame[frame]; timebases.emplace_back(player.pid, timebase); if (timebases.size() >= m_players.size()) { // we have all records for this frame if (!std::all_of(timebases.begin(), timebases.end(), [&](std::pair<PlayerId, u64> pair) { return pair.second == timebases[0].second; })) { int pid_to_blame = -1; for (auto pair : timebases) { if (std::all_of(timebases.begin(), timebases.end(), [&](std::pair<PlayerId, u64> other) { return other.first == pair.first || other.second != pair.second; })) { // we are the only outlier pid_to_blame = pair.first; break; } } sf::Packet spac; spac << (MessageId)NP_MSG_DESYNC_DETECTED; spac << pid_to_blame; spac << frame; SendToClients(spac); m_desync_detected = true; } m_timebase_by_frame.erase(frame); } } break; case NP_MSG_MD5_PROGRESS: { int progress; packet >> progress; sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_MD5_PROGRESS); spac << player.pid; spac << progress; SendToClients(spac); } break; case NP_MSG_MD5_RESULT: { std::string result; packet >> result; sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_MD5_RESULT); spac << player.pid; spac << result; SendToClients(spac); } break; case NP_MSG_MD5_ERROR: { std::string error; packet >> error; sf::Packet spac; spac << static_cast<MessageId>(NP_MSG_MD5_ERROR); spac << player.pid; spac << error; SendToClients(spac); } break; case NP_MSG_SYNC_SAVE_DATA: { MessageId sub_id; packet >> sub_id; switch (sub_id) { case SYNC_SAVE_DATA_SUCCESS: { if (m_start_pending) { m_save_data_synced_players++; if (m_save_data_synced_players >= m_players.size() - 1) { m_dialog->AppendChat(GetStringT("All players synchronized.")); StartGame(); } } } break; case SYNC_SAVE_DATA_FAILURE: { m_dialog->AppendChat( StringFromFormat(GetStringT("%s failed to synchronize.").c_str(), player.name.c_str())); m_dialog->OnSaveDataSyncFailure(); m_start_pending = false; } break; default: PanicAlertT( "Unknown SYNC_SAVE_DATA message with id:%d received from player:%d Kicking player!", sub_id, player.pid); return 1; } } break; default: PanicAlertT("Unknown message with id:%d received from player:%d Kicking player!", mid, player.pid); // unknown message, kick the client return 1; } return 0; }