Add DLC support (#596)

* fs/core: Add DLC support

* fs/core: Fix extraction paths

* Fix DLC mounting

* gui: Add translations
This commit is contained in:
Dzmitry Dubrova 2024-08-29 12:55:40 +03:00 committed by GitHub
parent e1382b43c8
commit 8827c72a1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 544 additions and 92 deletions

View File

@ -106,6 +106,7 @@ static auto UserPaths = [] {
create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR);
create_path(PathType::CheatsDir, user_dir / CHEATS_DIR);
create_path(PathType::PatchesDir, user_dir / PATCHES_DIR);
create_path(PathType::AddonsDir, user_dir / ADDONS_DIR);
return paths;
}();

View File

@ -22,6 +22,7 @@ enum class PathType {
CapturesDir, // Where rdoc captures are stored.
CheatsDir, // Where cheats are stored.
PatchesDir, // Where patches are stored.
AddonsDir, // Where additional content is stored.
};
constexpr auto PORTABLE_DIR = "user";
@ -39,6 +40,7 @@ constexpr auto DOWNLOAD_DIR = "download";
constexpr auto CAPTURES_DIR = "captures";
constexpr auto CHEATS_DIR = "cheats";
constexpr auto PATCHES_DIR = "patches";
constexpr auto ADDONS_DIR = "addcont";
// Filenames
constexpr auto LOG_FILE = "shad_log.txt";

View File

@ -67,15 +67,19 @@ bool PKG::Open(const std::filesystem::path& filepath) {
file.Seek(0x47); // skip first 7 characters of content_id
file.Read(pkgTitleID);
file.Seek(0);
pkg.resize(pkgheader.pkg_promote_size);
file.Read(pkg);
u32 offset = pkgheader.pkg_table_entry_offset;
u32 n_files = pkgheader.pkg_table_entry_count;
file.Seek(offset);
for (int i = 0; i < n_files; i++) {
PKGEntry entry;
std::memcpy(&entry, &pkg[offset + i * 0x20], sizeof(entry));
PKGEntry entry{};
file.Read(entry.id);
file.Read(entry.filename_offset);
file.Read(entry.flags1);
file.Read(entry.flags2);
file.Read(entry.offset);
file.Read(entry.size);
file.Seek(8, Common::FS::SeekOrigin::CurrentPosition);
// Try to figure out the name
const auto name = GetEntryNameByType(entry.id);
@ -113,9 +117,6 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
failreason = "Content size is bigger than pkg size";
return false;
}
file.Seek(0);
pkg.resize(pkgheader.pkg_promote_size);
file.Read(pkg);
u32 offset = pkgheader.pkg_table_entry_offset;
u32 n_files = pkgheader.pkg_table_entry_count;
@ -126,9 +127,18 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
std::array<std::array<u8, 256>, 7> key1;
std::array<u8, 256> imgkeydata;
file.Seek(offset);
for (int i = 0; i < n_files; i++) {
PKGEntry entry;
std::memcpy(&entry, &pkg[offset + i * 0x20], sizeof(entry));
PKGEntry entry{};
file.Read(entry.id);
file.Read(entry.filename_offset);
file.Read(entry.flags1);
file.Read(entry.flags2);
file.Read(entry.offset);
file.Read(entry.size);
file.Seek(8, Common::FS::SeekOrigin::CurrentPosition);
auto currentPos = file.Tell();
// Try to figure out the name
const auto name = GetEntryNameByType(entry.id);
@ -139,8 +149,15 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
// Just print with id
Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id),
Common::FS::FileAccessMode::Write);
out.WriteRaw<u8>(pkg.data() + entry.offset, entry.size);
file.Seek(entry.offset);
std::vector<u8> data;
data.resize(entry.size);
file.ReadRaw<u8>(data.data(), entry.size);
out.WriteRaw<u8>(data.data(), entry.size);
out.Close();
file.Seek(currentPos);
continue;
}
@ -178,14 +195,25 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
}
Common::FS::IOFile out(extract_path / "sce_sys" / name, Common::FS::FileAccessMode::Write);
out.WriteRaw<u8>(pkg.data() + entry.offset, entry.size);
file.Seek(entry.offset);
std::vector<u8> data;
data.resize(entry.size);
file.ReadRaw<u8>(data.data(), entry.size);
out.WriteRaw<u8>(data.data(), entry.size);
out.Close();
// Decrypt Np stuff and overwrite.
if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 ||
entry.id == 0x403) { // somehow 0x401 is not decrypting
decNp.resize(entry.size);
std::span<u8> cipherNp(pkg.data() + entry.offset, entry.size);
file.Seek(entry.offset);
std::vector<u8> data;
data.resize(entry.size);
file.ReadRaw<u8>(data.data(), entry.size);
std::span<u8> cipherNp(data.data(), entry.size);
std::array<u8, 64> concatenated_ivkey_dk3_;
std::memcpy(concatenated_ivkey_dk3_.data(), &entry, sizeof(entry));
std::memcpy(concatenated_ivkey_dk3_.data() + sizeof(entry), dk3_.data(), sizeof(dk3_));
@ -197,6 +225,8 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
out.Write(decNp);
out.Close();
}
file.Seek(currentPos);
}
// Extract trophy files
@ -214,6 +244,9 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
PKG::crypto.PfsGenCryptoKey(ekpfsKey, seed, dataKey, tweakKey);
const u32 length = pkgheader.pfs_cache_size * 0x2; // Seems to be ok.
int num_blocks = 0;
std::vector<u8> pfsc(length);
if (length != 0) {
// Read encrypted pfs_image
std::vector<u8> pfs_encrypted(length);
file.Seek(pkgheader.pfs_image_offset);
@ -225,18 +258,18 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
// Retrieve PFSC from decrypted pfs_image.
pfsc_offset = GetPFSCOffset(pfs_decrypted);
std::vector<u8> pfsc(length);
std::memcpy(pfsc.data(), pfs_decrypted.data() + pfsc_offset, length - pfsc_offset);
PFSCHdr pfsChdr;
std::memcpy(&pfsChdr, pfsc.data(), sizeof(pfsChdr));
const int num_blocks = (int)(pfsChdr.data_length / pfsChdr.block_sz2);
num_blocks = (int)(pfsChdr.data_length / pfsChdr.block_sz2);
sectorMap.resize(num_blocks + 1); // 8 bytes, need extra 1 to get the last offset.
for (int i = 0; i < num_blocks + 1; i++) {
std::memcpy(&sectorMap[i], pfsc.data() + pfsChdr.block_offsets + i * 8, 8);
}
}
u32 ent_size = 0;
u32 ndinode = 0;
@ -296,7 +329,15 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
} else {
// Set the the folder according to the current inode.
// Can be 2 or more (rarely)
extractPaths[ndinode_counter] = extract_path.parent_path() / GetTitleID();
auto parent_path = extract_path.parent_path();
auto title_id = GetTitleID();
if (parent_path.filename() != title_id) {
extractPaths[ndinode_counter] = parent_path / title_id;
} else {
// DLCs path has different structure
extractPaths[ndinode_counter] = extract_path;
}
uroot_reached = false;
break;
}

View File

@ -149,7 +149,6 @@ public:
private:
Crypto crypto;
TRP trp;
std::vector<u8> pkg;
u64 pkgSize = 0;
char pkgTitleID[9];
PKGHeader pkgheader;

View File

@ -7,14 +7,33 @@
#include <common/singleton.h>
#include <core/file_format/psf.h>
#include <core/file_sys/fs.h>
#include "app_content.h"
#include "common/io_file.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
namespace Libraries::AppContent {
int32_t addcont_count = 0;
struct AddContInfo {
char entitlementLabel[ORBIS_NP_UNIFIED_ENTITLEMENT_LABEL_SIZE];
OrbisAppContentAddcontDownloadStatus status;
OrbisAppContentGetEntitlementKey key;
};
std::array<AddContInfo, ORBIS_APP_CONTENT_INFO_LIST_MAX_SIZE> addcont_info = {{
{"0000000000000000",
ORBIS_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_INSTALLED,
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00}},
}};
std::string title_id;
int PS4_SYSV_ABI _Z5dummyv() {
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
return ORBIS_OK;
@ -35,11 +54,33 @@ int PS4_SYSV_ABI sceAppContentAddcontEnqueueDownloadSp() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceAppContentAddcontMount() {
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
int PS4_SYSV_ABI sceAppContentAddcontMount(u32 service_label,
const OrbisNpUnifiedEntitlementLabel* entitlement_label,
OrbisAppContentMountPoint* mount_point) {
LOG_INFO(Lib_AppContent, "called");
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) / title_id /
entitlement_label->data;
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
for (int i = 0; i < addcont_count; i++) {
if (strncmp(entitlement_label->data, addcont_info[i].entitlementLabel,
ORBIS_NP_UNIFIED_ENTITLEMENT_LABEL_SIZE - 1) != 0) {
continue;
}
if (addcont_info[i].status != ORBIS_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_INSTALLED) {
return ORBIS_APP_CONTENT_ERROR_NOT_FOUND;
}
snprintf(mount_point->data, ORBIS_APP_CONTENT_MOUNTPOINT_DATA_MAXSIZE, "/addcont%d", i);
mnt->Mount(mount_dir, mount_point->data);
return ORBIS_OK;
}
return ORBIS_APP_CONTENT_ERROR_NOT_FOUND;
}
int PS4_SYSV_ABI sceAppContentAddcontShrink() {
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
return ORBIS_OK;
@ -124,24 +165,82 @@ int PS4_SYSV_ABI sceAppContentGetAddcontDownloadProgress() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceAppContentGetAddcontInfo() {
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
int PS4_SYSV_ABI sceAppContentGetAddcontInfo(u32 service_label,
const OrbisNpUnifiedEntitlementLabel* entitlementLabel,
OrbisAppContentAddcontInfo* info) {
LOG_INFO(Lib_AppContent, "called");
if (entitlementLabel == nullptr || info == nullptr) {
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
}
for (auto i = 0; i < addcont_count; i++) {
if (strncmp(entitlementLabel->data, addcont_info[i].entitlementLabel,
ORBIS_NP_UNIFIED_ENTITLEMENT_LABEL_SIZE - 1) != 0) {
continue;
}
LOG_INFO(Lib_AppContent, "found DLC {}", entitlementLabel->data);
strncpy(info->entitlement_label.data, addcont_info[i].entitlementLabel,
ORBIS_NP_UNIFIED_ENTITLEMENT_LABEL_SIZE);
info->status = addcont_info[i].status;
return ORBIS_OK;
}
return ORBIS_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT;
}
int PS4_SYSV_ABI sceAppContentGetAddcontInfoList(u32 service_label,
OrbisAppContentAddcontInfo* list, u32 list_num,
u32* hit_num) {
*hit_num = 0;
LOG_ERROR(Lib_AppContent, "(DUMMY) called");
LOG_INFO(Lib_AppContent, "called");
if (list_num == 0 || list == nullptr) {
if (hit_num == nullptr) {
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
}
*hit_num = addcont_count;
return ORBIS_OK;
}
int PS4_SYSV_ABI sceAppContentGetEntitlementKey() {
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
int dlcs_to_list = addcont_count < list_num ? addcont_count : list_num;
for (int i = 0; i < dlcs_to_list; i++) {
strncpy(list[i].entitlement_label.data, addcont_info[i].entitlementLabel,
ORBIS_NP_UNIFIED_ENTITLEMENT_LABEL_SIZE);
list[i].status = addcont_info[i].status;
}
if (hit_num != nullptr) {
*hit_num = dlcs_to_list;
}
return ORBIS_OK;
}
int PS4_SYSV_ABI sceAppContentGetEntitlementKey(
u32 service_label, const OrbisNpUnifiedEntitlementLabel* entitlement_label,
OrbisAppContentGetEntitlementKey* key) {
LOG_ERROR(Lib_AppContent, "called");
if (entitlement_label == nullptr || key == nullptr) {
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
}
for (int i = 0; i < addcont_count; i++) {
if (strncmp(entitlement_label->data, addcont_info[i].entitlementLabel,
ORBIS_NP_UNIFIED_ENTITLEMENT_LABEL_SIZE - 1) != 0) {
continue;
}
memcpy(key->data, addcont_info[i].key.data, ORBIS_APP_CONTENT_ENTITLEMENT_KEY_SIZE);
return ORBIS_OK;
}
return ORBIS_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT;
}
int PS4_SYSV_ABI sceAppContentGetRegion() {
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
return ORBIS_OK;
@ -150,7 +249,25 @@ int PS4_SYSV_ABI sceAppContentGetRegion() {
int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initParam,
OrbisAppContentBootParam* bootParam) {
LOG_ERROR(Lib_AppContent, "(DUMMY) called");
bootParam->attr = 0; // always 0
auto* param_sfo = Common::Singleton<PSF>::Instance();
const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir);
title_id = param_sfo->GetString("TITLE_ID");
auto addon_path = addons_dir / title_id;
if (std::filesystem::exists(addon_path)) {
for (const auto& entry : std::filesystem::directory_iterator(addon_path)) {
if (entry.is_directory()) {
auto entitlement_label = entry.path().filename().string();
AddContInfo info{};
info.status = ORBIS_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_INSTALLED;
strcpy(info.entitlementLabel, entitlement_label.c_str());
addcont_info[addcont_count++] = info;
}
}
}
return ORBIS_OK;
}

View File

@ -41,6 +41,16 @@ struct OrbisAppContentMountPoint {
constexpr int ORBIS_APP_CONTENT_TEMPORARY_DATA_OPTION_NONE = 0;
constexpr int ORBIS_APP_CONTENT_TEMPORARY_DATA_OPTION_FORMAT = (1 << 0);
constexpr int ORBIS_NP_UNIFIED_ENTITLEMENT_LABEL_SIZE = 17;
constexpr int ORBIS_APP_CONTENT_ENTITLEMENT_KEY_SIZE = 16;
constexpr int ORBIS_APP_CONTENT_INFO_LIST_MAX_SIZE = 2500;
enum OrbisAppContentAddcontDownloadStatus : u32 {
ORBIS_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_NO_EXTRA_DATA = 0,
ORBIS_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_NO_IN_QUEUE = 1,
ORBIS_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_DOWNLOADING = 2,
ORBIS_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_DOWNLOAD_SUSPENDED = 3,
ORBIS_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_INSTALLED = 4
};
struct OrbisNpUnifiedEntitlementLabel {
char data[ORBIS_NP_UNIFIED_ENTITLEMENT_LABEL_SIZE];
@ -54,11 +64,17 @@ struct OrbisAppContentAddcontInfo {
u32 status;
};
struct OrbisAppContentGetEntitlementKey {
char data[ORBIS_APP_CONTENT_ENTITLEMENT_KEY_SIZE];
};
int PS4_SYSV_ABI _Z5dummyv();
int PS4_SYSV_ABI sceAppContentAddcontDelete();
int PS4_SYSV_ABI sceAppContentAddcontEnqueueDownload();
int PS4_SYSV_ABI sceAppContentAddcontEnqueueDownloadSp();
int PS4_SYSV_ABI sceAppContentAddcontMount();
int PS4_SYSV_ABI sceAppContentAddcontMount(u32 service_label,
const OrbisNpUnifiedEntitlementLabel* entitlement_label,
OrbisAppContentMountPoint* mount_point);
int PS4_SYSV_ABI sceAppContentAddcontShrink();
int PS4_SYSV_ABI sceAppContentAddcontUnmount();
int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* value);
@ -70,11 +86,15 @@ int PS4_SYSV_ABI sceAppContentDownload1Shrink();
int PS4_SYSV_ABI sceAppContentDownloadDataFormat();
int PS4_SYSV_ABI sceAppContentDownloadDataGetAvailableSpaceKb();
int PS4_SYSV_ABI sceAppContentGetAddcontDownloadProgress();
int PS4_SYSV_ABI sceAppContentGetAddcontInfo();
int PS4_SYSV_ABI sceAppContentGetAddcontInfo(u32 service_label,
const OrbisNpUnifiedEntitlementLabel* entitlementLabel,
OrbisAppContentAddcontInfo* info);
int PS4_SYSV_ABI sceAppContentGetAddcontInfoList(u32 service_label,
OrbisAppContentAddcontInfo* list, u32 list_num,
u32* hit_num);
int PS4_SYSV_ABI sceAppContentGetEntitlementKey();
int PS4_SYSV_ABI sceAppContentGetEntitlementKey(
u32 service_label, const OrbisNpUnifiedEntitlementLabel* entitlement_label,
OrbisAppContentGetEntitlementKey* key);
int PS4_SYSV_ABI sceAppContentGetRegion();
int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initParam,
OrbisAppContentBootParam* bootParam);

View File

@ -461,3 +461,5 @@ constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF;
// AppContent library
constexpr int ORBIS_APP_CONTENT_ERROR_PARAMETER = 0x80D90002;
constexpr int ORBIS_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT = 0x80D90007;
constexpr int ORBIS_APP_CONTENT_ERROR_NOT_FOUND = 0x80D90005;

View File

@ -7,6 +7,7 @@
#include "about_dialog.h"
#include "cheats_patches.h"
#include "common/io_file.h"
#include "common/string_util.h"
#include "common/version.h"
#include "core/file_format/pkg.h"
#include "core/loader.h"
@ -615,15 +616,24 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
pkg = PKG();
pkg.Open(file);
std::string failreason;
const auto extract_path =
std::filesystem::path(Config::getGameInstallDir()) / pkg.GetTitleID();
auto extract_path = std::filesystem::path(Config::getGameInstallDir()) / pkg.GetTitleID();
QString pkgType = QString::fromStdString(pkg.GetPkgFlags());
QDir game_dir(QString::fromStdString(extract_path.string()));
if (game_dir.exists()) {
QMessageBox msgBox;
msgBox.setWindowTitle(tr("PKG Extraction"));
if (pkgType.contains("PATCH")) {
psf.open("", pkg.sfo);
std::string content_id = psf.GetString("CONTENT_ID");
std::string entitlement_label = Common::SplitString(content_id, '-')[2];
auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) /
pkg.GetTitleID() / entitlement_label;
QDir addon_dir(QString::fromStdString(addon_extract_path.string()));
auto category = psf.GetString("CATEGORY");
if (pkgType.contains("PATCH")) {
QString pkg_app_version = QString::fromStdString(psf.GetString("APP_VER"));
psf.open(extract_path.string() + "/sce_sys/param.sfo", {});
QString game_app_version = QString::fromStdString(psf.GetString("APP_VER"));
@ -657,6 +667,34 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
} else {
return;
}
} else if (category == "ac") {
if (!addon_dir.exists()) {
QMessageBox addonMsgBox;
addonMsgBox.setWindowTitle(tr("DLC Installation"));
addonMsgBox.setText(QString(tr("Would you like to install DLC: %1?"))
.arg(QString::fromStdString(entitlement_label)));
addonMsgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
addonMsgBox.setDefaultButton(QMessageBox::No);
int result = addonMsgBox.exec();
if (result == QMessageBox::Yes) {
extract_path = addon_extract_path;
} else {
return;
}
} else {
msgBox.setText(
QString("DLC already installed\n%1\nWould you like to overwrite?")
.arg(QString::fromStdString(addon_extract_path.string())));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
int result = msgBox.exec();
if (result == QMessageBox::Yes) {
extract_path = addon_extract_path;
} else {
return;
}
}
} else {
msgBox.setText(
QString(tr("Game already installed\n%1\nWould you like to overwrite?"))
@ -685,6 +723,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
} else {
int nfiles = pkg.GetNumberOfFiles();
if (nfiles > 0) {
QVector<int> indices;
for (int i = 0; i < nfiles; i++) {
indices.append(i);
@ -725,6 +764,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
QtConcurrent::map(indices, [&](int index) { pkg.ExtractFiles(index); }));
dialog.exec();
}
}
} else {
QMessageBox::critical(this, tr("PKG ERROR"),
tr("File doesn't appear to be a valid PKG file"));

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Spil allerede installeret\n%1\nVil du overskrive?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Spiel bereits installiert\n%1\nMöchten Sie überschreiben?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Το παιχνίδι είναι ήδη εγκατεστημένο\n%1\nΘέλετε να αντικαταστήσετε;</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Game already installed\n%1\nWould you like to overwrite?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Juego ya instalado\n%1\n¿Te gustaría sobrescribirlo?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Peli on jo asennettu\n%1\nHaluatko korvata sen?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Jeu déjà installé\n%1\nSouhaitez-vous écraser ?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>A játék már telepítve van\n%1\nSzeretnéd felülírni?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Game sudah terpasang\n%1\nApakah Anda ingin menimpa?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Game sudah terinstal\n%1\nApakah Anda ingin menimpa?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>\n%1\n上書きしますか</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Game already installed\n%1\nWould you like to overwrite?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Žaidimas jau įdiegtas\n%1\nAr norėtumėte perrašyti?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Spill allerede installert\n%1\nØnsker du å overskrive?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Spel al geïnstalleerd\n%1\nWil je het overschrijven?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Gra już zainstalowana\n%1\nCzy chcesz nadpisać?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Jogo instalado\n%1\nGostaria de substituir?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Jocul este deja instalat\n%1\nAi dori suprascrii?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Игра уже установлена\n%1\nХотите перезаписать?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Oyun zaten yüklü\n%1\nÜzerine yazmak ister misiniz?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>Trò chơi đã đưc cài đt\n%1\nBạn muốn ghi đè không?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>\n%1\n您想要覆盖吗</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>

View File

@ -605,6 +605,16 @@
<source>Game already installed\n%1\nWould you like to overwrite?</source>
<translation>\n%1\n您是否希望覆蓋</translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
<source>DLC Installation</source>
<translation>DLC Installation</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation>Would you like to install DLC: %1?</translation>
</message>
<message>
<location filename="../main_window.cpp" line="674"/>
<source>PKG is a patch, please install the game first!</source>