mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-12-19 09:55:53 +01:00
153 lines
4.2 KiB
C
153 lines
4.2 KiB
C
#ifndef _KTSR_STREAMFILE_H_
|
|
#define _KTSR_STREAMFILE_H_
|
|
|
|
#include "../streamfile.h"
|
|
#include "../util/log.h"
|
|
#include "../util/cipher_blowfish.h"
|
|
|
|
/* decrypts blowfish in realtime (as done by games) */
|
|
typedef struct {
|
|
uint8_t key[0x20];
|
|
uint8_t block[0x08];
|
|
blowfish_ctx* ctx;
|
|
} ktsr_io_data;
|
|
|
|
static int ktsr_io_init(STREAMFILE* sf, ktsr_io_data* data) {
|
|
/* ktsr keys start with size then random bytes (usually 7), assumed max 0x20 */
|
|
if (data->key[0] >= sizeof(data->key) - 1)
|
|
return -1;
|
|
|
|
data->ctx = blowfish_init_ecb(data->key + 1, data->key[0]);
|
|
if (!data->ctx)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static void ktsr_io_close(STREAMFILE* sf, ktsr_io_data* data) {
|
|
blowfish_free(data->ctx);
|
|
}
|
|
|
|
|
|
static int read_part(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, ktsr_io_data* data) {
|
|
|
|
off_t offset_rem = offset % 0x08;
|
|
offset -= offset_rem;
|
|
|
|
if (offset_rem == 0 && length >= 0x08) /* handled in main */
|
|
return 0;
|
|
|
|
/* read one full block, regardless of requested length */
|
|
int bytes = read_streamfile(data->block, offset, 0x08, sf);
|
|
|
|
/* useless since KTSR data is padded and blocks don't work otherwise but for determinability */
|
|
if (bytes < 0x08)
|
|
memset(data->block + bytes, 0, 0x08 - bytes);
|
|
|
|
blowfish_decrypt_ecb(data->ctx, data->block);
|
|
|
|
int max_copy = bytes - offset_rem;
|
|
if (max_copy > length)
|
|
max_copy = length;
|
|
|
|
memcpy(dest, data->block + offset_rem, max_copy);
|
|
|
|
return max_copy;
|
|
}
|
|
|
|
static int read_main(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, ktsr_io_data* data) {
|
|
int read = 0;
|
|
|
|
off_t offset_rem = offset % 0x08;
|
|
size_t length_rem = length % 0x08;
|
|
length -= length_rem;
|
|
|
|
if (offset_rem != 0 || length == 0) /* handled in part */
|
|
return 0;
|
|
|
|
int bytes = read_streamfile(dest, offset, length, sf);
|
|
|
|
while (read < bytes) {
|
|
blowfish_decrypt_ecb(data->ctx, dest);
|
|
dest += 0x08;
|
|
read += 0x08;
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
/* blowfish is a 64-bit block cipher, so arbitrary reads will need to handle partial cases. ex
|
|
* - reading 0x00 to 0x20: direct decrypt (4 blocks of 0x08)
|
|
* - reading 0x03 to 0x07: decrypt 0x00 to 0x08 but copy 4 bytes at 0x03
|
|
* - reading 0x03 to 0x22: handle as 0x00 to 0x08 (head, copy 5 at 0x3), 0x08 to 0x20 (body, direct), and 0x20 to 0x28 (tail, copy 2 at 0x0). */
|
|
static size_t ktsr_io_read(STREAMFILE* sf, uint8_t* dest, off_t offset, size_t length, ktsr_io_data* data) {
|
|
int bytes = 0;
|
|
|
|
|
|
/* head */
|
|
if (length) {
|
|
int done = read_part(sf, dest, offset, length, data);
|
|
dest += done;
|
|
offset += done;
|
|
length -= done;
|
|
|
|
bytes += done;
|
|
}
|
|
|
|
/* body */
|
|
if (length) {
|
|
int done = read_main(sf, dest, offset, length, data);
|
|
dest += done;
|
|
offset += done;
|
|
length -= done;
|
|
|
|
bytes += done;
|
|
}
|
|
|
|
/* tail */
|
|
if (length) {
|
|
int done = read_part(sf, dest, offset, length, data);
|
|
dest += done;
|
|
offset += done;
|
|
length -= done;
|
|
|
|
bytes += done;
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
|
|
|
|
/* Decrypts blowfish KTSR streams */
|
|
static STREAMFILE* setup_ktsr_streamfile(STREAMFILE* sf, bool is_external, uint32_t subfile_offset, uint32_t subfile_size, const char* extension) {
|
|
STREAMFILE* new_sf = NULL;
|
|
ktsr_io_data io_data = {0};
|
|
|
|
if (is_external) {
|
|
uint32_t offset = 0x00;
|
|
if (is_id32be(0x00, sf, "TSRS"))
|
|
offset += 0x10;
|
|
if (!is_id32be(offset + 0x00, sf, "KTSR"))
|
|
goto fail;
|
|
|
|
read_streamfile(io_data.key, offset + 0x20, sizeof(io_data.key), sf);
|
|
}
|
|
|
|
/* setup subfile */
|
|
new_sf = open_wrap_streamfile(sf);
|
|
|
|
/* no apparent flag other than key at offset. Only data at subfile is encrypted, so this reader assumes it will be clamped */
|
|
if (io_data.key[0] != 0)
|
|
new_sf = open_io_streamfile_ex_f(new_sf, &io_data, sizeof(ktsr_io_data), ktsr_io_read, NULL, ktsr_io_init, ktsr_io_close);
|
|
|
|
new_sf = open_clamp_streamfile_f(new_sf, subfile_offset, subfile_size);
|
|
if (extension)
|
|
new_sf = open_fakename_streamfile_f(new_sf, NULL, extension);
|
|
|
|
return new_sf;
|
|
fail:
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|