Skip to content

Instantly share code, notes, and snippets.

@nvllsvm
Last active March 12, 2019 17:22
Show Gist options
  • Save nvllsvm/838cb8eba006ce8a46a49053688fa8cb to your computer and use it in GitHub Desktop.
Save nvllsvm/838cb8eba006ce8a46a49053688fa8cb to your computer and use it in GitHub Desktop.
Unlock all levels with all achievements in the game DUSK
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