294 lines
9.2 KiB
Python
Executable File
294 lines
9.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from RuneSolver import VigenereSolver
|
|
from RuneText import alphabet, RuneText
|
|
import lib as LIB
|
|
|
|
SOLVER = VigenereSolver()
|
|
SOLVER.input.load(file='_input.txt')
|
|
|
|
|
|
def main():
|
|
help_str = '''
|
|
Welcome pilgrim,
|
|
|
|
Available commands are:
|
|
a : Generate all 29 rotations of a given rune-word (a) or inverted rune (ai)
|
|
d : Get decryption key (substitution) for a single phrase
|
|
f : Find words with a given length (f 4, or f word)
|
|
g : Print Gematria Primus (gp) or reversed Gematria (gpr)
|
|
h : Highlight occurrences of interrupt jumps (hj) or reset (h)
|
|
k : Re/set decryption key (k), invert key (ki),
|
|
': change key shift (ks), rotation (kr), offset (ko), or after padding (kp)
|
|
': set key jumps (kj) e.g., [1,2] (first appearence of ᚠ is index 1)
|
|
l : Toggle log level: normal (ln), quiet (lq) verbose (lv)
|
|
p : Prime number and emirp check
|
|
t : Translate between runes, text, and indices (0-28)
|
|
x : Execute decryption. Also: load data into memory
|
|
': set manually (x DATA) or load from file (xf p0-2) (default: _input.txt)
|
|
': limit/trim loaded data up to nth character (xl 300)
|
|
|
|
? : Show currently set variables
|
|
q : Quit
|
|
'''
|
|
print(help_str)
|
|
while True:
|
|
try:
|
|
inpt = input('$>: ').strip()
|
|
if not inpt:
|
|
continue
|
|
cmd_p = inpt.split(' ', 1)
|
|
cmd = cmd_p[0].strip().lower()
|
|
args = cmd_p[1].strip() if len(cmd_p) > 1 else ''
|
|
|
|
if cmd[0] != 'l': # only log mode allowed after find operation
|
|
SOLVER.reset_highlight()
|
|
|
|
if cmd == 'help':
|
|
print(help_str)
|
|
elif cmd == 'q' or cmd == 'exit' or cmd == 'quit':
|
|
exit()
|
|
elif cmd == '?':
|
|
print(SOLVER)
|
|
else:
|
|
cmdX = {'a': command_a, 'd': command_d, 'f': command_f,
|
|
'g': command_g, 'h': command_h, 'k': command_k,
|
|
'l': command_l, 'p': command_p, 't': command_t,
|
|
'x': command_x}
|
|
res = cmdX[cmd[0]](cmd, args) if cmd[0] in cmdX else False
|
|
if res is False:
|
|
print('Command not found.')
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
|
|
# this allow you to write `ks3` instead of `ks 3`
|
|
def get_cmd_int(cmd, args, desc=None, start=2):
|
|
val = int(args) if args else 0
|
|
if not val and cmd[start:]:
|
|
val = int(cmd[start:])
|
|
if desc:
|
|
print(f'set {desc}: {val}')
|
|
return val
|
|
|
|
|
|
#########################################
|
|
# A
|
|
#########################################
|
|
|
|
def command_a(cmd, args): # [a]ll variations
|
|
if cmd.strip('aliq'):
|
|
return False
|
|
if not args:
|
|
raise Warning('No input provided.')
|
|
root = RuneText(args)
|
|
inclIndex = 'q' not in cmd
|
|
if 'i' in cmd:
|
|
root = 28 - root
|
|
for i in range(0, 29):
|
|
print('{:02d}: {}'.format(i, (root + i).description(index=inclIndex)))
|
|
|
|
|
|
#########################################
|
|
# D
|
|
#########################################
|
|
|
|
def command_d(cmd, args): # [d]ecrypt or single substitution
|
|
if not (cmd in 'decrypt'):
|
|
return False
|
|
if not args:
|
|
trythis = input('What is the encrypted text?: ').strip()
|
|
else:
|
|
trythis = args
|
|
enc = RuneText(trythis)
|
|
print('encrypted:', enc.description())
|
|
target = input('What should the decrypted clear text be?: ').strip()
|
|
plain = RuneText(target)
|
|
print('plaintext:', plain.description())
|
|
if len(enc) != len(plain):
|
|
print('Error: key length mismatch')
|
|
else:
|
|
print('Substition:')
|
|
print((enc - plain).description())
|
|
|
|
|
|
#########################################
|
|
# F
|
|
#########################################
|
|
|
|
def command_f(cmd, args): # (f)ind word
|
|
if not (cmd in 'find'):
|
|
return False
|
|
if not args:
|
|
raise Warning('No input provided.')
|
|
try:
|
|
search_term = None
|
|
s_len = int(args.strip()) # search words with n-length
|
|
except ValueError:
|
|
search_term = RuneText(args)
|
|
s_len = len(search_term)
|
|
|
|
cur_words = SOLVER.highlight_words_with_len(s_len)
|
|
SOLVER.run()
|
|
print()
|
|
print('Found:')
|
|
for _, _, pos, _, w in cur_words:
|
|
print('{:04}: {}'.format(pos, w.description(count=True)))
|
|
if search_term:
|
|
print()
|
|
print('Available substition:')
|
|
for _, _, pos, _, w in cur_words:
|
|
word = search_term - w
|
|
print('{:04}: {}'.format(pos, word.description(count=True)))
|
|
|
|
|
|
#########################################
|
|
# G
|
|
#########################################
|
|
|
|
def command_g(cmd, args): # (g)ematria primus
|
|
if cmd not in 'gp gpr gpi':
|
|
return False
|
|
|
|
def pp(i):
|
|
p, r, t = alphabet[28 - i if rev else i]
|
|
return '{:2d} {} {:3d} {}'.format(i, r, p, '/'.join(t))
|
|
|
|
rev = cmd[-1] in 'ir' or len(args) > 0 and args[0] in 'ir'
|
|
print('Gematria Primus (reversed)' if rev else 'Gematria Primus')
|
|
half = (len(alphabet) >> 1) + (len(alphabet) & 1) # ceil
|
|
for i in range(half):
|
|
if i < len(alphabet) // 2:
|
|
print('{:22} {}'.format(pp(i), pp(i + half)))
|
|
else:
|
|
print(pp(i))
|
|
|
|
|
|
#########################################
|
|
# H
|
|
#########################################
|
|
|
|
def command_h(cmd, args): # (h)ighlight
|
|
if cmd == 'h':
|
|
SOLVER.reset_highlight()
|
|
SOLVER.run()
|
|
elif cmd in 'hj hi':
|
|
res = SOLVER.highlight_interrupt()
|
|
SOLVER.run()
|
|
txt = ''
|
|
bits = ''
|
|
# first appearance of ᚠ is l_pos == 1; r_pos is the index on runes only
|
|
for l_pos, r_pos, _, is_set in res:
|
|
txt += '{}.{}.{} '.format(l_pos, 'T' if is_set else 'F', r_pos)
|
|
bits += '1' if is_set else '0'
|
|
print(f'\nInterrupts: {bits}\n{txt}')
|
|
else:
|
|
return False
|
|
|
|
|
|
#########################################
|
|
# K
|
|
#########################################
|
|
|
|
def command_k(cmd, args): # (k)ey manipulation
|
|
if cmd == 'k' or cmd == 'key':
|
|
SOLVER.KEY_DATA = [x.index for x in RuneText(args)]
|
|
print('set key: {}'.format(SOLVER.KEY_DATA))
|
|
elif cmd[1] == 's':
|
|
SOLVER.KEY_SHIFT = get_cmd_int(cmd, args, 'shift')
|
|
elif cmd[1] == 'r':
|
|
SOLVER.KEY_ROTATE = get_cmd_int(cmd, args, 'rotation')
|
|
elif cmd[1] == 'o':
|
|
SOLVER.KEY_OFFSET = get_cmd_int(cmd, args, 'offset')
|
|
elif cmd[1] == 'p':
|
|
SOLVER.KEY_POST_PAD = get_cmd_int(cmd, args, 'post padding')
|
|
elif cmd[1] == 'i':
|
|
SOLVER.KEY_INVERT = not SOLVER.KEY_INVERT
|
|
print('set key invert: {}'.format(SOLVER.KEY_INVERT))
|
|
elif cmd[1] == 'j':
|
|
args = args.strip('[]')
|
|
pos = [int(x) for x in args.split(',')] if args else []
|
|
SOLVER.INTERRUPT_POS = pos
|
|
print('set interrupt jumps: {}'.format(pos))
|
|
else:
|
|
return False # command not found
|
|
SOLVER.run()
|
|
|
|
|
|
#########################################
|
|
# L
|
|
#########################################
|
|
|
|
def command_l(cmd, args): # (l)og level
|
|
if cmd == 'lv' or args == 'v' or args == 'verbose':
|
|
SOLVER.output.VERBOSE = not SOLVER.output.VERBOSE
|
|
elif cmd == 'lq' or args == 'q' or args == 'quiet':
|
|
SOLVER.output.QUIET = not SOLVER.output.QUIET
|
|
elif cmd == 'ln' or args == 'n' or args == 'normal':
|
|
SOLVER.output.VERBOSE = False
|
|
SOLVER.output.QUIET = False
|
|
else:
|
|
return False
|
|
SOLVER.run()
|
|
|
|
|
|
#########################################
|
|
# P
|
|
#########################################
|
|
|
|
def command_p(cmd, args): # (p)rime test
|
|
if args and cmd not in 'prime':
|
|
return False
|
|
p = get_cmd_int(cmd, args, start=1)
|
|
print(p, ':', LIB.is_prime(p))
|
|
print(LIB.rev(p), ':', LIB.is_prime(LIB.rev(p)))
|
|
|
|
|
|
#########################################
|
|
# T
|
|
#########################################
|
|
|
|
def command_t(cmd, args): # (t)ranslate
|
|
if cmd != 't':
|
|
return False
|
|
word = RuneText(args)
|
|
rev_word = 28 - word
|
|
word = word.as_dict()
|
|
psum = sum(word['p'])
|
|
sffx = '*' if LIB.is_prime(psum) else ''
|
|
if LIB.is_prime(LIB.rev(psum)):
|
|
sffx += '√'
|
|
print('runes({}): {}'.format(len(word['r']), word['r']))
|
|
print('plain({}): {}'.format(len(word['t']), word['t']))
|
|
print('reversed: {}'.format(''.join([x.rune for x in rev_word])))
|
|
print('indices: {}'.format(word['i']))
|
|
print('prime({}{}): {}'.format(psum, sffx, word['p']))
|
|
|
|
|
|
#########################################
|
|
# X
|
|
#########################################
|
|
|
|
def command_x(cmd, args): # e(x)ecute decryption
|
|
if cmd == 'x':
|
|
pass # just run the solver
|
|
elif cmd == 'xf': # reload from file
|
|
file = f'pages/{args}.txt' if args else '_input.txt'
|
|
print('loading file:', file)
|
|
SOLVER.input.load(file=file)
|
|
args = None # so run() won't override data
|
|
elif len(cmd) > 0 and cmd[1] == 'l': # limit content
|
|
limit = get_cmd_int(cmd, args, 'read limit')
|
|
last_file = SOLVER.input.loaded_file
|
|
if last_file:
|
|
SOLVER.input.load(file=last_file)
|
|
if limit > 0:
|
|
SOLVER.input.data.trim(limit)
|
|
args = None
|
|
else:
|
|
return False
|
|
SOLVER.run(args if args else None)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|