Mself / Sdata: on the fly partial decoding support (#2468)

This commit is contained in:
Jake 2017-03-06 18:59:05 -06:00 committed by raven02
parent 039e295e53
commit 87fe93ee9a
8 changed files with 616 additions and 459 deletions

View file

@ -140,8 +140,7 @@ int decompress(unsigned char *out, unsigned char *in, unsigned int size)
unsigned int range = 0xFFFFFFFF; unsigned int range = 0xFFFFFFFF;
unsigned int code = (in[1] << 24) | (in[2] << 16) | (in[3] << 8) | in[4]; 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 > 0x80) // Check if we have a valid starting byte.
if (head < 0) // Check if we have a valid starting byte.
{ {
// The dictionary header is invalid, the data is not compressed. // The dictionary header is invalid, the data is not compressed.
result = -1; result = -1;

File diff suppressed because it is too large Load diff

View file

@ -4,33 +4,95 @@
#include <string.h> #include <string.h>
#include "utils.h" #include "utils.h"
#define SDAT_FLAG 0x01000000 constexpr u32 SDAT_FLAG = 0x01000000;
#define EDAT_COMPRESSED_FLAG 0x00000001 constexpr u32 EDAT_COMPRESSED_FLAG = 0x00000001;
#define EDAT_FLAG_0x02 0x00000002 constexpr u32 EDAT_FLAG_0x02 = 0x00000002;
#define EDAT_ENCRYPTED_KEY_FLAG 0x00000008 constexpr u32 EDAT_ENCRYPTED_KEY_FLAG = 0x00000008;
#define EDAT_FLAG_0x10 0x00000010 constexpr u32 EDAT_FLAG_0x10 = 0x00000010;
#define EDAT_FLAG_0x20 0x00000020 constexpr u32 EDAT_FLAG_0x20 = 0x00000020;
#define EDAT_DEBUG_DATA_FLAG 0x80000000 constexpr u32 EDAT_DEBUG_DATA_FLAG = 0x80000000;
typedef struct struct NPD_HEADER
{ {
unsigned char magic[4]; u32 magic;
int version; s32 version;
int license; s32 license;
int type; s32 type;
unsigned char content_id[0x30]; u8 content_id[0x30];
unsigned char digest[0x10]; u8 digest[0x10];
unsigned char title_hash[0x10]; u8 title_hash[0x10];
unsigned char dev_hash[0x10]; u8 dev_hash[0x10];
unsigned long long unk1; u64 unk1;
unsigned long long unk2; u64 unk2;
} NPD_HEADER; };
typedef struct struct EDAT_HEADER
{ {
int flags; s32 flags;
int block_size; s32 block_size;
unsigned long long file_size; u64 file_size;
} EDAT_HEADER; };
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; }
};

View file

@ -9,28 +9,10 @@
#include <memory> #include <memory>
// Auxiliary functions (endian swap, xor and prng). // 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); for(int i = 0; i < 0x10; i++)
}
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++)
{ {
dest[i] = src1[i] ^ src2[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) void prng(unsigned char *dest, int size)
{ {
unsigned char *buffer = new unsigned char[size];
srand((u32)time(0)); srand((u32)time(0));
int i; for(int i = 0; i < size; i++)
for(i = 0; i < size; i++) dest[i] = (unsigned char)(rand() & 0xFF);
buffer[i] = (unsigned char)(rand() & 0xFF);
memcpy(dest, buffer, size);
delete[] buffer;
} }
// Hex string conversion auxiliary functions. // 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) 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) if (hex_str == NULL)
return false; return false;
unsigned int i; unsigned int i;
for (i = 0; i < str_length; i++) for (i = 0; i < str_length; i++)
{ {
if (strchr(hex_chars, hex_str[i]) == 0) if (strchr(hex_chars, hex_str[i]) == 0)
return false; return false;
} }
return true; return true;
} }
// Crypto functions (AES128-CBC, AES128-ECB, SHA1-HMAC and AES-CMAC). // 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) 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++) return std::memcmp(out.get(), hash, hash_len) == 0;
{
if (out[i] != hash[i])
{
delete[] out;
return false;
}
}
delete[] out;
return true;
} }
void hmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash) 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) 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_context ctx;
aes_setkey_enc(&ctx, key, 128); 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++) return std::memcmp(out.get(), hash, hash_len) == 0;
{
if (out[i] != hash[i])
{
delete[] out;
return false;
}
}
delete[] out;
return true;
} }
void cmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash) void cmac_hash_forge(unsigned char *key, int key_len, unsigned char *in, int in_len, unsigned char *hash)

View file

@ -15,10 +15,40 @@
#include "ec.h" #include "ec.h"
// Auxiliary functions (endian swap, xor, prng and file name). // Auxiliary functions (endian swap, xor, prng and file name).
u16 swap16(u16 i); inline u16 swap16(u16 i)
u32 swap32(u32 i); {
u64 swap64(u64 i); #if defined(__GNUG__)
void xor_key(unsigned char *dest, unsigned char *src1, unsigned char *src2, int size); 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); void prng(unsigned char *dest, int size);
char* extract_file_name(const char* file_path, char real_file_name[MAX_PATH]); char* extract_file_name(const char* file_path, char real_file_name[MAX_PATH]);

View file

@ -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& 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. // 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::remove_file(enc_drm_path_local);
fs::rename(dec_drm_path_local, enc_drm_path_local); fs::rename(dec_drm_path_local, enc_drm_path_local);
} }

View file

@ -3,6 +3,8 @@
#include <mutex> #include <mutex>
#include "Emu/Cell/PPUThread.h"
#include "Crypto/unedat.h"
#include "Emu/VFS.h" #include "Emu/VFS.h"
#include "Emu/IdManager.h" #include "Emu/IdManager.h"
#include "Utilities/StrUtil.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_app_home;
lv2_fs_mount_point g_mp_sys_host_root; 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) lv2_fs_mount_point* lv2_fs_object::get_mp(const char* filename)
{ {
// TODO // TODO
@ -75,8 +108,6 @@ struct lv2_file::file_view : fs::file_base
u64 read(void* buffer, u64 size) override 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 old_pos = m_file->file.pos();
const u64 new_pos = m_file->file.seek(m_off + m_pos); const u64 new_pos = m_file->file.seek(m_off + m_pos);
const u64 result = m_file->file.read(buffer, size); 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 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; 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)) if (const u32 id = idm::make<lv2_fs_object, lv2_file>(path.get_ptr(), std::move(file), mode, flags))
{ {
*fd = id; *fd = id;
@ -559,8 +624,16 @@ error_code sys_fs_fcntl(u32 fd, u32 op, vm::ptr<void> _arg, u32 _size)
return CELL_EBADF; return CELL_EBADF;
} }
// TODO auto sdata_file = std::make_unique<SDATADecrypter>(lv2_file::make_view(file, arg->offset));
if (const u32 id = idm::make<lv2_fs_object, lv2_file>(file->mp, lv2_file::make_view(file, arg->offset), file->mode, file->flags))
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_code = CELL_OK;
arg->out_fd = id; arg->out_fd = id;

View file

@ -91,6 +91,26 @@ struct CellFsUtimbuf
CHECK_SIZE_ALIGN(CellFsUtimbuf, 16, 4); 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_mount_point;
struct lv2_fs_object struct lv2_fs_object