mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-20 11:36:13 +00:00
Mself / Sdata: on the fly partial decoding support (#2468)
This commit is contained in:
parent
039e295e53
commit
87fe93ee9a
8 changed files with 616 additions and 459 deletions
|
@ -140,8 +140,7 @@ int decompress(unsigned char *out, unsigned char *in, unsigned int size)
|
|||
unsigned int range = 0xFFFFFFFF;
|
||||
unsigned int code = (in[1] << 24) | (in[2] << 16) | (in[3] << 8) | in[4];
|
||||
|
||||
// TODO:: Syphurith: There was a check against the unsigned char head. if (head < 0) would always be false.. I don't know are you tried to if (head > 0x80)?
|
||||
if (head < 0) // Check if we have a valid starting byte.
|
||||
if (head > 0x80) // Check if we have a valid starting byte.
|
||||
{
|
||||
// The dictionary header is invalid, the data is not compressed.
|
||||
result = -1;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,33 +4,95 @@
|
|||
#include <string.h>
|
||||
#include "utils.h"
|
||||
|
||||
#define SDAT_FLAG 0x01000000
|
||||
#define EDAT_COMPRESSED_FLAG 0x00000001
|
||||
#define EDAT_FLAG_0x02 0x00000002
|
||||
#define EDAT_ENCRYPTED_KEY_FLAG 0x00000008
|
||||
#define EDAT_FLAG_0x10 0x00000010
|
||||
#define EDAT_FLAG_0x20 0x00000020
|
||||
#define EDAT_DEBUG_DATA_FLAG 0x80000000
|
||||
constexpr u32 SDAT_FLAG = 0x01000000;
|
||||
constexpr u32 EDAT_COMPRESSED_FLAG = 0x00000001;
|
||||
constexpr u32 EDAT_FLAG_0x02 = 0x00000002;
|
||||
constexpr u32 EDAT_ENCRYPTED_KEY_FLAG = 0x00000008;
|
||||
constexpr u32 EDAT_FLAG_0x10 = 0x00000010;
|
||||
constexpr u32 EDAT_FLAG_0x20 = 0x00000020;
|
||||
constexpr u32 EDAT_DEBUG_DATA_FLAG = 0x80000000;
|
||||
|
||||
typedef struct
|
||||
struct NPD_HEADER
|
||||
{
|
||||
unsigned char magic[4];
|
||||
int version;
|
||||
int license;
|
||||
int type;
|
||||
unsigned char content_id[0x30];
|
||||
unsigned char digest[0x10];
|
||||
unsigned char title_hash[0x10];
|
||||
unsigned char dev_hash[0x10];
|
||||
unsigned long long unk1;
|
||||
unsigned long long unk2;
|
||||
} NPD_HEADER;
|
||||
u32 magic;
|
||||
s32 version;
|
||||
s32 license;
|
||||
s32 type;
|
||||
u8 content_id[0x30];
|
||||
u8 digest[0x10];
|
||||
u8 title_hash[0x10];
|
||||
u8 dev_hash[0x10];
|
||||
u64 unk1;
|
||||
u64 unk2;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
struct EDAT_HEADER
|
||||
{
|
||||
int flags;
|
||||
int block_size;
|
||||
unsigned long long file_size;
|
||||
} EDAT_HEADER;
|
||||
s32 flags;
|
||||
s32 block_size;
|
||||
u64 file_size;
|
||||
};
|
||||
|
||||
int DecryptEDAT(const std::string& input_file_name, const std::string& output_file_name, int mode, const std::string& rap_file_name, unsigned char *custom_klic, bool verbose);
|
||||
// Decrypts full file, or null/empty file
|
||||
extern fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, int mode, const std::string& rap_file_name, u8 *custom_klic, bool verbose);
|
||||
|
||||
struct SDATADecrypter final : fs::file_base
|
||||
{
|
||||
// file stream
|
||||
const fs::file sdata_file;
|
||||
const u64 file_offset;
|
||||
u64 file_size{0};
|
||||
u32 total_blocks{0};
|
||||
u64 pos{0};
|
||||
|
||||
NPD_HEADER npdHeader;
|
||||
EDAT_HEADER edatHeader;
|
||||
|
||||
// Internal data buffers.
|
||||
std::unique_ptr<u8[]> data_buf;
|
||||
u64 data_buf_size{0};
|
||||
|
||||
std::array<u8, 0x10> dec_key{};
|
||||
public:
|
||||
SDATADecrypter(fs::file&& input, u64 offset=0);
|
||||
~SDATADecrypter() override {}
|
||||
// false if invalid
|
||||
bool ReadHeader();
|
||||
u64 ReadData(u64 pos, u8* data, u64 size);
|
||||
|
||||
fs::stat_t stat() override
|
||||
{
|
||||
fs::stat_t stats;
|
||||
stats.is_directory = false;
|
||||
stats.is_writable = false;
|
||||
stats.size = file_size;
|
||||
stats.atime = -1;
|
||||
stats.ctime = -1;
|
||||
stats.mtime = -1;
|
||||
return stats;
|
||||
}
|
||||
bool trunc(u64 length) override
|
||||
{
|
||||
return true;
|
||||
};
|
||||
u64 read(void* buffer, u64 size) override
|
||||
{
|
||||
u64 bytesRead = ReadData(pos, (u8*)buffer, size);
|
||||
pos += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
u64 write(const void* buffer, u64 size) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 seek(s64 offset, fs::seek_mode whence) override
|
||||
{
|
||||
return
|
||||
whence == fs::seek_set ? pos = offset :
|
||||
whence == fs::seek_cur ? pos = offset + pos :
|
||||
whence == fs::seek_end ? pos = offset + size() :
|
||||
(fmt::raw_error("SDATADecrypter::seek(): invalid whence"), 0);
|
||||
}
|
||||
u64 size() override { return file_size; }
|
||||
};
|
||||
|
|
|
@ -9,28 +9,10 @@
|
|||
#include <memory>
|
||||
|
||||
// Auxiliary functions (endian swap, xor and prng).
|
||||
u16 swap16(u16 i)
|
||||
{
|
||||
return ((i & 0xFF00) >> 8) | ((i & 0xFF) << 8);
|
||||
}
|
||||
|
||||
u32 swap32(u32 i)
|
||||
void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2)
|
||||
{
|
||||
return ((i & 0xFF000000) >> 24) | ((i & 0xFF0000) >> 8) | ((i & 0xFF00) << 8) | ((i & 0xFF) << 24);
|
||||
}
|
||||
|
||||
u64 swap64(u64 i)
|
||||
{
|
||||
return ((i & 0x00000000000000ff) << 56) | ((i & 0x000000000000ff00) << 40) |
|
||||
((i & 0x0000000000ff0000) << 24) | ((i & 0x00000000ff000000) << 8) |
|
||||
((i & 0x000000ff00000000) >> 8) | ((i & 0x0000ff0000000000) >> 24) |
|
||||
((i & 0x00ff000000000000) >> 40) | ((i & 0xff00000000000000) >> 56);
|
||||
}
|
||||
|
||||
void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2, int size)
|
||||
{
|
||||
int i;
|
||||
for(i = 0; i < size; i++)
|
||||
for(int i = 0; i < 0x10; i++)
|
||||
{
|
||||
dest[i] = src1[i] ^ src2[i];
|
||||
}
|
||||
|
@ -38,16 +20,10 @@ void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2, int
|
|||
|
||||
void prng(unsigned char *dest, int size)
|
||||
{
|
||||
unsigned char *buffer = new unsigned char[size];
|
||||
srand((u32)time(0));
|
||||
|
||||
int i;
|
||||
for(i = 0; i < size; i++)
|
||||
buffer[i] = (unsigned char)(rand() & 0xFF);
|
||||
|
||||
memcpy(dest, buffer, size);
|
||||
|
||||
delete[] buffer;
|
||||
for(int i = 0; i < size; i++)
|
||||
dest[i] = (unsigned char)(rand() & 0xFF);
|
||||
}
|
||||
|
||||
// Hex string conversion auxiliary functions.
|
||||
|
@ -102,19 +78,19 @@ void hex_to_bytes(unsigned char *data, const char *hex_str, unsigned int str_len
|
|||
|
||||
bool is_hex(const char* hex_str, unsigned int str_length)
|
||||
{
|
||||
static const char hex_chars[] = "0123456789abcdefABCDEF";
|
||||
static const char hex_chars[] = "0123456789abcdefABCDEF";
|
||||
|
||||
if (hex_str == NULL)
|
||||
return false;
|
||||
if (hex_str == NULL)
|
||||
return false;
|
||||
|
||||
unsigned int i;
|
||||
for (i = 0; i < str_length; i++)
|
||||
unsigned int i;
|
||||
for (i = 0; i < str_length; i++)
|
||||
{
|
||||
if (strchr(hex_chars, hex_str[i]) == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Crypto functions (AES128-CBC, AES128-ECB, SHA1-HMAC and AES-CMAC).
|
||||
|
@ -147,22 +123,11 @@ void aesecb128_encrypt(unsigned char *key, unsigned char *in, unsigned char *out
|
|||
|
||||
bool hmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash, int hash_len)
|
||||
{
|
||||
unsigned char *out = new unsigned char[key_len];
|
||||
std::unique_ptr<u8> out(new u8[key_len]);
|
||||
|
||||
sha1_hmac(key, key_len, in, in_len, out);
|
||||
sha1_hmac(key, key_len, in, in_len, out.get());
|
||||
|
||||
for (int i = 0; i < hash_len; i++)
|
||||
{
|
||||
if (out[i] != hash[i])
|
||||
{
|
||||
delete[] out;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] out;
|
||||
|
||||
return true;
|
||||
return std::memcmp(out.get(), hash, hash_len) == 0;
|
||||
}
|
||||
|
||||
void hmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash)
|
||||
|
@ -172,24 +137,13 @@ void hmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_
|
|||
|
||||
bool cmac_hash_compare(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash, int hash_len)
|
||||
{
|
||||
unsigned char *out = new unsigned char[key_len];
|
||||
std::unique_ptr<u8> out(new u8[key_len]);
|
||||
|
||||
aes_context ctx;
|
||||
aes_setkey_enc(&ctx, key, 128);
|
||||
aes_cmac(&ctx, in_len, in, out);
|
||||
aes_cmac(&ctx, in_len, in, out.get());
|
||||
|
||||
for (int i = 0; i < hash_len; i++)
|
||||
{
|
||||
if (out[i] != hash[i])
|
||||
{
|
||||
delete[] out;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] out;
|
||||
|
||||
return true;
|
||||
return std::memcmp(out.get(), hash, hash_len) == 0;
|
||||
}
|
||||
|
||||
void cmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash)
|
||||
|
|
|
@ -15,10 +15,40 @@
|
|||
#include "ec.h"
|
||||
|
||||
// Auxiliary functions (endian swap, xor, prng and file name).
|
||||
u16 swap16(u16 i);
|
||||
u32 swap32(u32 i);
|
||||
u64 swap64(u64 i);
|
||||
void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2, int size);
|
||||
inline u16 swap16(u16 i)
|
||||
{
|
||||
#if defined(__GNUG__)
|
||||
return __builtin_bswap16(i);
|
||||
#else
|
||||
return _byteswap_ushort(i);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline u32 swap32(u32 i)
|
||||
{
|
||||
#if defined(__GNUG__)
|
||||
return __builtin_bswap32(i);
|
||||
#else
|
||||
return _byteswap_ulong(i);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline u64 swap64(u64 i)
|
||||
{
|
||||
#if defined(__GNUG__)
|
||||
return __builtin_bswap64(i);
|
||||
#else
|
||||
return _byteswap_uint64(i);
|
||||
#endif
|
||||
}
|
||||
|
||||
void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2);
|
||||
inline void xor_key_sse(u8* dest, const u8* src1, const u8* src2)
|
||||
{
|
||||
_mm_storeu_si128(&(((__m128i*)dest)[0]),
|
||||
_mm_xor_si128(_mm_loadu_si128((__m128i*)src1), _mm_loadu_si128((__m128i*)src2)));
|
||||
}
|
||||
|
||||
void prng(unsigned char *dest, int size);
|
||||
char* extract_file_name(const char* file_path, char real_file_name[MAX_PATH]);
|
||||
|
||||
|
|
|
@ -89,11 +89,16 @@ s32 npDrmIsAvailable(vm::cptr<u8> k_licensee_addr, vm::cptr<char> drm_path)
|
|||
}
|
||||
|
||||
const std::string& enc_drm_path_local = vfs::get(enc_drm_path);
|
||||
const std::string& dec_drm_path_local = vfs::get(dec_drm_path);
|
||||
const fs::file enc_file(enc_drm_path_local);
|
||||
|
||||
if (DecryptEDAT(enc_drm_path_local, dec_drm_path_local, 8, rap_lpath, k_licensee, false) >= 0)
|
||||
if (const fs::file dec_file = DecryptEDAT(enc_file, enc_drm_path_local, 8, rap_lpath, k_licensee, false))
|
||||
{
|
||||
// If decryption succeeds, replace the encrypted file with it.
|
||||
const std::string& dec_drm_path_local = vfs::get(dec_drm_path);
|
||||
|
||||
fs::file dec_out(dec_drm_path_local, fs::rewrite);
|
||||
dec_out.write(dec_file.to_vector<u8>());
|
||||
|
||||
fs::remove_file(enc_drm_path_local);
|
||||
fs::rename(dec_drm_path_local, enc_drm_path_local);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <mutex>
|
||||
|
||||
#include "Emu/Cell/PPUThread.h"
|
||||
#include "Crypto/unedat.h"
|
||||
#include "Emu/VFS.h"
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Utilities/StrUtil.h"
|
||||
|
@ -23,6 +25,37 @@ lv2_fs_mount_point g_mp_sys_dev_bdvd;
|
|||
lv2_fs_mount_point g_mp_sys_app_home;
|
||||
lv2_fs_mount_point g_mp_sys_host_root;
|
||||
|
||||
bool verify_mself(u32 fd, fs::file const& mself_file)
|
||||
{
|
||||
FsMselfHeader mself_header;
|
||||
if (!mself_file.read<FsMselfHeader>(mself_header))
|
||||
{
|
||||
sys_fs.error("verify_mself: Didn't read expected bytes for header.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mself_header.m_magic != 0x4D534600)
|
||||
{
|
||||
sys_fs.error("verify_mself: Header magic is incorrect.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mself_header.m_format_version != 1)
|
||||
{
|
||||
sys_fs.error("verify_mself: Unexpected header format version.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if (mself_header.m_entry_size != sizeof(FsMselfEntry))
|
||||
{
|
||||
sys_fs.error("verify_mself: Unexpected header entry size.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
lv2_fs_mount_point* lv2_fs_object::get_mp(const char* filename)
|
||||
{
|
||||
// TODO
|
||||
|
@ -75,8 +108,6 @@ struct lv2_file::file_view : fs::file_base
|
|||
|
||||
u64 read(void* buffer, u64 size) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_file->mp->mutex);
|
||||
|
||||
const u64 old_pos = m_file->file.pos();
|
||||
const u64 new_pos = m_file->file.seek(m_off + m_pos);
|
||||
const u64 result = m_file->file.read(buffer, size);
|
||||
|
@ -182,7 +213,17 @@ error_code sys_fs_open(vm::cptr<char> path, s32 flags, vm::ptr<u32> fd, s32 mode
|
|||
}
|
||||
}
|
||||
|
||||
if (flags & ~(CELL_FS_O_ACCMODE | CELL_FS_O_CREAT | CELL_FS_O_TRUNC | CELL_FS_O_APPEND | CELL_FS_O_EXCL))
|
||||
if (flags & CELL_FS_O_MSELF)
|
||||
{
|
||||
open_mode = fs::read;
|
||||
// mself can be mself or mself | rdonly
|
||||
if (flags & ~(CELL_FS_O_MSELF | CELL_FS_O_RDONLY))
|
||||
{
|
||||
open_mode = {};
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & ~(CELL_FS_O_ACCMODE | CELL_FS_O_CREAT | CELL_FS_O_TRUNC | CELL_FS_O_APPEND | CELL_FS_O_EXCL | CELL_FS_O_MSELF))
|
||||
{
|
||||
open_mode = {}; // error
|
||||
}
|
||||
|
@ -211,6 +252,30 @@ error_code sys_fs_open(vm::cptr<char> path, s32 flags, vm::ptr<u32> fd, s32 mode
|
|||
return CELL_ENOENT;
|
||||
}
|
||||
|
||||
if ((flags & CELL_FS_O_MSELF) && (!verify_mself(*fd, file)))
|
||||
return CELL_ENOTMSELF;
|
||||
|
||||
// sdata encryption arg flag
|
||||
const be_t<u32>* casted_args = static_cast<const be_t<u32> *>(arg.get_ptr());
|
||||
if (size == 8 && casted_args[0] == 0x180 && casted_args[1] == 0x10)
|
||||
{
|
||||
// check if the file has the NPD header, or else assume its not encrypted
|
||||
u32 magic;
|
||||
file.read<u32>(magic);
|
||||
file.seek(0);
|
||||
if (magic == "NPD\0"_u32)
|
||||
{
|
||||
auto sdata_file = std::make_unique<SDATADecrypter>(std::move(file));
|
||||
if (!sdata_file->ReadHeader())
|
||||
{
|
||||
sys_fs.error("sys_fs_open(%s): Error reading sdata header!", path);
|
||||
return CELL_EFSSPECIFIC;
|
||||
}
|
||||
|
||||
file.reset(std::move(sdata_file));
|
||||
}
|
||||
}
|
||||
|
||||
if (const u32 id = idm::make<lv2_fs_object, lv2_file>(path.get_ptr(), std::move(file), mode, flags))
|
||||
{
|
||||
*fd = id;
|
||||
|
@ -559,8 +624,16 @@ error_code sys_fs_fcntl(u32 fd, u32 op, vm::ptr<void> _arg, u32 _size)
|
|||
return CELL_EBADF;
|
||||
}
|
||||
|
||||
// TODO
|
||||
if (const u32 id = idm::make<lv2_fs_object, lv2_file>(file->mp, lv2_file::make_view(file, arg->offset), file->mode, file->flags))
|
||||
auto sdata_file = std::make_unique<SDATADecrypter>(lv2_file::make_view(file, arg->offset));
|
||||
|
||||
if (!sdata_file->ReadHeader())
|
||||
{
|
||||
return CELL_EFSSPECIFIC;
|
||||
}
|
||||
|
||||
fs::file stream;
|
||||
stream.reset(std::move(sdata_file));
|
||||
if (const u32 id = idm::make<lv2_fs_object, lv2_file>(file->mp, std::move(stream), file->mode, file->flags))
|
||||
{
|
||||
arg->out_code = CELL_OK;
|
||||
arg->out_fd = id;
|
||||
|
|
|
@ -91,6 +91,26 @@ struct CellFsUtimbuf
|
|||
|
||||
CHECK_SIZE_ALIGN(CellFsUtimbuf, 16, 4);
|
||||
|
||||
// MSelf file structs
|
||||
struct FsMselfHeader
|
||||
{
|
||||
be_t<u32> m_magic;
|
||||
be_t<u32> m_format_version;
|
||||
be_t<u64> m_file_size;
|
||||
be_t<u32> m_entry_num;
|
||||
be_t<u32> m_entry_size;
|
||||
u8 m_reserve[40];
|
||||
|
||||
};
|
||||
|
||||
struct FsMselfEntry
|
||||
{
|
||||
char m_name[32];
|
||||
be_t<u64> m_offset;
|
||||
be_t<u64> m_size;
|
||||
u8 m_reserve[16];
|
||||
};
|
||||
|
||||
struct lv2_fs_mount_point;
|
||||
|
||||
struct lv2_fs_object
|
||||
|
|
Loading…
Add table
Reference in a new issue