GameSpector GSL patch file format description

GameSpector loads patches from the "GameSpector" directory on the first storage card. Patch files must have .gsl extension and can contain one or more patches. GSL files are just text files (UTF-8 encoding is recommended) containing patch definitions.

GSL patch definition

GSL patch is a structure containing global metadata, list of actions to take when applying the patch and list of actions to take when removing the patch:

Patch {

  [ required metadata variables ]
  [ optional global variables ]

  Apply {
    [ optional local variables ]
    [ list of actions that will be performed when applying the patch ]
  }

  Remove {
    [ optional local variables ]
    [ list of actions that will be performed when removing the patch ]
  }

}

Example patch:

Patch {
  target_package: "com.android.game"
  target_version: 1102
  title         : "Unlimited money"
  author        : "The Hacker"
  revision      : 1

  Apply {

    my_var_msg    : "Example local variable used as a message below"

    FileReplaceHex("1234:55:66")
    SelectMethod(".*/class/path/ClassName;$", "^methodName\\(S\\)I$")
    MethodReplaceHex("12:33:44")
    OpenZip("#DATADIR#/file_data.zip")
    SelectFile("path/in/zip/file.txt")
    FileSedScript("s/rank=1/rank=99/g")
    CloseZip(1)
    Message("Information", my_var_msg)
  }

  Remove {
    RestoreFiles()
  }
}

Metadata and global variables block

Metadata block must be the first section in the Patch{} block. The block contains a set of "name:value" variable definitions. Some of these variables are reserved to describe the patch and its target application, but you can also define any variables that you want. The variables will be globally accessible anywhere within Patch{}.

  target_package: "com.android.game" // Required
  target_version: 1102               // Required   
  target_version: "1102-1105"        // Target version defined as a range
  title         : "Unlimited money"  // Required   
  author        : "The Hacker"       // Required   
  revision      : 1                  // Required   
  cpu           : "all"              // Optional
  flags         : 0x1234             // Reserved, do not use 

  my_var_string : "This is a string" // User-defined global string var
  my_var_dec    : 12345              // User-defined global number var
  my_var_hex    : 0xaabbccdd         // User-defined global number var

Reserved metadata variables

Variable name Type Description
target_package Required Defines target package, for example, target_package: "my.app.pkg"
target_version Required Defines target package's version number that this patch supports ( integer number from AndroidManifest.xml), for example, version:23 versions can be defiend as ranges, for example, version:"225-267".
revision Required Patch revision number (integer), used to identify newer/updated versions of the same patch, must be greater than 0 and must be incremented for new revisions. Example: revision:3
title Required Patch title (displayed in GameSpector's patch list, so keep it short). A combination of title + target_package + author must be unique. Example: title:"Unlimited life"
author Required Patch author/credits string. Used to identify patches. Example: author:"John the patcher"
cpu Optional CPU tag, if present and not empty, will be matched against Build.CPU_ABI. This tag is useful to hide patches that do not apply to current device's architecture. For example, an application may come with libraries compiled for armeabi, armeabi-v7a, and x86 processors, but ony one of these libraries will be used. The cpu variable cal list either one CPU or multiple CPUs separated with commas, for example: cpu:"x86,armeabi-v7a". The default value is cpu:"all".
flags Reserved Reserved, do not use.

Apply{} and Remove{} blocks

Apply{} and Remove{} blocks define functions (actions) that will be sequentially executed when applying or removing the patch. Same actions can be used for Apply and Remove with the exception of RestoreFiles() that can only be used within Remove{} block. All actions are executed sequentially and some actions change state valiables used further down the script. For example, OpenZip("file.zip") selects a zip file. All further File*() actions will apply to files within the selected zip. It is possible to define local variables within Apply{} and Remove{} blocks. Local variables will override global variables with the same names. The following functions/actions are available:

Function Description
SetCpu("cpu_name[,cpu_name, ...]") Sets current CPU. All further actions will only be applied to matching CPUs. This is useful to create patches that require different flows for different CPUs.
Confirm("Message text") Displays a message and "Continue/Cancel", aborts script if "Cancel" is selected
Message("Title", "Message Text") Displays a message and "OK"
OpenZip("zip/file/path.zip") Selects ZIP file to use. Further SelectFile() actions will apply to files in selected ZIP. Don't forget to close the ZIP at the end -- see CloseZip() below.
SelectFile("lib/mylib.so") Selects current target file. All further File*() and Method*() actions will be applied to the selected file. By default, dex cache file is selected. You can also select it using SelectFile("").
FileCheckSize(file_size) Aborts patch if the size of the currently selected file size does not match
FileCheckSha1("hex_sha1_sum_value") Aborts patch if SHA1 sum of the currently file size does not match
SetProgress("progress message") Message to be displayed in the patch progress dialog
FileLockRO() Make selected file immutable *** APPS WON't be able to change/remove the file ***
FileUnlock() Unlock currently selected file that was previously locked
SetFailOnError(0) Instructs to not aborts patching if any of the actions (following the statement) fail. Default is SetFailOnError(1).
FileReplaceHex("00123456:AA:BB, 0054321:00??11??33:44??22??99") Replaces bytes within file with new values. The string argument is expected to contain a list of comma-separated ADDRESS:OLD_DATA:NEW_DATA sequences. All values are hexadecimal. OLD_DATA and NEW_DATA can be one or more bytes long, but must have the same length. Byte value of "??" represents "match any value" (for OLD_DATA) or "leave as is" (for NEW_DATA). Spaces and line breaks are optional and will be ignored. if a file is selected by SelectFile(), patch will be applied to that file (ADDRESS points to file offset), otherwise patch will be applied to the target package's dalvik cache file (for cache patches ADDRESS is an offset into package's classes.dex file, NOT the offset in the dalvik cache file)
FileReplaceAllHex("AABBCCDD", "BBCCDDAA") Same as above, but replaces all matching byte sequences in the selected file
FileReplaceStr(0x12345, "Old String", "new string") Replaces a string at a fiven offset in currently selected file.
FileReplaceAllStr("Old String", "new string") Replaces all mathing strings in the file.
FileSedScript("s/rank=1/rank=99/g") Pipes currently selected file through a sed script and saves the result back to the file. This action is irreversible, RestoreFiles() will not revert it
FileQuery("update table blah where x=y") Runs a database query against currently selected database file
FileQueryDisplay("select blah") Runs a database query against currently selected database file and displays results
ShellExec("asdasdas") Runs a shell command (as root)
ShellExecDisplay("asdas") Runs a shell command and displays stdout
CloseZip(1) Closed previously open ZIP file. All extracted/modified files will be packed back into the ZIP. CloseZip(1) will also re-sign the file. CloseZip(0) will not sign.
RestoreFiles() Restores all files previously modified using File*() actions. RestoreFiles() can only be used within Remove{} block.
SelectMethod("class_regexp","method_regexp") Selects a method in a class. Search is performed against currently selected file (or dalvik cache, if no file selected). Standard java regular expressions and standard class naming conventions can be used, for example SelectMethod(".*/class/path/MyClass;$","^getInteger\\(\\)I$") will select getInteger() method in MyClass. Make sure to properly escape parentheses in method names. This function fail if no class/method is found or if more than one class/method match given regexp patterns. Only methods that have code (e.g. not abstract) can be selected.
MethodReplaceHex("0A2:AA:BB, 26:00??11??33:44??22??99") Replaces bytes in the selected method. Argument is the same as in FileReplaceHex() except that offset specifies byte offset in method code, not byte offset in the file.

Substitution Macros

The following macros can be used within most string variables. For example: OpenZip("#DATADIR#/filename.zip")

Macro Description
#BB# Full path to busybox executable
#APKFILE# Full path to the target package file
#APKDIR# Full path to directory where target apk resides
#DATADIR# Full path to the package's data directory
#CACHEFILE# Full path to the package's cache file
#TMPDIR# Full path to GameSpector's temp directory
#EXTDIR# Full path to GameSpector's directory on SD card
#GSDATADIR# Full path to GameSpector's data directory
#PKGNAME# Target package name
#EXTSTORAGE# Full path to external storage root (primary SD Card)
#PKGVERSION# Packave version number (Integer from AndroidManifest.xml)
#OBBMAIN# Path to main obb file. Equivalent to #EXTSTORAGE#/Android/obb/#PKGNAME#/main.#PKGVERSION#.#PKGNAME#.obb
#OBBPATH# Path to obb patch file. Equivalent to #EXTSTORAGE#/Android/obb/#PKGNAME#/patch.#PKGVERSION#.#PKGNAME#.obb