Skip to content

Scripts Development

Bernardo Giordano edited this page Dec 31, 2018 · 22 revisions

This section contains a collection of info on creating scripts for PKSM.

Setting up your environment

To start creating scripts for PKSM you will need to make sure you have the following tools/resources installed:

PC

3DS

  • PKSM -- v6.0.0 or later
  • Save manager app for Homebrew/CFW
    • CFW - Checkpoint - works with both 3DS and DS games

Compiling Existing Scripts

Open your Command Prompt (Windows) or Terminal (Mac/Linux) to PKSM-Scripts then follow the instructions for your scripting language below

Python 3

  • Run one of the following commands
    • Windows
      • All scripts (in the .txt files): py -3 genScripts.py
      • Single script: py -3 PKSMScript.py "USUM - Set max money" -i 0x4404 4 9999999 1
    • Mac / Linux
      • All scripts (in the .txt files): python3 genScripts.py
      • Single script: python3 PKSMScript.py "USUM - Set max money" -i 0x4404 4 9999999 1

Node.js

  • Run one of the following commands
    • All scripts (in the .txt files): node genScripts.js
    • Single script: node PKSMScript.js "USUM - Set max money" -i 0x4404 4 9999999 1

"Legacy" Script file format

## PKSM script structure
# u8 magic[10]        // PKSMSCRIPT
# u32 offset          // save offset to write the data to
# u32 length          // payload length
# u8 payload[len]     // payload data
# u32 repeat_times    // repeat payload n times
# ...

Making New Scripts

To create entirely new scripts you will need the following data:

  • offset -- where in the save file you find the value controlling the change you want to make
  • new value -- the result of the change you want to make
  • value length -- the number of bytes the save uses to represent the value(s) you're changing

Finding Offsets

There are a few options for finding the offset(s) you want to edit in the save files:

  • This folder contains a consolidation of much of the save offset info from PKHeX's source code and Project Pokémon's Tech Doc pages. While it may not cover everything, it should at least give you an idea of where to look when searching for your offset manually
  • PKHeX's source code - it helps if you can read C# code (another C-like language works too) and understand the hexadecimal system
  • Project Pokémon's Technical Documentation pages
  • search for the offset manually
  • ask for help on the FlagBrew Discord server (preferably in #pksm-tools-general)

Searching for an Offset Manually

If the value you want to edit has already had its offset documented, skip down to "Testing Offsets and Values". If you can't find the value you want to edit documented, you'll have to search for the proper offset manually

  1. Save your game before performing an in-game action that will make the change you want
  2. Use the save manager on your 3DS to backup your save (if possible give it a name letting you know what the save is for)
  3. Go back into your game and perform an action that will make the change you want
  4. Make a new backup of your save (separate from the previous one)
    • for repeatable changes (eg. gaining or losing money), repeating steps 3 and 4 (and making a new backup each time) can help narrow down the possibilities for your target offset
  5. Move/copy your saves to your PC
  6. Open and compare the saves in your hex editor
    • any offset that changes between each file is a possibility for your desired change
    • NOTE: be sure to compare your list to the offsets documented in this folder so that you don't accidentally use the offset of something unrelated that often changes during normal gameplay, like play time or checksums
    • NOTE: some effects may require changing multiple offsets
  7. Once you've found a likely candidate for the change you want to make, move on to "Testing Offsets and Values" below

Testing Offsets and Values

  1. In your hex editor, change the value of your offset and save the changes
  2. To make your edited save recognizable by your game you need to get it resigned by doing the following:
    1. Open the save in PKHeX
    2. Export the save (File > Export SAV... > Export main), saving it over the version you opened
  3. Transfer your edited save back to your 3DS and import/restore with your save manager
  4. Boot your game and check what has changed
    1. If your change affected what you wanted, try different values for your change until you get a final result you're happy with, then move on to "Compilation" below
    2. If your change didn't affect what you wanted, return to "Testing Offsets" step 1 and try another possible offset
    3. If you don't have any more possibilities remaining, try starting your search over again from "Searching for an Offset Manually" step 1

Compilation

Once you have the correct offset and value for the change you want to make, all that's left is to construct the command for compiling your new script and making sure it works as a script.

The command you need to use to compile your scripts can vary depending on what operating system you're using. Replace the <bracketed_values> with the appropriate values for your script.

  • Python
    • Windows: py -3 PKSMScript.py <name> -i <offset> <length> <payload> <repeat>
    • Mac / Linux: python3 PKSMScript.py <name> -i <offset> <length> <payload> <repeat>
  • Node.js: node PKSMScript.js <name> -i <offset> <length> <payload> <repeat>

If your script changes multiple, non-consecutive offsets, just add an extra set of -i <offset> <length> <payload> <repeat> for each one

Where...

  • <name> -- the name you want your new script to have, enclosed in quotation marks: "Set max money"
  • <offset> -- the save offset you are editing
  • <length> -- the number of bytes you are writing to the save
  • <payload> -- the value(s) you are writing to the save; can be one of the following
    • an integer (either decimal or hex)
    • the name of a binary file containing the data (values) to use, enclosed in quotation marks: "data/USUM_AllItems.bin"
  • <repeat> -- how many times in succession the value should be written to the save

PKSMScript Syntax

PKSMScript.py [-h] output [-d subdir] [-i ofs len pld rpt]
PKSMScript.js [-h] output [-d subdir] [-i ofs len pld rpt]

You can use PKSMScript.py -h (Python) or PKSMScript.js -h (Node.js) to view PKSMScript's own documentation

To create completely new scripts, you will need to find the following values:

  • output -- the name of your new script
  • -d subdir -- denotes an optional subdirectory to place the compiled script in
  • -i -- denotes the beginning of input values (can be repeated, along with extra sets of ofs, len, pld, and rpt values, to change more than one offset with a single script)
  • ofs -- the offset (location) in the game's save of the value you want to edit
  • len -- how many bytes (offsets) need to be written over
  • pld -- the new value you want to write to the save (or a .bin file containing a list of values to write)
  • rpt -- how many times you want pld to be written to the save in succession

picoC Script Documentation

API

Header inclusion

#include <pksm.h>

Arguments passed to main

  • argv
    • argv[0] pointer to save data, converted to int, passed as text
    • argv[1] save data length (int passed as text)
    • argv[2] save's game version (game's index number as found in a Pokémon's source game field)
      • D = 10, P = 11, Pt = 12, HG = 7, SS = 8
        • Diamond saves cannot be told apart from Pearl saves and HeartGold saves cannot be told apart from SoulSilver saves
      • B = 21, W = 20, B2 = 23, W2 = 22
      • X = 24, Y = 25, OR = 27, AS = 26
      • S = 30, M = 31, US = 32, UM = 33

Functions Available to Scripts

int gui_choice(char* lineOne, char* lineTwo);
void gui_warn(char* lineOne, char* lineTwo);
int gui_menu_6x5(char* question, int options, char** labels, struct pkx* pokemon, enum Generation generation);
int gui_menu_20x2(char* question, int options, char** labels);
void gui_keyboard(char* out, char* hint, int maxChars);
void gui_numpad(int* out, char* hint, int maxDigits);
int sav_sbo();
int sav_gbo();
void sav_box_decrypt();
void sav_box_encrypt();
void sav_inject_pkx(char* data, enum Generation type, int box, int slot);
void sav_inject_ekx(char* data, enum Generation type, int box, int slot);
char* current_directory();
struct directory* read_directory(char* dir);
char* cfg_default_ot();
unsigned int cfg_default_tid();
unsigned int cfg_default_sid();
int cfg_default_day();
int cfg_default_month();
int cfg_default_year();

Differences From C90

From this wiki page

How picoC differs from C90

picoC is a tiny C language, not a complete implementation of C90. It doesn't aim to implement every single feature of C90 but it does aim to be close enough that most programs will run without modification.

picoC also has scripting abilities which enhance it beyond what C90 offers.

C preprocessor

There is no true preprocessor in picoC. The most popular preprocessor features are implemented in a slightly limited way.

#define

define macros are implemented but have some limitations. They can only be used as part of expressions and operate a bit like functions. Since they're used in expressions they must result in a value.

#if / #ifdef / #else / #endif

The conditional compilation operators are implemented, but have some limitations. The operator "defined()" is not implemented. These operators can only be used at statement boundaries.

#include

include is supported however the level of support depends on the specific port of picoC on your platform. Linux/UNIX and cygwin support #include fully.

Function declarations

This style of function declaration is supported:

int my_function(char param1, int param2, char *param3) { ... }

The old "K&R" form of function declaration is not supported.

Predefined macros

A few macros are pre-defined:

  • picoC_VERSION - gives the picoC version as a string eg. "v2.1 beta r524"
  • LITTLE_ENDIAN - is 1 on little-endian architectures or 0 on big-endian architectures
  • BIG_ENDIAN - is 1 on big-endian architectures or 0 on little-endian architectures

Function pointers

Pointers to functions are currently not supported.

Storage classes

Many of the storage classes in C90 really only have meaning in a compiler so they're not implemented in picoC. This includes: static, extern, volatile, register and auto. They're recognised but currently ignored.

struct and unions

Structs and unions can only be defined globally. It's not possible to define them within the scope of a function.

Bitfields in structs are not supported.

Linking with libraries

Because picoC is an interpreter (and not a compiler) libraries must be linked with picoC itself. Also a glue module must be written to interface to picoC. This is the same as other interpreters like python.

If you're looking for an example check the interface to the C standard library time functions in cstdlib/time.c.

goto

The goto statement is implemented, but only supports forward gotos, not backward. The rationale for this is that backward gotos are not necessary for any "legitimate" use of goto.

Some discussion on this topic: * http://www.cprogramming.com/tutorial/goto.html * http://kerneltrap.org/node/553/2131

Clone this wiki locally