#!/usr/bin/perl # vim: set sw=2 expandtab : # # Master version is at https://developer.berlios.de/projects/games-hack/ package Games::Hack::Patch::i686; $VERSION=0.52; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(GetNOP); sub GetNOP { my($adr_start, $adr_end, $disass)=@_; my($binary, $diff); $binary=""; ### Floating point store? Stores always %ST(0). if ($disass =~ m#^f[bi]?st(p)?[sl]? #) { if ($1) { # If the "Pop" flag is given in the "fst" ("floating point store") # operation, we need to clean the floating point stack. # We use the "ffreep st(0)" instruction, . $binary .= "\xdf\xc0"; $adr_start += 2; } # Rest gets done by jump. } # Popping values from the stack elsif ($disass =~ m#^pop#) { # increment esp; rest done by jump. # is there a 64bit pop? $binary .= "\x83\xc4\x04"; $adr_start += 3; } ### Integer move or direct modification elsif ($disass =~ m#^(mov|or|and)#) { # done by jump. } else { warn "Unknown instruction '" . $disass . "', patching unsafe!"; return undef; } ### short jump if (($diff=$adr_end-$adr_start) >= 2) { # The opcode needs two bytes; these are already consumed, and so the jump # distance has to be reduced. $binary .= "\xeb" . pack("C", $diff-2); $adr_end=$adr_start; } ### NOP. # That should never be needed ... all instructions involving memory access # (even via a register) have at least 2 bytes. # (If some value would be persistently stored in a register, we wouldn't # find it by looking at the memory contents.) $binary .= "\x90" x $diff if ($diff=$adr_end-$adr_start) >= 1; return $binary; } __END__ =head1 NAME Games::Hack::Patch::i686 - How to patch code sequences on i686 =head1 SYNOPSIS $bytes=GetNOP( $adr_start, $adr_end, @disass ); =head1 DESCRIPTION Not useful in itself; is used by C, and will possibly be used by C. Addresses given to this library are always in integer/decimal, so that the script can simply add and subtract. (C returns hex values.) =head2 GetNOP Given a start and an end address, and the disassembled instructions (although normally only one) in the given range (via C), return a binary string that, when written at the start address, causes this part of the program to be ignored. =over =item Memory moves from register The easiest way is simply returning the NOP opcode (0x90 on x86), as many times as needed. A bit better, because it's shorter, is to return a C, with the correct offset. =item Floating point operations Unfortunately there are some instructions with side effects; eg. the coprocessor instructions are typically issued with the suffix I, which causes this instruction to change the internal state. Simply jumping over such sequences leaves the old values on the coprocessor stack and can cause irregular behaviour, aborts, core dumps, and other crashes. So some care must be taken for them. =back =head1 BUGS/CAVEATS/TODO/IDEAS/WISHLIST =over =item Some QA A look from someone that knows all possible instructions, along with their side-effects, would be appreciated. =item Hardware support Modules for other CPUs would be nice. =back Patches are welcome. =head1 AUTHOR Ph. Marek =head1 COPYRIGHT AND LICENSE Copyright (C) 2007 by Ph. Marek; licensed under the GPLv3. =cut