-
-
Notifications
You must be signed in to change notification settings - Fork 53
Writing Patches
A patch is a mechanism by which the WARP tool specifies changes to be done in an Exe. When defined and found valid they get added to the Patch list.
Every patch works by means of an associated function in the underlying QJS engine.
When a user selects a patch in the Main GUI , this function gets called and sets up the modifications to be done.
The modifications are only done when the patches get 'applied'.
Patches are defined by means of it's name
along with title
, author
, desc
, needs
& recommend
keys.
Every patch needs to be a part of a group and each such group also has name
, title
, mutex
& color
keys.
For both patches & their groups, the name
is the only mandatory information required.
The tool reads the patch & group definitions from a specific YAML file called 'Patches.yml'. It has the following format:
GroupName1:
title : <Brief title for the group which gets displayed along with the patch title. All Caps are suggested.>
mutex : true/false #Indicates whether patches in this group are mutually exclusive or not
color : <Color to use for the title expressed as a keyword or #hexcode or [r,g,b,a]>
patches:
- PatchName11:
title : <Brief title for the patch>
recommend : yes/no #Whether to mark it as recommended or not
author : <Name of the author(s)>
desc : <Proper description of the patch. Can be as long as you need.>
needs:
- PatchName21
- PatchName31
- PatchName12:
title: <Brief title>
recommend : yes/no
author : <Author name(s)>
desc : <Proper description of the patch>
allowSkip: true
# etc.
GroupName2:
title : <Brief title>
mutex : true/false
color : <Valid color>
allowSkip: true
patches:
- PatchName21:
title: <Brief title>
recommend : yes/no
author : <Author name(s)>
desc : <Proper description of the patch>
#etc.
include:
- <yaml path1>
- <yaml path2>
Couple of points to consider:
-
The name of the patch also serves as the name of the Patch function that needs to be called.
-
The
title
,author
,desc
&recommend
keys are used while displaying the patch in the Patch list. -
The
recommend
key also serves to identify the patch as a 'recommended' one or not which subsequently ties into the Select Recommended action. -
The
allowSkip
key can be used to allow skipping a patch, if it's function is not defined. This means that no warning would be given for these patches.You can also specify the key on patch groups to allow skipping the entire group.
-
The
needs
key can be used to define a dependency chain.It should be either an existing patch
name
or a list of such names which needs to be enabled first before the current patch.Conversely if the dependent patch gets de-selected the current one will deselect as well.
-
The group name and patch name have no inter-dependency, however neither one can be empty.
-
Personally, I use the group name to either
- name the QJS file containing the Patch functions OR
- name a common function used in implementation of the constituent patches if any.
-
The
title
&color
of a group are utilized while displaying it's constituent patches in the Patch list. -
The
mutex
part is used internally to deselect other patches in the same group when one gets selected. -
As mentioned earlier, aside from the
name
all other members of a patch as well as a group are optional.
If not specified, they pick up the following default values:
| Group Member | Default value |
| :----------- | :---------------- |
| **`title`** | group name itself |
| **`mutex`** | `true` |
| **`color`** | transparent |
| **`allowSkip`** | `false` |
| Patch Member | Default value |
| :----------- | :------------ |
| **`title`** | patch name itself |
| **`author`** | `Unknown` |
| **`recommend`** | no |
| **`desc`** | will be empty |
| **`needs`** | will be empty |
| **`allowSkip`** | `false` |
-
Now, you can also add hyperlinks in the description of patches and extensions if you want using the
<a> </a>
tag just like in html. -
Similarly if you wish to copy the details of a patch, simply
right click
instead ofleft click
on the selector to copy to clipboard.You will get a notification saying that the details has been copied.
The same also works for Extension details and Exe names in Test Bench
-
You can make use of the
include
key to import additional Patch definitions from other files.This process is recursive and helps to keep a proper hierarchy without making 1 single bloated file. Nevertheless, the root is still
Patches.yml
.
As stated before, every patch has an associated QJS function and it shares the name
with the patch.
For a patch to become available in the Patch list, this function should have already been defined.
For this reason, the tool auto-loads scripts first if not done atleast once. The function can be implemented as either a regular function or an arrow function. Always ensure that it returns true to indicate success.
The general syntax is shown below (both the arguments are optional):
PatchName = function(name, title)
{
<bunch of code>
return true;
};
And here is the arrow function form
PatchName = (name, title) =>
{
<bunch of code>
return true;
};
In addition the Patch function can also have some member functions and variables for specific purposes as listed below:
-
validate
This function (if defined), will check whether this patch can be used with the loaded Exe and return
true/false
accordingly.The patch is added to the Patch list only if this function returns
true
or if the function is not defined.General Syntax:
PatchName.validate = function(name, title) { <prep code> return <validation expression> };
-
init
Once a patch has been validated and enabled, this function (if defined) will get executed.
While mostly redundant with
validate
function, this function's purpose is to enable initialization of client specific values required by the patch, whenever a client gets loaded.General Syntax:
PatchName.init = function(name) { <init code> };
-
onSelected
Once the 'selection' status is set to
true
for a patch, this function (if defined) gets called.One use case would be for some post-processing work like logging the status.
General Syntax:
PatchName.onSelected = function(name, title) { <bunch of code> };
-
cleanup
&onDeselected
When the user tries to deselect a patch, the following events occur in sequence:
-
The tool will clear the changes staged by it.
-
cleanup
function (if defined), gets called to perform any additional cleanup aside what was already done. -
Selection status is set to
false
. -
onDeselected
function (if defined), gets called .
A typical use case for
cleanup
is to transfer some shared data between related patches if the other one is still selected.On the other hand,
onDeselected
can be used for some post-processing work similar toonSelected
.General syntax for both:
PatchName.cleanup = function(name, title) { <bunch of code> }; PatchName.onDeselected = function(name, title) { <bunch of code> };
-
-
onApplied
This function if defined, gets invoked when a patch gets applied to the Exe.
It can be used to perform any additional steps like copying necessary supporting files.
General syntax:
PatchName.onApplied = function(name) { <bunch of code> };
As shown above, the tool supplies the Patch Name as an argument (and also the Patch Title in some cases) to the member functions as well.
However, they are optional and it is upto you whether you want to accept it or ignore it.
For e.g.
JustAPatch = () =>
{
Exe.SetHex(0x2424, MOV(EAX, ECX));
return true;
};
is completely valid and you can use similar format for member functions as well.
-
initvars
This property if set should be an array of variable names and corresponding values that they need to be initialized to whenever a client gets loaded.
For e.g.
MyPatch.initvars = ["V1", 100, "V2", ECX];
This will set
MyPatch.V1
to100
andMyPatch.V2
toECX
Register when the client loads.If the patch function is not an arrow function, You can use
this.V1
andthis.V2
inside the function to refer to these values. -
clearvars
Similarly, this property if set should be an array of variable names that need to be wiped whenever a client gets loaded.
For e.g.
MyPatch.clearvrs = ["LCache"];
This will delete
MyPatch.LCache
when the client loads.
Both of these activities are performed before the init
function
clearvars
takes precedence overinitvars
.
This means that if you use the same variable name in both arrays, then it gets wiped rather than set to the value. So avoid mixing them.
There are 3 scenarios in which a Patch function stops further execution and return control back to the tool.
-
Error occured:
The QJS engine will automatically detect known errors like syntax issues, unknown variable access etc.
In addition to this, it is also possible to report an error from the script by means of the
throw
keyword and anError
object.throw Error("Something failed");
This message will get displayed in an Error MessageBox.
-
Cancellation needed:
Often times the patch function needs to stop further progress either because the user cancelled while entering an input or it was pointless to continue any further.
To indicate this scenario to the tool, the function simply needs to
return
orthrow
either afalse
OR a non-boolean value.- no message (using
false
)
return false;
- with message
throw "Not going forward";
If a non-boolean value is 'returned/thrown' , it will get displayed in a Warning MessageBox.
For convenience, there is a Cancel function available.
Please note: a QJS function always returns something.
If you do not have an explicitreturn
orthrow
statement, then the returned value isundefined
and it will be treated as a cancellation. - no message (using
-
Normal exit:
To perform a normal exit from the function, it needs to
return true
.
Every patch function is different and therefore difficult to generalize the steps.
However, these are some of the usual steps involved in a patch function:
-
Gathering information
-
Find the reference location (usually a
PUSH
) of a string using combination of the Exe.Find functions. -
Find the address(es) where a known code pattern occurs using Exe.FindHex & Exe.FindHexN functions.
-
Use the reference address to discover a second pattern in it's vicinity.
-
Extract some data from the addresses found with one of the Exe.Get functions.
-
Retrieve inputs from user with the Exe.GetUserInput function.
-
Sometimes, the inputs are YAML files which need to be loaded using Warp.LoadYaml function to get an array or hashmap.
-
Use TextFile & BinFile classes for loading custom input files.
-
-
Processing the information
-
Prepare code and/or new strings to replace or insert utilizing the information obtained so far.
Filler function will be used for making placeholders for unknown values.
-
If you are trying to insert something, then allocate space for it with Exe.FindSpace function.
-
Substitute placeholders if any with SwapFillers and/or SetFillTargets functions.
-
-
Staging changes
-
Finally the mandatory step => return
true