Skip to content

Commit

Permalink
Fix rerolling race, add mem write code, cleanup
Browse files Browse the repository at this point in the history
So the rerolling race was solved by sending another keypress.  Fucking
end me.

I also cleaned up the commands.
  • Loading branch information
imayhaveborkedit committed Sep 8, 2017
1 parent aeb11ed commit 810536c
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 81 deletions.
50 changes: 25 additions & 25 deletions memhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,14 @@ def _press_n(self, delay=None):
def _press_n_no_focus(self, delay=None):
win32api.SendMessage(self.hwnd, WM_CHAR, 78)

def _read_mem_address(self, address, size, handle):
def _read_mem_address(self, raw_address, size, handle):
buf = (c_byte * size)()
bytesRead = c_ulong(0)
result = None

try:
result = ctypes.windll.kernel32.ReadProcessMemory(
handle, address, buf, size, ctypes.byref(bytesRead))
handle, raw_address, buf, size, ctypes.byref(bytesRead))

assert result != 0
return buf
Expand All @@ -272,7 +272,7 @@ def _read_mem_address(self, address, size, handle):
err_msg = win32api.FormatMessage(result).strip()

raise RuntimeError(
f"Could not read address {address} ({size}B), error code {result} ({err_msg})")
f"Could not read address {raw_address} ({size}B), error code {result} ({err_msg})")

def _read_address(self, address, handle):
size = address.size
Expand All @@ -290,6 +290,20 @@ def _read_address(self, address, handle):
err = ctypes.windll.kernel32.GetLastError()
raise RuntimeError(f"Could not read address (err {err})")

def _write_address(self, address, data, handle):
try:
result = ctypes.windll.kernel32.WriteProcessMemory(
handle, address.address + self.base_addr, data, len(data), None)
except Exception as e:
err = ctypes.windll.kernel32.GetLastError()
raise RuntimeError(f"Could not write address (err {err})")

def write_to_address(self, address, value):
data = struct.pack(_size_to_struct[address.size], value)

with _open_proc(self.pid) as handle:
self._write_address(address, data, handle)

def read_address(self, address):
with _open_proc(self.pid) as handle:
return self._read_address(address, handle)
Expand All @@ -316,34 +330,24 @@ def reload(self):

return True

def safe_reroll(self, *, delay=None, retry_delay=0.001, retry=500):
def reroll(self):
self._last_stats = self.read_all()
last_reroll = self._read_rerolls()

self._press_n_no_focus(delay)
self._press_n_no_focus()
self._press_n_no_focus()

for x in range(retry):
stats = self.read_all()

if stats != self._last_stats:
return stats

time.sleep(retry_delay)

return self._last_stats # rip
stats = self.read_all()
assert stats != self._last_stats

return stats

def read_stat(self, stat):
if not self.is_running():
raise RuntimeError("Process is not running")

return self.read_address(_statmap[stat])

# def _read_all(self):
# with _open_proc(self.pid) as handle:
# for stat in statinfo.names:
# yield self._read_address(_statmap[stat], handle)

def _read_all_stats(self):
with _open_proc(self.pid) as handle:
data = self._read_mem_address(
Expand All @@ -367,12 +371,8 @@ def read_all(self, *, zip=False):
def _read_rerolls(self):
return self.read_address(_rerolls)

def reroll(self, *, delay=None):
if not self.is_foreground():
self.focus_game()

self._press_n(delay)
return self.read_all()
def reset_reroll_count(self, count=0):
self.write_to_address(_rerolls, count)

def zip(self, statlist):
return dict(zip(statinfo.names, statlist))
Expand Down
170 changes: 114 additions & 56 deletions ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
stat_args = {
'dont_extend_width': True,
'dont_extend_height': True,
'always_hide_cursor': True,
'always_hide_cursor': False,
'wrap_lines': True,
'height': D.exact(1),
'width': D.exact(38),
Expand All @@ -52,7 +52,7 @@ def hpad(height, ch=' ', token=Token.Padding):
def vpad(width, ch=' ', token=Token.Padding):
return Window(width=D.exact(width), content=FillControl(ch, token=token))

def make_stat_doc(name, value=4):
def make_stat_doc(name, value=0):
return Document(statinfo.Stats.format(name, value))

def make_stat_window(name):
Expand Down Expand Up @@ -241,7 +241,7 @@ def _gen_buffers(self):
initial_document=Document(),
is_multiline=True),

'REROLL_STAT_BUFFER': Buffer(
'REROLLS_STAT_BUFFER': Buffer(
initial_document=make_stat_doc('Rerolls', 0),
is_multiline=False),

Expand Down Expand Up @@ -271,7 +271,7 @@ def _gen_layout(self):
stat_windows.append(vpad(1))

stat_windows.append(Window(
content=BufferControl(buffer_name='REROLL_STAT_BUFFER'), **stat_args))
content=BufferControl(buffer_name='REROLLS_STAT_BUFFER'), **stat_args))
stat_windows.append(vpad(1))

@Condition
Expand Down Expand Up @@ -348,6 +348,17 @@ def ensure_cursor_bounds(buffer, pos, valids=None):

buffer.cursor_position = pos

@Condition
def _in_stat_buffer(cli):
return cli.current_buffer_name.endswith("_STAT_BUFFER")

@Condition
def _in_normal_stat_buffer(cli):
return not cli.current_buffer_name.startswith(tuple(name.upper() for group in statinfo.groups[2:] for name in group))


# Navigation binds

@bind(Keys.Left)
@self.stat_constraints.listen
def _(event):
Expand All @@ -361,28 +372,57 @@ def _(event):
buff = event.current_buffer
new_pos = buff.cursor_position + buff.document.get_cursor_right_position(count=event.arg)
ensure_cursor_bounds(buff, new_pos)
# TODO: set all stat buffers with no value set to have the same cursor_position

@bind(Keys.Up)
@self.stat_constraints.listen
def _(event):
current_buffer = event.cli.current_buffer
from_stat_buff = _in_normal_stat_buffer(event.cli)

self._focus(self.stat_buffer_state.up())

buff = event.cli.current_buffer
ensure_cursor_bounds(buff, buff.cursor_position)

if _in_normal_stat_buffer(event.cli) and from_stat_buff:
buff.cursor_position = current_buffer.cursor_position

@bind(Keys.Down)
@self.stat_constraints.listen
def _(event):
current_buffer = event.cli.current_buffer
from_stat_buff = _in_normal_stat_buffer(event.cli)

self._focus(self.stat_buffer_state.down())

buff = event.cli.current_buffer
ensure_cursor_bounds(buff, buff.cursor_position)

if _in_normal_stat_buffer(event.cli) and from_stat_buff:
buff.cursor_position = current_buffer.cursor_position

@bind(Keys.Enter, filter=_in_stat_buffer)
@self.stat_constraints.listen
def _(event):
pass


# Control binds

@bind(Keys.ControlC)
@bind(Keys.ControlD)
# @bind(Keys.ControlC)
def _(event):
event.cli.set_return_value(None)

@bind_with_help('?', name='Help', info="Shows a help screen")
@bind(Keys.PageUp)
def _(event):
self._scroll_up()

@bind(Keys.PageDown)
def _(event):
self._scroll_down()

@bind_with_help('?', name='Help', info="Shows the help screen")
def _(event):
if self._help_showing:
self._help_showing = False
Expand All @@ -392,88 +432,106 @@ def _(event):
self.set_info_text(help_text)
self._help_showing = True

@bind_with_help('t', name='Reroll test')
@bind_with_help('n', name='Reroll')
def _(event):
self.print("Dispatching runner")

def do():
self.print("ok running")

num = 50
t0 = time.time()

for x in range(num):
# self.reroll(delay=0.017, warning="WARNING SAME STATS ROLLED")
self.reroll(delay=0.09, retry_delay=0.05, retry=1)
self.print("Rolled")


t1 = time.time()
self.print(f'Rolled {num} times in {t1-t0:.4f}', 'sec')

event.cli.eventloop.run_in_executor(do)
self.print("alrighty then")
l = self.reroll()

@bind_with_help('r', name='Reroll')
@bind_with_help('y', name='Accept Stats', info="Accept current stats in game")
def _(event):
l = self.reroll()
self.set_stats(**self.hook.zip(l))
... # TODO

@bind_with_help('e', name='Update stats')
@bind_with_help('r', name='Refresh stats')
def _(event):
self.set_stats(**self.hook.zip(self.hook.read_all()))

@bind('a')
@bind_with_help(Keys.ControlZ, name='Undo', info="TODO: undo buffer")
def _(event):
self.print("Showing cursor")
memhook.Cursor.show()
self.print("I'll get to writing undo eventually")

@bind('s')
@bind_with_help(Keys.ControlY, name='Redo', info="TODO: undo buffer")
def _(event):
self.print("Hiding cursor")
memhook.Cursor.hide()
... # TODO


@bind_with_help('E', name='Embed IPython')
# Testing/Debug binds

@bind_with_help('`', name='Embed IPython')
def _(event):
def do():
# noinspection PyStatementEffect
self, event # behold the magic of closures and scope

__import__('IPython').embed()
os.system('cls')

event.cli.run_in_terminal(do)

@bind(Keys.PageUp)
@bind_with_help('t', name='Reroll test')
def _(event):
self._scroll_up()
def do():
self.print("Running reroll test")

@bind(Keys.PageDown)
num = 50
self.hook.reset_reroll_count()
rrbase = self.hook._read_rerolls()
t0 = time.time()

for x in range(num):
self.reroll()
self.print("Rerolled")

t1 = time.time()

rrcount = self.hook._read_rerolls() - rrbase
self.print(f'Rolled {num} ({rrcount}) times in {t1-t0:.4f}', 'sec')

self.run_in_executor(do)

@bind(',')
def _(event):
self._scroll_down()
self.print("Showing cursor")
memhook.Cursor.show()

@bind('.')
def _(event):
self.print("Hiding cursor")
memhook.Cursor.hide()

@bind('-')
def _(event):
self.print("got random stats")
self.set_stats(**memhook.get_random_stats())

@Condition
def _in_stat_buffer(cli):
return any(b == cli.current_buffer for n,b in self.buffers.items() if n.endswith("_STAT_BUFFER"))

@bind(Keys.Enter, filter=_in_stat_buffer)
@self.stat_constraints.listen
def _(event):
buf = event.cli.current_buffer
self.set_info_text(f"Enter on buffer {buf} at {buf.cursor_position}")

return registry


def _add_events(self):
def default_buffer_changed(_):
self.buffers['INFO_BUFFER'].reset(self.buffers[DEFAULT_BUFFER].document)
"""
Buffer events:
on_text_changed
on_text_insert
on_cursor_position_changed
Application events:
on_input_timeout
on_start
on_stop
on_reset
on_initialize
on_buffer_changed
on_render
on_invalidate
Container events:
report_dimensions_callback (cli, list)
"""

def vertical_cursor_move(cli):
pass



self.buffers[DEFAULT_BUFFER].on_text_changed += default_buffer_changed

def _finalize_build(self):
self.set_info_text(help_text)
Expand Down Expand Up @@ -535,7 +593,7 @@ def run_in_executor(self, func, *args, **kwargs):

def reroll(self, **kw):
# noinspection PyArgumentList
new_stats = self.hook.safe_reroll(**kw)
new_stats = self.hook.reroll()

self.set_stats(**self.hook.zip(new_stats))
self.stat_state['Rerolls'] += 1
Expand Down

0 comments on commit 810536c

Please sign in to comment.