Last active
March 12, 2019 17:22
-
-
Save nvllsvm/838cb8eba006ce8a46a49053688fa8cb to your computer and use it in GitHub Desktop.
Unlock all levels with all achievements in the game DUSK
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
import collections | |
import pathlib | |
import struct | |
import argparse | |
LEVELS = { | |
'E1M1': 'level3', | |
'E1M2': 'level4', | |
'E1M3': 'level5', | |
'E1M4': 'level6', | |
'E1M5': 'level7', | |
'E1M6': 'level8', | |
'E1M7': 'level9', | |
'E1M8': 'level10', | |
'E1M9': 'level11', | |
'E1M10': 'level12', | |
'E1MS': 'level14', | |
'E2M1': 'level22', | |
'E2M2': 'level23', | |
'E2M3': 'level24', | |
'E2M4': 'level25', | |
'E2M5': 'level26', | |
'E2M6': 'level27', | |
'E2M7': 'level28', | |
'E2M8': 'level29', | |
'E2M9': 'level30', | |
'E2M10': 'level31', | |
'E2MS': 'level33', | |
'E3M1': 'level37', | |
'E3M2': 'level38', | |
'E3M3': 'level39', | |
'E3M4': 'level40', | |
'E3M5': 'level41', | |
'E3M6': 'level42', | |
'E3M7': 'level43', | |
'E3M8': 'level44', | |
'E3M9': 'level45', | |
'E3M10': 'level46', | |
'E3MS': 'level48', | |
} | |
class Entry: | |
def __init__(self, level_number, text): | |
self.level_number = level_number | |
self.text = text | |
@property | |
def full_text(self): | |
return f'{self.level_number}{self.text}' | |
@property | |
def text_len(self): | |
return len(self.text) + len(str(self.level_number)) | |
def to_bytes(self, *args): | |
return b''.join([ | |
b'~', | |
self.convert_int(len(self.full_text)), | |
self.full_text.encode(), | |
*args, | |
b'{' | |
]) | |
@staticmethod | |
def convert_int(value): | |
return int(value).to_bytes(1, 'little') | |
@staticmethod | |
def convert_float(value): | |
return struct.pack('f', value) | |
class Award(Entry): | |
AWARDS = [ | |
'completionist', | |
'lowtech', | |
'ninja', | |
'pacifist' | |
] | |
def __init__(self, *args): | |
super().__init__(*args) | |
if self.text not in self.AWARDS: | |
raise ValueError | |
def to_bytes(self): | |
return super().to_bytes( | |
b'\n\x00\x00\x00\xffV\x08\xa8\xe2\x01\x00\x00\x00' | |
) | |
class LevelName(Entry): | |
TEXT = 'name' | |
def __init__(self, level_name, num): | |
super().__init__(num, self.TEXT) | |
self.level_name = level_name | |
def to_bytes(self): | |
return super().to_bytes( | |
self.convert_int(len(self.level_name) + 7), | |
b'\x00\x00\x00\xff\xee\xf1\xe9\xfd', | |
self.convert_int(len(self.level_name)), | |
self.level_name.encode() | |
) | |
class LevelBeaten(Entry): | |
TEXT = 'levelbeaten' | |
def __init__(self, number): | |
super().__init__(number, self.TEXT) | |
def to_bytes(self): | |
return super().to_bytes( | |
b'\n\x00\x00\x00\xffV\x08\xa8\xe2', | |
self.convert_int(self.level_number), | |
b'\x00\x00\x00' | |
) | |
class IntegerEntry(Entry): | |
VALUES = [ | |
'kills', | |
'secrets', | |
'startingenemies', | |
'startingsecrets' | |
] | |
def __init__(self, number, *args): | |
super().__init__(*args) | |
self.number = number | |
def to_bytes(self): | |
return super().to_bytes( | |
b'\n\x00\x00\x00\xffV\x08\xa8\xe2', | |
self.convert_int(self.number), | |
b'\x00\x00\x00' | |
) | |
class FloatEntry(Entry): | |
VALUES = [ | |
'minutes', | |
'seconds' | |
] | |
def __init__(self, number, *args): | |
super().__init__(*args) | |
self.number = number | |
def to_bytes(self): | |
return super().to_bytes( | |
b'\n\x00\x00\x00\xffk\xd7>n', | |
self.convert_float(self.number) | |
) | |
class Level: | |
def __init__(self, name, number): | |
self.name = name | |
self.number = number | |
self.secrets = 0 | |
self.kills = 0 | |
self.startingenemies = 0 | |
self.startingsecrets = 0 | |
self.minutes = 0.0 | |
self.seconds = 0.0 | |
self.ninja = False | |
self.completionist = False | |
self.lowtech = False | |
self.pacifist = False | |
def to_bytes(self): | |
entries = [ | |
LevelBeaten(self.number).to_bytes(), | |
LevelBeaten(self.number + 1).to_bytes(), | |
LevelName(self.name, self.number).to_bytes() | |
] | |
for value_name in IntegerEntry.VALUES: | |
value = getattr(self, value_name) | |
entries.append( | |
IntegerEntry(value, self.number, value_name).to_bytes() | |
) | |
for value_name in FloatEntry.VALUES: | |
value = getattr(self, value_name) | |
entries.append( | |
FloatEntry(value, self.number, value_name).to_bytes() | |
) | |
for award in Award.AWARDS: | |
if getattr(self, award): | |
entries.append(Award(self.number, award).to_bytes()) | |
return entries | |
def read_file(path): | |
levels = collections.defaultdict(list) | |
data = path.read_bytes() | |
lines = [l for l in data.split(b'{') if l] | |
for line in lines: | |
line += b'{' | |
text_len = int.from_bytes(line[1:2], byteorder='little') | |
text = line[2:text_len+2].decode() | |
data = line[text_len+2:] | |
nums = [] | |
for c in text: | |
try: | |
int(c) | |
nums.append(c) | |
except ValueError: | |
break | |
text = text[len(nums):] | |
level_num = int(''.join(nums)) | |
if text in Award.AWARDS: | |
entry = Award(level_num, text) | |
elif text in IntegerEntry.VALUES: | |
number = int.from_bytes(data[9:10], byteorder='little') | |
entry = IntegerEntry(number, level_num, text) | |
elif text == 'levelbeaten': | |
entry = LevelBeaten(level_num) | |
elif text == 'name': | |
name_length = int.from_bytes(data[0:1], byteorder='little') | |
data = data[1:] | |
name = data[9:name_length+2].decode() | |
data = data[0:9] | |
entry = LevelName(name, level_num) | |
elif text in FloatEntry.VALUES: | |
number = struct.unpack('f', data[9:-1])[0] | |
entry = FloatEntry(number, level_num, text) | |
else: | |
entry = Entry(level_num, text) | |
parsed = entry.to_bytes() | |
print(parsed) | |
if line != parsed: | |
print(line) | |
raise RuntimeError('unable to parse') | |
def main(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument('path', type=pathlib.Path) | |
parser.add_argument('--write', action='store_const', const=True) | |
args = parser.parse_args() | |
if args.write: | |
levels = set() | |
for key, value in LEVELS.items(): | |
level = Level(key, int(value[5:])) | |
level.completionist = True | |
level.ninja = True | |
level.lowtech = True | |
level.pacifist = True | |
levels.update(level.to_bytes()) | |
args.path.write_bytes(b''.join(levels)) | |
else: | |
read_file(args.path) | |
exit() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment