making substitution search easier

This commit is contained in:
relikd
2021-01-25 22:05:40 +01:00
parent 0d6b4a1a16
commit 3ab77da4a8
4 changed files with 88 additions and 33 deletions

View File

@@ -249,7 +249,7 @@ If the output is too long, you can limit (the already loaded data) with `xl 180`
`Rune.kind` can be one of `r n s l w` meaning (r)une, (n)umber, (s)entence, (l)ine, or (w)hitespace. A line is what you see in the source file (which is equivalent to a line in the original jpg page). A sentence is one that ends with a period (`⁘`).
`Rune` as well as `RuneText` both support simple arithmetic operations: `Rune(i=2) - 2` will yield a `ᚠ` rune. For example, you can invert a text with `28 - RuneText('test me')`.
`Rune` as well as `RuneText` both support simple arithmetic operations: `Rune(i=2) - 2` will yield a `ᚠ` rune. For example, you can invert a text with `28 - RuneText('test me')` or simply `~RuneText('inverted')`.
__Note:__ Always initialize a rune with its rune character or its index, never ASCII or its prime value.

View File

@@ -20,6 +20,12 @@ class RuneSolver(RuneRunner):
def highlight_interrupt(self):
return self.highlight_rune(self.INTERRUPT, self.INTERRUPT_POS)
def substitute_get(self, pos, keylen, search_term, found_term):
return found_term.zip_sub(search_term).description(count=True)
def substitute_supports_keylen(self):
return False
def run(self, data=None):
if data:
self.input.load(data=data)
@@ -120,7 +126,9 @@ class RunningKeySolver(RuneSolver):
if self.KEY_INVERT:
r_idx = 28 - r_idx
pos = self.active_key_pos()
if pos != -1:
if pos == -1:
self.copy_unmodified(r_idx)
else:
i = (pos + self.KEY_SHIFT) % self.k_len
r_idx = (self.decrypt(r_idx, i) - self.KEY_ROTATE) % 29
# rotate_key
@@ -128,8 +136,11 @@ class RunningKeySolver(RuneSolver):
self.k_current_pos = (self.k_current_pos + 1) % self.k_full_len
return Rune(i=r_idx)
def decrypt(self, rune_index, key_index):
raise NotImplementedError # must subclass
def decrypt(self, rune_index, key_index): # must subclass
raise NotImplementedError
def copy_unmodified(self, rune_index): # subclass if needed
pass
def key__str__(self):
return self.KEY_DATA # you should override this
@@ -156,6 +167,15 @@ class VigenereSolver(RunningKeySolver):
def decrypt(self, rune_index, key_index):
return rune_index - self.KEY_DATA[key_index]
def substitute_supports_keylen(self):
return True
def substitute_get(self, pos, keylen, search_term, found_term):
ret = [Rune(r='')] * keylen
for i, r in enumerate(found_term.zip_sub(search_term)):
ret[(pos + i) % keylen] = r
return RuneText(ret).description(count=True, index=False)
def key__str__(self):
return self.key__str__basic_runes()
@@ -175,13 +195,35 @@ class AffineSolver(RunningKeySolver):
class AutokeySolver(RunningKeySolver):
def run(self, data=None):
self.running_key = self.KEY_DATA[:]
key = self.KEY_DATA[self.KEY_SHIFT:] + self.KEY_DATA[:self.KEY_SHIFT]
key = [29] * self.KEY_OFFSET + key + [29] * self.KEY_POST_PAD
self.running_key = key
super().run(data=data)
def decrypt(self, rune_index, key_index):
def decrypt(self, rune_index, _):
rune_index = (rune_index - self.running_key.pop(0)) % 29
self.running_key.append(rune_index)
return rune_index
def copy_unmodified(self, rune_index):
if self.k_len > 0:
self.running_key.pop(0)
self.running_key.append(rune_index)
def substitute_supports_keylen(self):
return True
def substitute_get(self, pos, keylen, search_term, found_term):
data = self.input.runes_no_whitespace()
ret = [Rune(r='')] * keylen
for o in range(len(search_term)):
plain = search_term[o]
i = pos + o
while i >= 0:
plain = data[i] - plain
i -= keylen
ret[i + keylen] = plain
return RuneText(ret).description(count=True, index=False)
def key__str__(self):
return self.key__str__basic_runes()

View File

@@ -101,6 +101,9 @@ class Rune(object):
def __rsub__(self, o):
return self if self.index == 29 else Rune(i=(o - self.index) % 29)
def __invert__(self):
return self if self.index == 29 else Rune(i=28 - self.index)
#########################################
# RuneText : Stores multiple Rune objects. Allows arithmetic operations
@@ -196,6 +199,11 @@ class RuneText(object):
d['i'] = [x for x in d['i'] if x != 29]
return fmt.format(d['r'], len(d['r']), d['t'], len(d['t']), d['i'])
def zip_sub(self, other):
if len(self) != len(other):
raise IndexError('RuneText length mismatch')
return RuneText([x - y for x, y in zip(self._data, other._data)])
def __getitem__(self, key):
if isinstance(key, str):
return [getattr(x, key) for x in self._data]
@@ -206,20 +214,10 @@ class RuneText(object):
self._data[key] = value
def __add__(self, other):
if isinstance(other, RuneText):
if len(self) != len(other):
raise IndexError('RuneText length mismatch')
return RuneText([x + y for x, y in zip(self._data, other._data)])
else:
return RuneText([x + other for x in self._data])
return RuneText([x + other for x in self._data])
def __sub__(self, other):
if isinstance(other, RuneText):
if len(self) != len(other):
raise IndexError('RuneText length mismatch')
return RuneText([x - y for x, y in zip(self._data, other._data)])
else:
return RuneText([x - other for x in self._data])
return RuneText([x - other for x in self._data])
def __radd__(self, other):
return RuneText([other + x for x in self._data])
@@ -227,5 +225,8 @@ class RuneText(object):
def __rsub__(self, other):
return RuneText([other - x for x in self._data])
def __invert__(self):
return RuneText([~x for x in self._data])
def __repr__(self):
return f'RuneText<{len(self._data)}>'

View File

@@ -85,8 +85,8 @@ def command_a(cmd, args): # [a]ll variations
root = RuneText(args)
inclIndex = 'q' not in cmd
if 'i' in cmd:
root = 28 - root
for i in range(0, 29):
root = ~root
for i in range(29):
print('{:02d}: {}'.format(i, (root + i).description(index=inclIndex)))
@@ -110,7 +110,7 @@ def command_d(cmd, args): # [d]ecrypt or single substitution
print('Error: key length mismatch')
else:
print('Substition:')
print((enc - plain).description())
print((enc.zip_sub(plain)).description())
#########################################
@@ -134,13 +134,26 @@ def command_f(cmd, args): # (f)ind word
print()
print('Found:')
for _, _, pos, _, w in cur_words:
print('{:04}: {}'.format(pos, w.description(count=True)))
print(f'{pos:04}: {w.description(count=True)}')
if search_term:
print()
keylen = [len(search_term)]
if SOLVER.substitute_supports_keylen():
try:
inp = input('What is the key length? (num or [a]ll): ').strip()
if inp:
if inp[0] == 'a':
keylen = range(len(search_term), 24)
else:
keylen = [int(inp)]
except ValueError:
raise ValueError('not a number.')
print()
print('Available substition:')
for _, _, pos, _, w in cur_words:
word = search_term - w
print('{:04}: {}'.format(pos, word.description(count=True)))
for kl in keylen:
res = SOLVER.substitute_get(pos, kl, search_term, w)
print(f'{pos:04}: {res}')
#########################################
@@ -253,17 +266,16 @@ 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'])
x = word.as_dict()
psum = sum(x['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']))
print('runes({}): {}'.format(len(x['r']), x['r']))
print('plain({}): {}'.format(len(x['t']), x['t']))
print('reversed: {}'.format(''.join([x.rune for x in ~word])))
print('indices: {}'.format(x['i']))
print('prime({}{}): {}'.format(psum, sffx, x['p']))
#########################################