446 lines
14 KiB
C
446 lines
14 KiB
C
#include "mxparallel.h"
|
|
|
|
#include "../../sysconf.h"
|
|
|
|
BYTE parallel_flags = 0x00;
|
|
BYTE parallel_ctrl = 0x00;
|
|
BYTE parallel_status = 0x00;
|
|
BYTE parallel_data = 0x00;
|
|
|
|
BYTE KEYCHIP_ID[16] = KEY_ID;
|
|
BYTE _MAIN_ID[16] = MAIN_ID;
|
|
|
|
appboot_t APPBOOT = {
|
|
.format = 1,
|
|
.game_id = GAME_ID,
|
|
.region = 8 | 4 | 2 | 1,
|
|
.model_type = 2,
|
|
// Bitfield
|
|
// 1 = use flash for appboot
|
|
.system_flag = 0x24,
|
|
._ = 0,
|
|
.platform_id = HW_ID,
|
|
.dvd_flag = 1,
|
|
.network_addr = (192 << 0) | (168 << 8) | (103 << 16) | (0 << 24),
|
|
.__ = 0,
|
|
.seed = 1,
|
|
};
|
|
billing_t BILLING = {
|
|
.playlimit = 21046,
|
|
.nearfull = 512,
|
|
};
|
|
uint32_t BILLING_PLAYCOUNT = 69420;
|
|
|
|
#define overlappedComplete(len) \
|
|
do { \
|
|
if (lpOverlapped) { \
|
|
SetLastError(ERROR_SUCCESS); \
|
|
lpOverlapped->Internal = 0; \
|
|
lpOverlapped->InternalHigh = len; \
|
|
SetEvent(lpOverlapped->hEvent); \
|
|
} \
|
|
} while (0)
|
|
#define outLen(len) \
|
|
do { \
|
|
if (lpBytesReturned) *lpBytesReturned = len; \
|
|
overlappedComplete(len); \
|
|
} while (0)
|
|
|
|
BOOL WINAPI mxparallel_DeviceIoControl(file_context_t* ctx, DWORD dwIoControlCode, LPVOID lpInBuffer,
|
|
DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
|
|
LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped) {
|
|
log_trace("mxparallel", "DeviceIoControl(<mxparallel>, 0x%08x, 0x%p, 0x%x, -, 0x%x, -, -)",
|
|
dwIoControlCode, lpInBuffer, nInBufferSize, nOutBufferSize);
|
|
|
|
switch (dwIoControlCode) {
|
|
case IOCTL_MXPARALLEL_READ_FLAGS:
|
|
((LPBYTE)lpOutBuffer)[0] = parallel_flags;
|
|
outLen(1);
|
|
return TRUE;
|
|
case IOCTL_MXPARALLEL_WRITE_FLAGS:
|
|
parallel_flags = ((LPBYTE)lpInBuffer)[0];
|
|
outLen(0);
|
|
return TRUE;
|
|
|
|
case IOCTL_MXPARALLEL_READ_CTRL_PORT:
|
|
((LPBYTE)lpOutBuffer)[0] = parallel_ctrl;
|
|
outLen(1);
|
|
return TRUE;
|
|
case IOCTL_MXPARALLEL_WRITE_CTRL_PORT:
|
|
parallel_ctrl = ((LPBYTE)lpInBuffer)[0];
|
|
// log_warning("mxparallel", "Write ctrl %08x", parallel_ctrl);
|
|
outLen(0);
|
|
return TRUE;
|
|
|
|
case IOCTL_MXPARALLEL_READ_STATUS:
|
|
((LPBYTE)lpOutBuffer)[0] = parallel_status;
|
|
outLen(1);
|
|
return TRUE;
|
|
case IOCTL_MXPARALLEL_WRITE_STATUS:
|
|
parallel_status = ((LPBYTE)lpInBuffer)[0];
|
|
outLen(0);
|
|
return TRUE;
|
|
|
|
case IOCTL_MXPARALLEL_READ_DATA:
|
|
((LPBYTE)lpOutBuffer)[0] = parallel_data;
|
|
outLen(1);
|
|
return TRUE;
|
|
case IOCTL_MXPARALLEL_WRITE_DATA:
|
|
parallel_data = ((LPBYTE)lpInBuffer)[0];
|
|
outLen(0);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// * Our implementation needs to use the opposite keys to mxkeychip
|
|
|
|
void micexkTransportSend(unsigned char* send_data, int nbytes) {
|
|
setAck;
|
|
for (int i = 0; i < nbytes; i++) {
|
|
while (_Strobe) YieldProcessor();
|
|
setBusy;
|
|
parallel_data = send_data[i];
|
|
while (!_Strobe) YieldProcessor();
|
|
clearBusy;
|
|
while (_Strobe) YieldProcessor();
|
|
}
|
|
clearAck;
|
|
}
|
|
void micexkSendPacket(unsigned char* send_data) {
|
|
BYTE encrypted[16];
|
|
memset(encrypted, 0, sizeof encrypted);
|
|
mxkCryptEncryptData(encrypted, send_data);
|
|
micexkTransportSend(encrypted, 16);
|
|
}
|
|
|
|
void micexkTransportRecv(unsigned char* buffer, int nbytes) {
|
|
for (int i = 0; i < nbytes; i++) {
|
|
clearBusy;
|
|
while (!_Strobe) YieldProcessor();
|
|
buffer[i] = parallel_data;
|
|
setBusy;
|
|
while (_Strobe) YieldProcessor();
|
|
clearBusy;
|
|
}
|
|
}
|
|
void micexkRecvPacket(unsigned char* packet) {
|
|
unsigned char buffer[16];
|
|
micexkTransportRecv(buffer, 16);
|
|
mxkCryptDecryptData(buffer, packet);
|
|
}
|
|
|
|
BYTE nvram[0x4000];
|
|
BYTE eeprom[0x1000];
|
|
BYTE flash[0x100000];
|
|
void init_nv_storage() {
|
|
memset(nvram, 0xff, sizeof nvram);
|
|
memset(eeprom, 0xff, sizeof eeprom);
|
|
memset(flash, 0xff, sizeof flash);
|
|
|
|
// NVRAM
|
|
nvram_data_block_t block;
|
|
ZeroMemory(&block, sizeof block);
|
|
block.length = sizeof BILLING_PUBKEY;
|
|
memcpy(block.data, BILLING_PUBKEY, sizeof BILLING_PUBKEY);
|
|
memcpy(&nvram[0x1800], &block, sizeof block);
|
|
|
|
block.length = sizeof BILLING_CACERT;
|
|
memcpy(block.data, BILLING_CACERT, sizeof BILLING_CACERT);
|
|
memcpy(&nvram[0x1c00], &block, sizeof block);
|
|
|
|
// Flash
|
|
billing_t billing_info;
|
|
ZeroMemory(&billing_info, sizeof billing_info);
|
|
billing_info.playlimit = 21046;
|
|
billing_info.nearfull = 512;
|
|
mxkSignValue(billing_info.nearfull, billing_info.nearfull_sig);
|
|
mxkSignValue(billing_info.playlimit, billing_info.playlimit_sig);
|
|
billing_info.crc = amiCrc32RCalc(sizeof billing_info - 4, (unsigned char*)&billing_info + 4, 0);
|
|
|
|
memcpy(&flash[0x7a000], &billing_info, sizeof billing_info);
|
|
memcpy(&flash[0x7b000], &billing_info, sizeof billing_info);
|
|
}
|
|
|
|
void dump_nv_storage() {
|
|
FILE* fp;
|
|
fopen_s(&fp, "dev/kc/nvram.bin", "wb");
|
|
fwrite(nvram, 1, sizeof nvram, fp);
|
|
fclose(fp);
|
|
fopen_s(&fp, "dev/kc/eeprom.bin", "wb");
|
|
fwrite(eeprom, 1, sizeof eeprom, fp);
|
|
fclose(fp);
|
|
fopen_s(&fp, "dev/kc/flash.bin", "wb");
|
|
fwrite(flash, 1, sizeof flash, fp);
|
|
fclose(fp);
|
|
}
|
|
|
|
/*
|
|
addr = (0x70 + block) * 0x1000 + offset
|
|
|
|
Eeprom:
|
|
Appears to store entries detailing tracedata metadata
|
|
[4:crc32] [12:?]
|
|
|
|
one copy at 0x000, one copy at 0x100
|
|
16 entries
|
|
first dword: CRC
|
|
second dword: ??
|
|
third dword: ??
|
|
fourth dword:
|
|
word 1: ??
|
|
word 2: 0xffff
|
|
|
|
Nvram:
|
|
2x 0x400 blocks
|
|
[4:length] [length:data]
|
|
first block: billing pubkey
|
|
second block: cacert
|
|
|
|
Flash (25DF041A):
|
|
nvram0: reads 070000<->070fff
|
|
nvram1: reads 071000<->071fff
|
|
nvram2: reads 072000<->072fff
|
|
nvram3: reads 073000<->073fff
|
|
nvram4: reads 074000<->074fff
|
|
nvram5: reads 075000<->075fff
|
|
nvram6: reads 076000<->076fff
|
|
nvram7: reads 077000<->077fff
|
|
nvram8: reads 078000<->078fff
|
|
nvram9: reads 079000<->079fff
|
|
|
|
billing info a: 0x7a000
|
|
billing info b: 0x7b000
|
|
|
|
- The first 0x80 bytes of each tracedata sector is a bitfield indicating which blocks have been
|
|
used
|
|
- There are 1022 0x40 blocks per tracedata sector (the last two are always unsed, to account for
|
|
the 0x80 of metadata)
|
|
- There are 7 tracedata sectors, at 0x00000, 0x10000, 0x20000, ..., 0x60000
|
|
- Each slot is DES ECB encrypted, with key 4D77F1748D6D1094
|
|
|
|
*/
|
|
|
|
BYTE kc_aes_iv[16];
|
|
|
|
void mxparallel_process_packet(BYTE* request) {
|
|
BYTE response[16];
|
|
memset(response, 10, sizeof response);
|
|
|
|
response[0] = request[0];
|
|
switch (request[0]) {
|
|
// We're pretending to be the keychip, so S and R are swapped for us!
|
|
case SetKeyS:
|
|
log_info("mxparallel", "SetKeyS");
|
|
micexkRecvPacket(request);
|
|
mxkSetKeyR(request);
|
|
micexkSendPacket(response);
|
|
break;
|
|
case SetKeyR:
|
|
log_info("mxparallel", "SetKeyR");
|
|
micexkRecvPacket(request);
|
|
mxkSetKeyS(request);
|
|
micexkSendPacket(response);
|
|
break;
|
|
|
|
case Encrypt:
|
|
log_info("mxparallel", "Encrypt");
|
|
micexkRecvPacket(request);
|
|
micexkSendPacket(request);
|
|
break;
|
|
case Decrypt:
|
|
log_info("mxparallel", "Decrypt");
|
|
micexkRecvPacket(request);
|
|
micexkSendPacket(request);
|
|
break;
|
|
case SetIV:
|
|
log_info("mxparallel", "SetIV");
|
|
ZeroMemory(kc_aes_iv, sizeof kc_aes_iv);
|
|
micexkSendPacket(response);
|
|
break;
|
|
case GetAppBootInfo:
|
|
log_info("mxparallel", "GetAppBootInfo");
|
|
if (request[1] != 0x00) {
|
|
log_warning("mxparallel", "GetAppBootInfo[%d] unexpected!", request[1]);
|
|
}
|
|
|
|
APPBOOT.crc = amiCrc32RCalc(sizeof APPBOOT - 4, (unsigned char*)&APPBOOT + 4, 0);
|
|
for (int i = 0; i < sizeof APPBOOT; i += 16) {
|
|
micexkSendPacket((unsigned char*)(&APPBOOT) + i);
|
|
}
|
|
break;
|
|
|
|
case KcGetVersion:
|
|
log_info("mxparallel", "GetVersion");
|
|
response[0] = 0x01;
|
|
response[1] = 0x04;
|
|
micexkSendPacket(response);
|
|
break;
|
|
|
|
case FlashRead: {
|
|
uint32_t addr = request[1] | (request[2] << 8) | (request[3] << 16);
|
|
uint32_t nbytes = request[4] | (request[5] << 8) | (request[6] << 16);
|
|
log_info("mxparallel", "FlashRead: %06x/%06x", addr, nbytes);
|
|
|
|
if (addr + nbytes <= sizeof flash)
|
|
micexkTransportSend(&flash[addr], nbytes);
|
|
else
|
|
log_error("mxparallel", "Flash read would exceed storage!");
|
|
|
|
break;
|
|
}
|
|
case FlashErase: {
|
|
uint32_t addr = request[1] | (request[2] << 8) | (request[3] << 16);
|
|
log_info("mxparallel", "FlashErase: %06x", addr);
|
|
|
|
micexkSendPacket(response);
|
|
break;
|
|
}
|
|
case FlashWrite: {
|
|
uint32_t addr = request[1] | (request[2] << 8) | (request[3] << 16);
|
|
uint32_t nbytes = request[4] | (request[5] << 8) | (request[6] << 16);
|
|
log_info("mxparallel", "FlashWrite: %06x/%06x", addr, nbytes);
|
|
|
|
if (addr + nbytes <= sizeof flash)
|
|
micexkTransportRecv(&flash[addr], nbytes);
|
|
else
|
|
log_error("mxparallel", "Flash write would exceed storage!");
|
|
|
|
micexkSendPacket(response);
|
|
dump_nv_storage();
|
|
break;
|
|
}
|
|
|
|
// EEPROM?
|
|
case EepromWrite: {
|
|
// TODO: What is this? Appears to be some sort of EEPROM write
|
|
uint8_t offset = request[1];
|
|
log_info("mxparallel", "EepromWrite: %02x", offset);
|
|
|
|
if (offset * 16 + 16 <= sizeof eeprom) {
|
|
micexkRecvPacket(&eeprom[offset * 16]);
|
|
} else {
|
|
log_error("mxparallel", "EEPROM write would exceed storage!");
|
|
}
|
|
micexkSendPacket(response);
|
|
dump_nv_storage();
|
|
break;
|
|
}
|
|
case EepromRead: {
|
|
// TODO: What is this? Appears to be some sort of EEPROM read
|
|
uint8_t offset = request[1];
|
|
log_info("mxparallel", "EepromRead: %02x", offset);
|
|
|
|
if (offset * 16 + 16 <= sizeof eeprom) {
|
|
micexkSendPacket(&eeprom[offset * 16]);
|
|
} else {
|
|
log_error("mxparallel", "EEPROM read would exceed storage!");
|
|
}
|
|
break;
|
|
}
|
|
|
|
// NVRAM?
|
|
case NvramWrite: {
|
|
// TODO: What is this? Appears to be some sort of memory write
|
|
uint16_t addr = request[1] | (request[2] << 8);
|
|
uint8_t blocks = request[3];
|
|
log_info("mxparallel", "NvramWrite: %04x (%02x)", addr, blocks);
|
|
|
|
if (addr + blocks * 16 <= sizeof nvram) {
|
|
for (byte i = 0; i < blocks; i++) {
|
|
micexkRecvPacket(&(nvram[addr + (i * 16)]));
|
|
}
|
|
micexkSendPacket(response);
|
|
} else {
|
|
log_error("mxparallel", "NVRAM write would exceed storage!");
|
|
}
|
|
dump_nv_storage();
|
|
break;
|
|
}
|
|
case NvramRead: {
|
|
// TODO: What is this? Appears to be some sort of memory read
|
|
uint16_t addr = request[1] | (request[2] << 8);
|
|
uint8_t blocks = request[3];
|
|
log_info("mxparallel", "NvramRead: %04x (%02x)", addr, blocks);
|
|
|
|
if (addr + blocks * 16 <= sizeof nvram) {
|
|
for (byte i = 0; i < blocks; i++) {
|
|
micexkSendPacket(&(nvram[addr + (i * 16)]));
|
|
}
|
|
} else {
|
|
log_error("mxparallel", "NVRAM read would exceed storage!");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AddPlayCount:
|
|
log_info("mxparallel", "AddPlayCount");
|
|
BILLING_PLAYCOUNT++;
|
|
micexkSendPacket(response);
|
|
break;
|
|
|
|
case SetMainId:
|
|
log_info("mxparallel", "SetMainId");
|
|
|
|
// micexkRecvPacket(_MAIN_ID);
|
|
micexkRecvPacket(request);
|
|
response[0] = 0xff;
|
|
micexkSendPacket(response);
|
|
break;
|
|
case GetMainId:
|
|
log_info("mxparallel", "GetMainId");
|
|
micexkSendPacket(_MAIN_ID);
|
|
break;
|
|
case SetKeyId:
|
|
log_info("mxparallel", "SetKeyId");
|
|
micexkRecvPacket(KEYCHIP_ID);
|
|
micexkSendPacket(response);
|
|
break;
|
|
case GetKeyId:
|
|
log_info("mxparallel", "GetKeyId");
|
|
micexkSendPacket(KEYCHIP_ID);
|
|
break;
|
|
|
|
case GetPlayCounter:
|
|
log_info("mxparallel", "GetPlayCounter");
|
|
((uint32_t*)response)[0] = BILLING_PLAYCOUNT;
|
|
micexkSendPacket(response);
|
|
break;
|
|
|
|
default:
|
|
log_error("mxparallel", "Unhandled opcode: %d", request[0]);
|
|
for (byte i = 0; i < 16; i++) {
|
|
printf("%02x ", request[i]);
|
|
}
|
|
puts("");
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
DWORD WINAPI mxparallel_thread(LPVOID _) {
|
|
log_info("mxparallel", "Parallel device thread spawned");
|
|
clearBusy;
|
|
clearAck;
|
|
|
|
BYTE last_strobe = 0x00;
|
|
while (1) {
|
|
unsigned char packet[16];
|
|
micexkRecvPacket(packet);
|
|
mxparallel_process_packet(packet);
|
|
}
|
|
}
|
|
|
|
void setup_mxparallel() {
|
|
// We're pretending to be a keychip, so need to swap the keys!
|
|
mxkSwapKeys();
|
|
|
|
init_nv_storage();
|
|
|
|
file_hook_t* mxparallel = new_file_hook(L"\\\\.\\mxparallel");
|
|
mxparallel->DeviceIoControl = &mxparallel_DeviceIoControl;
|
|
hook_file(mxparallel);
|
|
|
|
CreateThread(NULL, 0, mxparallel_thread, NULL, 0, NULL);
|
|
}
|