Last active
October 22, 2024 15:06
-
-
Save lazydogP/57c431cc7fddb5b969559c4c8cd8c881 to your computer and use it in GitHub Desktop.
Keygen for eCDP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# Keygen for McDonald's eCDP(eCrew Development Program), | |
# a Nintendo DS software to train employees. | |
# This keygen is for the only dumped Japanese version of eCDP. | |
# ROM: https://archive.org/details/mcdonalds-japan-ecdp-rom-training-nintendo-ds-cartridge-dump | |
# Usage: Select the third option in main menu, enter two 6-digit numbers as you like, | |
# and use this script to calculate the third code. | |
def rol(n, rotation, width=32): | |
rotation %= width | |
if rotation == 0: | |
return n | |
n &= (2**width-1) | |
return (n << rotation) | (n >> (width - rotation)) | |
def gen_index(sum_offset, length): | |
if length == 0: | |
return 0 | |
if length <= sum_offset: | |
count = 0x1c | |
seed = sum_offset >> 4 | |
if length <= seed >> 0xc: | |
count -= 0x10 | |
seed = seed >> 0x10 | |
if length <= seed >> 4: | |
count -= 8 | |
seed = seed >> 8 | |
if length <= seed: | |
count -= 4 | |
seed = seed >> 4 | |
# Doing bitwise operation in Python is painful... | |
sum_offset = rol(sum_offset, count) | |
sum_offset *= 2 | |
carry = (sum_offset & (2**32)) != 0 | |
for i in range(count, 32): | |
seed = seed * 2 + (~length & 2**32-1) + 1 | |
seed += 1 if carry else 0 | |
carry = (seed & (2 ** 32)) != 0 | |
seed = seed & (2**32-1) | |
if not carry: | |
seed += length | |
seed = seed & (2 ** 32 - 1) | |
sum_offset *= 2 | |
carry = (sum_offset & (2 ** 32)) != 0 | |
sum_offset &= (2 ** 32-1) | |
pass | |
return seed | |
else: | |
return sum_offset | |
def gen_index_2(sum_offset, length): | |
# Well, didn't expect that. | |
return sum_offset % length | |
def calc_shorten_index(input_merge): | |
hex_char = "0123456789ABCDEF" # located at 0x0225189c | |
sum_offset = 0 | |
for c in input_merge: | |
sum_offset += hex_char.index(c) | |
return gen_index(sum_offset, 7) | |
pass | |
def shuffle(input_merge, shuffle_index): | |
# located at 0x02251948 | |
shuffle_map = [[0x01,0x0A,0x16,0x04,0x07,0x18,0x0C,0x10,0x05,0x17,0x09,0x03,0x12,0x08,0x15,0x13,0x0B,0x02,0x0F,0x0D,0x11,0x0E,0x06,0x14], | |
[0x07,0x0C,0x0E,0x11,0x09,0x16,0x10,0x06,0x14,0x0D,0x01,0x02,0x12,0x08,0x13,0x0B,0x0F,0x0A,0x18,0x15,0x04,0x05,0x03,0x17], | |
[0x0F,0x04,0x09,0x03,0x06,0x07,0x11,0x12,0x15,0x16,0x02,0x08,0x05,0x17,0x0C,0x0D,0x01,0x18,0x0B,0x14,0x0E,0x10,0x13,0x0A], | |
[0x02,0x0A,0x0E,0x12,0x0B,0x03,0x0C,0x06,0x13,0x07,0x11,0x09,0x15,0x18,0x10,0x17,0x14,0x0F,0x04,0x01,0x05,0x08,0x16,0x0D], | |
[0x0B,0x02,0x09,0x16,0x14,0x01,0x12,0x11,0x15,0x06,0x0F,0x17,0x07,0x10,0x0C,0x0E,0x08,0x18,0x13,0x03,0x0A,0x0D,0x04,0x05], | |
[0x09,0x0F,0x05,0x0D,0x16,0x15,0x12,0x11,0x03,0x0A,0x04,0x10,0x0E,0x14,0x02,0x01,0x13,0x0C,0x06,0x0B,0x17,0x18,0x07,0x08], | |
[0x12,0x02,0x0C,0x09,0x0D,0x0E,0x04,0x07,0x16,0x14,0x17,0x01,0x11,0x03,0x10,0x15,0x08,0x0A,0x05,0x13,0x0B,0x18,0x0F,0x06]] | |
shuffle_method = shuffle_map[shuffle_index] | |
shuffled_str = "" | |
for i in range(0x18): | |
shuffled_str += input_merge[shuffle_method[i]-1] | |
return shuffled_str | |
pass | |
def hex2ints(hex_str): | |
result = [] | |
for i in range(6): | |
result.append(int(hex_str[4*i:4*i+4], 16)) | |
return result | |
def ints_encrypt(numbers): | |
key = 0x3e0f83e1 # located at 0x02251364 | |
result = [] | |
for i in range(6): | |
r0 = numbers[i] >> 0x1f | |
mul = key * numbers[i] | |
r2, r6 = mul & (2**32-1), mul >> 32 | |
r6 = r0 + r6 >> 3 | |
mul = 0x21 * r6 | |
r0, r2 = mul & (2**32-1), mul >> 32 | |
r6 = numbers[i] - r0 | |
result.append(r6 + 1) | |
return result | |
def ints2str(numbers): | |
chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZ" # located at 0x022518b0 | |
s = "" | |
for i in numbers: | |
s += chars[i-1] | |
return s | |
def calc_serial_code(mac_code, code1, code2): | |
full_code = mac_code.upper() + code1 + code2 | |
shorten_index = calc_shorten_index(full_code) | |
shuffle_str = shuffle(full_code, shorten_index) | |
shuffle_numbers = hex2ints(shuffle_str) | |
encrypted_numbers = ints_encrypt(shuffle_numbers) | |
serial_code = ints2str(encrypted_numbers) | |
return serial_code | |
print("eCDP Keygen by lazydog") | |
# My NO$GBA emulator shows "0009BF000031" | |
mac = input("Input MAC address:") | |
rest_no = input("Input Restaurant No.:") | |
manage_no = input("Input Management No. of DS card:") | |
print("Here's your serial:", calc_serial_code(mac, rest_no, manage_no)) | |
print("Have fun!") |
Btw, were you aware there is a master serialcode that works for any store and storemanagement combination?
Dont understand the ceil part, but that is no problem.
ceil() is probably the wrong way to put this out. That magic number is the modular inverse of 33 mod 0x100000000 (https://www.wolframalpha.com/input/?i=33%5E-1+mod+0x100000000).
Still I don't know why it takes the upper half of 64 bit results and does some operation with it. Normally you only look at the results within the mod space (in this case mod 0x100000000 aka 32-bit integer, etc.) when you "divide" or multiply by the inverse value. Maybe some better bit and number theory magicians can answer my question.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Dont understand the ceil part, but that is no problem.
I created a pull request for KuromeSan’s repo with a Java Swing App with an optimized and simplified algorithm, you might want to check out my change there, also has JUnit5 tests: https://github.com/cruuud/eCDP-Serial-Code
It is in a feature branch, the project ending with Java