mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2024-11-23 19:29:43 +00:00
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:
parent
e1382b43c8
commit
8827c72a1c
@ -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;
|
||||
}();
|
||||
|
@ -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";
|
||||
|
@ -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,28 +244,31 @@ 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.
|
||||
|
||||
// Read encrypted pfs_image
|
||||
std::vector<u8> pfs_encrypted(length);
|
||||
file.Seek(pkgheader.pfs_image_offset);
|
||||
file.Read(pfs_encrypted);
|
||||
file.Close();
|
||||
// Decrypt the pfs_image.
|
||||
std::vector<u8> pfs_decrypted(length);
|
||||
PKG::crypto.decryptPFS(dataKey, tweakKey, pfs_encrypted, pfs_decrypted, 0);
|
||||
|
||||
// Retrieve PFSC from decrypted pfs_image.
|
||||
pfsc_offset = GetPFSCOffset(pfs_decrypted);
|
||||
int num_blocks = 0;
|
||||
std::vector<u8> pfsc(length);
|
||||
std::memcpy(pfsc.data(), pfs_decrypted.data() + pfsc_offset, length - pfsc_offset);
|
||||
if (length != 0) {
|
||||
// Read encrypted pfs_image
|
||||
std::vector<u8> pfs_encrypted(length);
|
||||
file.Seek(pkgheader.pfs_image_offset);
|
||||
file.Read(pfs_encrypted);
|
||||
file.Close();
|
||||
// Decrypt the pfs_image.
|
||||
std::vector<u8> pfs_decrypted(length);
|
||||
PKG::crypto.decryptPFS(dataKey, tweakKey, pfs_encrypted, pfs_decrypted, 0);
|
||||
|
||||
PFSCHdr pfsChdr;
|
||||
std::memcpy(&pfsChdr, pfsc.data(), sizeof(pfsChdr));
|
||||
// Retrieve PFSC from decrypted pfs_image.
|
||||
pfsc_offset = GetPFSCOffset(pfs_decrypted);
|
||||
std::memcpy(pfsc.data(), pfs_decrypted.data() + pfsc_offset, length - pfsc_offset);
|
||||
|
||||
const int num_blocks = (int)(pfsChdr.data_length / pfsChdr.block_sz2);
|
||||
sectorMap.resize(num_blocks + 1); // 8 bytes, need extra 1 to get the last offset.
|
||||
PFSCHdr pfsChdr;
|
||||
std::memcpy(&pfsChdr, pfsc.data(), sizeof(pfsChdr));
|
||||
|
||||
for (int i = 0; i < num_blocks + 1; i++) {
|
||||
std::memcpy(§orMap[i], pfsc.data() + pfsChdr.block_offsets + i * 8, 8);
|
||||
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(§orMap[i], pfsc.data() + pfsChdr.block_offsets + i * 8, 8);
|
||||
}
|
||||
}
|
||||
|
||||
u32 ent_size = 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;
|
||||
}
|
||||
|
@ -149,7 +149,6 @@ public:
|
||||
private:
|
||||
Crypto crypto;
|
||||
TRP trp;
|
||||
std::vector<u8> pkg;
|
||||
u64 pkgSize = 0;
|
||||
char pkgTitleID[9];
|
||||
PKGHeader pkgheader;
|
||||
|
@ -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,9 +54,31 @@ int PS4_SYSV_ABI sceAppContentAddcontEnqueueDownloadSp() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAppContentAddcontMount() {
|
||||
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
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() {
|
||||
@ -124,22 +165,80 @@ int PS4_SYSV_ABI sceAppContentGetAddcontDownloadProgress() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAppContentGetAddcontInfo() {
|
||||
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
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 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() {
|
||||
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
|
||||
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() {
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -324,4 +441,4 @@ void RegisterlibSceAppContent(Core::Loader::SymbolsResolver* sym) {
|
||||
sceAppContentGetDownloadedStoreCountry);
|
||||
};
|
||||
|
||||
} // namespace Libraries::AppContent
|
||||
} // namespace Libraries::AppContent
|
||||
|
@ -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);
|
||||
|
@ -460,4 +460,6 @@ constexpr int ORBIS_AVPLAYER_ERROR_INFO_AES_ENCRY = 0x806A00B5;
|
||||
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_PARAMETER = 0x80D90002;
|
||||
constexpr int ORBIS_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT = 0x80D90007;
|
||||
constexpr int ORBIS_APP_CONTENT_ERROR_NOT_FOUND = 0x80D90005;
|
@ -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"));
|
||||
|
||||
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")) {
|
||||
psf.open("", pkg.sfo);
|
||||
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,45 +723,47 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
||||
} else {
|
||||
int nfiles = pkg.GetNumberOfFiles();
|
||||
|
||||
QVector<int> indices;
|
||||
for (int i = 0; i < nfiles; i++) {
|
||||
indices.append(i);
|
||||
}
|
||||
|
||||
QProgressDialog dialog;
|
||||
dialog.setWindowTitle(tr("PKG Extraction"));
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
QString extractmsg = QString(tr("Extracting PKG %1/%2")).arg(pkgNum).arg(nPkg);
|
||||
dialog.setLabelText(extractmsg);
|
||||
dialog.setAutoClose(true);
|
||||
dialog.setRange(0, nfiles);
|
||||
|
||||
QFutureWatcher<void> futureWatcher;
|
||||
connect(&futureWatcher, &QFutureWatcher<void>::finished, this, [=, this]() {
|
||||
if (pkgNum == nPkg) {
|
||||
QString path = QString::fromStdString(Config::getGameInstallDir());
|
||||
QMessageBox extractMsgBox(this);
|
||||
extractMsgBox.setWindowTitle(tr("Extraction Finished"));
|
||||
extractMsgBox.setText(
|
||||
QString(tr("Game successfully installed at %1")).arg(path));
|
||||
extractMsgBox.addButton(QMessageBox::Ok);
|
||||
extractMsgBox.setDefaultButton(QMessageBox::Ok);
|
||||
connect(&extractMsgBox, &QMessageBox::buttonClicked, this,
|
||||
[&](QAbstractButton* button) {
|
||||
if (extractMsgBox.button(QMessageBox::Ok) == button) {
|
||||
extractMsgBox.close();
|
||||
emit ExtractionFinished();
|
||||
}
|
||||
});
|
||||
extractMsgBox.exec();
|
||||
if (nfiles > 0) {
|
||||
QVector<int> indices;
|
||||
for (int i = 0; i < nfiles; i++) {
|
||||
indices.append(i);
|
||||
}
|
||||
});
|
||||
connect(&dialog, &QProgressDialog::canceled, [&]() { futureWatcher.cancel(); });
|
||||
connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog,
|
||||
&QProgressDialog::setValue);
|
||||
futureWatcher.setFuture(
|
||||
QtConcurrent::map(indices, [&](int index) { pkg.ExtractFiles(index); }));
|
||||
dialog.exec();
|
||||
|
||||
QProgressDialog dialog;
|
||||
dialog.setWindowTitle(tr("PKG Extraction"));
|
||||
dialog.setWindowModality(Qt::WindowModal);
|
||||
QString extractmsg = QString(tr("Extracting PKG %1/%2")).arg(pkgNum).arg(nPkg);
|
||||
dialog.setLabelText(extractmsg);
|
||||
dialog.setAutoClose(true);
|
||||
dialog.setRange(0, nfiles);
|
||||
|
||||
QFutureWatcher<void> futureWatcher;
|
||||
connect(&futureWatcher, &QFutureWatcher<void>::finished, this, [=, this]() {
|
||||
if (pkgNum == nPkg) {
|
||||
QString path = QString::fromStdString(Config::getGameInstallDir());
|
||||
QMessageBox extractMsgBox(this);
|
||||
extractMsgBox.setWindowTitle(tr("Extraction Finished"));
|
||||
extractMsgBox.setText(
|
||||
QString(tr("Game successfully installed at %1")).arg(path));
|
||||
extractMsgBox.addButton(QMessageBox::Ok);
|
||||
extractMsgBox.setDefaultButton(QMessageBox::Ok);
|
||||
connect(&extractMsgBox, &QMessageBox::buttonClicked, this,
|
||||
[&](QAbstractButton* button) {
|
||||
if (extractMsgBox.button(QMessageBox::Ok) == button) {
|
||||
extractMsgBox.close();
|
||||
emit ExtractionFinished();
|
||||
}
|
||||
});
|
||||
extractMsgBox.exec();
|
||||
}
|
||||
});
|
||||
connect(&dialog, &QProgressDialog::canceled, [&]() { futureWatcher.cancel(); });
|
||||
connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog,
|
||||
&QProgressDialog::setValue);
|
||||
futureWatcher.setFuture(
|
||||
QtConcurrent::map(indices, [&](int index) { pkg.ExtractFiles(index); }));
|
||||
dialog.exec();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("PKG ERROR"),
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -605,6 +605,16 @@
|
||||
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||
<translation>Gra już zainstalowana\n%1\nCzy chcesz ją 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>
|
||||
|
@ -605,6 +605,16 @@
|
||||
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||
<translation>Jogo já 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>
|
||||
|
@ -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 să 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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 có 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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user