diff --git a/scripts/update-file-progress.py b/.github/scripts/update-file-progress.py
similarity index 70%
rename from scripts/update-file-progress.py
rename to .github/scripts/update-file-progress.py
index b4492c773f..cbdb3b15f7 100644
--- a/scripts/update-file-progress.py
+++ b/.github/scripts/update-file-progress.py
@@ -1,7 +1,6 @@
import glob
src_files = glob.glob("./goal_src/**/*.g[cs]", recursive=True)
-data_files = glob.glob("./goal_src/**/*.gd", recursive=True)
# Find how many of each have been started
@@ -20,12 +19,6 @@ for f in src_files:
else:
src_files_started = src_files_started + 1
-for f in data_files:
- with open(f, "r") as temp_file:
- line_count = len(temp_file.readlines())
- if line_count > 7:
- data_files_started = data_files_started + 1
-
import json
with open('./docs/gh-pages-proj/src/config/progress.json', 'r+', encoding='utf-8') as f:
data = {
@@ -33,9 +26,7 @@ with open('./docs/gh-pages-proj/src/config/progress.json', 'r+', encoding='utf-8
'fileProgress': {
'src_files_total': len(src_files),
'src_files_finished': src_files_finished,
- 'src_files_started': src_files_started,
- 'data_files_total': len(data_files),
- 'data_files_started': data_files_started
+ 'src_files_started': src_files_started
}
}
}
diff --git a/.github/scripts/update-gallery.py b/.github/scripts/update-gallery.py
new file mode 100644
index 0000000000..c6bc332feb
--- /dev/null
+++ b/.github/scripts/update-gallery.py
@@ -0,0 +1,63 @@
+import glob
+import os
+from pathlib import Path
+import json
+
+galleryLinks = {
+ 'jak1': {
+ 'name': "Jak 1",
+ 'media': [],
+ },
+ 'jak2': {
+ 'name': "Jak 2",
+ 'media': [],
+ },
+ 'jak3': {
+ 'name': "Jak 3",
+ 'media': [],
+ },
+ 'jakx': {
+ 'name': "Jak X",
+ 'media': [],
+ },
+ 'misc': {
+ 'name': "Miscellaneous",
+ 'media': [],
+ }
+}
+
+def get_links(key, folder_to_search):
+ if os.path.isdir(folder_to_search):
+ files = glob.glob(folder_to_search + "/*.png", recursive=True)
+ files.extend(glob.glob(folder_to_search + "/*.jpg", recursive=True))
+ files.extend(glob.glob(folder_to_search + "/*.jpeg", recursive=True))
+ for f in files:
+ galleryLinks[key]["media"].append({
+ 'fileName': os.path.basename(f),
+ 'timestamp': Path(f).stem.split("_")[1],
+ 'caption': Path(f).stem.split("_")[0].replace("-", " ").title(),
+ 'video': False
+ })
+ # get videos potentially
+ if os.path.exists("{}/videos.json".format(folder_to_search)):
+ with open("{}/videos.json".format(folder_to_search), 'r') as f:
+ data = json.load(f)
+ for video in data:
+ galleryLinks[key]["media"].append({
+ 'link': video["link"].replace("watch?v=", "embed/"),
+ 'timestamp': video["timestamp"],
+ 'video': True
+ })
+ # sort by timestamp
+ galleryLinks[key]["media"].sort(key=lambda x: x["timestamp"], reverse=True)
+
+get_links('jak1', './docs/gh-pages-proj/src/assets/gallery/jak1')
+get_links('jak2', './docs/gh-pages-proj/src/assets/gallery/jak2')
+get_links('jak3', './docs/gh-pages-proj/src/assets/gallery/jak3')
+get_links('jakx', './docs/gh-pages-proj/src/assets/gallery/jakx')
+get_links('misc', './docs/gh-pages-proj/src/assets/gallery/misc')
+
+with open('./docs/gh-pages-proj/src/config/gallery.json', 'r+', encoding='utf-8') as f:
+ f.seek(0)
+ json.dump(galleryLinks, f, ensure_ascii=False, indent=2)
+ f.truncate()
diff --git a/.github/workflows/build-doc-app.yaml b/.github/workflows/build-doc-app.yaml
index 5e33b79828..f361a44259 100644
--- a/.github/workflows/build-doc-app.yaml
+++ b/.github/workflows/build-doc-app.yaml
@@ -21,7 +21,10 @@ jobs:
uses: actions/checkout@v2
- name: Update Line Count
- run: python ./scripts/update-file-progress.py
+ run: python ./.github/scripts/update-file-progress.py
+
+ - name: Update Gallery Links
+ run: python ./.github/scripts/update-gallery.py
- name: Update Site
run: |
diff --git a/.github/workflows/windows-workflow.yaml b/.github/workflows/windows-workflow.yaml
index b6e36d381b..f5da63552e 100644
--- a/.github/workflows/windows-workflow.yaml
+++ b/.github/workflows/windows-workflow.yaml
@@ -48,7 +48,7 @@ jobs:
run: git submodule update --init --recursive -j 2
- name: Install Dependencies
- run: choco install nasm
+ run: Choco-Install -PackageName nasm
- name: Setup Buildcache
uses: mikehardy/buildcache-action@v1.2.2
diff --git a/.gitignore b/.gitignore
index c5ce8c560b..76c0d4785f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,6 @@ gfx_dumps/*
# game stuff
game_config/*
imgui.ini
+
+# website stuff
+node_modules/
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 69a3110e63..08ff5ce5e2 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -48,13 +48,31 @@
}
]
},
+ "(lwu .., 4\\(..\\).*)(dma-buffer)": {
+ "filterFileRegex": ".*ir2\\.asm",
+ "decorations": [
+ {},
+ {
+ "overviewRulerColor": "transparent",
+ "color": "red"
+ }
+ ]
+ },
+ "(\\d+\\(sp\\))": {
+ "filterFileRegex": ".*ir2\\.asm",
+ "decorations": [
+ {
+ "overviewRulerColor": "transparent",
+ "color": "#00ff08"
+ }
+ ]
+ },
"(sp, -?\\d+ )": {
"filterFileRegex": ".*ir2\\.asm",
"decorations": [
{
"overviewRulerColor": "transparent",
- "color": "#00ff08",
- "fontWeight": "bold"
+ "color": "#00ff08"
}
]
},
@@ -272,7 +290,18 @@
}
]
},
- "(WARN:.*)": {
+ "(WARN: Unsupported.*)": {
+ "filterFileRegex": ".*ir2\\.asm",
+ "decorations": [
+ {
+ "overviewRulerColor": "#ea00ff",
+ "color": "#ea00ff",
+ "fontWeight": "bold",
+ "filterFileRegex": ".*ir2\\.asm"
+ }
+ ]
+ },
+ "(WARN: (?!Unsupported).*)": {
"filterFileRegex": ".*ir2\\.asm",
"decorations": [
{
diff --git a/README.md b/README.md
index eb13621d99..04acb53d07 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
+
## Table of Contents
@@ -17,15 +18,20 @@
- [Table of Contents](#table-of-contents)
- [Project Description](#project-description)
+- [Current Status](#current-status)
+- [What's Next](#whats-next)
- [Getting Started - Linux (Ubuntu)](#getting-started---linux-ubuntu)
- [Getting Started - Linux (Arch)](#getting-started---linux-arch)
- [Getting Started - Nixpkgs](#getting-started---nixpkgs)
- [Getting Started - Windows](#getting-started---windows)
+- [Building and Running the Game](#building-and-running-the-game)
+ - [Extract Assets](#extract-assets)
+ - [Build Game](#build-game)
+ - [Run Game](#run-game)
- [Project Layout](#project-layout)
- [Directory Layout](#directory-layout)
-- [More Documentation](#more-documentation)
-- [ASan Build](#asan-build)
- - [On Windows / Visual Studio](#on-windows--visual-studio)
+ - [On Windows / Visual Studio](#on-windows--visual-studio)
+
## Project Description
@@ -44,6 +50,8 @@ Our objectives are:
We support both Linux and Windows on x86-64.
+We have a Discord server where we discuss development. https://discord.gg/BVEHQmm8
+
## Current Status
So far, we've decompiled around 341,233 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.
@@ -138,14 +146,16 @@ 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
+Install Visual Studio 2022 and get the `Desktop development with C++` workload during the installation process.
+
+> if you already have visual studio and don't have this installed - open your `Visual Studio Installer` and modify the installation
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:
-```ps1
-scoop install llvm nasm
+```sh
+scoop install git llvm nasm
```
Initialize the repository's third-party dependencies:
@@ -154,17 +164,13 @@ 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`:
+Open the project as a CMake project.
-
+
-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.
+Then build the entire project
-
-
-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
@@ -200,7 +206,7 @@ gc> (test-play)
(play :use-vis #t :init-game #f) has been called!
0 #x0 0.0000 0
-gc>
+gc>
```
Then, in the graphics window, you can use the period key to bring up the debug menu. Controllers also work, using the same mapping as the original game.
diff --git a/Taskfile.yml b/Taskfile.yml
index a6f5c6a09c..229c5d42d0 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -9,7 +9,7 @@ tasks:
ignore_error: true
run-game:
cmds:
- - ./out/build/Release/bin/gk.exe -fakeiso -debug
+ - ./out/build/Release/bin/gk.exe -fakeiso -debug -v
run-game-headless:
cmds:
- ./out/build/Release/bin/gk.exe -fakeiso -debug -nodisplay
diff --git a/common/custom_data/TFrag3Data.cpp b/common/custom_data/TFrag3Data.cpp
index 78fcfafa8b..b7f004021f 100644
--- a/common/custom_data/TFrag3Data.cpp
+++ b/common/custom_data/TFrag3Data.cpp
@@ -11,6 +11,20 @@ void StripDraw::serialize(Serializer& ser) {
ser.from_ptr(&num_triangles);
}
+void InstancedStripDraw::serialize(Serializer& ser) {
+ ser.from_ptr(&mode);
+ ser.from_ptr(&tree_tex_id);
+ ser.from_pod_vector(&vertex_index_stream);
+ ser.from_pod_vector(&instance_groups);
+ ser.from_ptr(&num_triangles);
+}
+
+void TieWindInstance::serialize(Serializer& ser) {
+ ser.from_ptr(&matrix);
+ ser.from_ptr(&wind_idx);
+ ser.from_ptr(&stiffness);
+}
+
void TfragTree::serialize(Serializer& ser) {
ser.from_ptr(&kind);
@@ -38,6 +52,24 @@ void TieTree::serialize(Serializer& ser) {
draw.serialize(ser);
}
+ if (ser.is_saving()) {
+ ser.save(instanced_wind_draws.size());
+ } else {
+ instanced_wind_draws.resize(ser.load());
+ }
+ for (auto& draw : instanced_wind_draws) {
+ draw.serialize(ser);
+ }
+
+ if (ser.is_saving()) {
+ ser.save(instance_info.size());
+ } else {
+ instance_info.resize(ser.load());
+ }
+ for (auto& inst : instance_info) {
+ inst.serialize(ser);
+ }
+
ser.from_pod_vector(&vertices);
ser.from_pod_vector(&colors);
bvh.serialize(ser);
diff --git a/common/custom_data/Tfrag3Data.h b/common/custom_data/Tfrag3Data.h
index 00654e46ab..7c19a8eff1 100644
--- a/common/custom_data/Tfrag3Data.h
+++ b/common/custom_data/Tfrag3Data.h
@@ -1,16 +1,17 @@
#pragma once
// Data format for the tfrag3 renderer.
+#include
#include "common/common_types.h"
-#include "common/util/assert.h"
#include "common/dma/gs.h"
#include "common/util/Serializer.h"
#include "common/math/Vector.h"
+#include "common/util/assert.h"
namespace tfrag3 {
-constexpr int TFRAG3_VERSION = 7;
+constexpr int TFRAG3_VERSION = 9;
// These vertices should be uploaded to the GPU at load time and don't change
struct PreloadedVertex {
@@ -41,8 +42,8 @@ struct StripDraw {
// to do culling, the above vertex stream is grouped.
// by following the visgroups and checking the visibility, you can leave out invisible vertices.
struct VisGroup {
- u32 num = 0; // number of vertex indices in this group
- u32 vis_idx = 0; // the visibility group they belong to
+ u32 num = 0; // number of vertex indices in this group
+ u32 vis_idx_in_pc_bvh = 0; // the visibility group they belong to (in BVH)
};
std::vector vis_groups;
@@ -51,12 +52,34 @@ struct StripDraw {
void serialize(Serializer& ser);
};
+struct InstancedStripDraw {
+ DrawMode mode; // the OpenGL draw settings.
+ u32 tree_tex_id = 0; // the texture that should be bound for the draw
+
+ // the list of vertices in the draw. This includes the restart code of UINT32_MAX that OpenGL
+ // will use to start a new strip.
+ std::vector vertex_index_stream;
+
+ // the vertex stream above is segmented by instance.
+ struct InstanceGroup {
+ u32 num = 0; // number of vertex indices in this group
+ u32 instance_idx = 0; // the instance they belong to
+ u32 vis_idx = 0;
+ };
+ std::vector instance_groups;
+
+ // for debug counting.
+ u32 num_triangles = 0;
+ void serialize(Serializer& ser);
+};
+
// node in the BVH.
struct VisNode {
math::Vector bsphere; // the bounding sphere, in meters (4096 = 1 game meter). w = rad
u16 child_id = 0xffff; // the ID of our first child.
- u8 num_kids = 0xff; // number of children. The children are consecutive in memory
- u8 flags = 0; // flags. If 1, we have a DrawVisNode child, otherwise a leaf.
+ u16 my_id = 0xffff;
+ u8 num_kids = 0xff; // number of children. The children are consecutive in memory
+ u8 flags = 0; // flags. If 1, we have a DrawVisNode child, otherwise a leaf.
};
// The leaf nodes don't actually exist in the vector of VisNodes, but instead they are ID's used
@@ -114,6 +137,13 @@ struct TfragTree {
void serialize(Serializer& ser);
};
+struct TieWindInstance {
+ std::array matrix;
+ u16 wind_idx;
+ float stiffness;
+ void serialize(Serializer& ser);
+};
+
// A shrub model
struct ShrubTree {
BVH bvh;
@@ -132,7 +162,8 @@ struct TieTree {
std::vector vertices; // mesh vertices
std::vector colors; // vertex colors (pre-interpolation)
- // TODO wind stuff
+ std::vector instanced_wind_draws;
+ std::vector instance_info;
void serialize(Serializer& ser);
};
diff --git a/common/dma/dma.h b/common/dma/dma.h
index a4ab7ba959..667c103470 100644
--- a/common/dma/dma.h
+++ b/common/dma/dma.h
@@ -7,8 +7,8 @@
#include
#include
-#include "common/util/assert.h"
#include "common/common_types.h"
+#include "common/util/assert.h"
struct DmaStats {
double sync_time_ms = 0;
@@ -117,4 +117,4 @@ struct VifCodeUnpack {
u16 addr_qw;
bool is_unsigned; // only care for 8/16 bit data.
bool use_tops_flag; // uses double buffering
-};
\ No newline at end of file
+};
diff --git a/common/dma/gs.h b/common/dma/gs.h
index 03a84136d5..00468774fa 100644
--- a/common/dma/gs.h
+++ b/common/dma/gs.h
@@ -339,7 +339,7 @@ struct AdGifData {
u64 tex1_addr;
u64 mip_data;
u64 mip_addr;
- u64 clamp_data;
+ u64 clamp_data; // can also be zbuf!!
u64 clamp_addr;
u64 alpha_data;
u64 alpha_addr;
@@ -366,6 +366,13 @@ class DrawMode {
bool get_depth_write_enable() const { return m_val & 0b1; }
void enable_depth_write() { m_val = m_val | 0b1; }
void disable_depth_write() { m_val = m_val & ~(0b1); }
+ void set_depth_write_enable(bool x) {
+ if (x) {
+ enable_depth_write();
+ } else {
+ disable_depth_write();
+ }
+ }
GsTest::ZTest get_depth_test() const { return (GsTest::ZTest)((m_val >> 1) & 0b11); }
void set_depth_test(GsTest::ZTest dt) { m_val = (m_val & ~(0b110)) | ((u32)(dt) << 1); }
diff --git a/common/goos/Interpreter.cpp b/common/goos/Interpreter.cpp
index 62d7ee7a76..46886bc253 100644
--- a/common/goos/Interpreter.cpp
+++ b/common/goos/Interpreter.cpp
@@ -7,7 +7,7 @@
#include "Interpreter.h"
#include "ParseHelpers.h"
#include "common/util/FileUtil.h"
-#include
+#include "third-party/fmt/core.h"
namespace goos {
Interpreter::Interpreter(const std::string& username) {
diff --git a/common/goos/Object.h b/common/goos/Object.h
index 134901dc64..e799629005 100644
--- a/common/goos/Object.h
+++ b/common/goos/Object.h
@@ -41,7 +41,6 @@
*/
#include
-#include "common/util/assert.h"
#include
#include
#include
@@ -50,6 +49,7 @@
#include
#include
#include "common/common_types.h"
+#include "common/util/assert.h"
namespace goos {
diff --git a/common/goos/PrettyPrinter.cpp b/common/goos/PrettyPrinter.cpp
index d1fd9cbbc0..8933b24da7 100644
--- a/common/goos/PrettyPrinter.cpp
+++ b/common/goos/PrettyPrinter.cpp
@@ -4,7 +4,6 @@
* It is not very good, but significantly better than putting everything on one line
*/
-#include "common/util/assert.h"
#include
#include
#include
@@ -14,6 +13,7 @@
#include "common/log/log.h"
#include "common/goos/PrettyPrinter2.h"
+#include "common/util/assert.h"
namespace pretty_print {
diff --git a/common/goos/PrettyPrinter2.cpp b/common/goos/PrettyPrinter2.cpp
index 102d790f10..5a6b6f9ccd 100644
--- a/common/goos/PrettyPrinter2.cpp
+++ b/common/goos/PrettyPrinter2.cpp
@@ -1,7 +1,7 @@
#include "PrettyPrinter2.h"
#include "common/common_types.h"
-#include "common/util/assert.h"
#include "third-party/fmt/core.h"
+#include "common/util/assert.h"
namespace pretty_print {
@@ -11,9 +11,6 @@ namespace v2 {
// The previous issues we had with stack overflow only happened when there was a stack frame per
// element in a list.
-// TODO: there's a different style of splitting that we should do for forms like:
-// set!, and, or, <, >, +... where we try leaving operator + one other.
-
// The main node type.
// unlike v1, this nests lists.
// these have pointers to parents, so generally not safe to copy.
@@ -71,6 +68,19 @@ struct Node {
return false;
}
+ std::string debug_to_string() const {
+ switch (kind) {
+ case Kind::ATOM:
+ return fmt::format("[atom {}]", atom_str);
+ case Kind::LIST:
+ return "[list]";
+ case Kind::IMPROPER_LIST:
+ return "[improper list]";
+ default:
+ assert(false);
+ }
+ }
+
// how wide is this text? not including the indentation of this subtree.
u32 text_len = 0;
@@ -193,7 +203,8 @@ void break_list(Node* node) {
node->top_line_count = 1;
const std::unordered_set sameline_splitters = {
- "if", "<", ">", "<=", ">=", "set!", "=", "!=", "+", "-", "*", "/", "the", "->"};
+ "if", "<", ">", "<=", ">=", "set!", "=", "!=", "+", "-", "*",
+ "/", "the", "->", "and", "or", "logand", "logior", "logxor", "+!", "*!", "logtest?"};
if (node->child_nodes.at(0).kind == Node::Kind::LIST) {
// ((foo
@@ -208,7 +219,7 @@ void break_list(Node* node) {
// things with 4 things in the top line: (defmethod
node->top_line_count = 4;
} else if (name == "until" || name == "while" || name == "dotimes" || name == "countdown" ||
- name == "when" || name == "behavior" || name == "lambda") {
+ name == "when" || name == "behavior" || name == "lambda" || name == "defpart") {
node->top_line_count = 2;
} else if (name == "let" || name == "let*" || name == "rlet") {
// special case for things like let.
@@ -253,9 +264,9 @@ void break_list(Node* node) {
void insert_required_breaks(const std::vector& bfs_order) {
const std::unordered_set always_break = {
- "when", "defun-debug", "countdown", "case", "defun", "defmethod",
- "let", "until", "while", "if", "dotimes", "cond",
- "else", "defbehavior", "with-pp", "rlet", "defstate"};
+ "when", "defun-debug", "countdown", "case", "defun", "defmethod", "let",
+ "until", "while", "if", "dotimes", "cond", "else", "defbehavior",
+ "with-pp", "rlet", "defstate", "behavior", "defpart"};
for (auto node : bfs_order) {
if (!node->break_list && node->kind == Node::Kind::LIST &&
node->child_nodes.at(0).kind == Node::Kind::ATOM) {
@@ -328,18 +339,26 @@ void append_node_to_string(const Node* node,
int listing_indent = next_indent_level + node->quoted + node->sub_elt_indent;
int extra_indent = 0;
+ int old_indent = listing_indent;
+ if (node->top_line_count) {
+ listing_indent -= node->sub_elt_indent;
+ listing_indent += node->child_nodes.front().kind == Node::Kind::LIST ? 1 : 2;
+ }
for (; node_idx < node->top_line_count; node_idx++) {
size_t s0 = str.length();
if (node->kind == Node::Kind::IMPROPER_LIST &&
&node->child_nodes.at(node_idx) == &node->child_nodes.back()) {
str.append(". ");
}
+ // so, if these need to break, they should have a bigger indent.
append_node_to_string(&node->child_nodes.at(node_idx), str, 0,
listing_indent + extra_indent);
- // extra_indent += (str.length() - s0);
extra_indent = compute_extra_offset(str, s0, extra_indent);
str.push_back(' ');
}
+ if (node->top_line_count) {
+ listing_indent = old_indent;
+ }
if (node->top_line_count > 0) {
str.pop_back();
}
diff --git a/common/goos/Reader.cpp b/common/goos/Reader.cpp
index 427600c0fc..92088cd534 100644
--- a/common/goos/Reader.cpp
+++ b/common/goos/Reader.cpp
@@ -217,9 +217,11 @@ std::optional Reader::read_from_stdin(const std::string& prompt, ReplWra
/*!
* Read a string.
*/
-Object Reader::read_from_string(const std::string& str, bool add_top_level) {
+Object Reader::read_from_string(const std::string& str,
+ bool add_top_level,
+ const std::optional& string_name) {
// create text fragment and add to the DB
- auto textFrag = std::make_shared(str);
+ auto textFrag = std::make_shared(str, string_name.value_or("Program string"));
db.insert(textFrag);
// perform read
diff --git a/common/goos/Reader.h b/common/goos/Reader.h
index 2578fb76ea..7beed321e5 100644
--- a/common/goos/Reader.h
+++ b/common/goos/Reader.h
@@ -12,7 +12,6 @@
*/
#include
-#include "common/util/assert.h"
#include
#include
#include
@@ -22,6 +21,8 @@
#include "ReplUtils.h"
+#include "common/util/assert.h"
+
namespace goos {
/*!
@@ -71,7 +72,9 @@ struct Token {
class Reader {
public:
Reader();
- Object read_from_string(const std::string& str, bool add_top_level = true);
+ Object read_from_string(const std::string& str,
+ bool add_top_level = true,
+ const std::optional& string_name = {});
std::optional read_from_stdin(const std::string& prompt, ReplWrapper& repl);
Object read_from_file(const std::vector& file_path, bool check_encoding = false);
bool check_string_is_valid(const std::string& str) const;
diff --git a/common/goos/TextDB.h b/common/goos/TextDB.h
index 3418036fa4..b6b62a1140 100644
--- a/common/goos/TextDB.h
+++ b/common/goos/TextDB.h
@@ -65,9 +65,14 @@ class ReplText : public SourceText {
*/
class ProgramString : public SourceText {
public:
- explicit ProgramString(const std::string& text_) : SourceText(text_) {}
- std::string get_description() override { return "Program string"; }
+ explicit ProgramString(const std::string& text_,
+ const std::string& string_name = "Program string")
+ : SourceText(text_), m_string_name(string_name) {}
+ std::string get_description() override { return m_string_name; }
~ProgramString() = default;
+
+ private:
+ std::string m_string_name;
};
/*!
diff --git a/common/log/log.cpp b/common/log/log.cpp
index dc49353510..5845efb98f 100644
--- a/common/log/log.cpp
+++ b/common/log/log.cpp
@@ -1,12 +1,12 @@
#include
#include
-#include "common/util/assert.h"
#include
#include "third-party/fmt/color.h"
#include "log.h"
#ifdef _WIN32 // see lg::initialize
#include
#endif
+#include "common/util/assert.h"
namespace lg {
struct Logger {
diff --git a/common/type_system/Type.cpp b/common/type_system/Type.cpp
index 1e9f52f83c..6b395a3be7 100644
--- a/common/type_system/Type.cpp
+++ b/common/type_system/Type.cpp
@@ -4,9 +4,9 @@
*/
#include
-#include "common/util/assert.h"
-#include
+#include "third-party/fmt/core.h"
#include "Type.h"
+#include "common/util/assert.h"
namespace {
std::string reg_kind_to_string(RegClass kind) {
@@ -696,9 +696,9 @@ StructureType::StructureType(std::string parent,
std::string StructureType::print() const {
std::string result = fmt::format(
"[StructureType] {}\n parent: {}\n boxed: {}\n dynamic: {}\n size: {}\n pack: {}\n misalign: "
- "{}\n heap-base: {}\n fields:\n",
- m_name, m_parent, m_is_boxed, m_dynamic, m_size_in_mem, m_pack, m_allow_misalign,
- m_heap_base);
+ "{}\n heap-base: {}\n stack-singleton: {}\n fields:\n",
+ m_name, m_parent, m_is_boxed, m_dynamic, m_size_in_mem, m_pack, m_allow_misalign, m_heap_base,
+ m_always_stack_singleton);
for (auto& x : m_fields) {
result += " " + x.print() + "\n";
}
@@ -727,7 +727,8 @@ bool StructureType::operator==(const Type& other) const {
m_pack == p_other->m_pack &&
m_allow_misalign == p_other->m_allow_misalign &&
m_offset == p_other->m_offset &&
- m_idx_of_first_unique_field == p_other->m_idx_of_first_unique_field;
+ m_idx_of_first_unique_field == p_other->m_idx_of_first_unique_field &&
+ m_always_stack_singleton == p_other->m_always_stack_singleton;
// clang-format on
}
@@ -773,6 +774,11 @@ std::string StructureType::diff_structure_common(const StructureType& other) con
result += fmt::format("allow_misalign: {} vs. {}\n", m_allow_misalign, other.m_allow_misalign);
}
+ if (m_always_stack_singleton != other.m_always_stack_singleton) {
+ result += fmt::format("always_stack_singleton: {} vs. {}\n", m_always_stack_singleton,
+ other.m_always_stack_singleton);
+ }
+
if (m_offset != other.m_offset) {
result += fmt::format("offset: {} vs. {}\n", m_offset, other.m_offset);
}
@@ -906,7 +912,8 @@ bool BasicType::operator==(const Type& other) const {
m_allow_misalign == p_other->m_allow_misalign &&
m_offset == p_other->m_offset &&
m_idx_of_first_unique_field == p_other->m_idx_of_first_unique_field &&
- m_final == p_other->m_final;
+ m_final == p_other->m_final &&
+ m_always_stack_singleton == p_other->m_always_stack_singleton;
// clang-format on
}
diff --git a/common/type_system/Type.h b/common/type_system/Type.h
index 2ff45f4b4d..364c11ae9c 100644
--- a/common/type_system/Type.h
+++ b/common/type_system/Type.h
@@ -7,10 +7,10 @@
#include
#include
-#include "common/util/assert.h"
#include
#include "common/goal_constants.h"
#include "TypeSpec.h"
+#include "common/util/assert.h"
class TypeSystem;
@@ -275,9 +275,11 @@ class StructureType : public ReferenceType {
bool is_dynamic() const { return m_dynamic; }
~StructureType() = default;
void set_pack(bool pack) { m_pack = pack; }
+ void set_always_stack_singleton() { m_always_stack_singleton = true; }
void set_heap_base(int hb) { m_heap_base = hb; }
bool is_packed() const { return m_pack; }
bool is_allowed_misalign() const { return m_allow_misalign; };
+ bool is_always_stack_singleton() const { return m_always_stack_singleton; }
void set_allow_misalign(bool misalign) { m_allow_misalign = misalign; }
void set_gen_inspect(bool gen_inspect) { m_generate_inspect = gen_inspect; }
@@ -300,6 +302,7 @@ class StructureType : public ReferenceType {
bool m_pack = false;
bool m_allow_misalign = false;
int m_offset = 0;
+ bool m_always_stack_singleton = false;
size_t m_idx_of_first_unique_field = 0;
};
diff --git a/common/type_system/TypeSpec.h b/common/type_system/TypeSpec.h
index 0aa5043972..7b2edbad44 100644
--- a/common/type_system/TypeSpec.h
+++ b/common/type_system/TypeSpec.h
@@ -8,8 +8,8 @@
#include
#include
#include
-#include "common/util/assert.h"
#include "common/util/SmallVector.h"
+#include "common/util/assert.h"
/*!
* A :name value modifier to apply to a type.
diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp
index d5aec965fc..53bb35429f 100644
--- a/common/type_system/TypeSystem.cpp
+++ b/common/type_system/TypeSystem.cpp
@@ -5,12 +5,12 @@
* access types, and reverse type lookups.
*/
-#include "common/util/assert.h"
+#include "third-party/fmt/core.h"
+#include "third-party/fmt/color.h"
#include
-#include
#include "TypeSystem.h"
#include "common/util/math_util.h"
-#include "third-party/fmt/color.h"
+#include "common/util/assert.h"
namespace {
template
@@ -1615,7 +1615,10 @@ std::string TypeSystem::generate_deftype_footer(const Type* type) const {
result.append(" :pack-me\n");
}
if (as_structure->is_allowed_misalign()) {
- result.append(" :allow-misaligned");
+ result.append(" :allow-misaligned\n");
+ }
+ if (as_structure->is_always_stack_singleton()) {
+ result.append(" :always-stack-singleton\n");
}
}
diff --git a/common/type_system/deftype.cpp b/common/type_system/deftype.cpp
index c0b632b7f1..173523da1b 100644
--- a/common/type_system/deftype.cpp
+++ b/common/type_system/deftype.cpp
@@ -277,6 +277,7 @@ struct StructureDefResult {
bool pack_me = false;
bool allow_misaligned = false;
bool final = false;
+ bool always_stack_singleton = false;
};
StructureDefResult parse_structure_def(StructureType* type,
@@ -347,6 +348,8 @@ StructureDefResult parse_structure_def(StructureType* type,
result.allow_misaligned = true;
} else if (opt_name == ":final") {
result.final = true;
+ } else if (opt_name == ":always-stack-singleton") {
+ result.always_stack_singleton = true;
} else {
throw std::runtime_error("Invalid option in field specification: " + opt_name);
}
@@ -572,6 +575,13 @@ DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) {
name);
throw std::runtime_error("invalid pack option on basic");
}
+ if (sr.always_stack_singleton) {
+ fmt::print(
+ "[TypeSystem] :always-stack-singleton was set on {}, which is a basic and cannot "
+ "be a stack singleton\n",
+ name);
+ throw std::runtime_error("invalid stack singleton option on basic");
+ }
new_type->set_heap_base(result.flags.heap_base);
if (sr.final) {
new_type->set_final();
@@ -592,6 +602,9 @@ DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) {
if (sr.allow_misaligned) {
new_type->set_allow_misalign(true);
}
+ if (sr.always_stack_singleton) {
+ new_type->set_always_stack_singleton();
+ }
if (sr.final) {
throw std::runtime_error(
fmt::format("[TypeSystem] :final option cannot be used on structure type {}", name));
diff --git a/common/util/BinaryReader.h b/common/util/BinaryReader.h
index d6d43b0aae..18ce2be386 100644
--- a/common/util/BinaryReader.h
+++ b/common/util/BinaryReader.h
@@ -7,9 +7,9 @@
#include
#include
-#include "common/util/assert.h"
-#include "common/common_types.h"
#include
+#include "common/common_types.h"
+#include "common/util/assert.h"
class BinaryReader {
public:
diff --git a/common/util/BinaryWriter.h b/common/util/BinaryWriter.h
index 7ab17d7106..498aaec9bc 100644
--- a/common/util/BinaryWriter.h
+++ b/common/util/BinaryWriter.h
@@ -5,11 +5,11 @@
* Write raw data like a stream.
*/
-#include "common/util/assert.h"
#include
#include
#include
#include
+#include "common/util/assert.h"
struct BinaryWriterRef {
size_t offset;
diff --git a/common/util/BitUtils.h b/common/util/BitUtils.h
index 4d493143a8..88eb015d45 100644
--- a/common/util/BitUtils.h
+++ b/common/util/BitUtils.h
@@ -1,9 +1,9 @@
#pragma once
#include
-#include "common/util/assert.h"
#include "common/util/Range.h"
#include "common/common_types.h"
+#include "common/util/assert.h"
constexpr int BITS_PER_BYTE = 8;
template
@@ -93,4 +93,4 @@ inline u32 count_leading_zeros_u32(u32 in) {
_BitScanReverse(&result, in);
return result;
#endif
-}
\ No newline at end of file
+}
diff --git a/common/util/FileUtil.cpp b/common/util/FileUtil.cpp
index 7a9398e928..5a3004cad3 100644
--- a/common/util/FileUtil.cpp
+++ b/common/util/FileUtil.cpp
@@ -9,7 +9,6 @@
#include /* defines FILENAME_MAX */
#include
#include
-#include "common/util/assert.h"
#include
#include "common/util/BinaryReader.h"
#include "BinaryWriter.h"
@@ -24,6 +23,7 @@
#include
#include
#endif
+#include "common/util/assert.h"
namespace file_util {
std::filesystem::path get_user_home_dir() {
diff --git a/common/util/FrameLimiter.h b/common/util/FrameLimiter.h
new file mode 100644
index 0000000000..3384149e32
--- /dev/null
+++ b/common/util/FrameLimiter.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "common/util/Timer.h"
+
+class FrameLimiter {
+ public:
+ void run(double target_fps, bool experimental_accurate_lag, double engine_time) {
+ double target_seconds;
+ if (experimental_accurate_lag) {
+ target_seconds = round_to_nearest_60fps(engine_time);
+ } else {
+ target_seconds = 1.f / target_fps;
+ }
+ double remaining_time = target_seconds - m_timer.getSeconds();
+ while (remaining_time > 0) {
+ if (remaining_time > 0.003) {
+ std::this_thread::sleep_for(std::chrono::microseconds(int(remaining_time * 1e6 * 0.5)));
+ }
+ remaining_time = target_seconds - m_timer.getSeconds();
+ }
+
+ m_timer.start();
+ }
+
+ private:
+ double round_to_nearest_60fps(double current) {
+ double one_frame = 1.f / 60.f;
+ int frames_missed = (current / one_frame); // rounds down
+ if (frames_missed > 4) {
+ frames_missed = 4;
+ }
+ return (frames_missed + 1) * one_frame;
+ }
+
+ Timer m_timer;
+};
\ No newline at end of file
diff --git a/common/util/Serializer.h b/common/util/Serializer.h
index c1b78a6da8..6c72bf848f 100644
--- a/common/util/Serializer.h
+++ b/common/util/Serializer.h
@@ -1,9 +1,9 @@
#pragma once
-#include "common/util/assert.h"
#include
#include
#include
+#include "common/util/assert.h"
/*!
* The Serializer is a tool to load or save data from a buffer.
@@ -216,4 +216,4 @@ class Serializer {
size_t m_size = 0;
size_t m_offset = 0;
bool m_writing = false;
-};
\ No newline at end of file
+};
diff --git a/common/util/SmallVector.h b/common/util/SmallVector.h
index f3dffb4831..ab18cfa89e 100644
--- a/common/util/SmallVector.h
+++ b/common/util/SmallVector.h
@@ -1,11 +1,11 @@
#pragma once
-#include
#include
#include
#include
#include
#include
+#include "assert.h"
namespace cu {
// This might seem stupid, but compiling an empty file with #include takes 0.5 seconds.
diff --git a/common/util/Timer.h b/common/util/Timer.h
index 3df3830fd3..62fa58d7e0 100644
--- a/common/util/Timer.h
+++ b/common/util/Timer.h
@@ -1,11 +1,8 @@
#pragma once
-#ifndef JAK_V2_TIMER_H
-#define JAK_V2_TIMER_H
-
-#include "common/util/assert.h"
#include
#include
+#include "common/util/assert.h"
/*!
* Timer for measuring time elapsed with clock_monotonic
@@ -45,5 +42,3 @@ class Timer {
struct timespec _startTime = {};
};
-
-#endif // JAK_V2_TIMER_H
diff --git a/common/util/Trie.h b/common/util/Trie.h
index 4e633e26e7..f4cf65cc75 100644
--- a/common/util/Trie.h
+++ b/common/util/Trie.h
@@ -1,8 +1,8 @@
#pragma once
-#include "common/util/assert.h"
#include
#include
+#include "common/util/assert.h"
/*!
* A simple prefix tree. It works similarly to a map, but also supports fast lookups by prefix with
@@ -182,4 +182,4 @@ T* Trie::operator[](const std::string& str) {
template
std::vector Trie::lookup_prefix(const std::string& str) const {
return m_root.lookup_prefix(str.c_str());
-}
\ No newline at end of file
+}
diff --git a/common/util/assert.h b/common/util/assert.h
index 90a0044c0b..684234a032 100644
--- a/common/util/assert.h
+++ b/common/util/assert.h
@@ -1,24 +1,19 @@
-#pragma once
-
/*!
* @file assert.h
* Wrapper around .
+ * Make sure this file is always the last one included.
*/
-#if defined NDEBUG && defined _WIN32
-
-#pragma push_macro("NDEBUG")
+#if defined NDEBUG
#undef NDEBUG
#undef assert
#include
-#pragma pop_macro("NDEBUG")
+#define NDEBUG 1
#else
#include
#endif
-
-#define ASSERT assert
diff --git a/common/util/dgo_util.cpp b/common/util/dgo_util.cpp
index b9f73be792..ab68bd5bda 100644
--- a/common/util/dgo_util.cpp
+++ b/common/util/dgo_util.cpp
@@ -1,8 +1,8 @@
-#include "common/util/assert.h"
#include
#include "dgo_util.h"
#include "common/versions.h"
#include "third-party/fmt/core.h"
+#include "common/util/assert.h"
/*!
* Assert false if the char[] has non-null data after the null terminated string.
@@ -50,4 +50,4 @@ std::string get_object_file_name(const std::string& original_name, u8* data, int
}
return original_name;
-}
\ No newline at end of file
+}
diff --git a/common/util/json_util.cpp b/common/util/json_util.cpp
index a1edd3a499..d5eae65c7a 100644
--- a/common/util/json_util.cpp
+++ b/common/util/json_util.cpp
@@ -1,6 +1,6 @@
#include "common/log/log.h"
-#include "common/util/assert.h"
#include "json_util.h"
+#include "common/util/assert.h"
/*!
* Strip out // and / * comments
@@ -107,4 +107,4 @@ Range parse_json_optional_integer_range(const nlohmann::json& json) {
} else {
throw std::runtime_error("Invalid json as input to parse_json_optional_integer_range");
}
-}
\ No newline at end of file
+}
diff --git a/common/util/print_float.cpp b/common/util/print_float.cpp
index 4227b5d15c..579ba81e64 100644
--- a/common/util/print_float.cpp
+++ b/common/util/print_float.cpp
@@ -1,7 +1,7 @@
#include
#include "third-party/fmt/core.h"
-#include "common/common_types.h"
+#include "common/goal_constants.h"
#include "third-party/dragonbox.h"
#include "print_float.h"
#include "common/util/assert.h"
@@ -19,6 +19,14 @@ std::string float_to_string(float value, bool append_trailing_decimal) {
return {buff};
}
+/*!
+ * Wrapper around float_to_string, for printing meters. Unlike float_to_string, it does not append
+ * decimals by default.
+ */
+std::string meters_to_string(float value, bool append_trailing_decimal) {
+ return float_to_string(value / METER_LENGTH, append_trailing_decimal);
+}
+
int float_to_cstr(float value, char* buffer, bool append_trailing_decimal) {
assert(std::isfinite(value));
// dragonbox gives us:
diff --git a/common/util/print_float.h b/common/util/print_float.h
index b208a152e2..3849b7b279 100644
--- a/common/util/print_float.h
+++ b/common/util/print_float.h
@@ -3,4 +3,5 @@
#include
std::string float_to_string(float value, bool append_trailing_decimal = true);
-int float_to_cstr(float value, char* buffer, bool append_trailing_decimal = true);
\ No newline at end of file
+std::string meters_to_string(float value, bool append_trailing_decimal = false);
+int float_to_cstr(float value, char* buffer, bool append_trailing_decimal = true);
diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt
index 324a94c686..b35f9b78d1 100644
--- a/decompiler/CMakeLists.txt
+++ b/decompiler/CMakeLists.txt
@@ -6,6 +6,7 @@ add_library(
analysis/cfg_builder.cpp
analysis/expression_build.cpp
analysis/final_output.cpp
+ analysis/find_defpartgroup.cpp
analysis/find_defstates.cpp
analysis/find_skelgroups.cpp
analysis/inline_asm_rewrite.cpp
diff --git a/decompiler/Disasm/InstructionDecode.cpp b/decompiler/Disasm/InstructionDecode.cpp
index 66448fedd1..e530bcedd0 100644
--- a/decompiler/Disasm/InstructionDecode.cpp
+++ b/decompiler/Disasm/InstructionDecode.cpp
@@ -5,8 +5,8 @@
*/
#include "InstructionDecode.h"
-#include "common/util/assert.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
+#include "common/util/assert.h"
namespace decompiler {
// utility class to extract fields of an opcode.
diff --git a/decompiler/Disasm/InstructionMatching.cpp b/decompiler/Disasm/InstructionMatching.cpp
index 21eee843fe..c51da30c6b 100644
--- a/decompiler/Disasm/InstructionMatching.cpp
+++ b/decompiler/Disasm/InstructionMatching.cpp
@@ -3,8 +3,8 @@
* Utilities for checking if an instruction matches some criteria.
*/
-#include "common/util/assert.h"
#include "InstructionMatching.h"
+#include "common/util/assert.h"
namespace decompiler {
/*!
diff --git a/decompiler/Disasm/InstructionParser.cpp b/decompiler/Disasm/InstructionParser.cpp
index 7bf85de01c..4ef1610408 100644
--- a/decompiler/Disasm/InstructionParser.cpp
+++ b/decompiler/Disasm/InstructionParser.cpp
@@ -1,9 +1,9 @@
-#include "common/util/assert.h"
#include
#include
#include
#include "common/common_types.h"
#include "InstructionParser.h"
+#include "common/util/assert.h"
namespace decompiler {
InstructionParser::InstructionParser() {
@@ -487,4 +487,4 @@ std::string ParsedProgram::print() {
}
return result;
}
-} // namespace decompiler
\ No newline at end of file
+} // namespace decompiler
diff --git a/decompiler/Disasm/Register.cpp b/decompiler/Disasm/Register.cpp
index b7af3c1d0c..fb4df68673 100644
--- a/decompiler/Disasm/Register.cpp
+++ b/decompiler/Disasm/Register.cpp
@@ -4,9 +4,9 @@
*/
#include "Register.h"
-#include "common/util/assert.h"
#include
#include "third-party/fmt/core.h"
+#include "common/util/assert.h"
namespace decompiler {
namespace Reg {
diff --git a/decompiler/Disasm/Register.h b/decompiler/Disasm/Register.h
index 7cc7b50862..ee8c48a18f 100644
--- a/decompiler/Disasm/Register.h
+++ b/decompiler/Disasm/Register.h
@@ -6,8 +6,8 @@
*/
#include
-#include "common/util/assert.h"
#include
+#include "common/util/assert.h"
namespace decompiler {
// Namespace for register name constants
diff --git a/decompiler/Function/BasicBlocks.cpp b/decompiler/Function/BasicBlocks.cpp
index afb107ccbb..9e9095a949 100644
--- a/decompiler/Function/BasicBlocks.cpp
+++ b/decompiler/Function/BasicBlocks.cpp
@@ -1,8 +1,8 @@
#include
-#include "common/util/assert.h"
#include "BasicBlocks.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "decompiler/Disasm/InstructionMatching.h"
+#include "common/util/assert.h"
namespace decompiler {
/*!
diff --git a/decompiler/Function/CfgVtx.cpp b/decompiler/Function/CfgVtx.cpp
index 14c650d739..a9e65ad411 100644
--- a/decompiler/Function/CfgVtx.cpp
+++ b/decompiler/Function/CfgVtx.cpp
@@ -1,9 +1,9 @@
-#include "common/util/assert.h"
#include "common/goos/PrettyPrinter.h"
#include "decompiler/Disasm/InstructionMatching.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "CfgVtx.h"
#include "Function.h"
+#include "common/util/assert.h"
namespace decompiler {
/////////////////////////////////////////
@@ -1247,6 +1247,43 @@ bool ControlFlowGraph::clean_up_asm_branches() {
old_seq->parent_claim(seq);
bds->parent_claim(seq);
+ return false;
+ } else if (b0_seq && !b1_seq) {
+ replaced = true;
+ m_blocks.at(bds->succ_branch->get_first_block_id())->needs_label = true;
+
+ auto* seq = dynamic_cast(b0);
+ assert(seq);
+
+ if (b0->succ_branch) {
+ b0->succ_branch->replace_preds_with_and_check({b0}, nullptr);
+ }
+
+ if (bds->succ_branch) {
+ // likely delay slots "branch" in this graph.
+ bds->succ_branch->replace_preds_with_and_check({bds}, nullptr);
+ }
+
+ seq->seq.push_back(bds);
+
+ seq->seq.push_back(b1);
+
+ for (auto* x : b1->succs()) {
+ // printf("fix preds of %s\n", x->to_string().c_str());
+ x->replace_pred_and_check(b1, seq);
+ }
+ seq->succ_branch = b1->succ_branch;
+ seq->succ_ft = b1->succ_ft;
+ seq->end_branch = b1->end_branch;
+ seq->next = b1->next;
+ if (seq->next) {
+ seq->next->prev = seq;
+ }
+
+ // todo - proper trash?
+ b1->parent_claim(seq);
+ bds->parent_claim(seq);
+
return false;
}
diff --git a/decompiler/Function/CfgVtx.h b/decompiler/Function/CfgVtx.h
index 0e5d981194..bc2563e3fd 100644
--- a/decompiler/Function/CfgVtx.h
+++ b/decompiler/Function/CfgVtx.h
@@ -1,8 +1,5 @@
#pragma once
-#ifndef JAK_DISASSEMBLER_CFGVTX_H
-#define JAK_DISASSEMBLER_CFGVTX_H
-
#include
#include
#include "common/util/assert.h"
@@ -394,4 +391,3 @@ std::shared_ptr build_cfg(const LinkedObjectFile& file,
const CondWithElseLengthHack& cond_with_else_hack,
const std::unordered_set& blocks_ending_in_asm_br);
} // namespace decompiler
-#endif // JAK_DISASSEMBLER_CFGVTX_H
diff --git a/decompiler/Function/Function.cpp b/decompiler/Function/Function.cpp
index c85c39fd45..5c09828924 100644
--- a/decompiler/Function/Function.cpp
+++ b/decompiler/Function/Function.cpp
@@ -1,4 +1,3 @@
-#include "common/util/assert.h"
#include
#include "Function.h"
#include "common/log/log.h"
@@ -9,6 +8,7 @@
#include "decompiler/IR/IR.h"
#include "decompiler/IR2/Form.h"
#include "common/util/BitUtils.h"
+#include "common/util/assert.h"
namespace decompiler {
namespace {
@@ -88,7 +88,8 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
// storing s7 on the stack is done by interrupt handlers, which we probably don't want to
// support
- if (instr.kind == InstructionKind::SD && instr.get_src(0).get_reg() == make_gpr(Reg::S7)) {
+ if (instr.kind == InstructionKind::SD && instr.get_src(0).get_reg() == make_gpr(Reg::S7) &&
+ instr.get_src(2).get_reg() == make_gpr(Reg::SP)) {
lg::warn(
"Function {} was flagged as asm due to this instruction: {}. Consider flagging as asm "
"in config!",
@@ -765,4 +766,4 @@ BlockTopologicalSort Function::bb_topo_sort() {
std::string Function::name() const {
return guessed_name.to_string();
}
-} // namespace decompiler
\ No newline at end of file
+} // namespace decompiler
diff --git a/decompiler/Function/Warnings.h b/decompiler/Function/Warnings.h
index 4bc5d7bfcf..8e4b65dbd8 100644
--- a/decompiler/Function/Warnings.h
+++ b/decompiler/Function/Warnings.h
@@ -2,9 +2,8 @@
#include
#include
#include
-#include "common/util/assert.h"
-
#include "third-party/fmt/core.h"
+#include "common/util/assert.h"
namespace decompiler {
class DecompWarnings {
@@ -110,4 +109,4 @@ class DecompWarnings {
std::vector m_warnings;
bool m_used_lq_sq = false;
};
-} // namespace decompiler
\ No newline at end of file
+} // namespace decompiler
diff --git a/decompiler/IR/IR.h b/decompiler/IR/IR.h
index bfee70afb2..ff6588f06c 100644
--- a/decompiler/IR/IR.h
+++ b/decompiler/IR/IR.h
@@ -1,7 +1,8 @@
+#pragma once
+
#ifndef JAK_IR_H
#define JAK_IR_H
-#include "common/util/assert.h"
#include
#include
#include
@@ -10,6 +11,7 @@
#include "common/type_system/TypeSpec.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/util/TP_Type.h"
+#include "common/util/assert.h"
namespace goos {
class Object;
diff --git a/decompiler/IR2/AtomicOp.cpp b/decompiler/IR2/AtomicOp.cpp
index 62ce19abc0..f0c0ea12da 100644
--- a/decompiler/IR2/AtomicOp.cpp
+++ b/decompiler/IR2/AtomicOp.cpp
@@ -1,4 +1,3 @@
-#include "common/util/assert.h"
#include
#include
#include "common/goal_constants.h"
@@ -8,6 +7,7 @@
#include "AtomicOp.h"
#include "OpenGoalMapping.h"
#include "Form.h"
+#include "common/util/assert.h"
namespace decompiler {
/////////////////////////////
@@ -306,6 +306,9 @@ std::string get_simple_expression_op_name(SimpleExpression::Kind kind) {
return "vec3dot";
case SimpleExpression::Kind::VECTOR_4_DOT:
return "vec4dot";
+ case SimpleExpression::Kind::SET_ON_LESS_THAN:
+ case SimpleExpression::Kind::SET_ON_LESS_THAN_IMM:
+ return "set-on-less-than";
default:
assert(false);
return {};
@@ -367,6 +370,9 @@ int get_simple_expression_arg_count(SimpleExpression::Kind kind) {
case SimpleExpression::Kind::VECTOR_3_DOT:
case SimpleExpression::Kind::VECTOR_4_DOT:
return 2;
+ case SimpleExpression::Kind::SET_ON_LESS_THAN:
+ case SimpleExpression::Kind::SET_ON_LESS_THAN_IMM:
+ return 2;
default:
assert(false);
return -1;
@@ -512,7 +518,10 @@ AsmOp::AsmOp(Instruction instr, int my_idx) : AtomicOp(my_idx), m_instr(std::mov
if (src.is_reg()) {
auto reg = src.get_reg();
if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR || reg.get_kind() == Reg::VF) {
- m_src[i] = RegisterAccess(AccessMode::READ, reg, my_idx, true);
+ if (reg != Register(Reg::GPR, Reg::R0) ||
+ (m_instr.kind == InstructionKind::PCPYUD || m_instr.kind == InstructionKind::PEXTUW)) {
+ m_src[i] = RegisterAccess(AccessMode::READ, reg, my_idx, true);
+ }
}
}
}
diff --git a/decompiler/IR2/AtomicOp.h b/decompiler/IR2/AtomicOp.h
index 1b1ad95036..e48e7efece 100644
--- a/decompiler/IR2/AtomicOp.h
+++ b/decompiler/IR2/AtomicOp.h
@@ -2,13 +2,13 @@
#include
#include
-#include "common/util/assert.h"
#include
#include "common/goos/Object.h"
#include "decompiler/Disasm/Register.h"
#include "decompiler/Disasm/Instruction.h"
#include "decompiler/IR2/IR2_common.h"
#include "Env.h"
+#include "common/util/assert.h"
namespace decompiler {
class FormElement;
@@ -235,7 +235,9 @@ class SimpleExpression {
VECTOR_CROSS,
SUBU_L32_S7, // use SUBU X, src0, s7 to check if lower 32-bits are s7.
VECTOR_3_DOT,
- VECTOR_4_DOT
+ VECTOR_4_DOT,
+ SET_ON_LESS_THAN,
+ SET_ON_LESS_THAN_IMM
};
// how many arguments?
diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp
index 17920ce73f..5f525dccb8 100644
--- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp
+++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp
@@ -437,7 +437,8 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
return TP_Type::make_from_ts(TypeSpec("int"));
}
- if (arg0_type.is_product_with(4) && tc(dts, TypeSpec("type"), arg1_type)) {
+ if (arg0_type.is_product_with(4) && tc(dts, TypeSpec("type"), arg1_type) &&
+ env.func->name() != "overrides-parent-method?") {
// dynamic access into the method array with shift, add, offset-load
// no need to track the type because we don't know the method index anyway.
return TP_Type::make_partial_dyanmic_vtable_access();
@@ -621,12 +622,12 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
return TP_Type::make_from_ts(arg0_type.typespec());
}
- if (m_kind == Kind::ADD && tc(dts, TypeSpec("structure"), arg0_type) &&
+ if ((m_kind == Kind::ADD || m_kind == Kind::SUB) && tc(dts, TypeSpec("structure"), arg0_type) &&
arg1_type.is_integer_constant()) {
auto type_info = dts.ts.lookup_type(arg0_type.typespec());
- // get next in memory, allow this as &+
- if ((u64)type_info->get_size_in_memory() == arg1_type.get_integer_constant()) {
+ // get next in memory, allow this as &+/&-
+ if ((s64)type_info->get_size_in_memory() == std::abs((s64)arg1_type.get_integer_constant())) {
return TP_Type::make_from_ts(arg0_type.typespec());
}
@@ -827,7 +828,8 @@ TypeState AsmOp::propagate_types_internal(const TypeState& input,
}
// sllv out, in, r0
- if (m_instr.kind == InstructionKind::SLLV && m_src[1]->reg() == Register(Reg::GPR, Reg::R0)) {
+ if (m_instr.kind == InstructionKind::SLLV &&
+ instruction().src[1].is_reg(Register(Reg::GPR, Reg::R0))) {
auto type = dts.ts.lookup_type(result.get(m_src[0]->reg()).typespec());
auto as_bitfield = dynamic_cast(type);
if (as_bitfield) {
diff --git a/decompiler/IR2/Env.cpp b/decompiler/IR2/Env.cpp
index aecab89250..1c6ad9b379 100644
--- a/decompiler/IR2/Env.cpp
+++ b/decompiler/IR2/Env.cpp
@@ -1,7 +1,7 @@
#include
#include
#include
-#include
+#include "decompiler/util/DecompilerTypeSystem.h"
#include "Env.h"
#include "Form.h"
#include "decompiler/analysis/atomic_op_builder.h"
diff --git a/decompiler/IR2/Env.h b/decompiler/IR2/Env.h
index 541ecfa895..be8d55b5fc 100644
--- a/decompiler/IR2/Env.h
+++ b/decompiler/IR2/Env.h
@@ -2,14 +2,14 @@
#include
#include
-#include "common/util/assert.h"
-#include
+#include "common/goos/Object.h"
#include "decompiler/util/TP_Type.h"
#include "decompiler/util/StackSpillMap.h"
#include "decompiler/Disasm/Register.h"
#include "decompiler/IR2/IR2_common.h"
#include "decompiler/analysis/reg_usage.h"
#include "decompiler/config.h"
+#include "common/util/assert.h"
namespace decompiler {
class LinkedObjectFile;
@@ -234,4 +234,4 @@ class Env {
StackSpillMap m_stack_spill_map;
};
-} // namespace decompiler
\ No newline at end of file
+} // namespace decompiler
diff --git a/decompiler/IR2/Form.cpp b/decompiler/IR2/Form.cpp
index 598152eb6b..adf61071d5 100644
--- a/decompiler/IR2/Form.cpp
+++ b/decompiler/IR2/Form.cpp
@@ -7,6 +7,7 @@
#include "common/type_system/TypeSystem.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/util/data_decompile.h"
+#include "decompiler/util/sparticle_decompile.h"
#include "common/util/print_float.h"
namespace decompiler {
@@ -2985,21 +2986,18 @@ goos::Object DefskelgroupElement::to_form_internal(const Env& env) const {
std::vector lod_forms;
for (const auto& e : m_info.lods) {
- auto f_dist = pretty_print::to_symbol(fmt::format(
- "(meters {})", float_to_string(e.lod_dist->to_form(env).as_float() / METER_LENGTH, false)));
+ auto f_dist = pretty_print::to_symbol(
+ fmt::format("(meters {})", meters_to_string(e.lod_dist->to_form(env).as_float())));
lod_forms.push_back(pretty_print::build_list(e.mgeo->to_form(env), f_dist));
}
forms.push_back(pretty_print::build_list(lod_forms));
+ forms.push_back(pretty_print::to_symbol(fmt::format(
+ ":bounds (static-spherem {} {} {} {})", meters_to_string(m_static_info.bounds.x()),
+ meters_to_string(m_static_info.bounds.y()), meters_to_string(m_static_info.bounds.z()),
+ meters_to_string(m_static_info.bounds.w()))));
forms.push_back(pretty_print::to_symbol(
- fmt::format(":bounds (static-spherem {} {} {} {})",
- float_to_string(m_static_info.bounds.x() / METER_LENGTH, false),
- float_to_string(m_static_info.bounds.y() / METER_LENGTH, false),
- float_to_string(m_static_info.bounds.z() / METER_LENGTH, false),
- float_to_string(m_static_info.bounds.w() / METER_LENGTH, false))));
- forms.push_back(pretty_print::to_symbol(
- fmt::format(":longest-edge (meters {})",
- float_to_string(m_static_info.longest_edge / METER_LENGTH, false))));
+ fmt::format(":longest-edge (meters {})", meters_to_string(m_static_info.longest_edge))));
if (m_static_info.shadow != 0) {
forms.push_back(pretty_print::to_symbol(fmt::format(":shadow {}", m_static_info.shadow)));
@@ -3018,6 +3016,149 @@ goos::Object DefskelgroupElement::to_form_internal(const Env& env) const {
return pretty_print::build_list(forms);
}
+////////////////////////////////
+// DefpartgroupElement
+////////////////////////////////
+
+DefpartgroupElement::DefpartgroupElement(const StaticInfo& data, int group_id)
+ : m_static_info(data), m_group_id(group_id) {}
+
+void DefpartgroupElement::apply(const std::function& f) {
+ f(this);
+}
+
+void DefpartgroupElement::apply_form(const std::function&) {}
+void DefpartgroupElement::collect_vars(RegAccessSet&, bool) const {}
+void DefpartgroupElement::get_modified_regs(RegSet&) const {}
+
+goos::Object DefpartgroupElement::to_form_internal(const Env& env) const {
+ std::vector forms;
+ forms.push_back(pretty_print::to_symbol(fmt::format("defpartgroup {}", name())));
+ forms.push_back(pretty_print::to_symbol(fmt::format(":id {}", m_group_id)));
+ if (m_static_info.duration != 3000) {
+ forms.push_back(pretty_print::to_symbol(fmt::format(":duration {}", m_static_info.duration)));
+ }
+ if (m_static_info.linger != 1500) {
+ forms.push_back(
+ pretty_print::to_symbol(fmt::format(":linger-duration {}", m_static_info.linger)));
+ }
+ if (m_static_info.flags != 0) {
+ auto things = decompile_bitfield_enum_from_int(TypeSpec("sp-group-flag"), env.dts->ts,
+ m_static_info.flags);
+ std::string result = ":flags (";
+ for (auto& thing : things) {
+ result += thing;
+ result += ' ';
+ }
+ result.pop_back();
+ result += ')';
+ forms.push_back(pretty_print::to_symbol(result));
+ }
+ forms.push_back(pretty_print::to_symbol(fmt::format(
+ ":bounds (static-bspherem {} {} {} {})", meters_to_string(m_static_info.bounds.x()),
+ meters_to_string(m_static_info.bounds.y()), meters_to_string(m_static_info.bounds.z()),
+ meters_to_string(m_static_info.bounds.w()))));
+
+ std::vector item_forms;
+ for (const auto& e : m_static_info.elts) {
+ s32 launcher = e.part_id;
+ u16 flags = e.flags;
+ u16 period = e.period;
+ u16 length = e.length;
+ u16 offset = e.offset;
+ u32 hour_mask = e.hour_mask;
+ u32 binding = e.binding;
+
+ std::string result =
+ fmt::format("(sp-item {}", launcher); // use decimal, so it matches array idx
+
+ if (e.fade != 0.0) {
+ result += fmt::format(" :fade-after (meters {})", meters_to_string(e.fade));
+ }
+
+ if (e.falloff != 0.0) {
+ result += fmt::format(" :falloff-to (meters {})", meters_to_string(e.falloff));
+ }
+
+ if (flags) {
+ auto things =
+ decompile_bitfield_enum_from_int(TypeSpec("sp-group-item-flag"), env.dts->ts, flags);
+ result += " :flags (";
+ for (auto& thing : things) {
+ result += thing;
+ result += ' ';
+ }
+ result.pop_back();
+ result += ')';
+ }
+
+ if (period) {
+ result += fmt::format(" :period {}", period);
+ }
+
+ if (length) {
+ result += fmt::format(" :length {}", length);
+ }
+
+ if (offset) {
+ result += fmt::format(" :offset {}", offset);
+ }
+
+ if (hour_mask) {
+ result += fmt::format(" :hour-mask #b{:b}", hour_mask);
+ }
+
+ if (binding) {
+ result += fmt::format(" :binding {}", binding);
+ }
+
+ result += ')';
+
+ item_forms.push_back(pretty_print::to_symbol(result));
+ }
+ if (!item_forms.empty()) {
+ forms.push_back(pretty_print::to_symbol(":parts"));
+ forms.push_back(pretty_print::build_list(item_forms));
+ }
+
+ return pretty_print::build_list(forms);
+}
+
+////////////////////////////////
+// DefpartElement
+////////////////////////////////
+
+DefpartElement::DefpartElement(const StaticInfo& data, int id) : m_static_info(data), m_id(id) {}
+
+void DefpartElement::apply(const std::function& f) {
+ f(this);
+}
+
+void DefpartElement::apply_form(const std::function&) {}
+void DefpartElement::collect_vars(RegAccessSet&, bool) const {}
+void DefpartElement::get_modified_regs(RegSet&) const {}
+
+goos::Object DefpartElement::to_form_internal(const Env& env) const {
+ std::vector forms;
+ forms.push_back(pretty_print::to_symbol("defpart"));
+ forms.push_back(pretty_print::to_symbol(fmt::format("{}", m_id)));
+
+ std::vector item_forms;
+ for (const auto& e : m_static_info.fields) {
+ if (e.field_id == 67) {
+ // sp-end
+ break;
+ }
+ item_forms.push_back(decompile_sparticle_field_init(e, env.dts->ts));
+ }
+ if (!item_forms.empty()) {
+ forms.push_back(pretty_print::to_symbol(":init-specs"));
+ forms.push_back(pretty_print::build_list(item_forms));
+ }
+
+ return pretty_print::build_list(forms);
+}
+
////////////////////////////////
// ResLumpMacroElement
////////////////////////////////
diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h
index 84a927a3d6..15908907a0 100644
--- a/decompiler/IR2/Form.h
+++ b/decompiler/IR2/Form.h
@@ -12,6 +12,7 @@
#include "common/type_system/state.h"
#include "decompiler/IR2/LabelDB.h"
#include "common/math/Vector.h"
+#include "decompiler/ObjectFile/LinkedWord.h"
namespace decompiler {
class Form;
@@ -571,6 +572,10 @@ class ConditionElement : public FormElement {
FormPool& pool,
const std::vector