mirror of
https://github.com/BrianPugh/game-and-watch-patch.git
synced 2025-12-16 07:16:26 +01:00
207 lines
6.6 KiB
Python
Executable File
207 lines
6.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Goal: try and find unused regions of RAM.
|
|
"""
|
|
|
|
import argparse
|
|
import pickle
|
|
import random
|
|
import sys
|
|
import termios
|
|
import tty
|
|
|
|
import numpy as np
|
|
import matplotlib
|
|
import matplotlib.pyplot as plt
|
|
|
|
from functools import partial
|
|
from pathlib import Path
|
|
from pyocd.core.helpers import ConnectHelper
|
|
from pyocd.flash.file_programmer import FileProgrammer
|
|
from time import strftime
|
|
|
|
|
|
time_str = strftime("%Y%m%d-%H%M%S")
|
|
auto_int = partial(int, base=0) # Auto detect input format
|
|
ENTER = "\r"
|
|
|
|
|
|
def get_char(prompt="", valid=None, echo=True, newline=True):
|
|
"""reads a single character"""
|
|
fd = sys.stdin.fileno()
|
|
old_settings = termios.tcgetattr(fd)
|
|
try:
|
|
while True:
|
|
sys.stdout.write(prompt)
|
|
sys.stdout.flush()
|
|
|
|
tty.setraw(fd)
|
|
char = sys.stdin.read(1)
|
|
|
|
if char == "\x03": # CTRL + C
|
|
sys.exit(1)
|
|
|
|
if echo:
|
|
sys.stdout.write(char)
|
|
sys.stdout.flush()
|
|
|
|
if valid is None or char in valid:
|
|
return char
|
|
finally:
|
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
if newline:
|
|
sys.stdout.write("\n")
|
|
sys.stdout.flush()
|
|
|
|
|
|
def zero_runs(a):
|
|
"""
|
|
Source: https://stackoverflow.com/a/24892274
|
|
"""
|
|
# Create an array that is 1 where a is 0, and pad each end with an extra 0.
|
|
iszero = np.concatenate(([0], np.equal(a, 0).view(np.int8), [0]))
|
|
absdiff = np.abs(np.diff(iszero))
|
|
# Runs start and end where absdiff is 1.
|
|
ranges = np.where(absdiff == 1)[0].reshape(-1, 2)
|
|
return ranges
|
|
|
|
|
|
class Main:
|
|
def __init__(self):
|
|
parser = argparse.ArgumentParser(description="Memory observer.")
|
|
parser.add_argument("command")
|
|
args = parser.parse_args(sys.argv[1:2])
|
|
|
|
if not hasattr(self, args.command):
|
|
print("Unrecognized command")
|
|
parser.print_help()
|
|
exit(1)
|
|
|
|
if args.command in set(["analyze"]):
|
|
# Commands that don't want an ocd session
|
|
getattr(self, args.command)()
|
|
else:
|
|
with ConnectHelper.session_with_chosen_probe() as session:
|
|
board = session.board
|
|
target = board.target
|
|
target.resume()
|
|
getattr(self, args.command)(board, target)
|
|
|
|
def capture(self, board, target):
|
|
parser = argparse.ArgumentParser(description="Capture memory data from device.")
|
|
parser.add_argument("addr_start", type=auto_int)
|
|
parser.add_argument("addr_end", type=auto_int)
|
|
|
|
group = parser.add_mutually_exclusive_group()
|
|
group.add_argument("--random", action="store_true",
|
|
help="Write random initial data to address range.")
|
|
group.add_argument("--zero", action="store_true",
|
|
help="Write zeros initial data to address range.")
|
|
|
|
parser.add_argument("--output", "-o", type=Path, default=Path(f"captures/{time_str}.pkl"))
|
|
args = parser.parse_args(sys.argv[2:])
|
|
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
size = args.addr_end - args.addr_start
|
|
samples = []
|
|
|
|
def read():
|
|
return bytes(target.read_memory_block8(args.addr_start, size))
|
|
|
|
def write(data):
|
|
return target.write_memory_block8(args.addr_start, data)
|
|
|
|
if args.random:
|
|
random_data = random.randbytes(size)
|
|
write(random_data)
|
|
samples.append(random_data)
|
|
elif args.zero:
|
|
zero_data = b"\x00" * size
|
|
write(zero_data)
|
|
samples.append(zero_data)
|
|
|
|
###################
|
|
# Collect samples #
|
|
###################
|
|
while True:
|
|
char = get_char("Enter command (h for help): ",
|
|
[ENTER, " ", "h", "r", "q"])
|
|
if char == "h":
|
|
print("Help:")
|
|
print(" Enter or Space - Capture a memory screenshot")
|
|
print(" r - Reset Target")
|
|
print(" q - save and quit")
|
|
elif char == ENTER or char == " ":
|
|
print("Capturing... ", end="", flush=True)
|
|
target.halt()
|
|
data = read()
|
|
target.resume()
|
|
print("Captured!")
|
|
samples.append(data)
|
|
elif char == "r":
|
|
print("Reseting Target")
|
|
target.reset()
|
|
elif char == "q":
|
|
print("Quitting")
|
|
break
|
|
else:
|
|
raise ValueError(f"Unknown option \"{char}\"")
|
|
# Serialize
|
|
with open(args.output, "wb") as f:
|
|
pickle.dump(samples, f)
|
|
print(f"Saved session to {args.output}")
|
|
|
|
|
|
def analyze(self):
|
|
parser = argparse.ArgumentParser(description="Analyze captured data.")
|
|
parser.add_argument("src", type=Path, help="Load a pkl file for analysis.")
|
|
parser.add_argument("--show", action="store_true", help="Show matplotlib plot")
|
|
|
|
args = parser.parse_args(sys.argv[2:])
|
|
|
|
with open(args.src, "rb") as f:
|
|
samples = pickle.load(f)
|
|
|
|
samples = [np.frombuffer(sample, dtype=np.uint8) for sample in samples]
|
|
|
|
COLOR_SAME = np.array([0x71, 0xc4, 0x94], dtype=np.uint8)
|
|
COLOR_DIFF = np.array([0x8a, 0x58, 0x17], dtype=np.uint8)
|
|
COLOR_PAD = np.array([0xFF, 0xFF, 0xFF], dtype=np.uint8)
|
|
|
|
start = samples[0]
|
|
width = 1024
|
|
new_len = int(width * np.ceil(len(start) / width))
|
|
padding = np.full(new_len - len(start), -1)
|
|
n_comparisons = len(samples) - 1
|
|
for i, sample in enumerate(samples[1:]):
|
|
i += 1
|
|
diff = start != sample
|
|
|
|
free_segs = zero_runs(diff)
|
|
free_segs_lens = free_segs[:,1] - free_segs[:, 0]
|
|
free_segs_max_idx = free_segs_lens.argmax()
|
|
|
|
if args.show:
|
|
diff_padded = np.concatenate((diff, padding))
|
|
diff_padded = diff_padded.reshape(-1, width)
|
|
h, w = diff_padded.shape
|
|
|
|
canvas = np.zeros((h, w, 3), dtype=np.uint8)
|
|
canvas[diff_padded == 0] = COLOR_SAME
|
|
canvas[diff_padded == 1] = COLOR_DIFF
|
|
canvas[diff_padded == -1] = COLOR_PAD
|
|
|
|
plt.subplot(n_comparisons, 1, i)
|
|
plt.imshow(canvas)
|
|
plt.title(f"Comparison from {i} to 0")
|
|
|
|
free_seg_max = free_segs[free_segs_max_idx, :]
|
|
print(f"The longest untouched memory segment is inclusive offset {free_seg_max}")
|
|
|
|
if args.show:
|
|
plt.show()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
Main()
|