// license:BSD-3-Clause // copyright-holders:Nicola Salmoria, Andreas Naive, Charles MacDonald /*************************************************************************** Hitachi FD1089A/FD1089B encryption emulation **************************************************************************** Copyright Nicola Salmoria, Andreas Naive, and Charles MacDonald. All rights reserved. **************************************************************************** Decryption tables provided by Charles MacDonald Decryption algorithm by Nicola Salmoria LCG algorithm by Andreas Naive The FD1089 is a 68000 with built-in encryption. It contains some battery-backed RAM, when the battery dies the CPU stops working. Both opcodes and data are encrypted, using different (but related) mappings. Decryption works on 16-bit words, but only 8 bits are affected, the other 8 are left untouched. A special value in internal RAM disables the encryption, this is necessary otherwise RAM would not work as expected (writing data and reading it back would return a different number). The FD1089A and FD1089B work in the same way, but the decryption tables are different. The internal RAM contains the 8-bit key to use at every address (only 12 bits of the address are used, so the encryption repeats). The FD1089 design is clearly derived from the MC8123. The MC8123 is a Z80 so it made sense to encrypt all 8 data bits and use 12 of the 16 address bits. It makes a lot less sense to encrypt only half of the 16 data bits; using 12 of the 24 address bits might be ok, but not in the way it was done. The choice of address bits to use was probably dictated by the need to not encrypt data in certain areas of the address space, so they had to include the top 8 bits of the address. However this means that if you pick e.g. area 000000-00FFFF, where most of the program code resides, just 4 address bits affect the encryption, making it very weak when compared to the MC8123. Out of the 16KB of internal RAM, you need less than 128 bytes to decrypt a whole game - the rest is not used. A waste of space and security. Also, since only 8 of the 16 bits are encrypted, it is very easy to use the unencrypted ones to search for known sequences of code or data. Like for the MC8123, the contents of the internal RAM were generated using a linear congruential generator, so the whole key can be generated starting from a single 24-bit seed. Note however that the "don't decrypt" data sections need special treatment so it's not possible to derive the precise key without access to the CPU. static int rndseed; int rnd() { rndseed = rndseed * 0x290029; return (rndseed >> 16) & 0xff; } void generate_key(int seed) { int i; rndseed = seed; for (i = 0; i < 0x1000; ++i) { if ("we must encrypt this data table position") { uint8_t byteval; do { byteval = rnd(); } while (byteval == 0x00); opcode_key[i] = byteval; do { byteval = rnd(); } while (byteval == 0x00); data_key[i] = byteval; } } for (i = 0; i < 0x1000; ++i) { if ("we mustn't encrypt this data table position") { uint8_t byteval; do { byteval = rnd(); } while (byteval == 0x00); opcode_key[i] = byteval; data_key[i] = 0x00; } } } Note that when both FD1089A and FD1089B versions of a game exist, they use the same key. Known games that use this CPU: CPU # Type Status Game Seed Unencrypted data range --------- ------- --- -------------------- -- ----------------------------------- 317-0013A FD1089B [1] Enduro Racer 01 030000-04ffff + 100000-1fffff 317-0016 FD1089B [2] Fantasy Zone 05 ? 317-0018 FD1089A [1] Action Fighter 03 400000-4fffff + 840000-8dffff + c00000-c4ffff + ff0000-ffffff 317-0019 FD1089A [1] Outrun 07 000000-03ffff 317-0021 FD1089A [2] Alex Kidd 0b ? 317-0022 FD1089A [1] Dunk Shot 0d 030000-ffffff 317-0024 FD1089B [1] Time Scanner 0f 000000-02ffff 317-0027 FD1089B [1] SDI 11 000000-03ffff 317-0028 FD1089A [2] Defense 11 ? 317-0033 FD1089A [1] Alien Syndrome 13 030000-ffffff 317-0037 FD1089B [2] Alien Syndrome 13 030000-ffffff 317-0034 FD1089B [1] Super Hang-On 15 030000-06ffff + 100000-2fffff + ff0000-ffffff 317-0086 FD1089A [2] Wonder Boy III 43 ? 317-0167 FD1089A [2] Aurail 30 010000-ffffff 317-0168 FD1089B [1] Aurail 30 010000-ffffff 317-5021 FD1089B [1] Sukeban Jansi Ryuko 4b 000000-00ffff [1] Complete [2] Partial ***************************************************************************/ #include "emu.h" #include "fd1089.h" #include "segaic16.h" //************************************************************************** // CONSTANTS //************************************************************************** // device type definition DEFINE_DEVICE_TYPE(FD1089A, fd1089a_device, "fd1089a", "Hitachi FD1089A Encrypted CPU") DEFINE_DEVICE_TYPE(FD1089B, fd1089b_device, "fd1089b", "Hitachi FD1089B Encrypted CPU") // common base lookup table, shared between A and B variants const uint8_t fd1089_base_device::s_basetable_fd1089[0x100] = { 0x00,0x1c,0x76,0x6a,0x5e,0x42,0x24,0x38,0x4b,0x67,0xad,0x81,0xe9,0xc5,0x03,0x2f, 0x45,0x69,0xaf,0x83,0xe7,0xcb,0x01,0x2d,0x02,0x1e,0x78,0x64,0x5c,0x40,0x2a,0x36, 0x32,0x2e,0x44,0x58,0xe4,0xf8,0x9e,0x82,0x29,0x05,0xcf,0xe3,0x93,0xbf,0x79,0x55, 0x3f,0x13,0xd5,0xf9,0x85,0xa9,0x63,0x4f,0xb8,0xa4,0xc2,0xde,0x6e,0x72,0x18,0x04, 0x0c,0x10,0x7a,0x66,0xfc,0xe0,0x86,0x9a,0x47,0x6b,0xa1,0x8d,0xbb,0x97,0x51,0x7d, 0x17,0x3b,0xfd,0xd1,0xeb,0xc7,0x0d,0x21,0xa0,0xbc,0xda,0xc6,0x50,0x4c,0x26,0x3a, 0x3e,0x22,0x48,0x54,0x46,0x5a,0x3c,0x20,0x25,0x09,0xc3,0xef,0xc1,0xed,0x2b,0x07, 0x6d,0x41,0x87,0xab,0x89,0xa5,0x6f,0x43,0x1a,0x06,0x60,0x7c,0x62,0x7e,0x14,0x08, 0x0a,0x16,0x70,0x6c,0xdc,0xc0,0xaa,0xb6,0x4d,0x61,0xa7,0x8b,0xf7,0xdb,0x11,0x3d, 0x5b,0x77,0xbd,0x91,0xe1,0xcd,0x0b,0x27,0x80,0x9c,0xf6,0xea,0x56,0x4a,0x2c,0x30, 0xb0,0xac,0xca,0xd6,0xee,0xf2,0x98,0x84,0x37,0x1b,0xdd,0xf1,0x95,0xb9,0x73,0x5f, 0x39,0x15,0xdf,0xf3,0x9b,0xb7,0x71,0x5d,0xb2,0xae,0xc4,0xd8,0xec,0xf0,0x96,0x8a, 0xa8,0xb4,0xd2,0xce,0xd0,0xcc,0xa6,0xba,0x1f,0x33,0xf5,0xd9,0xfb,0xd7,0x1d,0x31, 0x57,0x7b,0xb1,0x9d,0xb3,0x9f,0x59,0x75,0x8c,0x90,0xfa,0xe6,0xf4,0xe8,0x8e,0x92, 0x12,0x0e,0x68,0x74,0xe2,0xfe,0x94,0x88,0x65,0x49,0x8f,0xa3,0x99,0xb5,0x7f,0x53, 0x35,0x19,0xd3,0xff,0xc9,0xe5,0x23,0x0f,0xbe,0xa2,0xc8,0xd4,0x4e,0x52,0x34,0x28, }; // address decryption parameters, shared between A and B variants const fd1089_base_device::decrypt_parameters fd1089_base_device::s_addr_params[16] = { { 0x23, 6,4,5,7,3,0,1,2 }, { 0x92, 2,5,3,6,7,1,0,4 }, { 0xb8, 6,7,4,2,0,5,1,3 }, { 0x74, 5,3,7,1,4,6,0,2 }, { 0xcf, 7,4,1,0,6,2,3,5 }, { 0xc4, 3,1,6,4,5,0,2,7 }, { 0x51, 5,7,2,4,3,1,6,0 }, { 0x14, 7,2,0,6,1,3,4,5 }, { 0x7f, 3,5,6,0,2,1,7,4 }, { 0x03, 2,3,4,0,6,7,5,1 }, { 0x96, 3,1,7,5,2,4,6,0 }, { 0x30, 7,6,2,3,0,4,5,1 }, { 0xe2, 1,0,3,7,4,5,2,6 }, { 0x72, 1,6,0,5,7,2,4,3 }, { 0xf5, 0,4,1,2,6,5,7,3 }, { 0x5b, 0,7,5,3,1,4,2,6 }, }; // data decryption parameters for the A variant const fd1089_base_device::decrypt_parameters fd1089_base_device::s_data_params_a[16] = { { 0x55, 6,5,1,0,7,4,2,3 }, { 0x94, 7,6,4,2,0,5,1,3 }, { 0x8d, 1,4,2,3,0,6,7,5 }, { 0x9a, 4,3,5,6,0,2,1,7 }, { 0x72, 4,3,7,0,5,6,1,2 }, { 0xff, 1,7,2,3,6,4,5,0 }, { 0x06, 6,5,3,2,4,1,0,7 }, { 0xc5, 3,5,1,4,2,7,0,6 }, { 0xec, 4,7,5,1,6,0,2,3 }, { 0x89, 3,5,0,6,1,2,7,4 }, { 0x5c, 1,3,0,7,5,2,4,6 }, { 0x3f, 7,3,0,2,4,6,1,5 }, { 0x57, 6,4,7,2,1,5,3,0 }, { 0xf7, 6,3,7,0,5,4,2,1 }, { 0x3a, 6,1,3,2,7,4,5,0 }, { 0xac, 1,6,3,5,0,7,4,2 }, }; void fd1089_base_device::decrypted_opcodes_map(address_map &map) { map(0x00000, 0xfffff).rom().share(":fd1089_decrypted_opcodes"); } //************************************************************************** // CORE IMPLEMENTATION //************************************************************************** //------------------------------------------------- // fd1089_base_device - constructor //------------------------------------------------- fd1089_base_device::fd1089_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : m68000_device(mconfig, type, tag, owner, clock), m_region(*this, DEVICE_SELF), m_key(*this, "key"), m_decrypted_opcodes(*this, ":fd1089_decrypted_opcodes") { // add the decrypted opcodes map set_addrmap(AS_OPCODES, address_map_constructor(FUNC(fd1089_base_device::decrypted_opcodes_map), this)); } fd1089a_device::fd1089a_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : fd1089_base_device(mconfig, FD1089A, tag, owner, clock) { } fd1089b_device::fd1089b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : fd1089_base_device(mconfig, FD1089B, tag, owner, clock) { } //------------------------------------------------- // device_start - one-time device initialization //------------------------------------------------- void fd1089_base_device::device_start() { // start the base device m68000_device::device_start(); // get a pointer to the ROM region uint16_t *rombase = reinterpret_cast(m_region->base()); // determine length and resize our internal buffers uint32_t romsize = m_region->bytes(); m_plaintext.resize(romsize/2); // copy the plaintext memcpy(&m_plaintext[0], rombase, romsize); // decrypt it, overwriting original data with the decrypted data decrypt(0x000000, romsize, &m_plaintext[0], &m_decrypted_opcodes[0], rombase); } //************************************************************************** // INTERNAL HELPERS //************************************************************************** //------------------------------------------------- // rearrange_key - shuffle bits in the table // based on whether this is an opcode or a data // decode //------------------------------------------------- uint8_t fd1089_base_device::rearrange_key(uint8_t table, bool opcode) { if (!opcode) { table ^= (1<<4); table ^= (1<<5); if (BIT(~table,3)) table ^= (1<<1); table = bitswap<8>(table,1,0,6,4,3,5,2,7); if (BIT(table,6)) table = bitswap<8>(table,7,6,2,4,5,3,1,0); } else { table ^= (1<<2); table ^= (1<<3); table ^= (1<<4); if (BIT(~table,3)) table ^= (1<<5); if (BIT(table,7)) table ^= (1<<6); table = bitswap<8>(table,5,7,6,4,2,3,1,0); if (BIT(table,6)) table = bitswap<8>(table,7,6,5,3,2,4,1,0); } if (BIT(table,6)) { if (BIT(table,5)) table ^= (1<<4); } else { if (BIT(~table,4)) table ^= (1<<5); } return table; } //------------------------------------------------- // decode_fd1089a - decode an 8-bit value // according to FD1089A rules //------------------------------------------------- uint8_t fd1089a_device::decode(uint8_t val, uint8_t key, bool opcode) { // special case - don't decrypt if (key == 0x00) return val; uint8_t table = rearrange_key(key, opcode); const decrypt_parameters &p = s_addr_params[table >> 4]; val = bitswap<8>(val, p.s7,p.s6,p.s5,p.s4,p.s3,p.s2,p.s1,p.s0) ^ p.xorval; if (BIT(table,3)) val ^= 0x01; if (BIT(table,0)) val ^= 0xb1; if (opcode) val ^= 0x34; if (opcode == 0) if (BIT(table,6)) val ^= 0x01; val = s_basetable_fd1089[val]; uint8_t family = table & 0x07; if (opcode == 0) { if (BIT(~table,6) & BIT(table,2)) family ^= 8; if (BIT(table,4)) family ^= 8; } else { if (BIT(table,6) & BIT(table,2)) family ^= 8; if (BIT(table,5)) family ^= 8; } if (BIT(table,0)) { if (BIT(val,0)) val ^= 0xc0; if (BIT(~val,6) ^ BIT(val,4)) val = bitswap<8>(val, 7,6,5,4,1,0,2,3); } else { if (BIT(~val,6) ^ BIT(val,4)) val = bitswap<8>(val, 7,6,5,4,0,1,3,2); } if (BIT(~val,6)) val = bitswap<8>(val, 7,6,5,4,2,3,0,1); const decrypt_parameters &q = s_data_params_a[family]; val ^= q.xorval; val = bitswap<8>(val, q.s7,q.s6,q.s5,q.s4,q.s3,q.s2,q.s1,q.s0); return val; } //------------------------------------------------- // decode_fd1089b - decode an 8-bit value // according to FD1089B rules //------------------------------------------------- uint8_t fd1089b_device::decode(uint8_t val, uint8_t key, bool opcode) { // special case - don't decrypt if (key == 0x00) return val; uint8_t table = rearrange_key(key, opcode); const decrypt_parameters &p = s_addr_params[table >> 4]; val = bitswap<8>(val, p.s7,p.s6,p.s5,p.s4,p.s3,p.s2,p.s1,p.s0) ^ p.xorval; if (BIT(table,3)) val ^= 0x01; if (BIT(table,0)) val ^= 0xb1; if (opcode) val ^= 0x34; if (opcode == 0) if (BIT(table,6)) val ^= 0x01; val = s_basetable_fd1089[val]; uint8_t xorval = 0; if (opcode == 0) { if (BIT(~table,6) & BIT(table,2)) xorval ^= 0x01; if (BIT(table,4)) xorval ^= 0x01; } else { if (BIT(table,6) & BIT(table,2)) xorval ^= 0x01; if (BIT(table,5)) xorval ^= 0x01; } val ^= xorval; if (BIT(table,2)) { val = bitswap<8>(val, 7,6,5,4,1,0,3,2); if (BIT(table,0) ^ BIT(table,1)) val = bitswap<8>(val, 7,6,5,4,0,1,3,2); } else { val = bitswap<8>(val, 7,6,5,4,3,2,0,1); if (BIT(table,0) ^ BIT(table,1)) val = bitswap<8>(val, 7,6,5,4,1,0,2,3); } return val; } //------------------------------------------------- // decrypt_one - decrypt a single 16-bit value // interpreted as being read at the given address // as either an opcode or as data //------------------------------------------------- uint16_t fd1089_base_device::decrypt_one(offs_t addr, uint16_t val, const uint8_t *key, bool opcode) { // pick the translation table from bits ff022a of the address int tbl_num = ((addr & 0x000002) >> 1) | ((addr & 0x000008) >> 2) | ((addr & 0x000020) >> 3) | ((addr & 0x000200) >> 6) | ((addr & 0xff0000) >> 12); uint16_t src = ((val & 0x0008) >> 3) | ((val & 0x0040) >> 5) | ((val & 0xfc00) >> 8); src = decode(src, key[tbl_num + (opcode ? 0 : 1) * 0x1000], opcode); src = ((src & 0x01) << 3) | ((src & 0x02) << 5) | ((src & 0xfc) << 8); return (val & ~0xfc48) | src; } //------------------------------------------------- // decrypt - decrypt a buffers' worth of opcodes // and data //------------------------------------------------- void fd1089_base_device::decrypt(offs_t baseaddr, uint32_t size, const uint16_t *srcptr, uint16_t *opcodesptr, uint16_t *dataptr) { for (offs_t offset = 0; offset < size; offset += 2) { uint16_t src = srcptr[offset / 2]; opcodesptr[offset / 2] = decrypt_one(baseaddr + offset, src, &m_key[0], true); dataptr[offset / 2] = decrypt_one(baseaddr + offset, src, &m_key[0], false); } }