mirror of
https://github.com/grumpycoders/pcsx-redux.git
synced 2025-04-02 10:41:54 -04:00
1 line
No EOL
134 KiB
JSON
1 line
No EOL
134 KiB
JSON
{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"<p>Welcome to the PCSX-Redux emulator documentation.</p> <p>You can get the emulator for various platforms here: https://github.com/grumpycoders/pcsx-redux#where</p> <p>To discuss this emulator specifically, please join our Discord server:</p> <p></p> <p>To discuss PlayStation 1 development, hacking, and reverse engineering in general, please join the PSX.Dev Discord server:</p> <p></p> <p>Compiling PCSX-Redux Menus Command line arguments Debugging with PCSX-Redux Internal MIPS api Web Server Lua API OpenBios </p>"},{"location":"cli_flags/","title":"Command Line Flags","text":"<p>You can launch <code>pcsx-redux</code> with the following command line parameters:</p> <p>The parsing code doesn't care about the number of dashes in the parameter's flag, so '-' can be used as well as '--', or any number of dashes.</p> Flag Meaning <code>-dumpproto</code> Dump the protobuf schemas for PCSX-Redux on stdout and exit immediately. <code>-run</code> Begin execution immediately on startup. <code>-stdout</code> Redirect log output to stdout. <code>-lua_stdout</code> Redirect Lua's console output to stdout. <code>-logfile</code> Specify a file to log output to. <code>-bios</code> Specify a BIOS file. <code>-testmode</code> Interpret internal API's <code>pcsx_exit()</code> command as a request to exit the emulator instead of pausing, and close the emulator. Implies <code>-safe</code>, <code>-no-gui-log</code>, and will also disable first chance exceptions. Use only when doing unit testing. <code>-exe</code> Load a PSX exe. <code>-loadexe</code> Load a PSX exe. <code>-iso</code> Load a PSX disk image (iso, bin/cue). <code>-loadiso</code> Load a PSX disk image (iso, bin/cue). <code>-memcard1</code> Specify a memory card file to use as memory card slot 1. <code>-memcard2</code> Specify a memory card file to use as memory card slot 2. <code>-pcdrv</code> Enable the pcdrv device interface. (Access PC filesystem through SIO). <code>-pcdrvbase</code> Specify base directory for pcdrv. <code>-safe</code> Resets configuration to defaults. <code>-resetui</code> Resets the UI to its defaults. <code>-kiosk</code> Enables kiosk mode, disabling UI interaction. Will change the saved setting. <code>-no-kiosk</code> Disables kiosk mode, allowing the user to interact with the UI. Will change the saved setting. <code>-interpreter</code> Use the interpreter CPU core. <code>-dynarec</code> Use the dynamic recompiler CPU core. <code>-debugger</code> Activates the debugger. Will change the saved setting. <code>-no-debugger</code> Deactivates the debugger. Will change the saved setting. <code>-fastboot</code> Skips the BIOS logo and boot animation. Will change the saved setting. <code>-no-fastboot</code> Shows the BIOS logo and boot animation. Will change the saved setting. <code>-gdb</code> Activates the gdb server. Will change the saved setting. <code>-no-gdb</code> Deactivates the gdb server. Will change the saved setting. <code>-gdb-port</code> Sets the TCP port the gdb server is listening on. Will change the saved setting. <code>-trace</code> Activates the CPU trace logging. Will change the saved setting. <code>-no-trace</code> Deactivates the CPU trace logging. Will change the saved setting. <code>-no-gui-log</code> Fully disables logs to be sent to the GUI. <code>-archive</code> Specifies a .zip file to load for the <code>Support.extra.dofile</code> function. <code>-dofile</code> Specifies a Lua file to load through the <code>Support.extra.dofile</code> function. <code>-exec</code> Specifies a Lua string to execute. <code>-luacov</code> Enables Lua code coverage report. Requires the <code>luacov</code> Lua module to be installed. <code>-portable</code> Enables portable mode. Settings and saves will be stored in the same directory as the executable, or in the directory specified by the optional argument to this flag."},{"location":"compiling/","title":"Compiling PCSX-Redux","text":""},{"location":"compiling/#getting-the-sources","title":"Getting the sources","text":"<p>The only location for the source is on github. Clone recursively, as the project uses submodules: </p> <p><code>git clone https://github.com/grumpycoders/pcsx-redux.git --recursive</code>.</p>"},{"location":"compiling/#windows","title":"Windows","text":"<p>Install Visual Studio 2019 Community Edition. Open the file <code>vsprojects\\pcsx-redux.sln</code>, select <code>pcsx-redux -> pcsx-redux</code>, right click, <code>Set as Startup Project</code>, and hit <code>F7</code> to build. The project follows the open-and-build paradigm with no extra step, so no specific dependency ought to be needed, as NuGet will take care of downloading them automatically for you on the first build.</p> <p>Note: If you get an error saying <code>hresult e_fail has been returned from a call to a com component</code>, you might need to delete the .suo file in vsproject/vs, restart Visual Studio and retry.</p>"},{"location":"compiling/#openbios","title":"Openbios","text":"<p>Using Visual Studio Code, one can use the task \"make_openbios\" to compile: CTRL-P then <code>task make_openbios</code> to compile.</p>"},{"location":"compiling/#linux","title":"Linux","text":""},{"location":"compiling/#compiling-with-docker","title":"Compiling with Docker","text":"<p>Run <code>./dockermake.sh</code>. You need docker for this to work. <pre><code># Debian derivative; Ubuntu, Mint...\nsudo apt install docker\n# Arch derivative; Manjaro...\nsudo pacman -S docker\n</code></pre></p> <p>You will also need a few libraries on your system for this to work. Check the Dockerfile for a list of library packages to install.</p>"},{"location":"compiling/#compiling-with-make","title":"Compiling with make","text":"<ul> <li>Debian derivatives ( for full emulator compilation ):</li> </ul> <pre><code>sudo apt-get install -y build-essential git make pkg-config clang g++ g++-mipsel-linux-gnu cpp-mipsel-linux-gnu binutils-mipsel-linux-gnu libfreetype-dev libavcodec-dev libavformat-dev libavutil-dev libcurl4-openssl-dev libglfw3-dev libswresample-dev libuv1-dev zlib1g-dev\n</code></pre> <ul> <li>Arch derivatives :</li> </ul> <pre><code>sudo pacman -S clang git make pkg-config ffmpeg libuv zlib glfw-x11 curl xorg-server-xvfb\n</code></pre> <p>You can then just enter the 'pcsx-redux' directory and compile without using docker with <code>make</code>.</p> <p>If you have a different mips compiler, you'll need to override some variables, such as <code>PREFIX=mipsel-none-elf FORMAT=elf32-littlemips</code>. </p>"},{"location":"compiling/#openbios_1","title":"Openbios","text":"<p>Building OpenBIOS on Linux can be done with docker : <code>./dockermake.sh openbios</code>, or using <code>make</code>, with the <code>g++-mipsel-linux-gnu</code> package installed ; <code>make openbios</code>. </p>"},{"location":"compiling/#macos","title":"MacOS","text":"<p>You need MacOS Catalina with the latest XCode to build, as well as a few homebrew packages. Run the brew installation script to get all the necessary dependencies.</p> <p>Run <code>make</code> to build. </p> <p>Compiling OpenBIOS will require a mips compiler, that you can generate using the following commands: </p>"},{"location":"compiling/#openbios_2","title":"Openbios","text":"<pre><code>brew install ./tools/macos-mips/mipsel-none-elf-binutils.rb\nbrew install ./tools/macos-mips/mipsel-none-elf-gcc.rb\n</code></pre> <p>Then, you can compile OpenBIOS using <code>make -C ./src/mips/openbios</code>.</p>"},{"location":"compiling/#compiling-psx-code","title":"Compiling PSX code","text":"<p>If you're only interested in compiling psx code, you can clone the PCSX-Redux repo; <pre><code>git clone https://github.com/grumpycoders/pcsx-redux.git --recursive\n</code></pre> then install a mips toolchain and get the converted PsyQ libraries in the <code>pcsx-redux/src/mips/psyq/</code> folder as per these instructions.</p> <p>You can also find the pre-compiled converted Psyq libraries online.</p>"},{"location":"compiling/#getting-the-toolchain-on-windows","title":"Getting the toolchain on Windows","text":"<p>Download the MIPS toolchain here : https://static.grumpycoder.net/pixel/mips/g++-mipsel-none-elf-10.3.0.zip and add the <code>bin</code> folder to your $PATH. You can test it's working by launching a command prompt and typing <code>mipsel-none-elf-gcc.exe --version</code>. If you get a message like <code>mipsel-none-gnu-gcc (GCC) 10.3.0</code>, then it's working !</p>"},{"location":"compiling/#getting-the-toolchain-on-gnulinux","title":"Getting the toolchain on GNU/Linux","text":""},{"location":"compiling/#debian-derivative-ubuntu-mint","title":"Debian derivative; Ubuntu, Mint...","text":"<pre><code>sudo apt install g++-mipsel-linux-gnu cpp-mipsel-linux-gnu binutils-mipsel-linux-gnu\n</code></pre>"},{"location":"compiling/#arch-derivative-manjaro","title":"Arch derivative; Manjaro...","text":"<p>The mipsel environment can be installed from AUR : cross-mipsel-linux-gnu-binutils and cross-mipsel-linux-gnu-gcc using your AURhelper of choice:</p> <pre><code>trizen -S cross-mipsel-linux-gnu-binutils cross-mipsel-linux-gnu-gcc\n</code></pre>"},{"location":"menus/","title":"PCSX-Redux menus","text":"<p>The menu bar holds some informations :</p> <p></p> <ul> <li>CPU mode</li> <li>Game ID</li> <li>ImGui FPS counter (not psx internal fps)</li> </ul>"},{"location":"menus/#file","title":"File","text":"<ul> <li>Open ISO</li> <li>Close ISO</li> <li>Load Binary</li> <li>Dump save state proto schema</li> <li>Save state slots</li> <li>Load state slots</li> <li>Save global state</li> <li>Load global state</li> <li>Open Lid : Simulate open lid</li> <li>Close Lid : Simulate closed lid</li> <li>Open and Close Lid : Simulate opening then closing the lid</li> <li>MC1 inserted: Insert or remove Memory Card 1</li> <li>MC2 inserted: Insert or remove Memory Card 2</li> <li>Reboot : Restart emulator</li> <li>Quit</li> </ul>"},{"location":"menus/#emulation","title":"Emulation","text":"<ul> <li>Start (F5): Start execution</li> <li>Pause (F6): Pause execution</li> <li>Soft reset (F8): Calls Redux's CPU reset function, which jumps to the BIOS entrypoint (0xBFC00000), resets some COP0 registers and the general purpose registers, and resets some IO. Does not clear vram.</li> <li>Hard reset (Shift-F8): Similar to a reboot of the PSX.</li> </ul>"},{"location":"menus/#configuration","title":"Configuration","text":"<ul> <li>Emulation : Emulation settings</li> <li>GPU : Graphics Processing Unit settings</li> <li>SPU : Sound Processing Unit settings</li> <li>UI : Change user interface settings (such as font size, language or UI theme)</li> <li>Controls : Edit KB/Pad controls</li> <li>Shader presets : Apply a shader preset</li> <li>Configure shaders : Show shader editor</li> </ul>"},{"location":"menus/#debug","title":"Debug","text":""},{"location":"menus/#help","title":"Help","text":"<ul> <li>Show ImGui demo</li> <li>About</li> </ul>"},{"location":"menus/#gpu-information","title":"GPU information","text":"<p>The 'About' dialog available in the 'Help' menu has an 'OpenGL information' tab that displays information on the GPU currently used by the program, such as the supported OpenGL extensions.</p> <p></p>"},{"location":"mips_api/","title":"Mips API","text":""},{"location":"mips_api/#description","title":"Description","text":"<p>PCSX-Redux has a special API that mips binaries can use : </p> <p><pre><code>static __inline__ void pcsx_putc(int c) { *((volatile char* const)0x1f802080) = c; }\nstatic __inline__ void pcsx_debugbreak() { *((volatile char* const)0x1f802081) = 0; }\nstatic __inline__ void pcsx_execSlot(uint8_t slot) { *((volatile uint8_t* const)0x1f802081) = slot; }\nstatic __inline__ void pcsx_exit(int code) { *((volatile int16_t* const)0x1f802082) = code; }\nstatic __inline__ void pcsx_message(const char* msg) { *((volatile char** const)0x1f802084) = msg; }\nstatic __inline__ void pcsx_checkKernel(int enable) { *((volatile char*)0x1f802088) = enable; }\nstatic __inline__ int pcsx_isCheckingKernel() { return *((volatile char* const)0x1f802088) != 0; }\n\nstatic __inline__ int pcsx_present() { return *((volatile uint32_t* const)0x1f802080) == 0x58534350; }\n</code></pre> Source : https://github.com/grumpycoders/pcsx-redux/blob/main/src/mips/common/hardware/pcsxhw.h#L31-L36</p> <p>The API needs DEV8/EXP2 (1f802000 to 1f80207f), which holds the hardware register for the bios POST status, to be expanded to 1f8020ff. Thus the need to use a custom <code>crt0.s</code> if you plan on running your code on real hardware. The default file provided with the Nugget+PsyQ development environment does that: </p> <p><pre><code>_start:\nlw $t2, SBUS_DEV8_CTRL\nlui $t0, 8\nlui $t1, 1\n_check_dev8:\nbge $t2, $t0, _store_dev8\nnop\nb _check_dev8\nadd $t2, $t1\n_store_dev8:\nsw $t2, SBUS_DEV8_CTRL\n</code></pre> Source : https://github.com/grumpycoders/pcsx-redux/blob/main/src/mips/common/crt0/crt0.s#L36-L46</p>"},{"location":"mips_api/#functions","title":"Functions","text":"<p>The following functions are available :</p> Function Usage <code>pcsx_putc(int c)</code> Print ASCII character with code <code>c</code> to console/stdout. <code>pcsx_debugbreak()</code> Break execution (Pause emulation). <code>pcsx_execSlot(uint8_t slot)</code> Executes Lua function at <code>PCSX.execSlots[slot]</code>. The <code>slot</code> value can be between 1 and 255. If no Lua function exists within a slot, then this behaves the same as <code>pcsx_debugbreak()</code>. <code>pcsx_exit(int code)</code> Exit emulator and forward <code>code</code> as exit code. <code>pcsx_message(const char* msg)</code> Create a UI dialog displaying <code>msg</code> <code>pcsx_checkKernel(int enable)</code> Enable or disable kernel checking. <code>pcsx_isCheckingKernel()</code> Returns truthy if kernel checking is enabled. <code>pcsx_present()</code> Returns 1 if code is running in PCSX-Redux <code>pcsx_initMsan()</code> Initialize memory sanitizer system. <code>pcsx_resetMsan()</code> Reset memory sanitizer system. <code>pcsx_msanAlloc(uint32_t size)</code> Allocate memory with memory sanitizer. <code>pcsx_msanFree(void* ptr)</code> Free memory with memory sanitizer. <code>pcsx_msanRealloc(void* ptr, uint32_t size)</code> Reallocate memory with memory sanitizer. <p>Example of a UI dialog created with <code>pcsx_message()</code> : </p> <p></p>"},{"location":"mips_api/#kernel-checker","title":"Kernel Checker","text":"<p>The kernel checking feature is used to try and catch unwanted accesses to the kernel, which are usually a sign of a bug in the code, such as a buffer overflow or a null pointer dereference. If the kernel checking feature is enabled, the emulator will break execution and display a message in the console if it detects an unwanted access to the kernel. The following actions are considered unwanted accesses to the kernel:</p> <ul> <li>Reading or writing to a kernel address from a user-mode address and while not in a kernel-mode context such as while in the ISR. The ISR sets up a stack frame within the kernel space, so callbacks from the kernel and into the user space will be using kernel space as the stack. This means that a null pointer dereference in a callback from the kernel during an interrupt or exception will not be caught by the kernel checking feature.</li> <li>An indirect jump to a kernel address from a user-mode address and that isn't 0xa0, 0xb0, or 0xc0, and that isn't a <code>jr $ra</code> instruction. Direct jumps and branches to kernel addresses should be compiler-level problems, so they are not checked for. The <code>jr $ra</code> exception to the rule is because callbacks from the kernel will use <code>jr $ra</code> to return to the kernel. Optimizations which bypass the <code>jr $ra</code> instruction by using a different register to return to the kernel during a callback will cause false positives.</li> </ul> <p>The feature is disabled by default as many games and software will access the kernel in various ways, and it can be enabled by calling <code>pcsx_checkKernel(1)</code>. The feature can be disabled by calling <code>pcsx_checkKernel(0)</code>. Since many startup sequences will access the kernel to patch it or clean it, it is recommended to enable the feature after the startup sequence has completed. Some libraries may also access the kernel during their normal operations. The user can simply disable the checker temporarily by toggling it before and after calling such APIs. The kernel space is considered to be all the memory addresses between 0x80000000 and 0x8000ffff. The BIOS is considered to be part of the kernel space in terms of code, so any access to the RAM Kernel space from the BIOS memory space will not trigger any of the kernel checks. The kernel checking feature is only available in the interpreter with the debugger enabled, and it is not available in the dynarec. Trying to enable the feature while using the dynarec, or while the debugger is disabled, will not have any effect.</p>"},{"location":"mips_api/#memory-sanitizer","title":"Memory Sanitizer","text":"<p>The memory sanitizer system of PCSX is inspired of various similar tools. It can detect use-after-frees, buffer overflows, and reads from uninitialized memory. Enabling the memory sanitizer is done through the <code>pcsx_initMsan()</code> function call. The emulator will immediately allocate an extra 2GB of memory to store the memory sanitizer data and metadata. Once enabled, the user can call <code>pcsx_msanAlloc()</code>, <code>pcsx_msanFree()</code>, and <code>pcsx_msanRealloc()</code> to allocate, free, and reallocate memory, working as expected from a normal C library. The memory sanitizer will keep track of the memory allocated and will check for the following issues:</p> <ul> <li>Use-after-frees: If the user tries to access memory that has been freed, the memory sanitizer will break execution and display a message in the console.</li> <li>Double frees: If the user tries to free memory that has already been freed, the memory sanitizer will break execution and display a message in the console.</li> <li>Corrupted pointer: If the user tries to free or reallocate a pointer that is not a valid pointer, the memory sanitizer will break execution and display a message in the console.</li> <li>Buffer overflows: If the user writes to memory before or after the allocated size, up to 1kB, the memory sanitizer will break execution and display a message in the console.</li> <li>Reads from uninitialized memory: If the user tries to read from memory that has not been written to first, the memory sanitizer will break execution and display a message in the console.</li> </ul> <p>Internally, the memory sanitizer will allocate memory to the range 0x20000000-0x80000000, which is 1.5GB large. Note that for the use-after-free detection to work, the memory sanitizer will never actually free anything, and so it is possible to run out of memory if the user allocates too much memory. Calling <code>pcsx_resetMsan()</code> will re-initialize the memory sanitizer back to its original state. The memory sanitizer is available at all times, and is not affected by the debugger setting nor the dynarec.</p>"},{"location":"openbios/","title":"Openbios","text":"<p>Openbios is, as its name implies, an open-source alternative to a retail PSX bios that can be non-trivial to dump.</p>"},{"location":"openbios/#purposes-of-openbios","title":"Purposes of Openbios","text":"<ul> <li>Educational</li> <li>Ease of distribution</li> <li>Automated testing</li> </ul> <p>See this page for more details.</p>"},{"location":"openbios/#building","title":"Building","text":"<p>It is compiled together with <code>pcsx-redux</code> or can be compiled on its own. </p> <p>See the corresponding sections in Compiling for instructions. </p> <p>The result of the compilation should be a file called <code>openbios.elf</code> that contains all useful debugging symbols, and a file called <code>openbios.bin</code> which can be used in emulators or even burned to a chip and placed on a retail console. </p>"},{"location":"openbios/#status","title":"Status","text":"<p>This subproject is still under construction, but is fairly functional and usable. OpenBIOS does almost all the same things as the retail BIOS does when booting, and implements most of its features. Many games are booting and working properly with this code. It can be used in emulators or on the real console, either while replacing the rom chip, or by using the \"cart\" build and programming the flash chip of a cheat cart with the result.</p>"},{"location":"openbios/#organization","title":"Organization","text":"<p>The BIOS is split in two major parts: the low level code for the bios itself, and the shell, which is the binary that's being loaded into memory at boot time by the bios, to display the SONY sound and logo, and has a small utility menu for playing audio discs, or shuffling around memory cards.</p> <p>While the first part is the main one that's being targeted here, the second one isn't currently present. This may change in the future, but this isn't currently the focus of this project.</p> <p>The original code was most likely chunked into several sub-projects, that were all linked together like a giant patchwork. This approach is less readable, and for this reason, we're not going to do this. However this will result in the ROM/RAM split to be less obvious, and slower at times than the original. Tuning of the hot functions is eventually required.</p>"},{"location":"openbios/#technicalities","title":"Technicalities","text":"<p>The code has been rewritten based off the reverse engineering of a dump of the BIOS of an american SCPH-7001 machine. MD5sum: 1e68c231d0896b7eadcad1d7d8e76129</p> <p>The ghidra database for it is currently being hosted on a server, alongside a few other pieces of software being reversed. Contact one of the authors if you want access.</p>"},{"location":"openbios/#commentary","title":"Commentary","text":"<p>The retail PlayStation BIOS code is a constellation of bugs and bad design. The fact that the retail console boots at all is nothing short of a miracle. Half of the provided libc in the A0 table is buggy. The BIOS code is barely able to initialize the CD-Rom, and read the game's binary off of it to boot it; anything beyond that will be crippled with bugs. And this only is viable if you respect a very strict method to create your CD-Rom. The memory card and gamepad code is a steaming-hot heap of human excrement. The provided GPU stubs are inefficient at best. </p> <p>The only sane thing that any software running on the PlayStation ought to do is to immediately disable interrupts, grab the function pointer located at 0x00000310 for <code>FlushCache</code>, in order put it inside a wrapper that disables interrupts before calling it, and then trash the whole memory to install its own code. The only reason <code>FlushCache</code> is required from the retail code is because since the function will unplug the main memory bus off the CPU in order to work, it HAS to run from the 0xbfc memory map, which will still be connected. Anything else from the retail code is virtually useless, and shouldn't be relied upon. </p>"},{"location":"openbios/#legality","title":"Legality","text":"<p>Disclaimer: the author is not a lawyer, and the following statement hasn't been reviewed by a professional of the law, so the rest of this document cannot be taken as legal advice. </p> <p>As explained above, this code has been written using disassembly and reverse engineering of a retail bios the author dumped from a second hand console. The same exact methodology was employed by Connectix for their PS1 bios. The conclusion of their lawsuit, and that of Sega v. Accolade seems to indicate that this project here follows and is impacted by the same doctrine.</p>"},{"location":"web_server/","title":"Web server","text":"<p>A web server can be activated. This allows the use of a REST api to access various features. The server only handles up to HTTP/1.1, without SSL support.</p>"},{"location":"web_server/#activation","title":"Activation","text":"<p>You can activate the web server by going to <code>Configuration > Emulation > Enable Web Server</code></p>"},{"location":"web_server/#rest-api","title":"REST API","text":"<p>By default, the server listens for incoming connection on <code>localhost:8080</code>. The port can be changed in the same settings above.</p> <p>These GET methods are available:</p> URL Function /api/v1/gpu/vram/raw Dump VRAM /api/v1/cpu/ram/raw Dump RAM /api/v1/execution-flow Emulation Status /api/v1/cd/files?filename= Dump a file from the loaded disc image <p>The following POST methods are available:</p> <p><code>/api/v1/gpu/vram/raw?x=<value>&y=<value>&width=<value>&height=<value></code></p> <p>The above needs to also send a form with binary contents. This will partially update the VRAM with the corresponding pixels. The updated rectangle has to be within the 1024x512 16bpp VRAM. The pixels need to be in 16bpp format, meaning the server is expecting exactly <code>width * height * 2</code> bytes in the form data. The server will properly parse requests with <code>Content-Type: multipart/form-data</code>, but raw bytes in the request body without this header is also acceptable. Any invalid query will result in a 400 error.</p> <p><code>/api/v1/cpu/ram/raw?offset=<value>&size=<value></code></p> <p>The above needs to also send a form with binary contents, which will update the RAM at the specified offset. Offset is expected to be a number from [0, 0x1FFFFF] in case of running redux with 2MB RAM, or [0, 0x7FFFFF] in case the 8MB memory expansion is enabled. The value of size + offset must not exceed the total space in the RAM.</p> <p><code>/api/v1/assembly/symbols?function=<value></code></p> Value Function reset Resets the symbols loaded in redux upload Uploads a <code>.map</code> file to redux <p>The above expects a <code>.map</code> file with symbols and addresses, which will be merged with the current symbols already loaded in redux. The map file should contain a pair of <code>symbol address</code> for each line. e.g <code>Foo 80010000</code> would load the symbol <code>Foo</code> in the address <code>0x80010000</code>.</p> <p><code>/api/v1/cpu/cache?function=<value></code></p> Value Function flush Flushes the CPU cache <p><code>/api/v1/execution-flow?function=<value>&type=<value></code></p> Value Type Function pause - Pauses the emulator. start - Starts/Resumes the emulator. resume - Starts/Resumes the emulator. reset hard Hard resets the emulator. Equivalent to a power cycle of the console. reset soft Soft resets the emulator. Equivalent to pressing the reset button. <p><code>/api/v1/cd/patch?filename=<value></code></p> <p>The above needs to also send a form with binary contents, which will patch the currently loaded iso file with the contents of the form. The server will look for the given filename in the iso file, and patch its contents. All changes are cumulative. If the file is not found, a 404 error will be returned. The file name is case sensitive, and must be a valid ISO9660 filename, which means it can only contain uppercase letters, numbers, and underscores, and ends with <code>;1</code>.</p> <p>For example:</p> <pre><code>$ curl -F file=@newsystem.cnf http://localhost:8080/api/v1/cd/patch?filename=SYSTEM.CNF;1\n</code></pre> <p><code>/api/v1/cd/patch?sector=<value>&mode=<value></code></p> <p>The above needs to also send a form with binary contents, which will patch the currently loaded iso file with the contents of the form. The iso sectors starting at the given value will be written to. The <code>mode</code> argument is optional, and can be of the following values:</p> Value Function GUESS Tries to guess the sector's mode. This is the default. RAW Writes the full sectors with no decoration, 2352 bytes per sector. M2_RAW Writes 2336 bytes per sector, with the first 16 bytes being the subheader. M2_FORM1 Writes 2048 bytes per sector. Will not update the subheader. M2_FORM2 Writes 2324 bytes per sector. Will not update the subheader. <p>All changes are cumulative.</p> <p><code>api/v1/cd/ppf?function=<value></code></p> Value Function save Saves the current state of the disc image patches to a PPF file. clear Clears the current list of patches."},{"location":"Debugging/gdb-server/","title":"GDB server","text":"<p>The GDB server allows you to set breakpoints and control your PSX program's execution from your gdb compatible IDE.</p>"},{"location":"Debugging/gdb-server/#enabling-the-gdb-server","title":"Enabling the GDB server","text":"<p>In PCSX-Redux: <code>Configuration > Emulation > Enable GDB server</code>. </p> <p>Make sure the debugger is also enabled. </p> <p> </p>"},{"location":"Debugging/gdb-server/#gdb-setup","title":"GDB setup","text":"<p>You need <code>gdb-multiarch</code> on your system :</p>"},{"location":"Debugging/gdb-server/#windows","title":"Windows","text":"<p>Download a pre-compiled version from here : https://static.grumpycoder.net/pixel/gdb-multiarch-windows/</p>"},{"location":"Debugging/gdb-server/#gnulinux","title":"GNU/Linux","text":""},{"location":"Debugging/gdb-server/#debian-based","title":"Debian based","text":"<p>Install via your package manager :</p> <pre><code># Debian derivative; Ubuntu, Mint...\nsudo apt install gdb-multiarch\n</code></pre>"},{"location":"Debugging/gdb-server/#arch-based","title":"Arch based","text":"<p>On Arch based distributions, multiarch is now enabled by default in regular builds and you don't need to install a specific version anymore. You can install the 'gdb' package with <code>pacman</code> : <pre><code>sudo pacman -S gdb\n</code></pre></p>"},{"location":"Debugging/gdb-server/#ide-setup","title":"IDE setup","text":""},{"location":"Debugging/gdb-server/#ms-vscode","title":"MS VScode","text":"<ul> <li>Install the <code>Native debug</code> extension : https://marketplace.visualstudio.com/items?itemName=webfreak.debug</li> </ul> <ul> <li>Adapt your <code>launch.json</code> file to your environment : A sample <code>lanuch.json</code> file is available here. This should go in <code>your-project/.vscode/</code>. </li> </ul> <p>You need to adapt the values of <code>\"executable\"</code>, <code>\"gdbpath\"</code> and <code>\"autorun\"</code> according to your system :</p>"},{"location":"Debugging/gdb-server/#executable","title":"executable","text":"<p>This is the path to your <code>.elf</code> executable : <pre><code> \"executable\": \"HelloWorld.elf\",\n</code></pre> https://github.com/grumpycoders/pcsx-redux/blob/a3bebd490388130e924124cdfeff3bc46b6149d9/.vscode/launch.json#L153 </p>"},{"location":"Debugging/gdb-server/#gdbpath","title":"gdbpath","text":"<p>This the path to the <code>gdb-multiarch</code> executable: <pre><code> \"gdbpath\": \"/usr/bin/gdb-multiarch\",\n</code></pre> https://github.com/grumpycoders/pcsx-redux/blob/a3bebd490388130e924124cdfeff3bc46b6149d9/.vscode/launch.json#L154-L157</p>"},{"location":"Debugging/gdb-server/#autorun","title":"autorun","text":"<pre><code> \"autorun\": [\n\"monitor reset shellhalt\",\n[...]\n\"load your-file.elf\",\n</code></pre> <p>Make sure that <code>\"load your-file.elf\"</code> corresponds to the <code>\"target\"</code> value. </p> <p>https://github.com/grumpycoders/pcsx-redux/blob/a3bebd490388130e924124cdfeff3bc46b6149d9/.vscode/launch.json#L159-L165</p> <p>By default, using <code>localhost</code> should work, but if encountering trouble, try using your computer's local IP (e.g; 192.168.x.x, 10.0.x.x, etc.)</p> <p>https://github.com/grumpycoders/pcsx-redux/blob/a3bebd490388130e924124cdfeff3bc46b6149d9/.vscode/launch.json#L150</p> <p></p>"},{"location":"Debugging/gdb-server/#geany","title":"Geany","text":"<p>Make sure you installed the official plugins and enable the <code>Scope debugger</code>.</p> <p>To enable the plugin, open Geany, go to <code>Tools > Plugin manager</code> and enable <code>Scope Debugger</code>.</p> <p>You can find the debugging facilities in the <code>Debug</code> menu ;</p> <p></p> <p>You can find the plugin's documentation here : https://plugins.geany.org/scope.html</p>"},{"location":"Debugging/gdb-server/#gdbinit","title":".gdbinit","text":"<p>Create a <code>.gdbinit</code> file at the root of your project with the following content, adapting the path to your <code>elf</code> file and the gdb server's ip.</p> <pre><code>target remote localhost:3333\nsymbol-file load /path/to/your/executable.elf\nmonitor reset shellhalt\nload /path/to/your/executable.elf\n</code></pre>"},{"location":"Debugging/gdb-server/#plugin-configuration","title":"Plugin configuration","text":"<p>In Geany : <code>Debug > Setup Program</code> : </p> <p></p>"},{"location":"Debugging/gdb-server/#clion","title":"CLion","text":"<p>Open the Run/Debug Configurations menu, which you can find here:</p> <p></p> <p>Then, add a new Remote Debug configuration:</p> <p></p> <p>Finally, set your new configuration up:</p> <p></p>"},{"location":"Debugging/gdb-server/#gdbinit_1","title":".gdbinit","text":"<p>Create a <code>.gdbinit</code> file at the root of your project with the following content, adapting the path to your <code>elf</code> file.</p> <pre><code>define target remote\ntarget extended-remote $arg0\nsymbol-file /path/to/your/executable.elf\nmonitor reset shellhalt\nload /path/to/your/executable.elf\nend\n</code></pre>"},{"location":"Debugging/gdb-server/#beginning-debugging","title":"Beginning Debugging","text":"<p>Launch <code>pcsx-redux</code>, then run the debugger from your IDE. It should load the <code>elf</code> file, and execute until the next breakpoint.</p>"},{"location":"Debugging/gdb-server/#starting-debugging-in-geany","title":"Starting debugging in Geany","text":"Your browser does not support the video tag. <p>Source : https://archive.org/details/pcsx_redux_geany_gdb</p>"},{"location":"Debugging/gdb-server/#additional-tools","title":"Additional tools","text":"<p>https://github.com/cyrus-and/gdb-dashboard/</p>"},{"location":"Debugging/ghidra/","title":"Connecting Ghidra to PCSX-Redux","text":"<p>Since version 10.3, Ghidra now supports debugging MIPS targets. This allows for a much more powerful reverse engineering experience than what was previously possible with the GDB server. This document will explain how to set up Ghidra to debug PCSX-Redux, as it is not entirely straightforward.</p>"},{"location":"Debugging/ghidra/#prerequisites","title":"Prerequisites","text":"<ul> <li>A gdb \"multiarch\" binary is required. For Windows, you can get it from here. For Linux, you can get it from your distribution's package manager; on Ubuntu and Debian, this is the package <code>gdb-multiarch</code>. And for MacOS, you can use the brew package manager to install it; this is the package <code>gdb</code>.</li> <li>Ghidra 10.3 or newer. You can get it from here.</li> <li>PCSX-Redux either configured to disable Dynarec, enable the debugger, and enable the gdb server, or started using the following command-line arguments: <code>-interpreter -debugger -gdb</code>.</li> <li>The following file downloaded somewhere on your computer, naming it <code>ghidra_debugger_scripts</code>. </li> </ul>"},{"location":"Debugging/ghidra/#setting-up-ghidra","title":"Setting up Ghidra","text":"<p>Before starting Ghidra, until version 10.3.3, the MIPS CPU isn't terribly well defined. One needs to go to the installation files of Ghidra, and edit the file <code>Ghidra/Processors/MIPS/data/languages/mips.ldef</code>. In this file, find the lines <code><external_name tool=\"gnu\" name=\"mips:4000\"/></code>, and change them to <code><external_name tool=\"gnu\" name=\"mips:3000\"/></code>. This will allow Ghidra to properly recognize the MIPS CPU used by the PlayStation 1. This step is no longer necessary starting with Ghidra 10.3.3.</p>"},{"location":"Debugging/ghidra/#setting-up-ghidras-debugger","title":"Setting up Ghidra's debugger","text":"<p>When in the main view of Ghidra, right click on the project you want to debug, and in the context menu, select <code>Open With > Debugger</code>. This will open the debugger tool instead of the default disassembler tool.</p> <p>First, identify the Debugger Targets window, and click its top right button: </p> <p>This will open the debugger connector window. In the drop down, select <code>gdb</code>, and as the launch command, enter the path to the gdb multiarch binary, followed by <code>-i mi2</code>. For example, on Windows, this could be <code>C:/gdb-multiarch/bin/gdb-multiarch.exe -i mi2</code>. Click <code>Connect</code>.</p> <p>A new <code>Interpreter</code> window should open on the right, with the prompt <code>(gdb)</code> allowing you to type commands. First, you need to source the <code>ghidra_debugger_scripts</code> file from before. To do this, type <code>source <path to ghidra_debugger_scripts></code>. For example, on Windows, this could be <code>source C:/Users/Pixel/Downloads/ghidra_debugger_scripts</code>. Then, you need to connect to the PCSX-Redux gdb server. To do this, type <code>target remote localhost:3333</code>. Finally, locate the <code>Modules</code> tab in the right window, next to the <code>Interpreter</code> tab, which should look like this: </p> <p>Select the top line, right click on it, and in the context menu, select <code>Map Module to <name of your project></code>. In the new window that appears, simply click <code>Ok</code>.</p> <p>At this point, Ghidra should be fully connected to PCSX-Redux, and should be able to place breakpoint, resume or pause execution, inspect variables, etc. Please be aware that, as of Ghidra 10.3, many features of the debugger are still work in progress, and won't necessarily be stable.</p>"},{"location":"Debugging/gpu-logger/","title":"GPU Logger","text":"<p>The GPU logger is a tool that allows you to see the GPU commands being executed by the emulator, and the resulting VRAM changes. It can be used to debug the GPU, and to understand how the executed software is rendering the scene. The logger will have a full frame worth of primitives, and will automatically clear the log when a new frame is started. Note that the notion of a frame may span over multiple vsyncs, if the PlayStation software isn't running at full FPS.</p> <p>Note that it can be fairly resource intensive, and may significantly slow down the emulation, depending on the context.</p> <p>The top of the GPU Logger window will have the following checkboxes:</p> <ul> <li>GPU Logging - Enable or disable the GPU logging.</li> <li>Breakpoint on vsync - Pause the emulation when a vsync occurs, allowing to inspect the current frame.</li> <li>Replay frame - Enables the replay of the current frame. See below for details.</li> <li>Show origins - Show the data path of the primitives. This will show the origin of the data, and the path it took to reach the GPU. For example, a sequence of primitives may be sent to the GPU via chained DMA.</li> </ul>"},{"location":"Debugging/gpu-logger/#understanding-the-logs","title":"Understanding the logs","text":"<p>The top of the logger can be expanded to display rough frame statistics. These values aren't necessarily too accurate, and are only meant to give a rough idea of the frame complexity.</p> <p>Each row of the logger displays one command sent to the GPU. The first button and checkbox will be used for the replay system. The next three buttons and checkboxes will be used for the highlighting system. The next column will display the command name, and opening the tree node will expand the command parameters.</p> <p>The expanded node may have buttons which will affect the main VRAM viewer, either by selecting CLUTs, or zooming in on the corresponding region. The VRAM viewer will also be updated when the replay system is used.</p>"},{"location":"Debugging/gpu-logger/#highlighting-primitives","title":"Highlighting Primitives","text":"<p>The GPU logger can highlight primitives in the VRAM viewer. One or more primitives may be selected, and the corresponding VRAM regions will be outlined. The highlighting will be cleared when a new frame is started. The default outlined colors will be red for written pixels, and green for read pixels. The colors can be changed in the main VRAM viewer settings.</p> <p>Checking the <code>Highlight on hover</code> checkbox will temporarily outline a primitive when hovering it in the logger. This can be useful to quickly identify the corresponding primitive in the VRAM viewer by flicking the mouse over the logger.</p> <p>Checking the second checkbox in a logger node will permanently highlight the corresponding primitive in the VRAM viewer. The <code>[B]</code> and <code>[E]</code> buttons will select the beginning and the end of a span of primitives, and highlight them in the VRAM viewer.</p>"},{"location":"Debugging/gpu-logger/#replay-system","title":"Replay System","text":"<p>Once a frame has been logged properly, and the emulator is paused, the replay system can be used to replay the frame. The replay system will constantly replay the frame as long as it is activated, and it will update the VRAM viewer accordingly. By default, all nodes in the logger will be selected for replaying. Unselecting the first checkbox in a node will prevent it from being replayed, and the VRAM viewer will show what happens when this primitive isn't executed, and potentially see what is underneath it. Clicking the <code>[T]</code> button of a node will select all nodes for replaying until this node, allowing to easily see the frame being built up to this point.</p>"},{"location":"Debugging/introduction/","title":"Debugging with PCSX-Redux","text":"<p>PCSX-Redux has strong debugging capabilities. It has a built-in GDB server, which allows you to connect to it with a GDB client, such as gdb itself when targeting MIPS, a vscode connector, IDA Pro, or Ghidra, and debug the MIPS CPU. See debugging with Ghidra for more information on debugging with Ghidra.</p> <p>There are also built-in debugging tools, available in the Debug menu. Most of the CPU debugging features will require switching the Dynarec off from the Emulation configuration menu, as the Dynarec is not compatible with the debugging features. Additionally, the debugger needs to be enabled, also in the Emulation configuration menu.</p> <p>The GPU debugging tools can work with the Dynarec enabled, and thus will be much faster than when the interpreter is used.</p>"},{"location":"Debugging/misc-features/","title":"Misc Features","text":""},{"location":"Debugging/misc-features/#mapping-breakpoints","title":"Mapping breakpoints","text":"<p>PCSX-Redux has a feature that allows mapping the memory of the console while the software is running, and to set breakpoints on the mapped memory. This can for instance help in finding codepath when performing certain activities when running code.</p> <p>First, map the kind of action you want to discover, such as executing code, reading memory, or writing memory. Then, run the code for some time without performing the specific action you want to discover. Finally, activate the map breakpoint mode, and then perform the action you want to discover. The breakpoint should be triggered when the action is performed.</p> <p>For example, say that in a game, you want to know what code is executed when you press the \"X\" button. First, check the <code>Map execution</code> checkbox. Then, run the game for a while without pressing the \"X\" button. This will map enough of the memory that's being run in a normal way. Finally, activate the <code>Break on execution map</code> checkbox, and press the \"X\" button. If the game takes a new codepath that hasn't been executed yet, the breakpoint should be triggered.</p> <p>Breakpoints are always checked before mapping the memory, so it's safe to keep both checkboxes on at the same time.</p> <p>Click the <code>Clear maps</code> button to zero out all of the maps, when starting anew.</p>"},{"location":"Debugging/misc-features/#cpu-trace-dump","title":"CPU trace dump","text":""},{"location":"Debugging/misc-features/#setup","title":"Setup","text":"<p>In PCSX-Redux, make sure <code>Debug > Show logs</code> is enabled.</p> <p>In the 'Logs' window, hide all logs : <code>Displayed > Hide all</code></p> <p>To avoid unnecessary noise, you can also skip ISR during CPU traces : <code>Special > Skip ISR during CPU traces</code></p> <p> </p>"},{"location":"Debugging/misc-features/#begin-dump","title":"Begin dump","text":"<p>To dump the CPU traces, launch pcsx-redux with the following command :</p> <pre><code>pcsx-redux -stdout -logfile log.txt\n# Alternatively, you can use -stdout on its own and pipe the output to a file.\npcsx-redux -stdout >> log.txt\n</code></pre> <p>You can use additional flags to launch an executable/disk image in one go, e.g :</p> <pre><code>pcsx-redux -stdout -logfile tst.log -iso image.cue -run\n</code></pre>"},{"location":"Debugging/misc-features/#source","title":"Source","text":"<p>https://discord.com/channels/642647820683444236/663664210525290507/882608398993063997</p>"},{"location":"Debugging/vram-viewer/","title":"VRAM viewer","text":""},{"location":"Debugging/vram-viewer/#navigating","title":"Navigating","text":"<p>Holding the middle button, or both the left and right buttons, allows you to pan the view around. Using the wheel allows you to zoom in and out, at the location of the mouse cursor.</p>"},{"location":"Debugging/vram-viewer/#lensing","title":"Lensing","text":"<p>Holding the CTRL key of your keyboard will bring up a lens, which will show you a locally zoomed version of the VRAM at the location of the mouse cursor. The lens can be resized by using the wheel while holding the CTRL key. Holding the CTRL and Shift buttons while using the wheel will change the size of the lens. The lens can be closed by releasing the CTRL key.</p>"},{"location":"Debugging/vram-viewer/#the-various-viewers","title":"The various viewers","text":"<p>There are different viewers available from the main menu, which can be used to visualize the VRAM in different ways. The main viewer will let you see the VRAM using various CLUTs. The CLUT viewer will let you select a CLUT to use for the main VRAM viewer. In order to do this, first select the 8-bits or 4-bits view in the main viewer. Then, in the CLUT viewer, select <code>View -> Select a CLUT</code>. At this point, hovering the CLUT viewer will automatically change the main viewer to use the hovered CLUT. Once the proper view is found, simply click on the first pixel of the CLUT viewer to select the CLUT more permanently.</p> <p>The GPU logger will also select CLUTs and change the main viewer's mode automatically, depending on the GPU commands being inspected.</p>"},{"location":"Lua/assembler/","title":"Inline assembler","text":"<p>There is a Lua API for an inline MIPS assembler.</p> <p>One can instantiate an assembler with <code>PCSX.Assembler.New()</code>, which will keep all the state of the assembler. The assembler can be used to assemble a string of MIPS code, and then compile it to memory or a file.</p> <p>The object has the following methods:</p> <ul> <li><code>:parse(code)</code> will parse the string <code>code</code> and assemble it. It will return the assembler object itself, so it can be chained with the compile methods. The parser is fairly simple, but it should be enough for most cases. The parser should handle all of the basic MIPS instructions, all of the PS1's GTE opcodes, and many pseudo-instructions. It will also handle labels. The parser is more lenient than normal MIPS assemblers, and will accept some invalid syntax, but it will throw an error if it can't parse the code.</li> <li><code>:compileToUint32Table(baseAddress)</code> will compile the assembled code to a table of <code>uint32_t</code> values. This is useful for debugging, but not very useful for actually running the code. The <code>baseAddress</code> is the address that the code will be loaded at, in order to handle relative jumps.</li> <li><code>:compileToMemory(memory, baseAddress, memoryStartAddress)</code> will compile the assembled code to an indexable memory object, such as an ffi array. The memory object must be at least as large as the assembled code. The memory object will be modified in-place. The <code>baseAddress</code> is the address that the code will be loaded at, in order to handle relative jumps. The <code>memoryStartAddress</code> is the address that the memory object starts at.</li> <li><code>:compileToFile(file, baseAddress, fileStartAddress)</code> will compile the assembled code to a file object. The file object must be at least as large as the assembled code. The file object will be modified in-place. The <code>baseAddress</code> is the address that the code will be loaded at, in order to handle relative jumps. The <code>fileStartAddress</code> is an optional argument which defaults to 0, and is the address that the file object starts at. Using a 0-based file address is relevant when using with the <code>PCSX.getMemoryAsFile()</code> function, or when using a <code>Support.mem4g()</code> File object.</li> </ul>"},{"location":"Lua/binary/","title":"Handling of PSX binaries","text":"<p>There is some support for handling PSX binaries in the Lua API. The <code>PCSX.Binary</code> module has the following functions:</p> <ul> <li><code>PCSX.Binary.load(input, output)</code>: loads an input <code>File</code> object into an output <code>File</code> object. The input file must be a valid PSX binary, which can be in the formats CPE, PS-EXE, PSF, or ELF, and the output file must be at least 4GB large, which means it's really only suitable with the <code>mem4g</code> object, or the object returned by <code>getMemoryAsFile()</code>. The output file will be modified in-place. The output file will be loaded at the address specified in the binary header. If successful, the function will return an info structure with the following optional fields:<ul> <li><code>pc</code>: the entry point of the binary</li> <li><code>gp</code>: the global pointer of the binary</li> <li><code>sp</code>: the stack pointer of the binary</li> <li><code>region</code>: the region of the binary, which can be one of the following:<ul> <li><code>'NTSC'</code>: NTSC region</li> <li><code>'PAL'</code>: PAL region</li> </ul> </li> </ul> </li> <li><code>PCSX.Binary.pack(src, dest, addr, pc, gp, sp, options)</code>: compresses the input binary stream into a self-decompressing stream. The input must be a <code>File</code> object, and the output must be a <code>File</code> object. The <code>addr</code> is the address that the binary will be loaded at. The <code>pc</code>, <code>gp</code>, and <code>sp</code> are the entry point, global pointer, and stack pointer of the binary. The <code>options</code> is an optional table with the following optional fields:<ul> <li><code>tload</code>: the address that the compressed binary will be loaded at. If not specified, it will be set to a suitable address. Not specifying this will generate an in-place decompression binary, which doesn't require much extra memory. When specifying this, the whole output stream will be loaded at this specific address, and the decompression code will be located at its beginning, meaning both the entry point and the loading addresses will be the same.</li> <li><code>nopad</code>: the generated PS-EXE will not be padded to 2048 bytes. It will not be suitable to boot from cd-rom, as the BIOS requires binaries to be aligned to sector sizes, but many other tools like unirom+nops or caetla+catflap will be able to handle it properly.</li> <li><code>booty</code>: a boolean specifying that the output stream will be suitable to boot as a PIO bytestream. Incompatible with <code>tload</code> or <code>raw</code>.</li> <li><code>nokernel</code>: a boolean specifying that the produced binary will not try to call into the kernel, in case the kernel has been wiped out. Results in a slightly bigger binary, but is necessary when the retail kernel is not present.</li> <li><code>shell</code>: a boolean specifying that the output stream will attempt to reboot the machine and load the binary, which can be useful when resetting the kernel.</li> <li><code>raw</code>: a boolean specifying that the output stream will be a raw binary, without a PS-EXE header. The generated binary will be completely position independent, and will not require any special loading address. It is up to the user to ensure no overlap can happen by loading the file to a high enough address. This option can be used to generate embedded binaries within others, or to be loaded by other means, and executed by jumping to it. The <code>tload</code> option will be ignored when this is specified.</li> <li><code>rom</code>: a boolean specifying that the output stream will be a ROM file suitable to be flashed on a cheat cartridge, as long as the cartridge itself has linear addressing, which is not necessarily the case for all cartridges. The <code>tload</code> option will be ignored when this is specified.</li> <li><code>cpe</code>: a boolean specifying that the output stream will be a CPE file, which is the file format used by the ancient toolchain by Sony. This can be useful when trying to load binaries with these ancient tools.</li> </ul> </li> <li><code>PCSX.Binary.createExe(src, dest, addr, pc, gp, sp)</code>: creates a PS-EXE binary from the input binary stream. The input must be a <code>File</code> object, and the output must be a <code>File</code> object. The <code>addr</code> is the address that the binary will be loaded at. The <code>pc</code>, <code>gp</code>, and <code>sp</code> are the entry point, global pointer, and stack pointer of the binary.</li> </ul> <p>The above methods can be used for example the following way:</p> <pre><code>local src = PCSX.getCurrentIso():createReader():open('SLUS_012.34;1')\n\nlocal m4g = Support.File.mem4g()\nlocal info = PCSX.Binary.load(src, m4g)\nlocal asm = PCSX.Assembler.New()\nasm:parse [[\n lui $a0, 0x8001\n addiu $a0, 0x1234\n]]:compileToFile(m4g, 0x80045678)\nlocal bytes = m4g:subFile(m4g:lowestAddress(), m4g:actualSize())\n\nlocal dst = Support.File.open('compressed-from-lua.ps-exe', 'TRUNCATE')\n\nPCSX.Binary.pack(bytes, dst, m4g:lowestAddress(), info.pc, info.gp, info.sp)\n</code></pre> <p>Additionally, the <code>PCSX.Misc</code> module has the following functions:</p> <ul> <li><code>PCSX.Misc.uclPack(src, dest)</code>: compresses the input binary stream into a ucl-compressed stream. Both the input and output arguments must be <code>File</code> objects. The output stream will be written at its current write pointer, and will be compressed using the UCL-NRV2E compression algorithm, which is a variant of the UCL compression algorithm. The output stream can be decompressed in-place with very little memory overhead. Simply place the compressed data at the end of the decompression buffer + 16 bytes. The stream doesn't require to be aligned in any particular way.</li> <li><code>PCSX.Misc.writeUclDecomp(dest)</code>: writes a MIPS UCL-NRV2E decompression routine to the output <code>File</code> object, at its current write pointer. The function returns the number of bytes written, which at the moment is 340 bytes. The code is position independent, and has the following function signature:<ul> <li><code>void decompress(const uint8_t* src, uint8_t* dest);</code></li> </ul> </li> </ul>"},{"location":"Lua/breakpoints/","title":"Breakpoints","text":"<p>If the debugger is activated, and while using the interpreter, the Lua code can insert powerful breakpoints using the following API:</p> <pre><code>PCSX.addBreakpoint(address, type, width, cause, invoker)\n</code></pre> <p>Important: the return value of this function will be an object that represents the breakpoint itself. If this object gets garbage collected, the corresponding breakpoint will be removed. Thus it is important to store it somewhere that won't get garbage collected right away.</p> <p>The only mandatory argument is <code>address</code>, which will by default place an execution breakpoint at the corresponding address. The second argument <code>type</code> is an enum which can be represented by one of the 3 following strings: <code>'Exec'</code>, <code>'Read'</code>, <code>'Write'</code>, and will set the breakpoint type accordingly. The third argument <code>width</code> is the width of the breakpoint, which indicates how many bytes should intersect from the base address with operations done by the emulated CPU in order to actually trigger the breakpoint. The fourth argument <code>cause</code> is a string that will be displayed in the logs about why the breakpoint triggered. It will also be displayed in the Breakpoint Debug UI. And the fifth and final argument <code>invoker</code> is a Lua function that will be called whenever the breakpoint is triggered. By default, this will simply call <code>PCSX.pauseEmulator()</code>. If the invoker returns <code>false</code>, the breakpoint will be permanently removed, permitting temporary breakpoints for example. The signature of the invoker callback is:</p> <pre><code>function(address, width, cause)\n -- body\nend\n</code></pre> <p>The <code>address</code> parameter will contain the address that triggered the breakpoint. For <code>'Exec'</code> breakpoints, this is going to be the same as the current <code>pc</code>, but for <code>'Read'</code> and <code>'Write'</code>, it's going to be the actual accessed address. The <code>width</code> parameter will contain the width of the access that triggered the breakpoint, which can be different from what the breakpoint is monitoring. And the <code>cause</code> parameter will contain a string describing the reason for the breakpoint; the latter may or may not be the same as what was passed to the <code>addBreakpoint</code> function. Note that you don't need to strictly adhere to the signature, and have zero, one, two, or three arguments for your invoker callback. The return value of the invoker callback is also optional.</p> <p>For example, these two examples are well formed and perfectly valid:</p> <pre><code>bp1 = PCSX.addBreakpoint(0x80000000, 'Write', 0x80000, 'Write tracing', function(address, width, cause)\n local regs = PCSX.getRegisters()\n local pc = regs.pc\n print('Writing at ' .. address .. ' from ' .. pc .. ' with width ' .. width .. ' and cause ' .. cause)\nend)\n\nbp2 = PCSX.addBreakpoint(0x80030000, 'Exec', 4, 'Shell reached - pausing', function()\n PCSX.pauseEmulator()\n return false\nend)\n</code></pre> <p>The returned breakpoint object will have a few methods attached to it:</p> <ul> <li><code>:disable()</code></li> <li><code>:enable()</code></li> <li><code>:isEnabled()</code></li> <li><code>:remove()</code></li> </ul> <p>A removed breakpoint will no longer have any effect whatsoever, and none of its methods will do anything. Remember it is possible for the user to still manually remove a breakpoint from the UI.</p> <p>Note that the breakpoint will run outside of any safe Lua environment, so it's possible to crash the emulator by doing something wrong which would normally be caught by the safe environment of the main thread. This is to ensure that the breakpoint can run as fast as possible. In order to avoid this, it's possible to wrap the invoker callback in a <code>pcall</code> call, which will catch any error and display it in the logs. For example:</p> <pre><code>local someActualFunction = function(address, width, cause)\n -- body\nend\nbp = PCSX.addBreakpoint(0x80030000, 'Write', 4, 'Shell write tracing', function(address, width, cause)\n local success, msg = pcall(function()\n someActualFunction(address, width, cause)\n end)\n if not success then\n print('Error while running Lua breakpoint callback: ' .. msg)\n end\nend)\n</code></pre> <p>This will ensure that the breakpoint will never crash the emulator, and will instead display the error in the logs, but it will also slow down the execution of the breakpoint. It's up to the user to decide whether or not this is acceptable.</p> <p>It is safe to add or remove breakpoints from within a breakpoint callback, but it's not safe to remove the breakpoint that is currently being executed. For this specific case, simply return <code>false</code> from the invoker callback, and the breakpoint will be removed after the callback returns.</p>"},{"location":"Lua/case-studies/","title":"Case studies","text":""},{"location":"Lua/case-studies/#spyro-year-of-the-dragon","title":"Spyro: Year of the Dragon","text":"<p>By looking up some of the gameshark codes for this game, we can determine the following memory addresses:</p> <ul> <li><code>0x8007582c</code> is the number of lives.</li> <li><code>0x80078bbc</code> is the health of Spyro.</li> <li><code>0x80075860</code> is the number of unspent jewels available to the player.</li> <li><code>0x80075750</code> is the number of dragons Spyro released so far.</li> </ul> <p>With this, we can build a small UI to visualize and manipulate these values in real time:</p> <pre><code>-- Declare a helper function with the following arguments:\n-- mem: the ffi object representing the base pointer into the main RAM\n-- address: the address of the uint32_t to monitor and mutate\n-- name: the label to display in the UI\n-- min, max: the minimum and maximum values of the slider\n--\n-- This function is local as to not pollute the global namespace.\nlocal function doSliderInt(mem, address, name, min, max)\n -- Clamping the address to the actual memory space, essentially\n -- removing the upper bank address using a bitmask. The result\n -- will still be a normal 32-bits value.\n address = bit.band(address, 0x1fffff)\n -- Computing the FFI pointer to the actual uint32_t location.\n -- The result will be a new FFI pointer, directly into the emulator's\n -- memory space, hopefully within normal accessible bounds. The\n -- resulting type will be a cdata[uint8_t*].\n local pointer = mem + address\n -- Casting this pointer to a proper uint32_t pointer.\n pointer = ffi.cast('uint32_t*', pointer)\n -- Reading the value in memory\n local value = pointer[0]\n -- Drawing the ImGui slider\n local changed\n changed, value = imgui.SliderInt(name, value, min, max, '%d')\n -- The ImGui Lua binding will first return a boolean indicating\n -- if the user moved the slider. The second return value will be\n -- the new value of the slider if it changed. Therefore we can\n -- reassign the pointer accordingly.\n if changed then pointer[0] = value end\nend\n\n-- Utilizing the DrawImguiFrame periodic function to draw our UI.\n-- We are declaring this function global so the emulator can\n-- properly call it periodically.\nfunction DrawImguiFrame()\n -- This is typical ImGui paradigm to display a window using\n -- the safe mode. This will ensure that the window is properly\n -- closed even if an exception is thrown during the rendering\n -- of the window.\n imgui.safe.Begin('Spyro internals', function()\n -- Grabbing the pointer to the main RAM, to avoid calling\n -- the function for every pointer we want to change.\n -- Note: it's not a good idea to hold onto this value between\n -- calls to the Lua VM, as the memory can potentially move\n -- within the emulator's memory space.\n local mem = PCSX.getMemPtr()\n\n -- Now calling our helper function for each of our pointer.\n doSliderInt(mem, 0x8007582c, 'Lives', 0, 9)\n doSliderInt(mem, 0x80078bbc, 'Health', -1, 3)\n doSliderInt(mem, 0x80075860, 'Jewels', 0, 65000)\n doSliderInt(mem, 0x80075750, 'Dragons', 0, 70)\n end)\nend\n</code></pre> <p>You can see this code in action in this demo video.</p>"},{"location":"Lua/case-studies/#crash-bandicoot","title":"Crash Bandicoot","text":"<p>Using exactly the same as above, we can repeat the same sort of cheats for Crash Bandicoot. Note that when the CPU is being emulated, the <code>DrawImguiFrame</code> function will be called at least when the emulation is issuing a vsync event. This means that cheat codes that regularly write to memory during vsync can be applied naturally.</p> <pre><code>local function crash_Checkbox(mem, address, name, value, original)\n address = bit.band(address, 0x1fffff)\n local pointer = mem + address\n pointer = ffi.cast('uint32_t*', pointer)\n local changed\n local check\n local tempvalue = pointer[0]\n if tempvalue == original then check = false end\n if tempvalue == value then check = true else check = false end\n changed, check = imgui.Checkbox(name, check)\n if check then pointer[0] = value else pointer[0] = original end\nend\n\nfunction DrawImguiFrame()\n imgui.safe.Begin('Crash Bandicoot Mods', function()\n local mem = PCSX.getMemPtr()\n crash_Checkbox(mem, 0x80027f9a, 'Neon Crash', 0x2400, 0x100c00)\n crash_Checkbox(mem, 0x8001ed5a, 'Unlimited Time Aku', 0x0003, 0x3403)\n crash_Checkbox(mem, 0x8001dd0c, 'Walk Mid-Air', 0x0000, 0x8e0200c8)\n crash_Checkbox(mem, 0x800618ec, '99 Lives at Map', 0x6300, 0x0200)\n crash_Checkbox(mem, 0x80061949, 'Unlock all Levels', 0x0020, 0x00)\n crash_Checkbox(mem, 0x80019276, 'Disable Draw Level', 0x20212400, 0x20210c00)\n end)\nend\n</code></pre>"},{"location":"Lua/case-studies/#crash-bandicoot-using-conditional-breakpoints","title":"Crash Bandicoot - Using Conditional BreakPoints","text":"<p>This example will showcase using the BreakPoints and Assembly UI, as well as using the Lua console to manipulate breakpoints.</p> <p>Crash Bandicoot 1 has several modes of execution. These modes tell the game what to do, such as which level to load into, or to load back into the map. These modes are passed to the main game loop routine as an argument. Due to this, manually manipulating memory at the right time with the correct value to can be tricky to ensure the desired result.</p> <p>The game modes are listed here.</p> <p>In Crash 1, there is a level that was included in the game but cut from the final level selection due to difficulty, 'Stormy Ascent'. This level can be accessed only by manipulating the game mode value that is passed to the main game routine. There is a gameshark code that points us to the memory location and value that needs to be written in order to set the game mode to the Story Ascent level.</p> <ul> <li><code>30011DB0 0022</code> - This is telling us to write the value 0x0022 at memory location <code>0x8001db0</code> 0x0022 is the value of the Stormy Ascent level we want to play.</li> </ul> <p>The issue is that GameShark uses a hook to achieve setting this value at the correct time. We will set up a breakpoint to see where the main game routine is.</p> <p>Setting the breakpoint can be done through the Breakpoint UI or in the Lua console. There is a link to a video at the bottom of the article showing the entire procedure.</p> <p>Breakpoints can alternatively be set through the Lua console. In PCSX-Redux top menu, click Debug \u2192 Show Lua Console</p> <p>We are going to add a breakpoint to pause execution when memory address 0x8001db0 is read. This will show where the main game loop is located in memory.</p> <p>In the Lua console, paste the following hit enter.</p> <pre><code>bp = PCSX.addBreakpoint(0x80011db0, 'Read', 1, 'Find main loop')\n</code></pre> <p>You should see where the breakpoint was added in the Lua console, as well as in the Breakpoints UI. Note that we need to assign the result of the function to a variable to avoid garbage collection.</p> <p>Now open Debug \u2192 Show Assembly</p> <p>Start the emulator with Crash Bandicoot 1 SCUS94900</p> <p>Right before the BIOS screen ends, the emulator should pause. In the assembly window we can see a yellow arrow pointing to <code>0x80042068</code>. We can see this is a <code>lw</code> instruction that is reading a value from <code>0x8001db0</code>. This is the main game loop reading the game mode value from memory!</p> <p>Now that we know where the main game loop is located in memory, we can set a conditional breakpoint to properly set the game mode value when the main game routine is executed.</p> <p>This breakpoint will be triggered when the main game loop at <code>0x80042068</code> is executed, and ensure the value at <code>0x80011db0</code> is set to <code>0x0022</code></p> <p>In the Lua console, paste the following and hit enter.</p> <pre><code>bp = PCSX.addBreakpoint(0x80042068, 'Exec', 4, 'Stormy Ascent', function()\n PCSX.getMemPtr()[0x11db0] = 0x22\nend)\n</code></pre> <p>We can now disable/remove our Read breakpoint using the Breakpoints UI, and restart the game. Emulation \u2192 Hard Reset</p> <p>If the Emulator status shows Idle, click Emulation \u2192 Start</p> <p>Once the game starts, instead of loading into the main menu, you should load directly into the Stormy Ascent level.</p> <p>You can see this in action in this demo video.</p>"},{"location":"Lua/case-studies/#more-references","title":"More references","text":"<p>Here's some projects using PCSX-Redux' Lua scripting capabilities, which can be used as references:</p> <ul> <li>https://github.com/NDR008/TensorFlowPSX</li> <li>https://github.com/Kuumba123/TeheManX4_Editor</li> <li>https://github.com/notdodgeball/vagrant-story-lua-script</li> <li>https://github.com/johnbaumann/lua-pio-cart</li> <li>https://github.com/FoxdieTeam/mgs_reversing</li> <li>https://github.com/kiletic/tekken3-ai</li> </ul>"},{"location":"Lua/events/","title":"Events","text":"<p>The Lua code can listen for events broadcasted from within the emulator. The following function is available to register a callback to be called when certain events happen:</p> <pre><code>PCSX.Events.createEventListener(eventName, callback)\n</code></pre> <p>Important: the return value of this function will be an object that represents the listener itself. If this object gets garbage collected, the corresponding listener will be removed. Thus it is important to store it somewhere that won't get garbage collected right away. The listener object has a <code>:remove</code> method to stop the listener before its garbage collection time.</p> <p>The callback function will be called from an unsecured environment, and it is advised to delegate anything complex or risky enough to <code>PCSX.nextTick</code>.</p> <p>The <code>eventName</code> argument is a string that can have the following values:</p> <ul> <li><code>Quitting</code>: The emulator is about to quit. The callback will be called with no arguments. This is where you'd need to close libuv objects held by Lua through luv in order to allow the emulator to quit gracefully. Otherwise you may soft lock the application where it'll wait for libuv objects to close.</li> <li><code>IsoMounted</code>: A new ISO file has been mounted into the virtual CDRom drive. The callback will be called with no arguments.</li> <li><code>GPU::Vsync</code>: The emulated GPU has just completed a vertical blanking interval. The callback will be called with no arguments.</li> <li><code>ExecutionFlow::ShellReached</code>: The emulation execution has reached the beginning of the BIOS' shell. The callback will be called with no arguments. This is the moment where the kernel is properly set up and ready to execute any arbitrary binary. The emulator may use this event to side load binaries, or signal gdb that the kernel is ready.</li> <li><code>ExecutionFlow::Run</code>: The emulator resumed execution. The callback will be called with no arguments. This event will fire when calling <code>PCSX.resumeEmulator()</code>, when the user presses Start, or other potential interactions.</li> <li><code>ExecutionFlow::Pause</code>: The emulator paused execution. The callback will be called with a table that contains a boolean named <code>exception</code>, indicating if the pause is the result of an execution exception within the emulated CPU. This event will fire on breakpoints too, so if breakpoints have Lua callbacks attached on them, they will be executed too.</li> <li><code>ExecutionFlow::Reset</code>: The emulator is resetting the emulated machine. The callback will be called with a table that contains a boolean named <code>hard</code>, indicating if the reset is a hard reset or a soft reset. This event will fire when calling <code>PCSX.resetEmulator()</code>, when the user presses Reset, or other potential interactions.</li> <li><code>ExecutionFlow::SaveStateLoaded</code>: The emulator just loaded a savestate. The callback will be called with no arguments. This event will fire when calling <code>PCSX.loadSaveState()</code>, when the user loads a savestate, or other potential interactions. This is useful to listen to in case some internal state needs to be reset within the Lua logic.</li> <li><code>GUI::JumpToPC</code>: The UI is being asked to move the assembly view cursor to the specified address. The callback will be called with a table that contains a number named <code>pc</code>, indicating the address to jump to.</li> <li><code>GUI::JumpToMemory</code>: The UI is being asked to move the memory view cursor to the specified address. The callback will be called with a table that contains a number named <code>address</code>, indicating the address to jump to, and <code>size</code>, indicating the number of bytes to highlight.</li> <li><code>Keyboard</code>: The emulator is dispatching keyboard events. The callback will be called with a table containing four numbers: <code>key</code>, <code>scancode</code>, <code>action</code>, and <code>mods</code>. They are the same values as the glfw callback set by <code>glfwSetKeyCallback</code>.</li> <li><code>Memory::SetLuts</code>: The emulator has updated the memory LUTs. The callback will be called with no arguments.</li> </ul>"},{"location":"Lua/file-api/","title":"File API","text":""},{"location":"Lua/file-api/#introduction-rationale","title":"Introduction & Rationale","text":"<p>While the normal Lua io API is loaded, there's a more powerful API that's more tightly integrated with the rest of the PCSX-Redux File handling code. It's an abstraction class that allows seamless manipulation of various objects using a common API.</p> <p>The File objects have different properties depending on how they are created and their intention. But generally speaking, the following rules apply:</p> <ul> <li>Files are reference counted. They will be deleted when the reference count reaches zero. The Lua garbage collector will only decrease the reference count.</li> <li>Whenever possible, writes are deferred to an asynchronous thread, making writes return basically instantly. This speed up comes at the trade off of data integrity, which means writes aren't guaranteed to be flushed to the disk yet when the function returns. Data will always have integrity internally within PCSX-Redux however, and when exiting normally, all data will be flushed to the disk.</li> <li>Some File objects can be cached. When caching, reads and writes will be done transparently, and the cache will be used instead of the actual file. This will make reads return basically instantly too.</li> <li>The Read and Write APIs can haul LuaBuffer objects. These are Lua objects that can be used to read and write data to the file. You can construct one using the <code>Support.NewLuaBuffer(size)</code> function. They can be cast to strings, and can be used as a table for reading and writing bytes off of it, in a 0-based fashion. The length operator will return the size of the buffer. The methods <code>:maxsize()</code> and <code>:resize(size)</code> are available. They also have a <code>.pbSlice</code> property that implicitly converts them to a Lua-Protobuf's <code>pb.slice</code>, which can then be passed to <code>pb.decode</code>.</li> <li>The Read and Write APIs can also function using Lua-Protobuf's buffers and slices respectively.</li> <li>If the file isn't closed when the file object is destroyed, it'll be closed then, but letting the garbage collector do the closing is not recommended. This is because the garbage collector will only run when the memory pressure is high enough, and the file handle will be held for a long time.</li> <li>When using streamed functions, unlike POSIX files handles, there's two distinct seeking pointers: one for reading and one for writing.</li> </ul>"},{"location":"Lua/file-api/#common-api-for-all-file-objects","title":"Common API for all File objects","text":"<p>All File objects have the following API attached to them as methods:</p> <p>Closes and frees any associated resources. Better to call this manually than letting the garbage collector do it: <pre><code>:close()\n</code></pre></p> <p>Reads from the File object and advances the read pointer accordingly. The return value depends on the variant used. <pre><code>:read(size) -- returns a LuaBuffer\n:read(ptr, size) -- returns the number of bytes read, ptr has to be a cdata of pointer type\n:read(buffer) -- returns the number of bytes read, and adjusts the buffer's size\n:read(pb_buffer, size) -- returns the number of bytes read, while appending to the pb_buffer's existing data\n:gets() -- returns a string, up to the next newline character\n</code></pre></p> <p>Reads from the File object at the specified position. No pointers are modified. The return value depends on the variant used, just like the non-At variants above. <pre><code>:readAt(size, pos)\n:readAt(ptr, size, pos)\n:readAt(buffer, pos)\n:readAt(pb_buffer, pos)\n</code></pre></p> <p>Writes to the File object. The non-At variants will advances the write pointer accordingly. The At variants will not modify the write pointer, and simply write at the requested location. Returns the number of bytes written. The <code>string</code> variants will in fact take any object that can be transformed to a string using <code>tostring()</code>. <pre><code>:write(string)\n:write(buffer)\n:write(slice)\n:write(pb_slice)\n:write(ptr, size)\n:writeAt(string, pos)\n:writeAt(buffer, pos)\n:writeAt(slice, pos)\n:writeAt(pb_slice, pos)\n:writeAt(ptr, size, pos)\n</code></pre></p> <p>Note that in this context, <code>pb_slice</code> and <code>pb_buffer</code> refer to Lua-Protobuf's <code>pb.slice</code> and <code>pb.buffer</code> objects respectively.</p> <p>Some APIs may return a <code>Slice</code> object, which is an opaque buffer coming from C++. The <code>write</code> and <code>writeAt</code> methods can take a <code>Slice</code>. It is possible to write a slice to a file in a zero-copy manner, which will be more efficient:</p> <pre><code>:writeMoveSlice(slice)\n:writeAtMoveSlice(slice, pos)\n</code></pre> <p>After which, the slice will be consumed and not reusable. The <code>Slice</code> object is convertible to a string using <code>tostring()</code>, and also has two members: <code>data</code>, which is a <code>const void*</code>, and <code>size</code>. Once consumed by the <code>MoveSlice</code> variants, the size of a slice will go down to zero.</p> <p>Finally, it is possible to convert a <code>Slice</code> object to a <code>pb.slice</code> one using the <code>Support.sliceToPBSlice</code> function. However, the same caveats as for normal <code>pb.slice</code> objects apply: it is fragile, and will be invalidated if the underlying Slice is moved or destroyed, so it is recommended to use it as a temporary object, such as an argument to <code>pb.decode</code>. Still, it is a much faster alternative to calling <code>tostring()</code> which will make a copy of the underlying slice.</p> <p>The following methods manipulate the read and write pointers. All of them return their corresponding pointer. The <code>wheel</code> argument can be of the values <code>'SEEK_SET'</code>, <code>'SEEK_CUR'</code>, and <code>'SEEK_END'</code>, and will default to <code>'SEEK_SET'</code>. <pre><code>:rSeek(pos[, wheel])\n:rTell()\n:wSeek(pos[, wheel])\n:wTell()\n</code></pre></p> <p>These will query the corresponding File object. <pre><code>:size() -- Returns the size in bytes, if possible. If the file is not seekable, will throw an error.\n:seekable() -- Returns true if the file is seekable.\n:writable() -- Returns true if the file is writable.\n:eof() -- Returns true if the read pointer is at the end of file.\n:failed() -- Returns true if the file failed in some ways. The File object is defunct if this is true.\n:cacheable() -- Returns true if the file is cacheable.\n:caching() -- Returns true if caching is in progress or completed.\n:cacheProgress() -- Returns a value between 0 and 1 indicating the progress of the caching operation.\n</code></pre></p> <p>If applicable, this will start caching the corresponding file in memory. <pre><code>:startCaching()\n</code></pre></p> <p>Same as above, but will suspend the current coroutine until the caching is done. Cannot be used with the main thread. <pre><code>:startCachingAndWait()\n</code></pre></p> <p>Duplicates the File object. This will re-open the file, and possibly duplicate all ressources associated with it. <pre><code>:dup()\n</code></pre></p> <p>Creates a read-only view of the file starting at the specified position, spanning the specified length. The view will be a new File object, and will be a view of the same underlying file. The default values of start and length are 0 and -1 respectively, which will effectively create a view of the entire file. The view may have less features than the underlying file, but will always be seekable, and keep its seeking position independent of the underlying file. The view will hold a reference to the underlying file. <pre><code>:subFile([start[, length]])\n</code></pre></p> <p>In addition to the above methods, the File API has these helpers, that'll read or write binary values off their corresponding stream position for the non-At variants, or at the indicated position for the At variants. All the values will be read or stored in Little Endian, regardless of the host's endianness. <pre><code>:readU8(), :readU16(), :readU32(), :readU64(),\n:readI8(), :readI16(), :readI32(), :readI64(),\n:readU8At(pos), :readU16At(pos), :readU32At(pos), :readU64At(pos),\n:readI8At(pos), :readI16At(pos), :readI32At(pos), :readI64At(pos),\n:writeU8(val), :writeU16(val), :writeU32(val), :writeU64(val),\n:writeI8(val), :writeI16(val), :writeI32(val), :writeI64(val),\n:writeU8At(val, pos), :writeU16At(val, pos), :writeU32At(val, pos), :writeU64At(val, pos),\n:writeI8At(val, pos), :writeI16At(val, pos), :writeI32At(val, pos), :writeI64At(val, pos),\n</code></pre></p>"},{"location":"Lua/file-api/#creating-file-objects","title":"Creating File objects","text":"<p>The Lua VM can create File objects in different ways: <pre><code>Support.File.open(filename[, type])\nSupport.File.buffer()\nSupport.File.buffer(ptr, size[, type])\nSupport.File.mem4g()\nSupport.File.uvFifo(address, port)\nSupport.File.zReader(file[, size[, raw]])\n</code></pre></p>"},{"location":"Lua/file-api/#basic-files","title":"Basic files","text":"<p>The <code>open</code> function will function on filesystem and network URLs, while the <code>buffer</code> function will generate a memory-only File object that's fully readable, writable, and seekable. The <code>type</code> argument of the <code>open</code> function will determine what happens exactly. It's a string that can have the following values:</p> <ul> <li><code>READ</code>: Opens the file for reading only. Will fail if the file does not exist. This is the default type.</li> <li><code>TRUNCATE</code>: Opens the file for reading and writing. If the file does not exist, it will be created. If it does exist, it will be truncated to 0 size.</li> <li><code>CREATE</code>: Opens the file for reading and writing. If the file does not exist, it will be created. If it does exist, it will be left untouched.</li> <li><code>READWRITE</code>: Opens the file for reading and writing. Will fail if the file does not exist.</li> <li><code>DOWNLOAD_URL</code>: Opens the file for reading only. Will immediately start downloading the file from the network. The <code>filename</code> argument will be treated as a URL. The curl is the backend for this feature, and its url schemes are supported. The progress of the download can be monitored with the <code>:cacheProgress()</code> method.</li> <li><code>DOWNLOAD_URL_AND_WAIT</code>: As above, but suspends the current coroutine until the download is done. Cannot be used with the main thread.</li> </ul>"},{"location":"Lua/file-api/#buffers","title":"Buffers","text":"<p>When calling <code>.buffer()</code> with no argument, this will create an empty read-write buffer. When calling it with a cdata pointer and a size, this will have the following behavior, depending on type:</p> <ul> <li><code>READWRITE</code> (or no type): The memory passed as an argument will be copied first.</li> <li><code>READ</code>: The memory passed as an argument will be referenced, and the lifespan of said memory needs to outlast the File object. The File object will be read-only.</li> <li><code>ACQUIRE</code>: It will acquire the pointer passed as an argument, and free it later using <code>free()</code>, meaning it needs to have been allocated using <code>malloc()</code> in the first place.</li> </ul> <p>The <code>.mem4g()</code> constructor will return a sparse buffer that has a virtual 4GB span. It can be used to read and write data in the 4GB range, but will not actually allocate any memory until the data is actually written to. This is useful for doing operations that are similar to that of the PlayStation memory. The <code>.mem4g()</code> constructor will return a File object that's fully readable, writable, and seekable. Its size will always be 4GB. The returned object will have 3 additional methods:</p> <ul> <li><code>:lowestAddress()</code>: Returns the lowest address that has been written to.</li> <li><code>:highestAddress()</code>: Returns the highest address that has been written to.</li> <li><code>:actualSize()</code>: Returns the size of the buffer, which is the highest address minus the lowest address.</li> </ul> <p>This is a useful object to use with the <code>:subFile()</code> method, as it will allow you to create a view of a specific range of the 4GB memory. Specifically, <code>obj:subFile(obj:lowestAddress(), obj:actualSize())</code> will create a view of the entire memory that has been written to.</p>"},{"location":"Lua/file-api/#network-streams","title":"Network streams","text":"<p>The <code>uvFifo</code> function will create a File object that will read from and write to the specified TCP address and port after connecting to it. The <code>:failed()</code> method will return true in case of a connection failure. The address is a string, and must be a strict IP address, no hostnames allowed. The port is a number between 1 and 65535 inclusive. As the name suggests, this object is a FIFO, meaning that incoming bytes will be consumed by any read operation. The <code>:size()</code> method will return the number of bytes in the FIFO. Writes will be immediately sent over. There are no reception guarantees, as the other side might have disconnected at any point. The <code>:eof()</code> method will return true when the opposite end of the stream has been disconnected and there's no more bytes in the FIFO. In addition to the normal <code>File</code> API, a <code>uvFifo</code> has a method called <code>:isConnecting()</code>, which returns a boolean indicating the fifo is still connecting, meaning it's possible to verify if the fifo has successfully connected using the boolean expression <code>not fifo:isConnecting() and not fifo:failed()</code>.</p>"},{"location":"Lua/file-api/#compressed-streams","title":"Compressed streams","text":"<p>The <code>zReader</code> function will create a read-only File object which decompresses the data from the specified File object. The <code>file</code> argument is a File object, and the <code>size</code> argument is an optional number that will be used to determine the size of the decompressed data. If not specified, the resulting file won't be seekable, and its <code>:size()</code> method won't work, but the file will be readable until <code>:eof()</code> returns true. The <code>raw</code> argument is an optional string that needs to be equal to <code>'RAW'</code>, and will determine whether the data is compressed using the raw deflate format, or the zlib format. Any other string means the zlib format will be used.</p>"},{"location":"Lua/file-api/#iso-files","title":"Iso files","text":"<p>There is some limited API for working with ISO files.</p> <ul> <li><code>PCSX.getCurrentIso()</code> will return an <code>Iso</code> object representing the currently loaded ISO file by the emulator.</li> <li><code>PCSX.openIso(pathOrFile)</code> will return an <code>Iso</code> object opened from the specified argument, which can either be a filesystem path, or a <code>File</code> object.</li> </ul> <p>The following methods are available on the <code>Iso</code> object:</p> <pre><code>:failed() -- Returns true if the Iso file failed in some ways. The Iso object is defunct if this is true.\n:createReader() -- Returns an ISOReader object off the Iso file.\n:open(lba[, size[, mode]]) -- Returns a File object off the specified span of sectors.\n:clearPPF() -- Clears out all of the currently applied patches.\n:savePPF() -- Saves the currently applied patches to a PPF file named after the ISO file.\n</code></pre> <p>The <code>:open</code> method has some magic built-in. The size argument is optional, and if missing, the code will attempt to guess the size of the underlying file within the Iso. It will represent the size of the virtual file in bytes. The size guessing mechanism can only work on MODE2 FORM1 or FORM2 sectors, and will result in a failed File object otherwise. The mode argument is optional, and can be one of the following:</p> <ul> <li><code>'GUESS'</code>: will attempt to guess the mode of the file. This is the default.</li> <li><code>'RAW'</code>: the returned File object will read 2352 bytes per sector.</li> <li><code>'M1'</code>: the returned File object will read 2048 bytes per sector.</li> <li><code>'M2_RAW'</code>: the returned File object will read 2336 bytes per sector. This can't be guessed. This is useful for extracting STR files that require the subheaders to be present.</li> <li><code>'M2_FORM1'</code>: the returned File object will read 2048 bytes per sector.</li> <li><code>'M2_FORM2'</code>: the returned File object will read 2324 bytes per sector.</li> </ul> <p>The resulting File object will cache a single full sector in memory, meaning that small sequential reads won't read the same sector over and over from the disk.</p> <p>The resulting File object will be writable, which will temporarily patch the CD-Rom image file in memory. It is possible to flush the patches to a PPF file by calling the <code>:savePPF()</code> method of the corresponding Iso object. When writing to one of these files, the filesystem metadata information will not be updated, meaning that the size of the file on the filesystem will not change, despite it being possible to write past the end of it and overflow on the next sectors. Note that while the virtual File object will enlarge to accommodate the writes, it will not be filled with zeroes as with typical filesystem operations, but instead will be filled with the existing data from the iso image. When applicable, sync headers, location, MODE2 subheaders will be added, and ECC and EDC will be recalculated on the fly, and the resulting data will be written to the virtual file, except for files opened in <code>'RAW'</code> mode. The <code>'M1'</code> mode cannot be written to, and will throw an error if attempted.</p> <p>The ISOReader object has the following methods:</p> <pre><code>:open(filename) -- Returns a File object off the specified file within the ISO.\n</code></pre> <p>This method is basically a helper over the <code>:open()</code> method of the Iso object, and will automatically guess the mode and size of the file.</p>"},{"location":"Lua/introduction/","title":"Introduction","text":"<p>PCSX-Redux features a Lua API that is available through either a direct Lua console, or a Lua editor, both available through the Debug menu. The Lua VM runs on the main thread, the same one as the UI and the emulated MIPS CPU. As a result, care must be taken to not stall for too long, or the UI will become unresponsive. Using coroutines to handle long-running tasks is recommended, yielding periodically to let the UI perform some work too. The UI is probably going to run at 60FPS or so, which gives a ballpark of 15ms per frame.</p>"},{"location":"Lua/introduction/#lua-engine","title":"Lua engine","text":"<p>The Lua engine that's being used is LuaJIT 2.1.0-beta3 compiled in Lua 5.2 compatibility mode. The Lua 5.1 user manual and LuaJIT user manual are recommended reads. In particular, the bindings heavily make use of LuaJIT's FFI capabilities, which allows for direct memory access within the emulator's process. This means there is little protection against dramatic crashes the LuaJIT FFI engine can cause into the emulator's process, and the user must pay extra attention while manipulating FFI objects. Despite that, the code tries as much as possible to sandbox what the Lua code does, and will prevent crashes on any recoverable exception, including OpenGL and ImGui exceptions.</p>"},{"location":"Lua/introduction/#lua-console","title":"Lua console","text":"<p>All of the messages coming from Lua should display into the Lua console directly. The input text there is a single line execution, so the user can type one-liner Lua statements and get an immediate result.</p>"},{"location":"Lua/introduction/#lua-editor","title":"Lua editor","text":"<p>The editor allows for more complex, multi-line statements to be written, such as complete functions. The editor will by default auto save its contents on the disc under the filename <code>pcsx.lua</code>, which can potentially be a problem if the last statement typed crashed the emulator, as it'll be reloaded on the next startup. It might become necessary to either edit the file externally, or simply delete it to recover from this state.</p> <p>The auto-execution of the editor permits for rapid development loop, with immediate feedback of what's done.</p> <p>For complex projects however, it is recommended to split your work into sub-modules, and use the <code>loadfile</code> function to load them in your main code. This implies working on your project using an external editor.</p>"},{"location":"Lua/libraries/","title":"Loaded libraries","text":""},{"location":"Lua/libraries/#basic-lua","title":"Basic Lua","text":"<p>The LuaJIT extensions are fully loaded, and can be used globally. The standard Lua libraries are loaded, and are usable. The <code>require</code> function exists, but isn't recommended as the loading of external DLLs might be difficult to properly accomplish. Loading pure Lua files is fine. The <code>ffi</code> table is loaded globally, there is no need to <code>require</code> it, but it'll work nonetheless. As a side-effect of Luv, Lua-compat-5.3 is loaded.</p>"},{"location":"Lua/libraries/#dear-imgui","title":"Dear ImGui","text":"<p>A good portion of ImGui is bound to the Lua environment, and it's possible for the Lua code to emit arbitrary widgets through ImGui. It is advised to consult the user manual of ImGui in order to properly understand how to make use of it. The list of current bindings can be found within the source code. Some usage examples will be provided within the case studies. Additional features and interaction is documented in the rendering page.</p>"},{"location":"Lua/libraries/#opengl","title":"OpenGL","text":"<p>OpenGL is bound directly to the Lua API through FFI bindings, loosely inspired and adapted from LuaJIT-OpenCL. Some usage examples can be seen in the CRT-Lottes shader configuration page.</p>"},{"location":"Lua/libraries/#nanovg","title":"NanoVG","text":"<p>The NanoVG library is mostly bound to the Lua API through FFI bindings, with some additional glue code. More explanation can be found in the rendering page.</p>"},{"location":"Lua/libraries/#luv","title":"Luv","text":"<p>For network access and interaction, PCSX-Redux uses libuv internally, and is exposed to the Lua API through Luv, tho its loop is tied to the main thread one, meaning it'll run only once per frame. There is another layer of network API available through the File API, which is more convenient and faster for simple tasks.</p>"},{"location":"Lua/libraries/#zlib","title":"Zlib","text":"<p>The Zlib C-API is exposed through FFI bindings. There is another layer of Zlib API available through the File API, which is more convenient and faster for simple tasks.</p>"},{"location":"Lua/libraries/#ffi-reflect","title":"FFI-Reflect","text":"<p>The FFI-Reflect library is loaded globally as the <code>reflect</code> symbol. It's able to generate reflection objects for the LuaJIT FFI module.</p>"},{"location":"Lua/libraries/#pprint","title":"PPrint","text":"<p>The PPrint library is loaded globally as the <code>pprint</code> symbol. It's a more powerful <code>print</code> function than the one provided by Lua, and can be used to print tables in a more readable way.</p>"},{"location":"Lua/libraries/#lua-protobuf","title":"Lua-Protobuf","text":"<p>The Lua-Protobuf library is available, but not loaded by default. All of its documented API should be usable straight with no additional work. It has been slightly modified, but nothing that should be visible to the user. There is some limited glue between its API and PCSX's.</p>"},{"location":"Lua/libraries/#luafilesystem","title":"luafilesystem","text":"<p>The luafilesystem library is loaded globally as the <code>lfs</code> symbol. It's a library that provides access to the filesystem.</p>"},{"location":"Lua/libraries/#lpeg","title":"LPeg","text":"<p>The LPeg library is available, but not loaded by default. It's a library that provides a pattern-matching library for Lua, which can be useful to create ad-hoc arbitrary parsers.</p>"},{"location":"Lua/memory-and-registers/","title":"Memory and registers","text":""},{"location":"Lua/memory-and-registers/#ffi-access","title":"FFI access","text":"<p>The Lua code can access the emulated memory and registers directly through some FFI bindings:</p> <ul> <li><code>PCSX.getMemPtr()</code> will return a <code>cdata[uint8_t*]</code> representing up to 8MB of emulated memory. This can be written to, but careful about the emulated i-cache in case code is being written to.</li> <li><code>PCSX.getParPtr()</code> will return a <code>cdata[uint8_t*]</code> representing up to 512kB of the EXP1/Parallel port memory space. This can be written to.</li> <li><code>PCSX.getRomPtr()</code> will return a <code>cdata[uint8_t*]</code> representing up to 512kB of the BIOS memory space. This can be written to.</li> <li><code>PCSX.getScratchPtr()</code> will return a <code>cdata[uint8_t*]</code> representing up to 1kB for the scratchpad memory space.</li> <li><code>PCSX.getRegisters()</code> will return a structured cdata representing all the registers present in the CPU:</li> <li><code>PCSX.getReadLUT()</code> will return a <code>cdata[uint8_t**]</code> representing the read LUT for the CPU.</li> <li><code>PCSX.getWriteLUT()</code> will return a <code>cdata[uint8_t**]</code> representing the write LUT for the CPU.</li> </ul> <pre><code>typedef union {\nstruct {\nuint32_t r0, at, v0, v1, a0, a1, a2, a3;\nuint32_t t0, t1, t2, t3, t4, t5, t6, t7;\nuint32_t s0, s1, s2, s3, s4, s5, s6, s7;\nuint32_t t8, t9, k0, k1, gp, sp, s8, ra;\nuint32_t lo, hi;\n} n;\nuint32_t r[34];\n} psxGPRRegs;\n\ntypedef union {\nuint32_t r[32];\n} psxCP0Regs;\n\ntypedef union {\nuint32_t r[32];\n} psxCP2Data;\n\ntypedef union {\nuint32_t r[32];\n} psxCP2Ctrl;\n\ntypedef struct {\npsxGPRRegs GPR;\npsxCP0Regs CP0;\npsxCP2Data CP2D;\npsxCP2Ctrl CP2C;\nuint32_t pc;\n} psxRegisters;\n</code></pre>"},{"location":"Lua/memory-and-registers/#safer-access","title":"Safer access","text":"<p>The above methods will return direct pointers into the emulated memory, so it's easy to crash the emulator if you're not careful. The <code>getMemoryAsFile()</code> method is safer, but will be slower:</p> <ul> <li><code>PCSX.getMemoryAsFile()</code> will return a <code>File</code> object representing the full 4GB of accessible memory. All operations on this file will be translated to the emulated memory space. This is slower than the direct access methods, but safer. Any read or write operation will be clamped to the emulated memory space, and will not crash the emulator.</li> </ul>"},{"location":"Lua/memory-and-registers/#memory-mapping","title":"Memory mapping","text":"<p>PCSX-Redux will attempt to forward reads and writes for memory not mapped in the LUTs. This is useful for debugging, but will be slower than the direct access methods.</p> <p>-<code>UnknownMemoryRead(address, size)</code> will be called when a read is attempted to an unmapped memory address. The function should return an 8, 16, or 32-bit value to be returned to the CPU. -<code>UnknownMemoryWrite(address, size, value)</code> will be called when a write is attempted to an unmapped memory address. The function should return <code>true</code> or <code>false</code> indicating whether the write was handled.</p>"},{"location":"Lua/redux-basics/","title":"Redux basic API","text":""},{"location":"Lua/redux-basics/#settings","title":"Settings","text":"<p>All of the settings are exposed to Lua via the <code>PCSX.settings</code> 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 <code>pprint</code> 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.</p>"},{"location":"Lua/redux-basics/#imgui-interaction","title":"ImGui interaction","text":"<p>PCSX-Redux will periodically try to call the Lua function <code>DrawImguiFrame</code> 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.</p>"},{"location":"Lua/redux-basics/#events-engine-interaction-execution-contexts","title":"Events Engine interaction & Execution Contexts","text":"<p>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.</p> <p>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 <code>PCSX.nextTick(func)</code> will execute the given function in the next main loop iteration. Here's some examples of how to use it:</p> <pre><code> local captures = {}\n captures.current = coroutine.running()\n captures.callback = function()\n PCSX.nextTick(function()\n captures.callback:free()\n coroutine.resume(captures.current)\n end)\n end\n captures.callback = ffi.cast('void (*)()', captures.callback)\n -- use the C callback somewhere...\n</code></pre> <pre><code>function createClient(ip, port)\n client = luv.new_tcp()\n\n luv.tcp_connect(client, ip, port, function (err)\n PCSX.nextTick(function()\n assert(not err, err)\n\n luv.read_start(client, function (err, chunk)\n PCSX.nextTick(function()\n pprint(\"received at client\", {err=err, chunk=chunk})\n assert(not err, err)\n if chunk then\n -- do something with the client\n else\n luv.close(client)\n end\n end)\n )\n\n pprint(\"writing from client\")\n luv.write(client, \"Hello\")\n luv.write(client, \"World\")\n\n end\n end)\n return client\nend\n</code></pre> <p>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.</p>"},{"location":"Lua/redux-basics/#constants","title":"Constants","text":"<p>The table <code>PCSX.CONSTS</code> 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 <code>pprint(PCSX.CONSTS)</code>.</p>"},{"location":"Lua/redux-basics/#pads","title":"Pads","text":"<p>You can access the pads API through <code>PCSX.SIO0.slots[s].pads[p]</code> where <code>s</code> is the slot number and <code>p</code> is the pad number, both indexed from 1, Lua-style. So <code>PCSX.SIO0.slots[1].pads[1]</code> accesses the first pad, and <code>PCSX.SIO0.slots[2].pads[1]</code> accesses the second pad.</p> <p>Each Pad table has the following functions:</p> <pre><code>getButton(button) -- Returns true if the specified button is pressed.\nsetOverride(button) -- Overrides the specified button.\nclearOverride(button) -- Clears the override for the specified button.\nsetAnalogMode(bool) -- Sets or clears the analog mode of this pad.\nmap() -- Forces the pad to be remapped. Useful after changing pad settings.\n</code></pre> <p>The button constants can be found in <code>PCSX.CONSTS.PAD.BUTTON</code>.</p> <p>You can for instance press the button Down on the first pad using the following code:</p> <pre><code>PCSX.SIO0.slots[1].pads[1].setOverride(PCSX.CONSTS.PAD.BUTTON.DOWN)\n</code></pre>"},{"location":"Lua/redux-basics/#execution-flow","title":"Execution flow","text":"<p>The Lua code has the following API functions available to it in order to control the execution flow of the emulator:</p> <ul> <li><code>PCSX.pauseEmulator()</code></li> <li><code>PCSX.resumeEmulator()</code></li> <li><code>PCSX.softResetEmulator()</code></li> <li><code>PCSX.hardResetEmulator()</code></li> </ul> <p>It's also possible to manipulate savestates using the following functions:</p> <ul> <li><code>PCSX.createSaveState() -- returns a slice representing the savestate</code></li> <li><code>PCSX.loadSaveState(slice)</code></li> <li><code>PCSX.loadSaveState(file)</code></li> </ul> <p>Additionally, the following function returns a string containing the .proto file used to serialize the savestate:</p> <ul> <li><code>PCSX.getSaveStateProtoSchema()</code></li> </ul> <p>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.</p> <p>Overall, this means the following is possible:</p> <pre><code>local compiler = require('protoc').new()\nlocal pb = require('pb')\n\nlocal state = PCSX.createSaveState()\ncompiler:load(PCSX.getSaveStateProtoSchema())\n\nlocal decodedState = pb.decode('SaveState', Support.sliceToPBSlice(state))\nprint(string.format('%08x', decodedState.registers.pc))\n</code></pre>"},{"location":"Lua/redux-basics/#messages","title":"Messages","text":"<p>The globals <code>print</code> and <code>printError</code> are available, and will display logs in the Lua Console. You can also use <code>PCSX.log</code> to display a line in the general Log window. All three functions should behave the way you'd expect from the normal <code>print</code> function in mainstream Lua.</p>"},{"location":"Lua/redux-basics/#gui","title":"GUI","text":"<p>You can move the cursor within the assembly window and the first memory view using the following functions:</p> <ul> <li><code>PCSX.GUI.jumpToPC(pc)</code></li> <li><code>PCSX.GUI.jumpToMemory(address[, width])</code></li> </ul>"},{"location":"Lua/redux-basics/#gpu","title":"GPU","text":"<p>You can take a screenshot of the current view of the emulated display using the following:</p> <ul> <li><code>PCSX.GPU.takeScreenShot()</code></li> </ul> <p>This will return a struct that has the following fields: <pre><code>struct ScreenShot {\nSlice data;\nuint16_t width, height;\nenum { BPP_16, BPP_24 } bpp;\n};\n</code></pre></p> <p>The <code>Slice</code> will contain the raw bytes of the screenshot data. It's meant to be written out using the <code>:writeMoveSlice()</code> method on a <code>File</code> object. The <code>width</code> and <code>height</code> will be the width and height of the screenshot, in pixels. The <code>bpp</code> will be either <code>BPP_16</code> or <code>BPP_24</code>, depending on the color depth of the screenshot. The size of the <code>data</code> Slice will be <code>height * width</code> multiplied by the number of bytes per pixel, depending on the <code>bpp</code>.</p>"},{"location":"Lua/redux-basics/#loading-and-executing-code","title":"Loading and executing code","text":"<p>While the basic Lua functions <code>dofile</code> and <code>loadfile</code> exist, some alternative functions are available to load and execute code in a more flexible way.</p> <ul> <li><code>Support.extra.addArchive(filename)</code> will load the given zip file, and will make it available to the <code>Support.extra.dofile</code> function. It is equivalent to the <code>-archive</code> command line flag. Note that if a file named <code>autoexec.lua</code> is found in the zip file, it will be executed automatically.</li> <li><code>Support.extra.dofile(filename)</code> will load the given file, and execute it. It is equivalent to <code>dofile</code>, 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 <code>Support.extra.addArchive</code> function.</li> <li><code>Support.extra.loadfile(filename)</code> will load the given file, and return a function that can be called to execute the file. It is equivalent to <code>loadfile</code>, but has the same file search algorithm as <code>Support.extra.dofile</code>.</li> <li><code>Support.extra.open(filename)</code> will open the given file as read only, and return a <code>File</code> object. It is roughly equivalent to <code>Support.File.open</code>, but has the same file search algorithm as <code>Support.extra.dofile</code>.</li> </ul> <p>If given the following directory structure:</p> <pre><code>.\n\u2514\u2500\u2500 bar.zip\n \u251c\u2500\u2500 test/baz.lua\n \u2514\u2500\u2500 test2/qux.lua\n</code></pre> <p>If <code>test/baz.lua</code> contains the following code:</p> <p><pre><code>Support.extra.dofile('../test2/qux.lua')\n</code></pre> Then running the following code: <pre><code>Support.extra.addArchive('bar.zip')\nSupport.extra.dofile('test/baz.lua')\n</code></pre> Will first load <code>test/baz.lua</code> from the zip file <code>bar.zip</code>, run it, which will in turn load <code>test2/qux.lua</code> from the zip file <code>bar.zip</code> again, and execute it.</p> <p>This allows distributing complex \"mods\" as zip files, which can be loaded and executed from the command line or the console.</p>"},{"location":"Lua/redux-basics/#miscellaneous","title":"Miscellaneous","text":"<ul> <li> <p><code>PCSX.quit([code])</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>code</code> parameter is optional, and will be the exit code of the emulator. If not specified, it'll default to 0.</p> </li> <li> <p><code>PCSX.getCPUCycles()</code> returns an unsigned 64-bit number indicating how many CPU cycles have elapsed. This can be paired with the <code>PCSX.CONSTS.CPU.CLOCKSPEED</code> constant to determine how much emulated time has passed.</p> </li> <li> <p><code>PCSX.Adpcm.NewEncoder</code> will return an Adpcm encoder object. The object has the following methods:</p> </li> <li><code>:reset([mode])</code> will reset the encoder, and set the mode to the given mode. The mode can be <code>'Normal'</code>, <code>'XA'</code>, <code>'High'</code>, <code>'Low'</code>, <code>'FourBits'</code>. The default mode is <code>'Normal'</code>, which enables all the filters available in the SPU. The <code>'XA'</code> mode limits the encoder to the filters available in the XA ADPCM format. The <code>'High'</code> mode uses the high-pass filter, and the <code>'Low'</code> mode uses the low-pass filter. The <code>'FourBits'</code> mode forces plain 4-bit Adpcm encoding.</li> <li><code>:processBlock(inData, [outData], [channels])</code> 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.</li> <li><code>:processSPUBlock(inData, [outData], [blockAttribute])</code> 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 <code>'OneShot'</code>. 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 <code>blockAttribute</code> parameter can be one of the following strings: <code>'OneShot'</code>, <code>'OneShotEnd'</code>, <code>'LoopStart'</code>, <code>'LoopBody'</code>, <code>'LoopEnd'</code>.</li> <li><code>:finishSPU([outData])</code> 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.</li> <li><code>:processXABlock(inData, [outData], [xaMode], [channels])</code> 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 <code>'XAFourBits'</code>. The other valid value is <code>'XAEightBits'</code>. 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:<ul> <li>4-bit mono: 224 samples aka 448 bytes</li> <li>4-bit stereo: 112 samples aka 448 bytes</li> <li>8-bit mono: 112 samples aka 224 bytes</li> <li>8-bit stereo: 56 samples aka 224 bytes</li> </ul> </li> </ul> <p>Using the encoder to process an input audio file is as simple as:</p> <pre><code>function encodeAudioLoop(inputFile, outputFile)\n local closeInput = false\n if type(inputFile) == 'string' then\n inputFile = Support.File.open(inputFile)\n closeInput = true\n end\n local audio = Support.File.ffmpegAudioFile(inputFile, {\n channels = 'Mono',\n frequency = 22050\n })\n\n local closeOutput = false\n if type(outputFile) == 'string' then\n outputFile = Support.File.open(outputFile, 'TRUNCATE')\n closeOutput = true\n end\n local blockCount = math.floor(audio:size() / (2 * 28))\n local bufferIn = ffi.new('int16_t[28]')\n local bufferOut = Support.NewLuaBuffer(16)\n\n local encoder = PCSX.Adpcm.NewEncoder()\n encoder:reset 'Normal'\n\n for i = 1, blockCount do\n audio:read(bufferIn, 28 * 2)\n local blockType = 'LoopBody'\n if i == 1 then blockType = 'LoopStart' end\n if i == blockCount then blockType = 'LoopEnd' end\n encoder:processSPUBlock(bufferIn, bufferOut, blockType)\n outputFile:write(bufferOut)\n end\n\n if closeInput then\n inputFile:close()\n end\n if closeOutput then\n outputFile:close()\n end\n audio:close()\nend\n</code></pre>"},{"location":"Lua/rendering/","title":"Rendering","text":"<p>PCSX-Redux is entirely running as an OpenGL3 application. All of its aspects, including the UI elements, are rendered using OpenGL primitives. This means there is very little boundaries between the various rendered elements on the screen.</p> <p>The rendering of the UI is done through ImGui, and a chunk of its API is bound is to Lua using bindings.</p> <p>A good portion of the OpenGL3 API is also bound to Lua, as well as the nanovg library.</p>"},{"location":"Lua/rendering/#emulated-gpu-rendering-pipeline","title":"Emulated GPU rendering pipeline","text":"<p>The content of the Output region is rendered in two steps. The first step is called the \"Offscreen rendering\", and is done during the emulated GPU vsyncs. Its job is to flush the contents of the VRAM texture to an offscreen texture, which may be of a different resolution. The resolution of the offscreen texture should be pixel perfect with that of the Output region. By default, the associated shader with this operation should only do a simple copy and interpolation, but as the first stage of the rendering pipeline, this can be used for some first pass output effect such as the first pass of a crt shader.</p> <p>The second step is called the \"Output rendering\", and is done every time the UI wants to refresh its display, which may or may not be at the same time as the emulated vsync. The resolution of the input will match exactly the resolution of the input texture, and the default shader should simply copy all the texels without any sort of interpolation, but as the second stage of the rendering pipeline, this can still be used for the second pass output effect.</p> <p>The crt-lottes implementation leverages these two passes to do the full CRT-like output.</p>"},{"location":"Lua/rendering/#shader-editor","title":"Shader editor","text":"<p>The shader editor is a simple text editor that allows to edit the shader code. It is not a full IDE, and it is not meant to be. Its point is to do quick iterations on the shader code, and to be able to see the result of the changes in real time.</p> <p>The shader editor is split in 3 regions:</p> <ul> <li> <p>The left tab is the vertex shader code. It is technically editable, but there shouldn't be much reason to edit it.</p> </li> <li> <p>The middle tab is the fragment shader code. This is the main shader code. It is editable, and the changes will be reflected in real time.</p> </li> <li> <p>The right tab is the Lua invoker code. This is the code that will be executed under multiple circumstances. It is editable, and the changes will be reflected in real time.</p> </li> </ul> <p>The Lua invoker code will be compiled and executed in a soft sandbox environment. The code can still access already created globals and mutate them, but any newly created global will be kept within the sandbox and won't be accessible from other Lua code. All these globals will be saved and restored with the normal emulator settings.</p> <p>When the shaders are compiled, the Vertex and Fragment shader code will be compiled together, and if the resulting program is valid, the Lua invoker code will be compiled and executed. If the Lua code fails to compile or execute, the shader will be considered invalid and the error will be displayed in the shader editor.</p> <p>This compilation order allows the Lua code to access the shader program uniforms, and to set them up as needed. The global <code>shaderProgramID</code> will be available to the Lua code, and will contain the ID of the shader program.</p> <p>The code is expected to export a few functions:</p> <ul> <li> <p><code>Draw</code>, which will be called periodically within the ImGui context, allowing to draw UI elements. The global <code>configureme</code> will be set to true when the user selects the \"Configure Shaders\" menu item. This allows to display a configuration UI to the user during this function call.</p> </li> <li> <p><code>Image(textureID, srcSizeX, srcSizeY, dstSizeX, dstSizeY)</code>, which will be called periodically within the ImGui context, when the emulator needs to draw the texture <code>textureID</code> at the given size. The texture ID is the OpenGL texture ID, and the size is in pixels. The code is at best expected to do a simple call to <code>imgui.Image(textureID, dstSizeX, dstSizeY, 0, 0, 1, 1)</code> to draw the texture. For the Emulated GPU Pipeline, this function will only be called on the Output shader, when being drawn to the Output region. As the function will be called during the ImGui context, it can capture certain ImGui state, such as the current ImGui cursor position, and use it to draw additional UI elements. Note that as with any normal ImGui function, this isn't the moment when the UI elements are actually drawn, but rather when the UI elements are queued to be drawn, meaning this isn't when the shader program will be executed, which is the point of the next function.</p> </li> <li> <p><code>BindAttributes(textureID, shaderProgramID, srcLocX, srcLocY, srcSizeX, srcSizeY, dstSizeX, dstSizeY)</code> will be called when the shader program is about to be executed, and needs to bind the attributes. The texture ID is the OpenGL texture ID, and the shader program ID is the OpenGL shader program ID. The location and sizes are in pixels, but are only used for the Emulated GPU Pipeline, when the Offscreen shader is being executed, as it needs to grab a portion of the VRAM texture to be rendered to the offscreen texture.</p> </li> </ul> <p>Additionally, it is possible to programmatically set the content of the editors using the following methods:</p> <pre><code>PCSX.GUI.OffscreenShader.setDefaults()\nPCSX.GUI.OffscreenShader.setTextVS(text)\nPCSX.GUI.OffscreenShader.setTextPS(text)\nPCSX.GUI.OffscreenShader.setTextL(text)\nPCSX.GUI.OutputShader.setDefaults()\nPCSX.GUI.OutputShader.setTextVS(text)\nPCSX.GUI.OutputShader.setTextPS(text)\nPCSX.GUI.OutputShader.setTextL(text)\n</code></pre> <p>The <code>setDefaults</code> method will set the default shader code, and the <code>setText*</code> methods will set the shader code to the given string. The <code>text</code> argument can be either an actual string, or a <code>File</code> object.</p>"},{"location":"Lua/rendering/#imgui","title":"ImGui","text":"<p>The ImGui API is bound to Lua, and can be used to draw UI elements. The ImGui API is documented on the ImGui source code. There is also an interactive manual available.</p> <p>Not all functions are necessarily bound to Lua, and one can check the bindings code to see which functions are bound, and why some functions are not bound.</p> <p>The main reason for not binding a function is that its arguments or return values are not trivial to bind. For example, the <code>ImGui::Text</code> C++ function is not bound, as it takes a variadic number of arguments, which is not possible to bind in Lua easily. Instead, the <code>ImGui::TextUnformatted</code> C++ function is bound, which takes a single string argument.</p> <p>The emulator will periodically try to call the global function <code>DrawImguiFrame</code> with no arguments. If the function is not defined, nothing will happen. If the function fails to execute, it will be removed from the global environment, and the emulator will stop trying to call it until a new global is defined.</p> <p>The <code>DrawImguiFrame</code> function is expected to call the <code>imgui.Begin</code> function to create a new ImGui window, as there is no default window created by the emulator for the Lua context. The <code>DrawImguiFrame</code> function is also expected to call the <code>imgui.End</code> function as normal with the ImGui API.</p> <p>Some extra functions are bound to Lua beyond the API listed above:</p> <ul> <li> <p><code>imgui.extra.ImVec2.New(x, y)</code> will create a new FFI <code>ImVec2</code> object. The <code>ImVec2</code> object is a simple struct with two fields, <code>x</code> and <code>y</code>. The <code>New</code> function takes two optional arguments, the <code>x</code> and <code>y</code> values, and returns the new <code>ImVec2</code> object.</p> </li> <li> <p><code>imgui.extra.getCurrentViewportId()</code> will return the current viewport ID. Viewports in ImGui are a way to split the ImGui context into multiple independent contexts, and the viewport ID is a unique identifier for each viewport. Basically, each viewport is a physical window from the operating system, and it can contain one or more ImGui windows.</p> </li> <li> <p><code>imgui.extra.getViewportFlags(id)</code> will return the viewport flags for the specified viewport. The viewport flags are of the type <code>ImGuiViewportFlags_</code> in the ImGui C++ API, and is a bitmask of flags, which are exposed as individual values in the Lua generated bindings.</p> </li> <li> <p><code>imgui.extra.setViewportFlags(id, flags)</code> will set the viewport flags for the specified viewport. The proper usage of this function is to call <code>imgui.extra.getViewportFlags</code> to get the current flags, modify the flags as needed, and then call <code>imgui.extra.setViewportFlags</code> to set the new flags.</p> </li> <li> <p><code>imgui.extra.getViewportPos(id)</code> will return the position of the specified viewport. The position is returned as an <code>ImVec2</code> object.</p> </li> <li> <p><code>imgui.extra.getViewportSize(id)</code> will return the size of the specified viewport. The size is returned as an <code>ImVec2</code> object.</p> </li> <li> <p><code>imgui.extra.getViewportWorkPos(id)</code> will return the work position of the specified viewport. The work position is returned as an <code>ImVec2</code> object.</p> </li> <li> <p><code>imgui.extra.getViewportWorkSize(id)</code> will return the work size of the specified viewport. The work size is returned as an <code>ImVec2</code> object.</p> </li> <li> <p><code>imgui.extra.getViewportDpiScale(id)</code> will return the DPI scale of the specified viewport. The DPI scale is returned as a number. A value of 1.0 means that the DPI scale for this viewport is 100%.</p> </li> <li> <p><code>imgui.extra.InputText(label, text[, flags])</code> will create an input text widget. The <code>label</code> is the label to display next to the input text, and the <code>text</code> is the current text to display in the input text. The <code>flags</code> are optional, and are the same flags as the ones used by the <code>imgui::InputText</code> C++ function. The function will return a boolean indicating if the text has changed or not, and the new text.</p> </li> <li> <p><code>imgui.extra.InputTextWithHint(label, hint, text[, flags])</code> will create an input text widget. The <code>label</code> is the label to display next to the input text, and the <code>hint</code> is the hint to display in the input text when the text is empty. The <code>text</code> is the current text to display in the input text. The <code>flags</code> are optional, and are the same flags as the ones used by the <code>imgui::InputTextWithHint</code> C++ function. The function will return a boolean indicating if the text has changed or not, and the new text.</p> </li> <li> <p><code>imgui.extra.logText(text)</code> will call the <code>imgui::LogText</code> C++ function, which will add the given text to current log buffer.</p> </li> <li> <p><code>PCSX.GUI.useMainFont()</code> will call the <code>imgui::PushFont</code> C++ function with the proportional font. It will need to be followed by a call to <code>imgui.PopFont()</code>.</p> </li> <li> <p><code>PCSX.GUI.useMonoFont()</code> will call the <code>imgui::PushFont</code> C++ function with the monospace font. It will need to be followed by a call to <code>imgui.PopFont()</code>.</p> </li> </ul>"},{"location":"Lua/rendering/#safety","title":"Safety","text":"<p>The ImGui API will frequently assert and crash the process if the API calls are imbalanced. For example, if the <code>imgui.BeginTable</code> function is called without calling the <code>imgui.EndTable</code> function, the process will most likely crash.</p> <p>This can be problematic when using the ImGui API from Lua, as the Lua code is not able to catch the crash, and the process will crash without any indication of what went wrong.</p> <p>The main reason for imbalanced API calls can be attributed to the user code throwing an exception, which will cause the Lua code to unwind the stack, and the ImGui API will not be able to properly clean up its state.</p> <p>For example, consider the following code:</p> <pre><code>function DrawImguiFrame()\n if imgui.Begin(\"My Window\") then\n error(\"Something went wrong\")\n end\n imgui.End()\nend\n</code></pre> <p>The <code>imgui.Begin</code> function will be called, but the <code>imgui.End</code> function will not be called, as the <code>error</code> function will unwind the stack, and the <code>imgui.End</code> function will never be called.</p> <p>In order to mitigate this, safe wrappers are provided for all of the ImGui Begin*/End* functions. The safe wrappers will catch any exception thrown by the user code, and will call the corresponding End* function if the Begin* function returned true. The error will be rethrown after the End* function is called. The wrapped lambda will only be called if the Begin* function returned true.</p> <p>The example above can be rewritten as:</p> <pre><code>function DrawImguiFrame()\n imgui.safe.Begin(\"My Window\", function()\n error(\"Something went wrong\")\n end)\nend\n</code></pre>"},{"location":"Lua/rendering/#nanovg","title":"NanoVG","text":"<p>The NanoVG library is bound to Lua, and can be used to draw arbitrary vector graphics on top of the emulator. The NanoVG API is documented on the NanoVG source code. The API is very similar to the HTML5 Canvas API, meaning that one can use the MDN CanvasRenderingContext2D documentation and other related documentation to learn how to use it.</p> <p>Using an HTML5 canvas toybox like this one is a good way to learn how to use this API safely.</p> <p>Note that the NanoVG rendering will happen after the ImGui rendering, meaning that the NanoVG rendering will be on top of the ImGui rendering, regardless of the order in which the NanoVG and ImGui functions are called.</p> <p>Most of the NanoVG API is bound to Lua, with the exception of the following functions:</p> <ul> <li><code>nvgBeginFrame</code></li> <li><code>nvgCancelFrame</code></li> <li><code>nvgEndFrame</code></li> <li><code>nvgCreateImage</code></li> <li><code>nvgCreateImageMem</code></li> </ul> <p>In addition, the enums and some constructors for the structures used in NanoVG are available as extra values and functions. Please refer to the Lua source code for more details.</p> <p>The general idea is that the emulator will call <code>nvgBeginFrame</code> and <code>nvgEndFrame</code> before and after the Lua code is executed, and the Lua code will be able to call the other functions to draw the vector graphics.</p> <p>The proper way to use the NanoVG API is to call <code>nvg:queueNvgRender(function() ... end)</code>, when in an ImGui window in order to queue the NanoVG rendering for this specific window.</p> <p>The <code>nvg:queueNvgRender</code> function takes a single argument, which is a function that will be called when the NanoVG rendering is being executed. The function will be called without argument.</p> <p>All of the NanoVG functions are bound to the <code>nvg</code> object, which is a proxy object to the proper NanoVG context, meaning it is only valid within the function passed to <code>nvg:queueNvgRender</code>.</p> <p>This allows the user to call the NanoVG functions without having to pass the NanoVG context as the first argument, as it is done automatically by the proxy object.</p> <p>Note that the font used by the emulator is also loaded into the NanoVG context, meaning that it is possible to use <code>nvg:Text</code> without having to load a font first.</p>"},{"location":"Lua/rendering/#example-of-using-everything-together","title":"Example of using everything together","text":"<p>As the NanoVG rendering is very low level, and requires a viewport to draw to, it is required to use the ImGui API to draw some UI, grab the positions of the vector graphics to add, and then queue some NanoVG calls within some ImGui context to draw the wanted vector graphics.</p> <p>The following example will draw a red rectangle in the middle of the Output region. The rectangle will be 100x100 pixels in size, and will be drawn on top of the emulator rendering. It should follow around the Output region when resizing or moving the window.</p> <p>In order to work, this example requires the code to be executed in the <code>Image</code> function of the Output shader invoker, so we can get the position of the Output region to draw to.</p> <pre><code>function Image(textureID, srcSizeX, srcSizeY, dstSizeX, dstSizeY)\n -- This helper is provided by the emulator, and will properly calculate\n -- arbitrary coordinates within an ImGui image that is dstSizeX x dstSizeY\n -- in size. The first two arguments are the coordinates to convert, and\n -- the middle two arguments are the boundaries of the source image.\n\n -- Here, we are using (1.0, 1.0) as the source image size, but it could\n -- be any other size, as long as the coordinates are within the boundaries\n -- of the source image. For example, if the source image is 320x240, then\n -- the coordinates should be within (0, 0) and (320, 240), and the helper\n -- will properly convert the coordinates to the destination image size.\n\n local cx, cy = PCSX.Helpers.UI.imageCoordinates(0.5, 0.5, 1.0, 1.0, dstSizeX, dstSizeY)\n\n -- As explained, we can't call NanoVG functions directly, so we need to\n -- queue the rendering of the vector graphics.\n nvg:queueNvgRender(function()\n nvg:beginPath()\n nvg:rect(cx - 50, cy - 50, 100, 100)\n nvg:fillColor(nvg.Color.New(1, 0, 0, 1))\n nvg:fill()\n end)\n imgui.Image(textureID, dstSizeX, dstSizeY, 0, 0, 1, 1)\nend\n</code></pre>"},{"location":"Lua/web-server/","title":"Webserver Lua API","text":"<p>When the webserver is enabled, it will expose the <code>/api/v1/lua/</code> prefix, which can be used to execute Lua code on the emulator. When an endpoint with this prefix is called, the Lua table <code>PCSX.WebServer.Handlers</code> will be inspected to find a handler for the rest of the path in the endpoint. If a handler is found, it will be called with a request object representing the query, and it has to return a string, which will be sent back to the client as the response. If no handler is found, a 404 error will be returned. If an error occurs while executing the handler, a 500 error will be returned.</p> <p>The request object has the following fields:</p> <ul> <li><code>form</code> is a table of the form data in the request. This is only available if the request is a POST request, and the content type is <code>application/x-www-form-urlencoded</code>.</li> <li><code>headers</code> is a table of the headers in the request.</li> <li><code>method</code> is the HTTP method of the request.</li> <li><code>urlData</code> is a table with more information about the URL. It has the following string fields:<ul> <li><code>fragment</code></li> <li><code>host</code></li> <li><code>path</code></li> <li><code>port</code></li> <li><code>query</code></li> <li><code>schema</code></li> <li><code>userInfo</code></li> </ul> </li> </ul> <p>If the returned string starts with the characters \"HTTP/\", then the web server will consider the response string is a full HTTP response with headers, and will send it as-is to the client. Otherwise, the response string will be sent as the body of a normal 200 response.</p>"}]} |