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::CapturesDir, user_dir / CAPTURES_DIR);
|
||||||
create_path(PathType::CheatsDir, user_dir / CHEATS_DIR);
|
create_path(PathType::CheatsDir, user_dir / CHEATS_DIR);
|
||||||
create_path(PathType::PatchesDir, user_dir / PATCHES_DIR);
|
create_path(PathType::PatchesDir, user_dir / PATCHES_DIR);
|
||||||
|
create_path(PathType::AddonsDir, user_dir / ADDONS_DIR);
|
||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
}();
|
}();
|
||||||
|
|
|
@ -22,6 +22,7 @@ enum class PathType {
|
||||||
CapturesDir, // Where rdoc captures are stored.
|
CapturesDir, // Where rdoc captures are stored.
|
||||||
CheatsDir, // Where cheats are stored.
|
CheatsDir, // Where cheats are stored.
|
||||||
PatchesDir, // Where patches are stored.
|
PatchesDir, // Where patches are stored.
|
||||||
|
AddonsDir, // Where additional content is stored.
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr auto PORTABLE_DIR = "user";
|
constexpr auto PORTABLE_DIR = "user";
|
||||||
|
@ -39,6 +40,7 @@ constexpr auto DOWNLOAD_DIR = "download";
|
||||||
constexpr auto CAPTURES_DIR = "captures";
|
constexpr auto CAPTURES_DIR = "captures";
|
||||||
constexpr auto CHEATS_DIR = "cheats";
|
constexpr auto CHEATS_DIR = "cheats";
|
||||||
constexpr auto PATCHES_DIR = "patches";
|
constexpr auto PATCHES_DIR = "patches";
|
||||||
|
constexpr auto ADDONS_DIR = "addcont";
|
||||||
|
|
||||||
// Filenames
|
// Filenames
|
||||||
constexpr auto LOG_FILE = "shad_log.txt";
|
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.Seek(0x47); // skip first 7 characters of content_id
|
||||||
file.Read(pkgTitleID);
|
file.Read(pkgTitleID);
|
||||||
|
|
||||||
file.Seek(0);
|
|
||||||
pkg.resize(pkgheader.pkg_promote_size);
|
|
||||||
file.Read(pkg);
|
|
||||||
|
|
||||||
u32 offset = pkgheader.pkg_table_entry_offset;
|
u32 offset = pkgheader.pkg_table_entry_offset;
|
||||||
u32 n_files = pkgheader.pkg_table_entry_count;
|
u32 n_files = pkgheader.pkg_table_entry_count;
|
||||||
|
|
||||||
|
file.Seek(offset);
|
||||||
for (int i = 0; i < n_files; i++) {
|
for (int i = 0; i < n_files; i++) {
|
||||||
PKGEntry entry;
|
PKGEntry entry{};
|
||||||
std::memcpy(&entry, &pkg[offset + i * 0x20], sizeof(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
|
// Try to figure out the name
|
||||||
const auto name = GetEntryNameByType(entry.id);
|
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";
|
failreason = "Content size is bigger than pkg size";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
file.Seek(0);
|
|
||||||
pkg.resize(pkgheader.pkg_promote_size);
|
|
||||||
file.Read(pkg);
|
|
||||||
|
|
||||||
u32 offset = pkgheader.pkg_table_entry_offset;
|
u32 offset = pkgheader.pkg_table_entry_offset;
|
||||||
u32 n_files = pkgheader.pkg_table_entry_count;
|
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<std::array<u8, 256>, 7> key1;
|
||||||
std::array<u8, 256> imgkeydata;
|
std::array<u8, 256> imgkeydata;
|
||||||
|
|
||||||
|
file.Seek(offset);
|
||||||
for (int i = 0; i < n_files; i++) {
|
for (int i = 0; i < n_files; i++) {
|
||||||
PKGEntry entry;
|
PKGEntry entry{};
|
||||||
std::memcpy(&entry, &pkg[offset + i * 0x20], sizeof(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
|
// Try to figure out the name
|
||||||
const auto name = GetEntryNameByType(entry.id);
|
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
|
// Just print with id
|
||||||
Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id),
|
Common::FS::IOFile out(extract_path / "sce_sys" / std::to_string(entry.id),
|
||||||
Common::FS::FileAccessMode::Write);
|
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();
|
out.Close();
|
||||||
|
|
||||||
|
file.Seek(currentPos);
|
||||||
continue;
|
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);
|
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();
|
out.Close();
|
||||||
|
|
||||||
// Decrypt Np stuff and overwrite.
|
// Decrypt Np stuff and overwrite.
|
||||||
if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 ||
|
if (entry.id == 0x400 || entry.id == 0x401 || entry.id == 0x402 ||
|
||||||
entry.id == 0x403) { // somehow 0x401 is not decrypting
|
entry.id == 0x403) { // somehow 0x401 is not decrypting
|
||||||
decNp.resize(entry.size);
|
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::array<u8, 64> concatenated_ivkey_dk3_;
|
||||||
std::memcpy(concatenated_ivkey_dk3_.data(), &entry, sizeof(entry));
|
std::memcpy(concatenated_ivkey_dk3_.data(), &entry, sizeof(entry));
|
||||||
std::memcpy(concatenated_ivkey_dk3_.data() + sizeof(entry), dk3_.data(), sizeof(dk3_));
|
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.Write(decNp);
|
||||||
out.Close();
|
out.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file.Seek(currentPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract trophy files
|
// 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);
|
PKG::crypto.PfsGenCryptoKey(ekpfsKey, seed, dataKey, tweakKey);
|
||||||
const u32 length = pkgheader.pfs_cache_size * 0x2; // Seems to be ok.
|
const u32 length = pkgheader.pfs_cache_size * 0x2; // Seems to be ok.
|
||||||
|
|
||||||
// Read encrypted pfs_image
|
int num_blocks = 0;
|
||||||
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);
|
|
||||||
std::vector<u8> pfsc(length);
|
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;
|
// Retrieve PFSC from decrypted pfs_image.
|
||||||
std::memcpy(&pfsChdr, pfsc.data(), sizeof(pfsChdr));
|
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);
|
PFSCHdr pfsChdr;
|
||||||
sectorMap.resize(num_blocks + 1); // 8 bytes, need extra 1 to get the last offset.
|
std::memcpy(&pfsChdr, pfsc.data(), sizeof(pfsChdr));
|
||||||
|
|
||||||
for (int i = 0; i < num_blocks + 1; i++) {
|
num_blocks = (int)(pfsChdr.data_length / pfsChdr.block_sz2);
|
||||||
std::memcpy(§orMap[i], pfsc.data() + pfsChdr.block_offsets + i * 8, 8);
|
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;
|
u32 ent_size = 0;
|
||||||
|
@ -296,7 +329,15 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
|
||||||
} else {
|
} else {
|
||||||
// Set the the folder according to the current inode.
|
// Set the the folder according to the current inode.
|
||||||
// Can be 2 or more (rarely)
|
// 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;
|
uroot_reached = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,6 @@ public:
|
||||||
private:
|
private:
|
||||||
Crypto crypto;
|
Crypto crypto;
|
||||||
TRP trp;
|
TRP trp;
|
||||||
std::vector<u8> pkg;
|
|
||||||
u64 pkgSize = 0;
|
u64 pkgSize = 0;
|
||||||
char pkgTitleID[9];
|
char pkgTitleID[9];
|
||||||
PKGHeader pkgheader;
|
PKGHeader pkgheader;
|
||||||
|
|
|
@ -7,14 +7,33 @@
|
||||||
#include <common/singleton.h>
|
#include <common/singleton.h>
|
||||||
#include <core/file_format/psf.h>
|
#include <core/file_format/psf.h>
|
||||||
#include <core/file_sys/fs.h>
|
#include <core/file_sys/fs.h>
|
||||||
|
|
||||||
#include "app_content.h"
|
#include "app_content.h"
|
||||||
#include "common/io_file.h"
|
#include "common/io_file.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
|
|
||||||
namespace Libraries::AppContent {
|
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() {
|
int PS4_SYSV_ABI _Z5dummyv() {
|
||||||
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
|
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
|
@ -35,9 +54,31 @@ int PS4_SYSV_ABI sceAppContentAddcontEnqueueDownloadSp() {
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAppContentAddcontMount() {
|
int PS4_SYSV_ABI sceAppContentAddcontMount(u32 service_label,
|
||||||
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
|
const OrbisNpUnifiedEntitlementLabel* entitlement_label,
|
||||||
return ORBIS_OK;
|
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() {
|
int PS4_SYSV_ABI sceAppContentAddcontShrink() {
|
||||||
|
@ -124,22 +165,80 @@ int PS4_SYSV_ABI sceAppContentGetAddcontDownloadProgress() {
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAppContentGetAddcontInfo() {
|
int PS4_SYSV_ABI sceAppContentGetAddcontInfo(u32 service_label,
|
||||||
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
|
const OrbisNpUnifiedEntitlementLabel* entitlementLabel,
|
||||||
return ORBIS_OK;
|
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,
|
int PS4_SYSV_ABI sceAppContentGetAddcontInfoList(u32 service_label,
|
||||||
OrbisAppContentAddcontInfo* list, u32 list_num,
|
OrbisAppContentAddcontInfo* list, u32 list_num,
|
||||||
u32* hit_num) {
|
u32* hit_num) {
|
||||||
*hit_num = 0;
|
LOG_INFO(Lib_AppContent, "called");
|
||||||
LOG_ERROR(Lib_AppContent, "(DUMMY) 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;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAppContentGetEntitlementKey() {
|
int PS4_SYSV_ABI sceAppContentGetEntitlementKey(
|
||||||
LOG_ERROR(Lib_AppContent, "(STUBBED) called");
|
u32 service_label, const OrbisNpUnifiedEntitlementLabel* entitlement_label,
|
||||||
return ORBIS_OK;
|
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() {
|
int PS4_SYSV_ABI sceAppContentGetRegion() {
|
||||||
|
@ -150,7 +249,25 @@ int PS4_SYSV_ABI sceAppContentGetRegion() {
|
||||||
int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initParam,
|
int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initParam,
|
||||||
OrbisAppContentBootParam* bootParam) {
|
OrbisAppContentBootParam* bootParam) {
|
||||||
LOG_ERROR(Lib_AppContent, "(DUMMY) called");
|
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;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,4 +441,4 @@ void RegisterlibSceAppContent(Core::Loader::SymbolsResolver* sym) {
|
||||||
sceAppContentGetDownloadedStoreCountry);
|
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_NONE = 0;
|
||||||
constexpr int ORBIS_APP_CONTENT_TEMPORARY_DATA_OPTION_FORMAT = (1 << 0);
|
constexpr int ORBIS_APP_CONTENT_TEMPORARY_DATA_OPTION_FORMAT = (1 << 0);
|
||||||
constexpr int ORBIS_NP_UNIFIED_ENTITLEMENT_LABEL_SIZE = 17;
|
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 {
|
struct OrbisNpUnifiedEntitlementLabel {
|
||||||
char data[ORBIS_NP_UNIFIED_ENTITLEMENT_LABEL_SIZE];
|
char data[ORBIS_NP_UNIFIED_ENTITLEMENT_LABEL_SIZE];
|
||||||
|
@ -54,11 +64,17 @@ struct OrbisAppContentAddcontInfo {
|
||||||
u32 status;
|
u32 status;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct OrbisAppContentGetEntitlementKey {
|
||||||
|
char data[ORBIS_APP_CONTENT_ENTITLEMENT_KEY_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
int PS4_SYSV_ABI _Z5dummyv();
|
int PS4_SYSV_ABI _Z5dummyv();
|
||||||
int PS4_SYSV_ABI sceAppContentAddcontDelete();
|
int PS4_SYSV_ABI sceAppContentAddcontDelete();
|
||||||
int PS4_SYSV_ABI sceAppContentAddcontEnqueueDownload();
|
int PS4_SYSV_ABI sceAppContentAddcontEnqueueDownload();
|
||||||
int PS4_SYSV_ABI sceAppContentAddcontEnqueueDownloadSp();
|
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 sceAppContentAddcontShrink();
|
||||||
int PS4_SYSV_ABI sceAppContentAddcontUnmount();
|
int PS4_SYSV_ABI sceAppContentAddcontUnmount();
|
||||||
int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* value);
|
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 sceAppContentDownloadDataFormat();
|
||||||
int PS4_SYSV_ABI sceAppContentDownloadDataGetAvailableSpaceKb();
|
int PS4_SYSV_ABI sceAppContentDownloadDataGetAvailableSpaceKb();
|
||||||
int PS4_SYSV_ABI sceAppContentGetAddcontDownloadProgress();
|
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,
|
int PS4_SYSV_ABI sceAppContentGetAddcontInfoList(u32 service_label,
|
||||||
OrbisAppContentAddcontInfo* list, u32 list_num,
|
OrbisAppContentAddcontInfo* list, u32 list_num,
|
||||||
u32* hit_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 sceAppContentGetRegion();
|
||||||
int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initParam,
|
int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initParam,
|
||||||
OrbisAppContentBootParam* bootParam);
|
OrbisAppContentBootParam* bootParam);
|
||||||
|
|
|
@ -460,4 +460,6 @@ constexpr int ORBIS_AVPLAYER_ERROR_INFO_AES_ENCRY = 0x806A00B5;
|
||||||
constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF;
|
constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF;
|
||||||
|
|
||||||
// AppContent library
|
// 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 "about_dialog.h"
|
||||||
#include "cheats_patches.h"
|
#include "cheats_patches.h"
|
||||||
#include "common/io_file.h"
|
#include "common/io_file.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
#include "common/version.h"
|
#include "common/version.h"
|
||||||
#include "core/file_format/pkg.h"
|
#include "core/file_format/pkg.h"
|
||||||
#include "core/loader.h"
|
#include "core/loader.h"
|
||||||
|
@ -615,15 +616,24 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
||||||
pkg = PKG();
|
pkg = PKG();
|
||||||
pkg.Open(file);
|
pkg.Open(file);
|
||||||
std::string failreason;
|
std::string failreason;
|
||||||
const auto extract_path =
|
auto extract_path = std::filesystem::path(Config::getGameInstallDir()) / pkg.GetTitleID();
|
||||||
std::filesystem::path(Config::getGameInstallDir()) / pkg.GetTitleID();
|
|
||||||
QString pkgType = QString::fromStdString(pkg.GetPkgFlags());
|
QString pkgType = QString::fromStdString(pkg.GetPkgFlags());
|
||||||
QDir game_dir(QString::fromStdString(extract_path.string()));
|
QDir game_dir(QString::fromStdString(extract_path.string()));
|
||||||
if (game_dir.exists()) {
|
if (game_dir.exists()) {
|
||||||
QMessageBox msgBox;
|
QMessageBox msgBox;
|
||||||
msgBox.setWindowTitle(tr("PKG Extraction"));
|
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")) {
|
if (pkgType.contains("PATCH")) {
|
||||||
psf.open("", pkg.sfo);
|
|
||||||
QString pkg_app_version = QString::fromStdString(psf.GetString("APP_VER"));
|
QString pkg_app_version = QString::fromStdString(psf.GetString("APP_VER"));
|
||||||
psf.open(extract_path.string() + "/sce_sys/param.sfo", {});
|
psf.open(extract_path.string() + "/sce_sys/param.sfo", {});
|
||||||
QString game_app_version = QString::fromStdString(psf.GetString("APP_VER"));
|
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 {
|
} else {
|
||||||
return;
|
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 {
|
} else {
|
||||||
msgBox.setText(
|
msgBox.setText(
|
||||||
QString(tr("Game already installed\n%1\nWould you like to overwrite?"))
|
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 {
|
} else {
|
||||||
int nfiles = pkg.GetNumberOfFiles();
|
int nfiles = pkg.GetNumberOfFiles();
|
||||||
|
|
||||||
QVector<int> indices;
|
if (nfiles > 0) {
|
||||||
for (int i = 0; i < nfiles; i++) {
|
QVector<int> indices;
|
||||||
indices.append(i);
|
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();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
connect(&dialog, &QProgressDialog::canceled, [&]() { futureWatcher.cancel(); });
|
QProgressDialog dialog;
|
||||||
connect(&futureWatcher, &QFutureWatcher<void>::progressValueChanged, &dialog,
|
dialog.setWindowTitle(tr("PKG Extraction"));
|
||||||
&QProgressDialog::setValue);
|
dialog.setWindowModality(Qt::WindowModal);
|
||||||
futureWatcher.setFuture(
|
QString extractmsg = QString(tr("Extracting PKG %1/%2")).arg(pkgNum).arg(nPkg);
|
||||||
QtConcurrent::map(indices, [&](int index) { pkg.ExtractFiles(index); }));
|
dialog.setLabelText(extractmsg);
|
||||||
dialog.exec();
|
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 {
|
} else {
|
||||||
QMessageBox::critical(this, tr("PKG ERROR"),
|
QMessageBox::critical(this, tr("PKG ERROR"),
|
||||||
|
|
|
@ -605,6 +605,16 @@
|
||||||
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Spil allerede installeret\n%1\nVil du overskrive?</translation>
|
<translation>Spil allerede installeret\n%1\nVil du overskrive?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Spiel bereits installiert\n%1\nMöchten Sie überschreiben?</translation>
|
<translation>Spiel bereits installiert\n%1\nMöchten Sie überschreiben?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Το παιχνίδι είναι ήδη εγκατεστημένο\n%1\nΘέλετε να αντικαταστήσετε;</translation>
|
<translation>Το παιχνίδι είναι ήδη εγκατεστημένο\n%1\nΘέλετε να αντικαταστήσετε;</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Game already installed\n%1\nWould you like to overwrite?</translation>
|
<translation>Game already installed\n%1\nWould you like to overwrite?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Juego ya instalado\n%1\n¿Te gustaría sobrescribirlo?</translation>
|
<translation>Juego ya instalado\n%1\n¿Te gustaría sobrescribirlo?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Peli on jo asennettu\n%1\nHaluatko korvata sen?</translation>
|
<translation>Peli on jo asennettu\n%1\nHaluatko korvata sen?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Jeu déjà installé\n%1\nSouhaitez-vous écraser ?</translation>
|
<translation>Jeu déjà installé\n%1\nSouhaitez-vous écraser ?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<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>
|
<translation>A játék már telepítve van\n%1\nSzeretnéd felülírni?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Game sudah terpasang\n%1\nApakah Anda ingin menimpa?</translation>
|
<translation>Game sudah terpasang\n%1\nApakah Anda ingin menimpa?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Game sudah terinstal\n%1\nApakah Anda ingin menimpa?</translation>
|
<translation>Game sudah terinstal\n%1\nApakah Anda ingin menimpa?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>ゲームはすでにインストールされています\n%1\n上書きしますか?</translation>
|
<translation>ゲームはすでにインストールされています\n%1\n上書きしますか?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Game already installed\n%1\nWould you like to overwrite?</translation>
|
<translation>Game already installed\n%1\nWould you like to overwrite?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<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>
|
<translation>Žaidimas jau įdiegtas\n%1\nAr norėtumėte perrašyti?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Spill allerede installert\n%1\nØnsker du å overskrive?</translation>
|
<translation>Spill allerede installert\n%1\nØnsker du å overskrive?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Spel al geïnstalleerd\n%1\nWil je het overschrijven?</translation>
|
<translation>Spel al geïnstalleerd\n%1\nWil je het overschrijven?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Gra już zainstalowana\n%1\nCzy chcesz ją nadpisać?</translation>
|
<translation>Gra już zainstalowana\n%1\nCzy chcesz ją nadpisać?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Jogo já instalado\n%1\nGostaria de substituir?</translation>
|
<translation>Jogo já instalado\n%1\nGostaria de substituir?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Jocul este deja instalat\n%1\nAi dori să suprascrii?</translation>
|
<translation>Jocul este deja instalat\n%1\nAi dori să suprascrii?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>Игра уже установлена\n%1\nХотите перезаписать?</translation>
|
<translation>Игра уже установлена\n%1\nХотите перезаписать?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<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>
|
<translation>Oyun zaten yüklü\n%1\nÜzerine yazmak ister misiniz?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<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>
|
<translation>Trò chơi đã được cài đặt\n%1\nBạn có muốn ghi đè không?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>游戏已安装\n%1\n您想要覆盖吗?</translation>
|
<translation>游戏已安装\n%1\n您想要覆盖吗?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<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>
|
<source>Game already installed\n%1\nWould you like to overwrite?</source>
|
||||||
<translation>遊戲已經安裝\n%1\n您是否希望覆蓋?</translation>
|
<translation>遊戲已經安裝\n%1\n您是否希望覆蓋?</translation>
|
||||||
</message>
|
</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>
|
<message>
|
||||||
<location filename="../main_window.cpp" line="674"/>
|
<location filename="../main_window.cpp" line="674"/>
|
||||||
<source>PKG is a patch, please install the game first!</source>
|
<source>PKG is a patch, please install the game first!</source>
|
||||||
|
|
Loading…
Reference in New Issue