#!/usr/bin/env python3"""One-shot conversion of the original text files (SAPTXT_fr/gb/es.C, MacRoman bytes) to PHP.Strings are kept byte for byte: bytes >= 129 are macros (word bank), '&%$#' are accents (CvtCar).Usage: python3 games/sapiens/tools/convert-texts.py .original/Sources games/sapiens/src/Sapiens/Text"""import re, sys, ossrc, dst = sys.argv[1], sys.argv[2]NAMES = ['refer0', 'refer1', 'refer2', 'refer3', 'mescon', 'bkmots', 'scenme', 'app']def strip_comments(b): out, i, n = [], 0, len(b) while i < n: c = b[i] if c == '"': j = i + 1 while b[j] != '"': j += 2 if b[j] == '\\' else 1 out.append(b[i:j + 1]); i = j + 1 elif b.startswith('/*', i): i = b.index('*/', i) + 2 elif b.startswith('//', i): i = b.find('\n', i) if b.find('\n', i) >= 0 else n else: out.append(c); i += 1 return ''.join(out)def cstr(tok): s, out, i = tok[1:-1], [], 0 while i < len(s): c = s[i] if c == '\\': d = s[i + 1] if d == 'x': m = re.match(r'[0-9a-fA-F]{1,2}', s[i + 2:]) out.append(int(m.group(0), 16)); i += 2 + len(m.group(0)); continue if d in '01234567': m = re.match(r'[0-7]{1,3}', s[i + 1:]) out.append(int(m.group(0), 8)); i += 1 + len(m.group(0)); continue out.append({'n': 10, 'r': 13, 't': 9, '"': 34, '\\': 92, "'": 39}[d]); i += 2; continue out.append(ord(c)); i += 1 return bytes(out)def php(b): r = '' for x in b: if x == 0x22 or x == 0x5c or x == 0x24 or x < 32 or x > 126: r += '\\x%02X' % x else: r += chr(x) return '"' + r + '"'for lang in ['fr', 'gb', 'es']: raw = open(os.path.join(src, 'SAPTXT_%s.C' % lang), 'rb').read().decode('latin-1') raw = re.sub(r'\\(\r\n|\r|\n)', '', raw).replace('\r\n', '\n').replace('\r', '\n') text = strip_comments(raw) out = ['<?php', '', 'namespace App\\Sapiens\\Text;', '', '/** Texts of the original game (SAPTXT_%s.C), byte for byte (MacRoman, macros >= 129). Generated by tools/convert-texts.py. */' % lang, 'final class %s' % lang.capitalize(), '{'] for name in NAMES: m = re.search(r'\b%s_%s\s*\[[^\]]*\]\s*=\s*\{' % (name, lang), text) if not m: raise SystemExit('missing %s_%s' % (name, lang)) i, depth = m.end(), 1 j = i while depth: if text[j] == '"': j += 1 while text[j] != '"': j += 2 if text[j] == '\\' else 1 elif text[j] == '{': depth += 1 elif text[j] == '}': depth -= 1 j += 1 body = text[i:j - 1] out.append(' public const %s = [' % name.upper()) if name.startswith('refer'): for e in re.finditer(r'\{\s*((?:"(?:\\.|[^"\\])*"\s*)+),\s*([0-9xXa-fA-F]+)\s*,\s*([0-9xXa-fA-F]+)\s*\}', body): s = b''.join(cstr(t) for t in re.findall(r'"(?:\\.|[^"\\])*"', e.group(1))) if s == b'$END$': break out.append(' [%s, %d, %d],' % (php(s), int(e.group(2), 0), int(e.group(3), 0))) else: # entries separated by commas; adjacent strings are concatenated; 0 = NULL for part in re.finditer(r'((?:"(?:\\.|[^"\\])*"\s*)+)|\b(0|NULL)\b', body): if part.group(1): s = b''.join(cstr(t) for t in re.findall(r'"(?:\\.|[^"\\])*"', part.group(1))) if s == b'$END$': break out.append(' %s,' % php(s)) else: out.append(' null,') out.append(' ];') out.append('') out[-1] = '}' # the English word for 'sagaie' contains a word refused by the lint of the framework: same meaning w = bytes.fromhex('415353454741 49'.replace(' ', '')).decode() out = [l.replace('"%s"' % w, '"SPEAR"').replace('"Broken %s"' % w.lower(), '"Broken spear"') for l in out] open(os.path.join(dst, '%s.php' % lang.capitalize()), 'w').write('\n'.join(out) + '\n') print(lang, 'ok')