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 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

View file

@ -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; }
};

View file

@ -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)

View file

@ -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]);

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& 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);
}

View file

@ -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;

View file

@ -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