Skip to content

Instantly share code, notes, and snippets.

@Treeki
Created March 25, 2020 03:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Treeki/d46ac2f8cd9111f60f48b6707207fa4a to your computer and use it in GitHub Desktop.
Save Treeki/d46ac2f8cd9111f60f48b6707207fa4a to your computer and use it in GitHub Desktop.
savefile decrypter for Animal Crossing: New Horizons
# savefile decrypter for Animal Crossing: New Horizons
# copyright Ash Wolf (Ninji) 2020
import struct, binascii, sys, Crypto.Cipher.AES, Crypto.Util.Counter
if len(sys.argv) != 4:
print('Usage: %s [main.dat] [mainHeader.dat] [output.dat]' % sys.argv[0])
sys.exit()
class SeadRandom:
def __init__(self, seed):
self.ctx = [0] * 4
self.ctx[0] = ((seed ^ (seed >> 30)) * 0x6C078965 + 1) & 0xFFFFFFFF
self.ctx[1] = ((self.ctx[0] ^ (self.ctx[0] >> 30)) * 0x6C078965 + 2) & 0xFFFFFFFF
self.ctx[2] = ((self.ctx[1] ^ (self.ctx[1] >> 30)) * 0x6C078965 + 3) & 0xFFFFFFFF
self.ctx[3] = ((self.ctx[2] ^ (self.ctx[2] >> 30)) * 0x6C078965 + 4) & 0xFFFFFFFF
def get_u64(self):
a = self.ctx[0] ^ ((self.ctx[0] << 11) & 0xFFFFFFFF)
b = self.ctx[1]
c = self.ctx[3]
self.ctx[0] = self.ctx[2]
self.ctx[1] = c
b ^= ((b << 11) & 0xFFFFFFFF)
a ^= (a >> 8) ^ c
c = a ^ (c >> 19)
a = b ^ (b >> 8) ^ c ^ (a >> 19)
self.ctx[2] = c
self.ctx[3] = a
return (c << 32) | a
def get_u32(self):
a = self.ctx[0] ^ ((self.ctx[0] << 11) & 0xFFFFFFFF)
self.ctx[0] = self.ctx[1]
self.ctx[1] = self.ctx[2]
self.ctx[2] = self.ctx[3]
self.ctx[3] = a ^ (a >> 8) ^ self.ctx[3] ^ (self.ctx[3] >> 19)
return self.ctx[3]
hdr = open(sys.argv[2], 'rb').read()
block = struct.unpack('<128I', hdr[0x100:0x300])
def generate(block, offset):
rand = SeadRandom(block[block[offset] & 0x7F])
skip_count = (block[block[offset + 1] & 0x7F] & 0xF) + 1
for i in range(skip_count):
rand.get_u64()
result = bytearray(16)
for i in range(16):
result[i] = rand.get_u32() >> 24
return bytes(result)
key = generate(block, 0)
iv = generate(block, 2)
print(binascii.hexlify(key))
print(binascii.hexlify(iv))
data = open(sys.argv[1], 'rb').read()
aligned_data_size = (len(data) + 15) & ~15
padded_data = data + (b'\0' * (aligned_data_size - len(data)))
# this is low key horrible
ctr = Crypto.Util.Counter.new(32, prefix=iv[:12], initial_value=struct.unpack('>I', iv[12:])[0])
aes = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_CTR, counter=ctr)
padded_data = aes.decrypt(padded_data)
open(sys.argv[3], 'wb').write(padded_data[:len(data)])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment