ff50cf2552
* improve debugger disasm, `:sym-name` and fix Windows builds * >:( * use this inline constexpr thing?? * fine use strings then * please.... please work... * fix windows debugger oopsie * display rip as goal addr as well * [debugger] attempt to backtrace even if landed on some garbage memory * Update CMakePresets.json |
||
---|---|---|
.github | ||
.vs | ||
.vscode | ||
assets | ||
bin | ||
common | ||
decompiler | ||
decompiler_out | ||
docs | ||
game | ||
goal_src | ||
goalc | ||
iso_data | ||
log | ||
out | ||
resources | ||
scripts | ||
test | ||
third-party | ||
tools | ||
.clang-format | ||
.editorconfig | ||
.gitattributes | ||
.gitignore | ||
.gitmodules | ||
.projectile | ||
CMakeLists.txt | ||
CMakePresets.json | ||
CMakeSettings.json | ||
default.nix | ||
flake.lock | ||
flake.nix | ||
LICENSE.txt | ||
README.md | ||
shell.nix | ||
sunken-obs.manual_restore_reminder | ||
Taskfile.yml | ||
test_code_coverage.sh | ||
test.sh |
Table of Contents
- Table of Contents
- Project Description
- Getting Started - Linux (Ubuntu)
- Getting Started - Linux (Arch)
- Getting Started - Nixpkgs
- Getting Started - Windows
- Project Layout
- Directory Layout
- More Documentation
- ASan Build
Project Description
This project is to port Jak 1 (NTSC, "black label" version) to PC. Over 99% of this game is written in GOAL, a custom Lisp language developed by Naughty Dog. Our strategy is:
- decompile the original game code into human-readable GOAL code
- develop our own compiler for GOAL and recompile game code for x86-64
- create a tool to extract game assets into formats that can be easily viewed or modified
- create tools to repack game assets into a format that our port uses.
Our objectives are:
- make the port a "native application" on x86-64, with high performance. It shouldn't emulated, interpreted, or transpiled.
- Our GOAL compiler's performance should be around the same as unoptimized C.
- try to match things from the original game and development as possible. For example, the original GOAL compiler supported live modification of code while the game is running, so we do the same, even though it's not required for just porting the game.
- support modifications. It should be possible to make edits to the code without everything else breaking.
We support both Linux and Windows on x86-64.
Current Status
So far, we've decompiled around 130,000 lines of GOAL code, out of an estimated 500,000 total lines and we've started work on an OpenGL renderer. Currently, the main display process (*dproc*
) runs and sends data to our renderer. We can load textures, text files, and level files. Using keyboard controls, we can open the debug menu and turn on some simple debug visualizations.
Video of debug menu and actor visibility: https://www.youtube.com/watch?v=0AoiYY4S7nI
To help with decompiling, we've built a decompiler that can process GOAL code and unpack game assets. We manually specify function types and locations where the original code had type casts until the decompiler succeeds, then we clean up the output of the decompiled code by adding comments and adjusting formatting, then save it in goal_src
. Our decompiler is designed specifically for processing the output of the original GOAL compiler. As a result, when given correct casts, it often produces code that can be directly fed into a compiler and works perfectly. This is tested as part of our unit tests, and so far we have around 130,000 lines (220 files) that pass.
We don't save any assets from the game - you must bring your own copy of the game and use the decompiler to extract assets.
What's Next
We are focusing on three areas:
- Continue decompilation of GOAL code. Recently we have started on the "gameplay" code for creatures and objects in the game world, which is going pretty fast.
- Improve the decompiler. We are always finding new features in the GOAL language.
- Investigate more complicated renderers. The font and debug rendering is much simpler than the highly optimized renderers used for characters and backgrounds. We are starting to look at some of these renderers in detail.
Getting Started - Linux (Ubuntu)
Install packages and init repository:
sudo apt install gcc make cmake build-essential g++ nasm clang-format
git submodule update --init --recursive
Compile:
cmake -B build && cmake --build build -j 8
Run tests:
./test.sh
Note: we have found that clang
and lld
are significantly faster to compile and link than gcc
, generate faster code, and have better warning messages. To install these:
sudo apt install lld clang
and run cmake
(in a fresh build directory) with:
cmake -DCMAKE_SHARED_LINKER_FLAGS="-fuse-ld=lld" -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ..
this decreases the compile and link time from ~10 seconds to ~4 seconds.
Getting Started - Linux (Arch)
Install packages and init repository:
sudo pacman -S gcc make cmake base-devel g++ nasm
git submodule update --init --recursive
Compile:
cmake -B build && cmake --build build -j 8
Run tests:
./test.sh
Getting Started - Nixpkgs
If your Nix supports flakes:
nix develop # development environment
nix build # package
nix develop '.#jak-asan-dev' # development environment with Clang
nix build '.#jak-asan' # package with Clang ASan build
Otherwise, with traditional Nix:
nix-shell # development environment
nix-build # package
nix-shell -A packages.x86_64-linux.jak-asan-dev # development environment with Clang
nix-build -A packages.x86_64-linux.jak-asan # package with Clang ASan build
Getting Started - Windows
Install Visual Studio 2019 and get the C++ and CMake tools via the Visual Studio Installer
On Windows, it's recommended to get Scoop to use as a package manager, making the follow steps much easier. Follow the steps on the bottom of the homepage here https://scoop.sh/
Once Scoop is installed, run the following command:
scoop install llvm nasm
Initialize the repository's third-party dependencies:
git submodule update --init --recursive
Open the project as a CMake project, browse for the root level CMakeLists.txt
:
In the toolbar, you should be able to select an individual component to compile, or combine within the root CMakeLists.txt. In the future we will pre-define configurations to make this easier.
You may also wish to view the files that pertain to each CMake target, rather than the project as it is normally:
Building and Running the Game
Getting a running game involves 4 steps:
- Build C++ tools (follow steps above)
- Extract assets from game
- Build game
- Run game
Extract Assets
Running the decompiler on the entire game is slow and not needed, so it is recommended to just run it on data. Edit decompiler/config/jak1-ntsc_black_label.jsonc
and disable decompile_code
.
"decompile_code": false, // change this to false, don't decompile code
Place a copy of the game's files in iso_data
, then run the decompiler with the scripts/decomp.sh
script.
Build Game
Run the OpenGOAL compiler build/goalc/goalc
. Enter (mi)
to build the "iso"
target, which contains everything we have so far.
Run Game
In a separate terminal, start the runtime with build/game/gk -fakeiso -debug
. Then, in the OpenGOAL window, run (mi)
to create the data for the game and give the REPL information for running code, (lt)
to connect, (lg)
to load the game engine and (test-play)
to start the game engine. If it all works right, it will look something like this:
g > (lt)
[Listener] Socket connected established! (took 0 tries). Waiting for version...
Got version 0.8 OK!
[Debugger] Context: valid = true, s7 = 0x147d24, base = 0x2123000000, tid = 2438049
gc> (lg)
10836466 #xa559f2 0.0000 ("game" "kernel")
gc> (test-play)
(play :use-vis #t :init-game #f) has been called!
0 #x0 0.0000 0
gc>
Then, in the graphics window, you can use the period key to bring up the debug menu.
Check out the pc_debug
and examples
folder under goal_src
for some examples of GOAL code we wrote. They have instructions for how to run them.
Project Layout
There are four main components to the project.
The first is goalc
, which is a GOAL compiler for x86-64. Our implementation of GOAL is called OpenGOAL. All of the compiler source code is in goalc
. To run the compiler on Linux, there is a script gc.sh
. The compiler is controlled through a prompt which can be used to enter commands to compile, connect to a running GOAL program for interaction, run the OpenGOAL debugger, or, if you are connected to a running GOAL program, can be used as a REPL to run code interactively. In addition to compiling code files, the compiler has features to pack and build data files.
The second component to the project is the decompiler. You must have a copy of the PS2 game and place all files from the DVD into the iso_data
folder. Then run decomp.sh
(Linux) to run the decompiler. For Windows, it is the decomp-jak1.bat
file, and it wants your game's DVD files in a jak1
folder inside iso_data
. The decompile will extract assets to the assets
folder. These assets will be used by the compiler when building the port, and you may want to turn asset extraction off after running it once. The decompiler will output code and other data intended to be inspected by humans in the decompiler_out
folder. Stuff in this folder will not be used by the compiler.
The third is the game source code, written in OpenGOAL. This is located in goal_src
. All GOAL and GOOS code should be in this folder. Right now most of this is placeholders, but you can take a look at kernel/gcommon.gc
or goal-lib.gc
to see some in-progress source code.
The final component is the "runtime", located in game
. This is the part of the game that's written in C++. In the port, that includes:
-
The "C Kernel", which contains the GOAL linker and some low-level GOAL language features. GOAL has a completely custom dynamically linked object file format so in order to load the first GOAL code, you need a linker written in C++. Some low-level functions for memory allocation, communicating with the I/O Processor, symbol table, strings, and the type system are also implemented in C, as these are required for the linker. It also listens for incoming messages from the compiler and passes them to the running game. This also initializes the game, by initializing the PS2 hardware, allocating the GOAL heaps, loading the GOAL kernel off of the DVD, and executing the kernel dispatcher function. This is in the
game/kernel
folder. This should be as close as possible to the game, and all differences should be noted with a comment. -
Implementation of Sony's standard library. GOAL code can call C library functions, and Naughty Dog used some Sony library functions to access files, memory cards, controllers, and communicate with the separate I/O Processor. The library functions are in
game/sce
. Implementations of library features specific to the PC port are located ingame/system
. -
The I/O Processor driver, Overlord. The PS2 had a separate CPU called the I/O Processor (IOP) that was directly connected to the DVD drive hardware and the sound hardware. Naughty Dog created a custom driver for the IOP that handled streaming data off of the DVD. It is much more complicated than I first expected. It's located in
game/overlord
. Like the C kernel, we try to keep this as close as possible to the actual game. -
Sound Code. Naughty Dog used a third party library for sound. We have not started on this yet.
-
PC specific graphics stuff. We have not started on this yet.
Directory Layout
.github
: GitHub actions CI setup.vs
: Visual Studio project configurationsassets
: extracted assets (textures, translated game text) generated by the decompiler. Not included in the repository. To be used when building the PC port.build
: C++ CMake build foldercommon
: common C++ code shared between the compiler, decompiler, and game.audio
: tools for decoding the audio filescross_os_debug
: platform-independent library for implementing the OpenGOAL debugger. Linux-only currentlycross_sockets
: platform-independent library for sockets. Used to connect the compiler to a running game. Linux and Windows.goos
: the compiler-time macro language and parser for OpenGOAL.type_system
: the OpenGOAL type systemtexture
: texture unpacking and format conversionutil
,math
,log
: Random utility functions for accessing files, timers, etc.
decompiler
: Source code for the decompileranalysis
: analysis algorithmsconfig
: JSON config files for the decompiler and type definition file.data
: utilities to extract assets from the gameDisasm
: MIPS disassemblerFunction
: Tools for analyzing GOAL functionsgui
: an early prototype of a Python GUI for reading the output of the decompilerIR2
: the "Intermediate Representation" for GOAL functions and expressionsObjectFile
: Utilities for processing the GOAL object file format.scripts
: Useful scripts for setting up the decompilationutil
: random utilitiesVuDisasm
: disassembler for VU code
decompiler_out
: output of the decompiler that's not automatically used by the compiler. This is for humans to read and use. Not included in the repository.docs
: more documentation!game
: the source code for the game executablecommon
: shared stuff between thekernel
(EE) andoverlord
(IOP)graphic
: PC Port graphicskernel
: the part of the GOAL kernel written in C. The entry point for the game is inkboot.cpp
.overlord
: the I/O processor driver used to get data off of the DVDsce
: the Sony library implementationsystem
: PC-port specific OS-level stuff, like file I/O, threads, controllers, debug network connection
goal_src
: The GOAL code for the game. It's mostly empty now.build
: info related to the GOAL build system.engine
: the game enginekernel
: The GOAL kernellevels
: Level specific code.
goalc
: The OpenGOAL compilercompiler
: The implementation of the OpenGOAL languagedata_compiler
: Tools for packing datadebugger
: The OpenGOAL debugger (part of the compiler)emitter
: x86-64 emitter and object file generatorlistener
: The OpenGOAL listener, which connects the compiler to a running GOAL program for the interactive REPLmake
: The OpenGOAL build system, builds both code and data filesregalloc
: Register allocator
iso_data
:out
: Outputs from the build process. Only theiso
subfolder should contain assets used by the game.iso
: Final outputs that are used by the game.obj
: Object files generated by the compiler.
resources
: To be removed. Contains fake versions of some files required to get things booting.scripts
: Utility scripts. Windows-specific batch files are in abatch
folder while Unix shell scripts are in ashell
folder.test
: Unit tests (run on GitHub Actions)third-party
: Third party libraries- CMake Code Coverage. For code coverage statistics on GitHub builds
fmt
. String formatting librarygoogletest
: Test frameworkinja
: templating library used for generating test code for compiler testslzokay
: decompression code for Jak 2 and later DGOsmman
: Windows library used to emulatemmap
on Linuxrun-clang-format
: Utility to check and enforce code formattingrun-clang-tidy
zydis
: x86-64 disassembler used in the OpenGOAL debuggerjson
: A JSON libraryreplxx
: Used for the REPL input. Supports history and useful editing shortcuts.svpng
: Save a PNG file
On Windows / Visual Studio
Until 16.9 Preview 4, when attaching a debugger to the ASan build, you must disable breaking on Win32 Access Violation exceptions. See the relevant section Debugging - Exceptions
here https://devblogs.microsoft.com/cppblog/asan-for-windows-x64-and-debug-build-support/#known-issues