#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(, 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); }