Skip to content

Redux basic API

Settings

All of the settings are exposed to Lua via the PCSX.settings table. It contains pseudo-tables that are reflections of the internal objects, and can be used to read and write the settings. The exact list of settings can vary quickly over time, so making a full list here would be fruitless. It is possible however to traverse the settings using pprint for example. The semantic of the settings is the same as from within the GUI, with the same caveats. For example, disabling the dynamic recompiler requires a reboot of the emulator.

ImGui interaction

PCSX-Redux will periodically try to call the Lua function DrawImguiFrame to allow the Lua code to draw some widgets on screen. The function will be called exactly once per actual UI frame draw, which, when the emulator is running, will correspond to the emulated GPU's vsync. If the function throws an exception however, it will be disabled until recompiled with new code.

Events Engine interaction & Execution Contexts

LuaJIT C callbacks aren't called from a safe execution context that can allow for coroutine resuming, and luv's execution context doesn't have any error handling.

It is possible to defer executing code to the main loop of PCSX-Redux, which can (a) resume coroutines and (b) execute code in a safe context. The function PCSX.nextTick(func) will execute the given function in the next main loop iteration. Here's some examples of how to use it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    local captures = {}
    captures.current = coroutine.running()
    captures.callback = function()
        PCSX.nextTick(function()
            captures.callback:free()
            coroutine.resume(captures.current)
        end)
    end
    captures.callback = ffi.cast('void (*)()', captures.callback)
    -- use the C callback somewhere...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function createClient(ip, port)
  client = luv.new_tcp()

  luv.tcp_connect(client, ip, port, function (err)
    PCSX.nextTick(function()
      assert(not err, err)

      luv.read_start(client, function (err, chunk)
        PCSX.nextTick(function()
          pprint("received at client", {err=err, chunk=chunk})
          assert(not err, err)
          if chunk then
            -- do something with the client
          else
            luv.close(client)
          end
        end)
      )

      pprint("writing from client")
      luv.write(client, "Hello")
      luv.write(client, "World")

    end
  end)
  return client
end

Of course, this can also delay processing significantly, as the main loop is usually bound to the speed of the UI, which can mean up to 20ms of delay.

Constants

The table PCSX.CONSTS contains numerical constants used throughout the rest of the API. Keeping an up to date list here is too exhausting, and it's simpler to print them using pprint(PCSX.CONSTS).

Pads

You can access the pads API through PCSX.SIO0.slots[s].pads[p] where s is the slot number and p is the pad number, both indexed from 1, Lua-style. So PCSX.SIO0.slots[1].pads[1] accesses the first pad, and PCSX.SIO0.slots[2].pads[1] accesses the second pad.

Each Pad table has the following functions:

1
2
3
4
5
getButton(button)     -- Returns true if the specified button is pressed.
setOverride(button)   -- Overrides the specified button.
clearOverride(button) -- Clears the override for the specified button.
setAnalogMode(bool)   -- Sets or clears the analog mode of this pad.
map()                 -- Forces the pad to be remapped. Useful after changing pad settings.

The button constants can be found in PCSX.CONSTS.PAD.BUTTON.

You can for instance press the button Down on the first pad using the following code:

1
PCSX.SIO0.slots[1].pads[1].setOverride(PCSX.CONSTS.PAD.BUTTON.DOWN)

Execution flow

The Lua code has the following API functions available to it in order to control the execution flow of the emulator:

  • PCSX.pauseEmulator()
  • PCSX.resumeEmulator()
  • PCSX.softResetEmulator()
  • PCSX.hardResetEmulator()

It's also possible to manipulate savestates using the following functions:

  • PCSX.createSaveState() -- returns a slice representing the savestate
  • PCSX.loadSaveState(slice)
  • PCSX.loadSaveState(file)

Additionally, the following function returns a string containing the .proto file used to serialize the savestate:

  • PCSX.getSaveStateProtoSchema()

Note that the actual savestates made from the UI are gzip-compressed, but the functions above don't compress or decompress the data, so if trying to reload a savestate made from the UI, it'll need to be decompressed first, possibly through the zReader File object.

Overall, this means the following is possible:

1
2
3
4
5
6
7
8
local compiler = require('protoc').new()
local pb = require('pb')

local state = PCSX.createSaveState()
compiler:load(PCSX.getSaveStateProtoSchema())

local decodedState = pb.decode('SaveState', Support.sliceToPBSlice(state))
print(string.format('%08x', decodedState.registers.pc))

Messages

The globals print and printError are available, and will display logs in the Lua Console. You can also use PCSX.log to display a line in the general Log window. All three functions should behave the way you'd expect from the normal print function in mainstream Lua.

GUI

You can move the cursor within the assembly window and the first memory view using the following functions:

  • PCSX.GUI.jumpToPC(pc)
  • PCSX.GUI.jumpToMemory(address[, width])

GPU

You can take a screenshot of the current view of the emulated display using the following:

  • PCSX.GPU.takeScreenShot()

This will return a struct that has the following fields:

1
2
3
4
5
struct ScreenShot {
    Slice data;
    uint16_t width, height;
    enum { BPP_16, BPP_24 } bpp;
};

The Slice will contain the raw bytes of the screenshot data. It's meant to be written out using the :writeMoveSlice() method on a File object. The width and height will be the width and height of the screenshot, in pixels. The bpp will be either BPP_16 or BPP_24, depending on the color depth of the screenshot. The size of the data Slice will be height * width multiplied by the number of bytes per pixel, depending on the bpp.

Loading and executing code

While the basic Lua functions dofile and loadfile exist, some alternative functions are available to load and execute code in a more flexible way.

  • Support.extra.addArchive(filename) will load the given zip file, and will make it available to the Support.extra.dofile function. It is equivalent to the -archive command line flag. Note that if a file named autoexec.lua is found in the zip file, it will be executed automatically.
  • Support.extra.dofile(filename) will load the given file, and execute it. It is equivalent to dofile, but will also search for the file next to the currently loaded Lua file which is calling this function, and will also search for the file in all of the loaded zip files, either through the command line, or through the Support.extra.addArchive function.
  • Support.extra.loadfile(filename) will load the given file, and return a function that can be called to execute the file. It is equivalent to loadfile, but has the same file search algorithm as Support.extra.dofile.
  • Support.extra.open(filename) will open the given file as read only, and return a File object. It is roughly equivalent to Support.File.open, but has the same file search algorithm as Support.extra.dofile.

If given the following directory structure:

1
2
3
4
.
└── bar.zip
    ├── test/baz.lua
    └── test2/qux.lua

If test/baz.lua contains the following code:

1
Support.extra.dofile('../test2/qux.lua')
Then running the following code:
1
2
Support.extra.addArchive('bar.zip')
Support.extra.dofile('test/baz.lua')
Will first load test/baz.lua from the zip file bar.zip, run it, which will in turn load test2/qux.lua from the zip file bar.zip again, and execute it.

This allows distributing complex "mods" as zip files, which can be loaded and executed from the command line or the console.

Miscellaneous

  • PCSX.quit([code]) schedules the emulator to quit. It's not instantaneous, and will only quit after the current block of Lua code has finished executing, which will be before the next main loop iteration. The code parameter is optional, and will be the exit code of the emulator. If not specified, it'll default to 0.

  • PCSX.getCPUCycles() returns an unsigned 64-bit number indicating how many CPU cycles have elapsed. This can be paired with the PCSX.CONSTS.CPU.CLOCKSPEED constant to determine how much emulated time has passed.

  • PCSX.Adpcm.NewEncoder will return an Adpcm encoder object. The object has the following methods:

  • :reset([mode]) will reset the encoder, and set the mode to the given mode. The mode can be 'Normal', 'XA', 'High', 'Low', 'FourBits'. The default mode is 'Normal', which enables all the filters available in the SPU. The 'XA' mode limits the encoder to the filters available in the XA ADPCM format. The 'High' mode uses the high-pass filter, and the 'Low' mode uses the low-pass filter. The 'FourBits' mode forces plain 4-bit Adpcm encoding.
  • :processBlock(inData, [outData], [channels]) will encode the given ffi input buffer, and write the result to the given ffi output buffer. The input buffer should be a buffer of 16-bit signed integers, and the output buffer should be a buffer of 16-bit signed integers. The channels parameter is optional, and will default to 2. The input buffer should contain exactly 28 samples, and so does the output buffer. If the output buffer is not given, the function will return a new buffer with the result. LuaBuffers are also accepted as input and output buffers. The function will return three values: the output buffer, the filter index used, and the shifting used. The function is intended to be used as an intermediate computation step, and the output still needs to be processed into 4 bits or 8 bits samples.
  • :processSPUBlock(inData, [outData], [blockAttribute]) will encode the given ffi input buffer, and write the result to the given ffi output buffer. The input buffer should be a buffer of 16-bit signed integers, and the output buffer should be a buffer which is at least 16 bytes large. The blockAttribute parameter is optional, and will default to 'OneShot'. The input buffer should contain exactly 28 samples. If the output buffer is not given, the function will return a new buffer with the result. LuaBuffers are also accepted as input and output buffers. The function will return the encoded block, suitable for SPU usage. The blockAttribute parameter can be one of the following strings: 'OneShot', 'OneShotEnd', 'LoopStart', 'LoopBody', 'LoopEnd'.
  • :finishSPU([outData]) will write the opinionated end of sample looping block, as prescribed by the original Sony API. The output buffer should be a buffer which is at least 16 bytes large. If the output buffer is not given, the function will return a new buffer with the result. LuaBuffers are also accepted as output buffers. The function will return the encoded block, suitable for SPU usage.
  • :processXABlock(inData, [outData], [xaMode], [channels]) will encode the given ffi input buffer, and write the result to the given ffi output buffer. The input buffer should be a buffer of 16-bit signed integers, and the output buffer should be a buffer which is at least 128 bytes large. Note that a MODE2 FORM2 XA sector requires subheaders and 18 of these blocks. The xaMode parameter is optional, and will default to 'XAFourBits'. The other valid value is 'XAEightBits'. It will defines the encoding output between either 4-bit and 8-bit. The channels parameter is optional, and will default to 1. If the output buffer is not given, the function will return a new buffer with the result. LuaBuffers are also accepted as input and output buffers. The function will return the encoded block, suitable for XA usage. The amount of required input samples varies depending of the number of channels and the encoding mode:
    • 4-bit mono: 224 samples aka 448 bytes
    • 4-bit stereo: 112 samples aka 448 bytes
    • 8-bit mono: 112 samples aka 224 bytes
    • 8-bit stereo: 56 samples aka 224 bytes

Using the encoder to process an input audio file is as simple as:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function encodeAudioLoop(inputFile, outputFile)
  local closeInput = false
  if type(inputFile) == 'string' then
    inputFile = Support.File.open(inputFile)
    closeInput = true
  end
  local audio = Support.File.ffmpegAudioFile(inputFile, {
    channels = 'Mono',
    frequency = 22050
  })

  local closeOutput = false
  if type(outputFile) == 'string' then
    outputFile = Support.File.open(outputFile, 'TRUNCATE')
    closeOutput = true
  end
  local blockCount = math.floor(audio:size() / (2 * 28))
  local bufferIn = ffi.new('int16_t[28]')
  local bufferOut = Support.NewLuaBuffer(16)

  local encoder = PCSX.Adpcm.NewEncoder()
  encoder:reset 'Normal'

  for i = 1, blockCount do
    audio:read(bufferIn, 28 * 2)
    local blockType = 'LoopBody'
    if i == 1 then blockType = 'LoopStart' end
    if i == blockCount then blockType = 'LoopEnd' end
    encoder:processSPUBlock(bufferIn, bufferOut, blockType)
    outputFile:write(bufferOut)
  end

  if closeInput then
    inputFile:close()
  end
  if closeOutput then
    outputFile:close()
  end
  audio:close()
end