"""Offline community-goal string inventory for the verified installed client."""
import argparse
import hashlib
import json
import re
from pathlib import Path
import pefile
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--image', type=Path, required=True)
parser.add_argument('--output', type=Path, required=True)
parser.add_argument('--terms', default='communitygoal,community-goal', help='Comma-separated static string terms')
args = parser.parse_args()
terms = args.terms.lower().split(',')
data = args.image.read_bytes()
expected = 'ed5a8f8cd10f9a80d3bda5badd7ba1d4fcfc15996c8c7e018852cb170e264b63'
if hashlib.sha256(data).hexdigest() != expected:
raise ValueError('Client build differs from the verified current image')
image = pefile.PE(data=data, fast_load=True)
rows = []
for section in image.sections:
if section.Characteristics & 0x20000000:
continue
section_data = section.get_data()
for pattern, encoding, terminator in ((rb'[\x20-\x7e]{6,1024}\x00', 'ascii', 1),
(rb'(?:[\x20-\x7e]\x00){6,1024}\x00\x00', 'utf-16-le', 2)):
for match in re.finditer(pattern, section_data):
text = match.group()[:-terminator].decode(encoding)
if not any(term in text.lower() for term in terms):
continue
rows.append({'rva': hex(section.VirtualAddress + match.start()), 'text': text, 'encoding': encoding})
if len(rows) > 1024:
raise ValueError('Unexpectedly large string inventory')
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps({'gameSha256': expected, 'offlineOnly': True, 'strings': rows}, indent=2), encoding='utf-8')
print(json.dumps({'output': str(args.output), 'count': len(rows), 'strings': rows[:60]}))
"""Offline community-goal string inventory for the verified installed client."""
import argparse
import hashlib
import json
import re
from pathlib import Path
import pefile
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--image', type=Path, required=True)
parser.add_argument('--output', type=Path, required=True)
parser.add_argument('--terms', default='communitygoal,community-goal', help='Comma-separated static string terms')
args = parser.parse_args()
terms = args.terms.lower().split(',')
data = args.image.read_bytes()
expected = 'ed5a8f8cd10f9a80d3bda5badd7ba1d4fcfc15996c8c7e018852cb170e264b63'
if hashlib.sha256(data).hexdigest() != expected:
raise ValueError('Client build differs from the verified current image')
image = pefile.PE(data=data, fast_load=True)
rows = []
for section in image.sections:
if section.Characteristics & 0x20000000:
continue
section_data = section.get_data()
for pattern, encoding, terminator in ((rb'[\x20-\x7e]{6,1024}\x00', 'ascii', 1),
(rb'(?:[\x20-\x7e]\x00){6,1024}\x00\x00', 'utf-16-le', 2)):
for match in re.finditer(pattern, section_data):
text = match.group()[:-terminator].decode(encoding)
if not any(term in text.lower() for term in terms):
continue
rows.append({'rva': hex(section.VirtualAddress + match.start()), 'text': text, 'encoding': encoding})
if len(rows) > 1024:
raise ValueError('Unexpectedly large string inventory')
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps({'gameSha256': expected, 'offlineOnly': True, 'strings': rows}, indent=2), encoding='utf-8')
print(json.dumps({'output': str(args.output), 'count': len(rows), 'strings': rows[:60]}))