mirror of
https://github.com/open-goal/jak-project
synced 2026-06-25 02:02:08 -04:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -18,9 +18,10 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-20.04]
|
||||
config: [Debug] # TODO - Eventually we need to make a Release Config
|
||||
compiler: [clang, gcc]
|
||||
experimental: [false]
|
||||
|
||||
name: ${{ matrix.config }}
|
||||
name: ${{ matrix.config }}-${{ matrix.compiler }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
# Set some sort of timeout in the event of run-away builds. We are limited on concurrent jobs so, get rid of them.
|
||||
@@ -44,7 +45,6 @@ jobs:
|
||||
key: submodules-${{ hashFiles('./.gitmodules') }}
|
||||
path: |
|
||||
./third-party/googletest
|
||||
./third-party/spdlog
|
||||
./third-party/zydis
|
||||
./.git/modules/
|
||||
|
||||
@@ -53,6 +53,7 @@ jobs:
|
||||
run: git submodule update --init --recursive --jobs 2
|
||||
|
||||
- name: Prepare Artifact Git Info
|
||||
id: git-vars
|
||||
shell: bash
|
||||
run: |
|
||||
echo "##[set-output name=branch;]${GITHUB_REF#refs/heads/}"
|
||||
@@ -66,10 +67,9 @@ jobs:
|
||||
echo "##[set-output name=short-sha;]$(git rev-parse --short "$GITHUB_SHA")"
|
||||
fi
|
||||
echo "##[set-output name=artifact-metadata;]${ARTIFACT_NAME}"
|
||||
id: git-vars
|
||||
|
||||
- name: Get Package Dependencies
|
||||
run: sudo apt install build-essential cmake ccache gcc g++ lcov make nasm
|
||||
run: sudo apt install build-essential cmake ccache clang gcc g++ lcov make nasm
|
||||
|
||||
# # -- SETUP CCACHE - https://cristianadam.eu/20200113/speeding-up-c-plus-plus-github-actions-using-ccache/
|
||||
# - name: Prepare ccache timestamp
|
||||
@@ -87,9 +87,18 @@ jobs:
|
||||
# restore-keys: |
|
||||
# ${{ matrix.config }}-ccache-
|
||||
|
||||
- name: CMake Generation
|
||||
- name: CMake Generation
|
||||
# run: cmake -D CMAKE_C_COMPILER_LAUNCHER=ccache -D CMAKE_CXX_COMPILER_LAUNCHER=ccache -B build -DCODE_COVERAGE=ON
|
||||
run: cmake -B build -DCODE_COVERAGE=ON
|
||||
run: |
|
||||
if [ "${{ matrix.compiler }}" == 'clang' ]; then
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
cmake -B build -DCODE_COVERAGE=ON -DASAN_BUILD=ON
|
||||
else
|
||||
export CC=gcc
|
||||
export CXX=g++
|
||||
cmake -B build -DCODE_COVERAGE=ON
|
||||
fi
|
||||
|
||||
- name: Build Project
|
||||
working-directory: ./build
|
||||
@@ -106,9 +115,15 @@ jobs:
|
||||
run: make -j4
|
||||
|
||||
- name: Run Tests
|
||||
run: ./test_code_coverage.sh
|
||||
run: |
|
||||
if [ "${{ matrix.compiler }}" == 'clang' ]; then
|
||||
./test.sh
|
||||
else
|
||||
./test_code_coverage.sh
|
||||
fi
|
||||
|
||||
- name: Coveralls
|
||||
if: ${{ matrix.compiler }} != 'clang'
|
||||
uses: coverallsapp/github-action@master
|
||||
continue-on-error: true # Sometimes Coveralls has intermittent problems, and codecoverage isn't critical to our success
|
||||
with:
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-2019]
|
||||
config: [Debug] # TODO - Eventually we need to make a Release Config
|
||||
config: [Release]
|
||||
experimental: [false]
|
||||
|
||||
name: ${{ matrix.config }}
|
||||
@@ -44,7 +44,6 @@ jobs:
|
||||
key: submodules-${{ hashFiles('./.gitmodules') }}
|
||||
path: |
|
||||
./third-party/googletest
|
||||
./third-party/spdlog
|
||||
./third-party/zydis
|
||||
./.git/modules/
|
||||
|
||||
@@ -76,7 +75,7 @@ jobs:
|
||||
run: |
|
||||
call "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat"
|
||||
cmake --version
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Debug -G "NMake Makefiles" .
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -G "NMake Makefiles" .
|
||||
|
||||
- name: Build Project
|
||||
working-directory: ./build
|
||||
|
||||
@@ -4,3 +4,4 @@ cmake-build-debug/*
|
||||
build/*
|
||||
decompiler_out/*
|
||||
logs/*
|
||||
log/*
|
||||
@@ -1,9 +1,6 @@
|
||||
[submodule "third-party/googletest"]
|
||||
path = third-party/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
[submodule "third-party/spdlog"]
|
||||
path = third-party/spdlog
|
||||
url = https://github.com/gabime/spdlog.git
|
||||
[submodule "third-party/zydis"]
|
||||
path = third-party/zydis
|
||||
url = https://github.com/zyantific/zydis.git
|
||||
|
||||
+3
-26
@@ -44,7 +44,7 @@ IF (WIN32)
|
||||
ENDIF ()
|
||||
|
||||
IF (ASAN_BUILD)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O1")
|
||||
message(STATUS "Doing ASAN build")
|
||||
ENDIF ()
|
||||
|
||||
@@ -64,28 +64,8 @@ include_directories(./)
|
||||
|
||||
include_directories(SYSTEM third-party/inja)
|
||||
|
||||
# build spdlog as a shared library to improve compile times
|
||||
# adding this as a SYSTEM include suppresses all the terrible warnings in spdlog
|
||||
include_directories(SYSTEM third-party/spdlog/include)
|
||||
# this makes spdlog generate a shared library that we can link against
|
||||
set(SPDLOG_BUILD_SHARED ON CACHE BOOL "a" FORCE)
|
||||
# this makes the spdlog includes not use the header only version, making compiling faster
|
||||
add_definitions(-DSPDLOG_COMPILED_LIB)
|
||||
|
||||
# build goos
|
||||
add_subdirectory(common/goos)
|
||||
|
||||
# build type_system library for compiler/decompiler
|
||||
add_subdirectory(common/type_system)
|
||||
|
||||
# build common_util library
|
||||
add_subdirectory(common/util)
|
||||
|
||||
# build cross platform socket library
|
||||
add_subdirectory(common/cross_sockets)
|
||||
|
||||
# build cross platform debug library
|
||||
add_subdirectory(common/cross_os_debug)
|
||||
# build common library
|
||||
add_subdirectory(common)
|
||||
|
||||
# build decompiler
|
||||
add_subdirectory(decompiler)
|
||||
@@ -108,9 +88,6 @@ add_subdirectory(third-party/minilzo)
|
||||
# build format library
|
||||
add_subdirectory(third-party/fmt)
|
||||
|
||||
# build spdlog library
|
||||
add_subdirectory(third-party/spdlog)
|
||||
|
||||
# build zydis third party library for disassembling x86
|
||||
option(ZYDIS_BUILD_TOOLS "" OFF)
|
||||
option(ZYDIS_BUILD_EXAMPLES "" OFF)
|
||||
|
||||
+40
-38
@@ -1,40 +1,42 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": "",
|
||||
"variables": [
|
||||
{
|
||||
"name": "INSTALL_GTEST",
|
||||
"value": "True",
|
||||
"type": "BOOL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "x64-Release",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Release",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": "",
|
||||
"variables": [
|
||||
{
|
||||
"name": "INSTALL_GTEST",
|
||||
"value": "True",
|
||||
"type": "BOOL"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"addressSanitizerEnabled": true,
|
||||
"ctestCommandArgs": "",
|
||||
"variables": [
|
||||
{
|
||||
"name": "INSTALL_GTEST",
|
||||
"value": "True",
|
||||
"type": "BOOL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Release",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "RelWithDebInfo",
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"addressSanitizerEnabled": true,
|
||||
"ctestCommandArgs": "",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"variables": [
|
||||
{
|
||||
"name": "INSTALL_GTEST",
|
||||
"value": "True",
|
||||
"type": "BOOL"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,16 +3,20 @@
|
||||

|
||||

|
||||
[](https://coveralls.io/github/water111/jak-project?branch=master)
|
||||
[](https://www.codacy.com/gh/water111/jak-project/dashboard?utm_source=github.com&utm_medium=referral&utm_content=xTVaser/jak-project&utm_campaign=Badge_Grade)
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
- [Project Description](#project-description)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Project Description](#project-description)
|
||||
- [Getting Started - Linux (Ubuntu)](#getting-started---linux-ubuntu)
|
||||
- [Getting Started - Windows](#getting-started---windows)
|
||||
- [Project Layout](#project-layout)
|
||||
- [Directory Layout](#directory-layout)
|
||||
- [More Documentation](#more-documentation)
|
||||
- [ASan Build](#asan-build)
|
||||
<!-- tocstop -->
|
||||
## Project Description
|
||||
|
||||
@@ -161,7 +165,6 @@ The final component is the "runtime", located in `game`. This is the part of the
|
||||
- `mman`: Windows library used to emulate `mmap` on Linux
|
||||
- `run-clang-format`: Utility to check and enforce code formatting
|
||||
- `run-clang-tidy`
|
||||
- `spdlog`: Logging library
|
||||
- `zydis`: x86-64 disassembler used in the OpenGOAL debugger
|
||||
- `json`: A JSON library
|
||||
- `linenoise`: Used for the REPL input. Support history and useful editing shortcuts.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
add_library(common
|
||||
SHARED
|
||||
cross_os_debug/xdbg.cpp
|
||||
cross_sockets/xsocket.cpp
|
||||
goos/Interpreter.cpp
|
||||
goos/Object.cpp
|
||||
goos/ParseHelpers.cpp
|
||||
goos/PrettyPrinter.cpp
|
||||
goos/Reader.cpp
|
||||
goos/TextDB.cpp
|
||||
log/log.cpp
|
||||
type_system/deftype.cpp
|
||||
type_system/Type.cpp
|
||||
type_system/TypeFieldLookup.cpp
|
||||
type_system/TypeSpec.cpp
|
||||
type_system/TypeSystem.cpp
|
||||
util/DgoWriter.cpp
|
||||
util/FileUtil.cpp
|
||||
util/Timer.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(common fmt)
|
||||
|
||||
IF(WIN32)
|
||||
target_link_libraries(common wsock32 ws2_32)
|
||||
ELSE()
|
||||
target_link_libraries(common stdc++fs)
|
||||
ENDIF()
|
||||
@@ -1,4 +0,0 @@
|
||||
add_library(cross_os_debug SHARED
|
||||
xdbg.cpp)
|
||||
|
||||
target_link_libraries(cross_os_debug fmt)
|
||||
@@ -92,32 +92,35 @@ bool attach_and_break(const ThreadID& tid) {
|
||||
*/
|
||||
bool check_stopped(const ThreadID& tid, SignalInfo* out) {
|
||||
int status;
|
||||
if (waitpid(tid.id, &status, WNOHANG) < 0) {
|
||||
int rv = waitpid(tid.id, &status, WNOHANG);
|
||||
if (rv < 0) {
|
||||
printf("[Debugger] Failed to waitpid: %s.\n", strerror(errno));
|
||||
// assert(false); // todo, temp because I think we should never hit this.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (WIFSTOPPED(status)) {
|
||||
auto sig = WSTOPSIG(status);
|
||||
if (out) {
|
||||
switch (sig) {
|
||||
case SIGSEGV:
|
||||
out->kind = SignalInfo::SEGFAULT;
|
||||
break;
|
||||
case SIGFPE:
|
||||
out->kind = SignalInfo::MATH_EXCEPTION;
|
||||
break;
|
||||
case SIGTRAP:
|
||||
out->kind = SignalInfo::BREAK;
|
||||
break;
|
||||
if (rv > 0) {
|
||||
// status has actually changed
|
||||
if (WIFSTOPPED(status)) {
|
||||
auto sig = WSTOPSIG(status);
|
||||
if (out) {
|
||||
switch (sig) {
|
||||
case SIGSEGV:
|
||||
out->kind = SignalInfo::SEGFAULT;
|
||||
break;
|
||||
case SIGFPE:
|
||||
out->kind = SignalInfo::MATH_EXCEPTION;
|
||||
break;
|
||||
case SIGTRAP:
|
||||
out->kind = SignalInfo::BREAK;
|
||||
break;
|
||||
|
||||
default:
|
||||
out->kind = SignalInfo::UNKNOWN;
|
||||
default:
|
||||
out->kind = SignalInfo::UNKNOWN;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
add_library(cross_sockets
|
||||
SHARED
|
||||
"xsocket.h"
|
||||
"xsocket.cpp")
|
||||
|
||||
IF (WIN32)
|
||||
# set stuff for windows
|
||||
target_link_libraries(cross_sockets wsock32 ws2_32)
|
||||
ELSE()
|
||||
# set stuff for other systems
|
||||
target_link_libraries(cross_sockets)
|
||||
ENDIF()
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
IF (WIN32)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /O2")
|
||||
ELSE()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
|
||||
ENDIF()
|
||||
|
||||
add_library(goos SHARED Object.cpp ParseHelpers.cpp TextDB.cpp Reader.cpp Interpreter.cpp PrettyPrinter.cpp ParseHelpers.cpp ParseHelpers.h)
|
||||
target_link_libraries(goos common_util fmt)
|
||||
@@ -0,0 +1,114 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include "third-party/fmt/color.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace lg {
|
||||
struct Logger {
|
||||
Logger() = default;
|
||||
|
||||
bool initialized = false;
|
||||
FILE* fp = nullptr;
|
||||
level stdout_log_level = level::trace;
|
||||
level file_log_level = level::trace;
|
||||
level flush_level = level::trace;
|
||||
std::mutex mutex;
|
||||
|
||||
~Logger() {
|
||||
// will run when program exits.
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Logger gLogger;
|
||||
|
||||
namespace internal {
|
||||
const char* log_level_names[] = {"trace", "debug", "info", "warn", "error", "die"};
|
||||
const fmt::color log_colors[] = {fmt::color::gray, fmt::color::turquoise, fmt::color::light_green,
|
||||
fmt::color::yellow, fmt::color::red, fmt::color::hot_pink};
|
||||
|
||||
void log_message(level log_level, LogTime& now, const char* message) {
|
||||
#ifdef __linux__
|
||||
char date_time_buffer[128];
|
||||
time_t now_seconds = now.tv.tv_sec;
|
||||
auto now_milliseconds = now.tv.tv_usec / 1000;
|
||||
strftime(date_time_buffer, 128, "%Y-%m-%d %H:%M:%S", localtime(&now_seconds));
|
||||
std::string date_string = fmt::format("[{}:{:03d}]", date_time_buffer, now_milliseconds);
|
||||
#else
|
||||
char date_time_buffer[128];
|
||||
strftime(date_time_buffer, 128, "%Y-%m-%d %H:%M:%S", localtime(&now.tim));
|
||||
std::string date_string = fmt::format("[{}]", date_time_buffer);
|
||||
#endif
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gLogger.mutex);
|
||||
if (gLogger.fp && log_level >= gLogger.file_log_level) {
|
||||
// log to file
|
||||
std::string file_string =
|
||||
fmt::format("{} [{}] {}\n", date_string, log_level_names[int(log_level)], message);
|
||||
fwrite(file_string.c_str(), file_string.length(), 1, gLogger.fp);
|
||||
if (log_level >= gLogger.flush_level) {
|
||||
fflush(gLogger.fp);
|
||||
}
|
||||
}
|
||||
|
||||
if (log_level >= gLogger.stdout_log_level) {
|
||||
fmt::print("{} [", date_string);
|
||||
fmt::print(fg(log_colors[int(log_level)]), "{}", log_level_names[int(log_level)]);
|
||||
fmt::print("] {}\n", message);
|
||||
if (log_level >= gLogger.flush_level) {
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (log_level == level::die) {
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
void set_file(const std::string& filename) {
|
||||
assert(!gLogger.fp);
|
||||
gLogger.fp = fopen(filename.c_str(), "w");
|
||||
assert(gLogger.fp);
|
||||
}
|
||||
|
||||
void set_flush_level(level log_level) {
|
||||
gLogger.flush_level = log_level;
|
||||
}
|
||||
|
||||
void set_file_level(level log_level) {
|
||||
gLogger.file_log_level = log_level;
|
||||
}
|
||||
|
||||
void set_stdout_level(level log_level) {
|
||||
gLogger.stdout_log_level = log_level;
|
||||
}
|
||||
|
||||
void set_max_debug_levels() {
|
||||
gLogger.flush_level = level::trace;
|
||||
gLogger.stdout_log_level = level::trace;
|
||||
gLogger.file_log_level = level::trace;
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
assert(!gLogger.initialized);
|
||||
gLogger.initialized = true;
|
||||
}
|
||||
|
||||
void finish() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gLogger.mutex);
|
||||
if (gLogger.fp) {
|
||||
fclose(gLogger.fp);
|
||||
gLogger.fp = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lg
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#include <string>
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
namespace lg {
|
||||
|
||||
#ifdef __linux__
|
||||
struct LogTime {
|
||||
timeval tv;
|
||||
};
|
||||
#else
|
||||
struct LogTime {
|
||||
time_t tim;
|
||||
};
|
||||
#endif
|
||||
|
||||
// Logging API
|
||||
enum class level { trace = 0, debug = 1, info = 2, warn = 3, error = 4, die = 5 };
|
||||
|
||||
namespace internal {
|
||||
// log implementation stuff, not to be called by the user
|
||||
void log_message(level log_level, LogTime& now, const char* message);
|
||||
} // namespace internal
|
||||
|
||||
void set_file(const std::string& filename);
|
||||
void set_flush_level(level log_level);
|
||||
void set_file_level(level log_level);
|
||||
void set_stdout_level(level log_level);
|
||||
void set_max_debug_levels();
|
||||
void initialize();
|
||||
void finish();
|
||||
|
||||
template <typename... Args>
|
||||
void log(level log_level, const std::string& format, Args&&... args) {
|
||||
LogTime now;
|
||||
#ifdef __linux__
|
||||
gettimeofday(&now.tv, nullptr);
|
||||
#else
|
||||
now.tim = time(nullptr);
|
||||
#endif
|
||||
std::string formatted_message = fmt::format(format, std::forward<Args>(args)...);
|
||||
internal::log_message(log_level, now, formatted_message.c_str());
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void trace(const std::string& format, Args&&... args) {
|
||||
log(level::trace, format, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void debug(const std::string& format, Args&&... args) {
|
||||
log(level::debug, format, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void info(const std::string& format, Args&&... args) {
|
||||
log(level::info, format, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void warn(const std::string& format, Args&&... args) {
|
||||
log(level::warn, format, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void error(const std::string& format, Args&&... args) {
|
||||
log(level::error, format, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void die(const std::string& format, Args&&... args) {
|
||||
log(level::die, format, std::forward<Args>(args)...);
|
||||
}
|
||||
} // namespace lg
|
||||
@@ -1,9 +0,0 @@
|
||||
add_library(type_system
|
||||
SHARED
|
||||
TypeSystem.cpp
|
||||
Type.cpp
|
||||
TypeSpec.cpp
|
||||
deftype.cpp
|
||||
TypeFieldLookup.cpp)
|
||||
|
||||
target_link_libraries(type_system fmt goos)
|
||||
@@ -1,9 +0,0 @@
|
||||
add_library(common_util
|
||||
SHARED
|
||||
FileUtil.cpp
|
||||
DgoWriter.cpp
|
||||
Timer.cpp)
|
||||
|
||||
IF(UNIX)
|
||||
target_link_libraries(common_util stdc++fs)
|
||||
ENDIF()
|
||||
+51
-30
@@ -1,38 +1,59 @@
|
||||
add_executable(decompiler
|
||||
main.cpp
|
||||
ObjectFile/ObjectFileDB.cpp
|
||||
Disasm/Instruction.cpp
|
||||
Disasm/InstructionDecode.cpp
|
||||
Disasm/OpcodeInfo.cpp
|
||||
Disasm/Register.cpp
|
||||
ObjectFile/LinkedObjectFileCreation.cpp
|
||||
ObjectFile/LinkedObjectFile.cpp
|
||||
Function/Function.cpp
|
||||
config.cpp
|
||||
util/DecompilerTypeSystem.cpp
|
||||
Function/BasicBlocks.cpp
|
||||
Disasm/InstructionMatching.cpp
|
||||
Function/CfgVtx.cpp
|
||||
IR/BasicOpBuilder.cpp
|
||||
IR/CfgBuilder.cpp
|
||||
IR/IR.cpp
|
||||
Function/TypeInspector.cpp
|
||||
data/tpage.cpp
|
||||
add_library(
|
||||
decomp
|
||||
SHARED
|
||||
data/game_count.cpp
|
||||
data/game_text.cpp
|
||||
data/StrFileReader.cpp
|
||||
data/game_count.cpp
|
||||
Function/TypeAnalysis.cpp
|
||||
IR/IR_TypeAnalysis.cpp
|
||||
util/TP_Type.cpp
|
||||
Function/RegUsage.cpp
|
||||
data/tpage.cpp
|
||||
|
||||
Disasm/Instruction.cpp
|
||||
Disasm/InstructionDecode.cpp
|
||||
Disasm/InstructionMatching.cpp
|
||||
Disasm/InstructionParser.cpp
|
||||
Disasm/OpcodeInfo.cpp
|
||||
Disasm/Register.cpp
|
||||
|
||||
Function/BasicBlocks.cpp
|
||||
Function/CfgVtx.cpp
|
||||
Function/ExpressionBuilder.cpp
|
||||
Function/ExpressionStack.cpp
|
||||
IR/IR_ExpressionStack.cpp)
|
||||
Function/Function.cpp
|
||||
Function/RegUsage.cpp
|
||||
Function/TypeAnalysis.cpp
|
||||
Function/TypeInspector.cpp
|
||||
|
||||
IR/BasicOpBuilder.cpp
|
||||
IR/CfgBuilder.cpp
|
||||
IR/IR.cpp
|
||||
IR/IR_ExpressionStack.cpp
|
||||
IR/IR_TypeAnalysis.cpp
|
||||
|
||||
IR2/AtomicOp.cpp
|
||||
IR2/AtomicOpBuilder.cpp
|
||||
IR2/Env.cpp
|
||||
|
||||
ObjectFile/LinkedObjectFile.cpp
|
||||
ObjectFile/LinkedObjectFileCreation.cpp
|
||||
ObjectFile/ObjectFileDB.cpp
|
||||
|
||||
util/DecompilerTypeSystem.cpp
|
||||
util/TP_Type.cpp
|
||||
|
||||
config.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(decomp
|
||||
minilzo
|
||||
common
|
||||
fmt
|
||||
)
|
||||
|
||||
add_executable(decompiler
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(decompiler
|
||||
goos
|
||||
decomp
|
||||
common
|
||||
minilzo
|
||||
common_util
|
||||
type_system
|
||||
spdlog
|
||||
fmt)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
* A label to a location in an object file.
|
||||
* Doesn't have to be word aligned.
|
||||
*/
|
||||
struct DecompilerLabel {
|
||||
std::string name;
|
||||
int target_segment;
|
||||
int offset; // in bytes
|
||||
};
|
||||
} // namespace decompiler
|
||||
@@ -8,17 +8,18 @@
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
* Convert atom to a string for disassembly.
|
||||
*/
|
||||
std::string InstructionAtom::to_string(const LinkedObjectFile& file) const {
|
||||
std::string InstructionAtom::to_string(const std::vector<DecompilerLabel>& labels) const {
|
||||
switch (kind) {
|
||||
case REGISTER:
|
||||
return reg.to_string();
|
||||
case IMM:
|
||||
return std::to_string(imm);
|
||||
case LABEL:
|
||||
return file.get_label_name(label_id);
|
||||
return labels.at(label_id).name;
|
||||
case VU_ACC:
|
||||
return "acc";
|
||||
case VU_Q:
|
||||
@@ -115,6 +116,25 @@ bool InstructionAtom::is_link_or_label() const {
|
||||
return kind == IMM_SYM || kind == LABEL;
|
||||
}
|
||||
|
||||
bool InstructionAtom::operator==(const InstructionAtom& other) const {
|
||||
if (kind != other.kind) {
|
||||
return false;
|
||||
}
|
||||
switch (kind) {
|
||||
case REGISTER:
|
||||
return reg == other.reg;
|
||||
case IMM:
|
||||
return imm == other.imm;
|
||||
case LABEL:
|
||||
return label_id == other.label_id;
|
||||
case VU_ACC:
|
||||
case VU_Q:
|
||||
return true;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert just the name of the opcode to a string, omitting src/dst, but including
|
||||
* suffixes (interlock, broadcasts and destination)
|
||||
@@ -169,7 +189,7 @@ std::string Instruction::op_name_to_string() const {
|
||||
/*!
|
||||
* Convert entire instruction to a string.
|
||||
*/
|
||||
std::string Instruction::to_string(const LinkedObjectFile& file) const {
|
||||
std::string Instruction::to_string(const std::vector<DecompilerLabel>& labels) const {
|
||||
auto& info = gOpcodeInfo[(int)kind];
|
||||
auto result = op_name_to_string();
|
||||
|
||||
@@ -178,33 +198,33 @@ std::string Instruction::to_string(const LinkedObjectFile& file) const {
|
||||
assert(n_dst == 0);
|
||||
assert(n_src == 3);
|
||||
result += " ";
|
||||
result += src[0].to_string(file);
|
||||
result += src[0].to_string(labels);
|
||||
result += ", ";
|
||||
result += src[1].to_string(file);
|
||||
result += src[1].to_string(labels);
|
||||
result += "(";
|
||||
result += src[2].to_string(file);
|
||||
result += src[2].to_string(labels);
|
||||
result += ")";
|
||||
} else if (info.is_load) {
|
||||
assert(n_dst == 1);
|
||||
assert(n_src == 2);
|
||||
result += " ";
|
||||
result += dst[0].to_string(file);
|
||||
result += dst[0].to_string(labels);
|
||||
result += ", ";
|
||||
result += src[0].to_string(file);
|
||||
result += src[0].to_string(labels);
|
||||
result += "(";
|
||||
result += src[1].to_string(file);
|
||||
result += src[1].to_string(labels);
|
||||
result += ")";
|
||||
} else {
|
||||
// for instructions that aren't a store or load, the dest/sources are comma separated.
|
||||
bool end_comma = false;
|
||||
|
||||
for (uint8_t i = 0; i < n_dst; i++) {
|
||||
result += " " + dst[i].to_string(file) + ",";
|
||||
result += " " + dst[i].to_string(labels) + ",";
|
||||
end_comma = true;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < n_src; i++) {
|
||||
result += " " + src[i].to_string(file) + ",";
|
||||
result += " " + src[i].to_string(labels) + ",";
|
||||
end_comma = true;
|
||||
}
|
||||
|
||||
@@ -312,3 +332,25 @@ int Instruction::get_label_target() const {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Instruction::operator==(const Instruction& other) const {
|
||||
if (kind != other.kind || n_src != other.n_src || n_dst != other.n_dst ||
|
||||
cop2_dest != other.cop2_dest || cop2_bc != other.cop2_bc || il != other.il) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_dst; i++) {
|
||||
if (dst[i] != other.dst[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_src; i++) {
|
||||
if (src[i] != other.src[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -9,10 +9,12 @@
|
||||
#ifndef NEXT_INSTRUCTION_H
|
||||
#define NEXT_INSTRUCTION_H
|
||||
|
||||
#include <vector>
|
||||
#include "OpcodeInfo.h"
|
||||
#include "Register.h"
|
||||
|
||||
class LinkedObjectFile;
|
||||
namespace decompiler {
|
||||
struct DecompilerLabel;
|
||||
|
||||
constexpr int MAX_INSTRUCTION_SOURCE = 3;
|
||||
constexpr int MAX_INTRUCTION_DEST = 1;
|
||||
@@ -41,7 +43,7 @@ struct InstructionAtom {
|
||||
int get_label() const;
|
||||
std::string get_sym() const;
|
||||
|
||||
std::string to_string(const LinkedObjectFile& file) const;
|
||||
std::string to_string(const std::vector<DecompilerLabel>& labels) const;
|
||||
|
||||
bool is_link_or_label() const;
|
||||
bool is_reg() const { return kind == REGISTER; }
|
||||
@@ -51,11 +53,13 @@ struct InstructionAtom {
|
||||
|
||||
bool is_reg(Register r) const { return kind == REGISTER && reg == r; }
|
||||
|
||||
bool operator==(const InstructionAtom& other) const;
|
||||
bool operator!=(const InstructionAtom& other) const { return !((*this) == other); }
|
||||
|
||||
private:
|
||||
int32_t imm;
|
||||
int label_id;
|
||||
Register reg;
|
||||
|
||||
std::string sym;
|
||||
};
|
||||
|
||||
@@ -66,7 +70,7 @@ class Instruction {
|
||||
InstructionKind kind = InstructionKind::UNKNOWN;
|
||||
|
||||
std::string op_name_to_string() const;
|
||||
std::string to_string(const LinkedObjectFile& file) const;
|
||||
std::string to_string(const std::vector<DecompilerLabel>& labels) const;
|
||||
bool is_valid() const;
|
||||
|
||||
void add_src(InstructionAtom& a);
|
||||
@@ -89,10 +93,13 @@ class Instruction {
|
||||
|
||||
int get_label_target() const;
|
||||
|
||||
bool operator==(const Instruction& other) const;
|
||||
bool operator!=(const Instruction& other) const { return !((*this) == other); }
|
||||
|
||||
// extra fields for some COP2 instructions.
|
||||
uint8_t cop2_dest = 0xff; // 0xff indicates "don't print dest"
|
||||
uint8_t cop2_bc = 0xff; // 0xff indicates "don't print bc"
|
||||
uint8_t il = 0xff; // 0xff indicates "don't print il"
|
||||
};
|
||||
|
||||
} // namespace decompiler
|
||||
#endif // NEXT_INSTRUCTION_H
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cassert>
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
|
||||
namespace decompiler {
|
||||
// utility class to extract fields of an opcode.
|
||||
struct OpcodeFields {
|
||||
OpcodeFields(uint32_t _data) : data(_data) {}
|
||||
@@ -1171,3 +1172,4 @@ Instruction decode_instruction(LinkedWord& word, LinkedObjectFile& file, int seg
|
||||
|
||||
return i;
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -11,9 +11,10 @@
|
||||
|
||||
#include "Instruction.h"
|
||||
|
||||
namespace decompiler {
|
||||
class LinkedWord;
|
||||
class LinkedObjectFile;
|
||||
|
||||
Instruction decode_instruction(LinkedWord& word, LinkedObjectFile& file, int seg_id, int word_id);
|
||||
|
||||
} // namespace decompiler
|
||||
#endif // NEXT_INSTRUCTIONDECODE_H
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cassert>
|
||||
#include "InstructionMatching.h"
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
* Check if the given instruction stores a GPR with the specified parameters.
|
||||
*/
|
||||
@@ -348,3 +349,4 @@ bool is_always_branch(const Instruction& instr) {
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace decompiler
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Instruction.h"
|
||||
#include "decompiler/util/MatchParam.h"
|
||||
|
||||
namespace decompiler {
|
||||
bool is_no_link_gpr_store(const Instruction& instr,
|
||||
MatchParam<int> size,
|
||||
MatchParam<Register> src,
|
||||
@@ -56,5 +57,5 @@ Register make_fpr(int fpr);
|
||||
|
||||
bool is_branch(const Instruction& instr, MatchParam<bool> likely);
|
||||
bool is_always_branch(const Instruction& instr);
|
||||
|
||||
} // namespace decompiler
|
||||
#endif // JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include "common/common_types.h"
|
||||
#include "InstructionParser.h"
|
||||
|
||||
namespace decompiler {
|
||||
InstructionParser::InstructionParser() {
|
||||
init_opcode_info();
|
||||
|
||||
// we only support a subset of the total instructions. These are common used and don't have
|
||||
// strange formatting.
|
||||
int added = 0;
|
||||
for (auto i : {InstructionKind::DADDIU, InstructionKind::ADDIU, InstructionKind::SLTI,
|
||||
InstructionKind::SLTIU, InstructionKind::SB, InstructionKind::SH,
|
||||
InstructionKind::SW, InstructionKind::SD, InstructionKind::SQ,
|
||||
InstructionKind::LB, InstructionKind::LBU, InstructionKind::LH,
|
||||
InstructionKind::LHU, InstructionKind::LW, InstructionKind::LWU,
|
||||
InstructionKind::LD, InstructionKind::LQ, InstructionKind::LDR,
|
||||
InstructionKind::LDL, InstructionKind::LWL, InstructionKind::LWR,
|
||||
InstructionKind::DADDU, InstructionKind::SUBU, InstructionKind::ADDU,
|
||||
InstructionKind::DSUBU, InstructionKind::MULT3, InstructionKind::MULTU3,
|
||||
InstructionKind::AND, InstructionKind::OR, InstructionKind::NOR,
|
||||
InstructionKind::XOR, InstructionKind::MOVN, InstructionKind::MOVZ,
|
||||
InstructionKind::SLT, InstructionKind::SLTU, InstructionKind::SLL,
|
||||
InstructionKind::SRA, InstructionKind::SRL, InstructionKind::DSLL,
|
||||
InstructionKind::DSLL32, InstructionKind::DSRA, InstructionKind::DSRA32,
|
||||
InstructionKind::DSRL, InstructionKind::DSRL32, InstructionKind::DSRAV,
|
||||
InstructionKind::SLLV, InstructionKind::DSLLV, InstructionKind::DSRLV,
|
||||
InstructionKind::DIV, InstructionKind::DIVU, InstructionKind::ORI,
|
||||
InstructionKind::XORI, InstructionKind::ANDI, InstructionKind::LUI,
|
||||
InstructionKind::JALR, InstructionKind::JR, InstructionKind::LWC1,
|
||||
InstructionKind::SWC1, InstructionKind::ADDS, InstructionKind::SUBS,
|
||||
InstructionKind::MULS, InstructionKind::DIVS, InstructionKind::MINS,
|
||||
InstructionKind::MAXS, InstructionKind::MADDS, InstructionKind::MSUBS,
|
||||
InstructionKind::RSQRTS, InstructionKind::ABSS, InstructionKind::NEGS,
|
||||
InstructionKind::CVTSW, InstructionKind::CVTWS, InstructionKind::MOVS,
|
||||
InstructionKind::SQRTS, InstructionKind::CLTS, InstructionKind::CLES,
|
||||
InstructionKind::CEQS, InstructionKind::BC1F, InstructionKind::BC1T,
|
||||
InstructionKind::BEQ, InstructionKind::BNE, InstructionKind::BEQL,
|
||||
InstructionKind::BNEL, InstructionKind::BC1FL, InstructionKind::BC1TL,
|
||||
InstructionKind::BLTZ, InstructionKind::BGEZ, InstructionKind::BLEZ,
|
||||
InstructionKind::BGTZ, InstructionKind::BLTZL, InstructionKind::BGTZL,
|
||||
InstructionKind::BGEZL}) {
|
||||
auto& info = gOpcodeInfo[int(i)];
|
||||
if (info.defined) {
|
||||
m_opcode_name_lookup[info.name] = int(i);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
assert(added == int(m_opcode_name_lookup.size()));
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::string get_until_space(std::string& instr) {
|
||||
assert(!instr.empty());
|
||||
size_t i;
|
||||
for (i = 0; i < instr.length(); i++) {
|
||||
if (instr[i] == ' ') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto name = instr.substr(0, i);
|
||||
if (i == instr.length()) {
|
||||
instr.clear();
|
||||
} else {
|
||||
instr = instr.substr(i + 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string get_comma_separated(std::string& instr) {
|
||||
assert(!instr.empty());
|
||||
auto arg = get_until_space(instr);
|
||||
if (instr.empty()) {
|
||||
assert(arg.back() != ',');
|
||||
} else {
|
||||
assert(arg.back() == ',');
|
||||
arg.pop_back();
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
std::string get_before_paren(std::string& instr) {
|
||||
size_t i;
|
||||
for (i = 0; i < instr.length(); i++) {
|
||||
if (instr[i] == '(') {
|
||||
auto result = instr.substr(0, i);
|
||||
instr = instr.substr(i);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
|
||||
std::string get_in_paren(std::string& instr) {
|
||||
assert(instr.length() > 2);
|
||||
assert(instr.front() == '(');
|
||||
size_t i;
|
||||
for (i = 0; i < instr.length(); i++) {
|
||||
if (instr[i] == ')') {
|
||||
auto result = instr.substr(1, i - 1);
|
||||
if (i == instr.length()) {
|
||||
instr.clear();
|
||||
} else {
|
||||
instr = instr.substr(i + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
|
||||
bool is_integer(const std::string& str) {
|
||||
assert(!str.empty());
|
||||
char* end;
|
||||
std::strtol(str.c_str(), &end, 10);
|
||||
return end == str.c_str() + str.length();
|
||||
}
|
||||
|
||||
int parse_integer(const std::string& str) {
|
||||
assert(!str.empty());
|
||||
char* end;
|
||||
int result = std::strtol(str.c_str(), &end, 10);
|
||||
assert(end == str.c_str() + str.length());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> string_to_lines(const std::string& str) {
|
||||
std::vector<std::string> result;
|
||||
std::string::size_type i;
|
||||
std::string::size_type start = 0;
|
||||
while (true) {
|
||||
i = str.find('\n', start);
|
||||
if (i == std::string::npos) {
|
||||
if (start < str.length()) {
|
||||
result.push_back(str.substr(start));
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
result.push_back(str.substr(start, i - start));
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Instruction InstructionParser::parse_single_instruction(
|
||||
std::string str,
|
||||
const std::vector<DecompilerLabel>& labels) {
|
||||
auto name = get_until_space(str);
|
||||
auto lookup = m_opcode_name_lookup.find(name);
|
||||
if (lookup == m_opcode_name_lookup.end()) {
|
||||
throw std::runtime_error("InstructionParser cannot handle opcode " + name);
|
||||
}
|
||||
|
||||
Instruction instr;
|
||||
instr.kind = InstructionKind(lookup->second);
|
||||
auto& info = gOpcodeInfo[lookup->second];
|
||||
for (u8 i = 0; i < info.step_count; i++) {
|
||||
auto& step = info.steps[i];
|
||||
switch (step.decode) {
|
||||
case DecodeType::GPR: {
|
||||
std::string gpr_name;
|
||||
if ((info.is_store || info.is_load) && i == 2) {
|
||||
gpr_name = get_in_paren(str);
|
||||
} else {
|
||||
gpr_name = get_comma_separated(str);
|
||||
}
|
||||
|
||||
Register reg(gpr_name);
|
||||
assert(reg.get_kind() == Reg::GPR);
|
||||
InstructionAtom atom;
|
||||
atom.set_reg(reg);
|
||||
if (step.is_src) {
|
||||
instr.add_src(atom);
|
||||
} else {
|
||||
instr.add_dst(atom);
|
||||
}
|
||||
} break;
|
||||
|
||||
case DecodeType::FPR: {
|
||||
auto reg_name = get_comma_separated(str);
|
||||
Register reg(reg_name);
|
||||
assert(reg.get_kind() == Reg::FPR);
|
||||
InstructionAtom atom;
|
||||
atom.set_reg(reg);
|
||||
if (step.is_src) {
|
||||
instr.add_src(atom);
|
||||
} else {
|
||||
instr.add_dst(atom);
|
||||
}
|
||||
} break;
|
||||
|
||||
case DecodeType::IMM: {
|
||||
InstructionAtom atom;
|
||||
std::string atom_str;
|
||||
if ((info.is_store || info.is_load) && i == 1) {
|
||||
// number before paren
|
||||
atom_str = get_before_paren(str);
|
||||
} else {
|
||||
atom_str = get_comma_separated(str);
|
||||
}
|
||||
|
||||
if (is_integer(atom_str)) {
|
||||
auto amt = parse_integer(atom_str);
|
||||
atom.set_imm(amt);
|
||||
} else {
|
||||
atom.set_sym(atom_str);
|
||||
}
|
||||
if (step.is_src) {
|
||||
instr.add_src(atom);
|
||||
} else {
|
||||
instr.add_dst(atom);
|
||||
}
|
||||
|
||||
} break;
|
||||
|
||||
case DecodeType::BRANCH_TARGET: {
|
||||
auto label = get_comma_separated(str);
|
||||
auto f = std::find_if(labels.begin(), labels.end(),
|
||||
[&](const DecompilerLabel& l) { return l.name == label; });
|
||||
assert(f != labels.end());
|
||||
auto idx = f - labels.begin();
|
||||
InstructionAtom atom;
|
||||
atom.set_label(idx);
|
||||
if (step.is_src) {
|
||||
instr.add_src(atom);
|
||||
} else {
|
||||
instr.add_dst(atom);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
assert(str.empty());
|
||||
return instr;
|
||||
}
|
||||
|
||||
ParsedProgram InstructionParser::parse_program(const std::string& str) {
|
||||
ParsedProgram program;
|
||||
auto lines = string_to_lines(str);
|
||||
int byte_offset = 0;
|
||||
// first pass
|
||||
for (auto& line : lines) {
|
||||
// strip off leading white space
|
||||
size_t i;
|
||||
for (i = 0; i < line.length(); i++) {
|
||||
if (line[i] != ' ') {
|
||||
line = line.substr(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.front() == 'L') {
|
||||
if (line.back() == ':') {
|
||||
line.pop_back();
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
DecompilerLabel label;
|
||||
label.target_segment = 0;
|
||||
label.offset = byte_offset;
|
||||
label.name = line;
|
||||
program.labels.push_back(label);
|
||||
} else {
|
||||
byte_offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// second pass
|
||||
for (auto& line : lines) {
|
||||
if (!line.empty() && line.front() != 'L') {
|
||||
program.instructions.push_back(parse_single_instruction(line, program.labels));
|
||||
}
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
std::string ParsedProgram::print() {
|
||||
std::string result;
|
||||
|
||||
int offset = 0;
|
||||
for (auto& instr : instructions) {
|
||||
for (auto& label : labels) {
|
||||
if (label.offset == offset) {
|
||||
result += label.name;
|
||||
result += ":\n";
|
||||
}
|
||||
}
|
||||
result += ' ';
|
||||
result += ' ';
|
||||
result += instr.to_string(labels);
|
||||
result += '\n';
|
||||
offset += 4;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -0,0 +1,29 @@
|
||||
/*!
|
||||
* The InstructionParser converts a string like "daddu a0, s7, r0" into an Instruction.
|
||||
* It is used to generate test sequences of instructions for decompiler algorithms.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "Instruction.h"
|
||||
#include "DecompilerLabel.h"
|
||||
|
||||
namespace decompiler {
|
||||
struct ParsedProgram {
|
||||
std::vector<DecompilerLabel> labels;
|
||||
std::vector<Instruction> instructions;
|
||||
std::string print();
|
||||
};
|
||||
|
||||
class InstructionParser {
|
||||
public:
|
||||
InstructionParser();
|
||||
Instruction parse_single_instruction(std::string str, const std::vector<DecompilerLabel>& labels);
|
||||
ParsedProgram parse_program(const std::string& str);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, int> m_opcode_name_lookup;
|
||||
};
|
||||
} // namespace decompiler
|
||||
@@ -6,8 +6,13 @@
|
||||
#include "OpcodeInfo.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace decompiler {
|
||||
OpcodeInfo gOpcodeInfo[(uint32_t)InstructionKind::EE_OP_MAX];
|
||||
|
||||
namespace {
|
||||
bool opcodes_initialized = false;
|
||||
}
|
||||
|
||||
typedef InstructionKind IK;
|
||||
typedef FieldType FT;
|
||||
typedef DecodeType DT;
|
||||
@@ -130,6 +135,9 @@ static OpcodeInfo& cd_dacc_svfs_svft(OpcodeInfo& info) {
|
||||
}
|
||||
|
||||
void init_opcode_info() {
|
||||
if (opcodes_initialized) {
|
||||
return;
|
||||
}
|
||||
gOpcodeInfo[0].name = ";; ??????";
|
||||
|
||||
// RT, RS, SIMM
|
||||
@@ -444,6 +452,7 @@ void init_opcode_info() {
|
||||
// for the UNKNOWN op which shouldn't be valid.
|
||||
total_count--;
|
||||
assert(total_count == valid_count);
|
||||
opcodes_initialized = true;
|
||||
}
|
||||
|
||||
void OpcodeInfo::step(DecodeStep& s) {
|
||||
@@ -501,4 +510,5 @@ OpcodeInfo& OpcodeInfo::dst_vf(FieldType field) {
|
||||
|
||||
OpcodeInfo& OpcodeInfo::dst_vi(FieldType field) {
|
||||
return dst(field, DT::VI);
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace decompiler {
|
||||
enum class InstructionKind {
|
||||
UNKNOWN,
|
||||
|
||||
@@ -342,12 +343,12 @@ struct OpcodeInfo {
|
||||
OpcodeInfo& dst_vf(FieldType field);
|
||||
OpcodeInfo& dst_vi(FieldType field);
|
||||
|
||||
uint8_t step_count;
|
||||
uint8_t step_count = 0;
|
||||
DecodeStep steps[MAX_DECODE_STEPS];
|
||||
};
|
||||
|
||||
extern OpcodeInfo gOpcodeInfo[(uint32_t)InstructionKind::EE_OP_MAX];
|
||||
|
||||
void init_opcode_info();
|
||||
|
||||
} // namespace decompiler
|
||||
#endif // NEXT_OPCODEINFO_H
|
||||
|
||||
@@ -7,6 +7,24 @@
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace decompiler {
|
||||
namespace Reg {
|
||||
// register which may hold GOAL local variables
|
||||
|
||||
// clang-format off
|
||||
const bool allowed_local_gprs[Reg::MAX_GPR] = {
|
||||
false /*R0*/, false /*AT*/, true /*V0*/, true /*V1*/,
|
||||
true /*A0*/, true /*A1*/, true /*A2*/, true /*A3*/,
|
||||
true /*T0*/, true /*T1*/, true /*T2*/, true /*T3*/,
|
||||
true /*T4*/, true /*T5*/, true /*T6*/, true /*T7*/,
|
||||
true /*S0*/, true /*S1*/, true /*S2*/, true /*S3*/,
|
||||
true /*S4*/, true /*S5*/, false /*S6*/, false /*S7*/,
|
||||
true /*T8*/, true /*T9*/, false /*K0*/, false /*K1*/,
|
||||
true /*GP*/, true /*SP*/, false /*FP*/, false /*RA*/
|
||||
};
|
||||
// clang-format on
|
||||
} // namespace Reg
|
||||
|
||||
////////////////////////////
|
||||
// Register Name Constants
|
||||
////////////////////////////
|
||||
@@ -233,4 +251,5 @@ bool Register::operator==(const Register& other) const {
|
||||
|
||||
bool Register::operator!=(const Register& other) const {
|
||||
return id != other.id;
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace decompiler {
|
||||
// Namespace for register name constants
|
||||
namespace Reg {
|
||||
enum RegisterKind {
|
||||
@@ -120,6 +121,9 @@ enum Vi {
|
||||
CMSAR1 = 31,
|
||||
MAX_COP2 = 32
|
||||
};
|
||||
|
||||
const extern bool allowed_local_gprs[Reg::MAX_GPR];
|
||||
|
||||
} // namespace Reg
|
||||
|
||||
// Representation of a register. Uses a 32-bit integer internally.
|
||||
@@ -148,5 +152,5 @@ class Register {
|
||||
private:
|
||||
uint16_t id = -1;
|
||||
};
|
||||
|
||||
} // namespace decompiler
|
||||
#endif // NEXT_REGISTER_H
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
#include "decompiler/Disasm/InstructionMatching.h"
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
* Find all basic blocks in a function.
|
||||
* All delay slot instructions are grouped with the branch instruction.
|
||||
@@ -48,4 +49,5 @@ std::vector<BasicBlock> find_blocks_in_function(const LinkedObjectFile& file,
|
||||
}
|
||||
|
||||
return basic_blocks;
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
#include "decompiler/util/TP_Type.h"
|
||||
|
||||
namespace decompiler {
|
||||
class LinkedObjectFile;
|
||||
class Function;
|
||||
|
||||
@@ -48,3 +49,4 @@ struct BlockTopologicalSort {
|
||||
std::vector<BasicBlock> find_blocks_in_function(const LinkedObjectFile& file,
|
||||
int seg,
|
||||
const Function& func);
|
||||
} // namespace decompiler
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "CfgVtx.h"
|
||||
#include "Function.h"
|
||||
|
||||
namespace decompiler {
|
||||
/////////////////////////////////////////
|
||||
/// CfgVtx
|
||||
/////////////////////////////////////////
|
||||
@@ -1912,3 +1913,4 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int se
|
||||
|
||||
return cfg;
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -11,6 +11,7 @@ namespace goos {
|
||||
class Object;
|
||||
}
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
* In v, find an item equal to old, and replace it with replace.
|
||||
* Will throw an error is there is not exactly one thing equal to old.
|
||||
@@ -351,5 +352,5 @@ class ControlFlowGraph {
|
||||
class LinkedObjectFile;
|
||||
class Function;
|
||||
std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int seg, Function& func);
|
||||
|
||||
} // namespace decompiler
|
||||
#endif // JAK_DISASSEMBLER_CFGVTX_H
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "decompiler/IR/IR.h"
|
||||
#include "ExpressionStack.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
bool expressionize_begin(IR_Begin* begin, LinkedObjectFile& file) {
|
||||
ExpressionStack stack;
|
||||
@@ -55,4 +56,5 @@ bool Function::build_expression(LinkedObjectFile& file) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "ExpressionStack.h"
|
||||
|
||||
namespace decompiler {
|
||||
std::string ExpressionStack::StackEntry::print(LinkedObjectFile& file) {
|
||||
return fmt::format("d: {} s: {} | {} <- {}", display, sequence_point,
|
||||
destination.has_value() ? destination.value().to_charp() : "N/A",
|
||||
@@ -107,4 +108,5 @@ ExpressionStack::StackEntry& ExpressionStack::get_display_stack_top() {
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
#include "decompiler/util/TP_Type.h"
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
* An ExpressionStack is used to track partial expressions when rebuilding the tree structure of
|
||||
* GOAL code. Linear sequences of operations are added onto the expression stack.
|
||||
@@ -33,4 +34,5 @@ class ExpressionStack {
|
||||
|
||||
bool display_stack_empty();
|
||||
StackEntry& get_display_stack_top();
|
||||
};
|
||||
};
|
||||
} // namespace decompiler
|
||||
@@ -1,13 +1,14 @@
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include "Function.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "common/log/log.h"
|
||||
#include "decompiler/Disasm/InstructionMatching.h"
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
#include "TypeInspector.h"
|
||||
#include "decompiler/IR/IR.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
std::vector<Register> gpr_backups = {make_gpr(Reg::GP), make_gpr(Reg::S5), make_gpr(Reg::S4),
|
||||
make_gpr(Reg::S3), make_gpr(Reg::S2), make_gpr(Reg::S1),
|
||||
@@ -70,8 +71,8 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
|
||||
// storing stack pointer on the stack is done by some ASM kernel functions
|
||||
if (instr.kind == InstructionKind::SW && instr.get_src(0).get_reg() == make_gpr(Reg::SP)) {
|
||||
printf("[Warning] %s Suspected ASM function based on this instruction in prologue: %s\n",
|
||||
guessed_name.to_string().c_str(), instr.to_string(file).c_str());
|
||||
warnings += ";; Flagged as ASM function because of " + instr.to_string(file) + "\n";
|
||||
guessed_name.to_string().c_str(), instr.to_string(file.labels).c_str());
|
||||
warnings += ";; Flagged as ASM function because of " + instr.to_string(file.labels) + "\n";
|
||||
suspected_asm = true;
|
||||
return;
|
||||
}
|
||||
@@ -92,9 +93,9 @@ 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)) {
|
||||
spdlog::warn("{} Suspected ASM function based on this instruction in prologue: {}\n",
|
||||
guessed_name.to_string(), instr.to_string(file));
|
||||
warnings += ";; Flagged as ASM function because of " + instr.to_string(file) + "\n";
|
||||
lg::warn("{} Suspected ASM function based on this instruction in prologue: {}\n",
|
||||
guessed_name.to_string(), instr.to_string(file.labels));
|
||||
warnings += ";; Flagged as ASM function because of " + instr.to_string(file.labels) + "\n";
|
||||
suspected_asm = true;
|
||||
return;
|
||||
}
|
||||
@@ -164,9 +165,9 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
|
||||
suspected_asm = true;
|
||||
printf("[Warning] %s Suspected asm function that isn't flagged due to stack store %s\n",
|
||||
guessed_name.to_string().c_str(),
|
||||
instructions.at(idx + i).to_string(file).c_str());
|
||||
instructions.at(idx + i).to_string(file.labels).c_str());
|
||||
warnings += ";; Suspected asm function due to stack store: " +
|
||||
instructions.at(idx + i).to_string(file) + "\n";
|
||||
instructions.at(idx + i).to_string(file.labels) + "\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -194,9 +195,9 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
|
||||
suspected_asm = true;
|
||||
printf("[Warning] %s Suspected asm function that isn't flagged due to stack store %s\n",
|
||||
guessed_name.to_string().c_str(),
|
||||
instructions.at(idx + i).to_string(file).c_str());
|
||||
instructions.at(idx + i).to_string(file.labels).c_str());
|
||||
warnings += ";; Suspected asm function due to stack store: " +
|
||||
instructions.at(idx + i).to_string(file) + "\n";
|
||||
instructions.at(idx + i).to_string(file.labels) + "\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -643,7 +644,7 @@ void Function::find_type_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts)
|
||||
// done!
|
||||
// fmt::print("Got type {} parent {}\n", type_name, parent_type);
|
||||
dts.add_type_parent(type_name, parent_type);
|
||||
Label flag_label = file.labels.at(label_idx);
|
||||
DecompilerLabel flag_label = file.labels.at(label_idx);
|
||||
u64 word = file.read_data_word(flag_label);
|
||||
flag_label.offset += 4;
|
||||
u64 word2 = file.read_data_word(flag_label);
|
||||
@@ -744,4 +745,5 @@ BlockTopologicalSort Function::bb_topo_sort() {
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "common/type_system/TypeSpec.h"
|
||||
#include "decompiler/config.h"
|
||||
|
||||
namespace decompiler {
|
||||
class DecompilerTypeSystem;
|
||||
class IR_Atomic;
|
||||
class IR;
|
||||
@@ -158,5 +159,5 @@ class Function {
|
||||
std::unordered_map<int, int> instruction_to_basic_op;
|
||||
std::unordered_map<int, int> basic_op_to_instruction;
|
||||
};
|
||||
|
||||
} // namespace decompiler
|
||||
#endif // NEXT_FUNCTION_H
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "Function.h"
|
||||
#include "decompiler/IR/IR.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
bool in_set(RegSet& set, const Register& obj) {
|
||||
return set.find(obj) != set.end();
|
||||
@@ -170,4 +171,5 @@ void Function::run_reg_usage() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "decompiler/config.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
TypeState construct_initial_typestate(const TypeSpec& f_ts) {
|
||||
TypeState result;
|
||||
@@ -132,4 +133,5 @@ bool Function::run_type_analysis(const TypeSpec& my_type,
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/type_system/deftype.h"
|
||||
#include "decompiler/IR/IR.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
struct FieldPrint {
|
||||
char format = '\0';
|
||||
@@ -843,4 +844,5 @@ std::string TypeInspectorResult::print_as_deftype() {
|
||||
result.append(")\n");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -8,10 +8,12 @@
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
class Field;
|
||||
|
||||
namespace decompiler {
|
||||
class Function;
|
||||
class DecompilerTypeSystem;
|
||||
class LinkedObjectFile;
|
||||
class Field;
|
||||
|
||||
struct TypeInspectorResult {
|
||||
bool success = false;
|
||||
@@ -34,3 +36,4 @@ TypeInspectorResult inspect_inspect_method(Function& inspect,
|
||||
const std::string& type_name,
|
||||
DecompilerTypeSystem& dts,
|
||||
LinkedObjectFile& file);
|
||||
} // namespace decompiler
|
||||
@@ -11,9 +11,11 @@
|
||||
#include "decompiler/Function/Function.h"
|
||||
#include "decompiler/Function/BasicBlocks.h"
|
||||
#include "decompiler/Disasm/InstructionMatching.h"
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
#include "decompiler/IR/IR.h"
|
||||
#include "common/symbols.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
|
||||
///////////////////////////////
|
||||
@@ -135,7 +137,7 @@ std::shared_ptr<IR_Atomic> to_asm_automatic(const std::string& str, Instruction&
|
||||
}
|
||||
|
||||
if (instr.n_src >= 3) {
|
||||
result->src1 = instr_atom_to_ir(instr.get_src(2), idx);
|
||||
result->src2 = instr_atom_to_ir(instr.get_src(2), idx);
|
||||
}
|
||||
|
||||
result->set_reg_info();
|
||||
@@ -2520,7 +2522,7 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
|
||||
// everything failed
|
||||
if (!result) {
|
||||
// temp hack for debug:
|
||||
printf("Instruction -> BasicOp failed on %s\n", i.to_string(*file).c_str());
|
||||
printf("Instruction -> BasicOp failed on %s\n", i.to_string(file->labels).c_str());
|
||||
func->add_basic_op(std::make_shared<IR_Failed_Atomic>(), instr, instr + 1);
|
||||
} else {
|
||||
if (!func->contains_asm_ops && dynamic_cast<IR_AsmOp*>(result.get())) {
|
||||
@@ -2536,3 +2538,4 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace decompiler {
|
||||
class Function;
|
||||
struct BasicBlock;
|
||||
class LinkedObjectFile;
|
||||
|
||||
void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjectFile* file);
|
||||
void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjectFile* file);
|
||||
} // namespace decompiler
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "decompiler/Disasm/InstructionMatching.h"
|
||||
#include "decompiler/IR/IR.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
|
||||
std::shared_ptr<IR> cfg_to_ir(Function& f, LinkedObjectFile& file, CfgVtx* vtx);
|
||||
@@ -1278,3 +1279,4 @@ std::shared_ptr<IR> build_cfg_ir(Function& function,
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace decompiler {
|
||||
class IR;
|
||||
class Function;
|
||||
class LinkedObjectFile;
|
||||
class ControlFlowGraph;
|
||||
|
||||
std::shared_ptr<IR> build_cfg_ir(Function& function, ControlFlowGraph& cfg, LinkedObjectFile& file);
|
||||
std::shared_ptr<IR> build_cfg_ir(Function& function, ControlFlowGraph& cfg, LinkedObjectFile& file);
|
||||
} // namespace decompiler
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "common/goos/PrettyPrinter.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
namespace decompiler {
|
||||
// hack to print out reverse deref paths on loads to help with debugging load stuff.
|
||||
bool enable_hack_load_path_print = false;
|
||||
// hack to print (begin x) as x to make debug output easier to read.
|
||||
@@ -1273,4 +1274,5 @@ goos::Object IR_Break::to_form(const LinkedObjectFile& file) const {
|
||||
void IR_Break::get_children(std::vector<std::shared_ptr<IR>>* output) const {
|
||||
output->push_back(return_code);
|
||||
output->push_back(dead_code);
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
+6
-5
@@ -11,14 +11,15 @@
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
#include "decompiler/util/TP_Type.h"
|
||||
|
||||
class LinkedObjectFile;
|
||||
class DecompilerTypeSystem;
|
||||
class ExpressionStack;
|
||||
|
||||
namespace goos {
|
||||
class Object;
|
||||
}
|
||||
|
||||
namespace decompiler {
|
||||
class LinkedObjectFile;
|
||||
class DecompilerTypeSystem;
|
||||
class ExpressionStack;
|
||||
|
||||
class IR {
|
||||
public:
|
||||
virtual goos::Object to_form(const LinkedObjectFile& file) const = 0;
|
||||
@@ -765,5 +766,5 @@ class IR_Break : public virtual IR {
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
} // namespace decompiler
|
||||
#endif // JAK_IR_H
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "IR.h"
|
||||
#include "decompiler/Function/ExpressionStack.h"
|
||||
|
||||
namespace decompiler {
|
||||
bool IR_Set_Atomic::expression_stack(ExpressionStack& stack, LinkedObjectFile& file) {
|
||||
// first determine the type of the set.
|
||||
switch (kind) {
|
||||
@@ -448,4 +449,5 @@ bool IR_FloatMath1::update_from_stack(const std::unordered_set<Register, Registe
|
||||
LinkedObjectFile& file) {
|
||||
update_from_stack_helper(&arg, consume, stack, file);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "decompiler/util/TP_Type.h"
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
// bool is_plain_type(const TP_Type& type, const TypeSpec& ts) {
|
||||
// return type.as_typespec() == ts;
|
||||
@@ -945,4 +946,5 @@ TP_Type IR_CMoveF::get_expression_type(const TypeState& input,
|
||||
(void)file;
|
||||
(void)dts;
|
||||
return TP_Type::make_from_typespec(TypeSpec("symbol"));
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,544 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <cassert>
|
||||
#include "common/goos/Object.h"
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
#include "decompiler/Disasm/Instruction.h"
|
||||
#include "Env.h"
|
||||
|
||||
namespace decompiler {
|
||||
class Expr;
|
||||
|
||||
/*!
|
||||
* A "Variable" represents a register at a given instruction index.
|
||||
* The register can either be a GOAL local variable or a GOAL register used in inline assembly.
|
||||
* Because OpenGOAL's registers don't one-to-one map to GOAL registers, GOAL "inline assembly
|
||||
* registers" will become OpenGOAL variables, and are treated similarly to variables in
|
||||
* decompilation.
|
||||
*
|
||||
* In the earlier parts of decompilation, this just behaves like a register in all cases.
|
||||
* But in later parts registers can be mapped to real local variables with types. A variable can
|
||||
* look itself up in an environment to determine what "local variable" it is.
|
||||
*
|
||||
* Note: a variable is _not_ allowed to be R0, AT, S7, K0, K1, FP, or RA by default, as these
|
||||
* can never hold normal GOAL locals. Inline assembly may use these, but you must set the allow_all
|
||||
* flag to true in the constructor of Variable to indicate this is what you really want.
|
||||
*
|
||||
* Note: access to the process pointer (s6) is handled as a variable. As a result, you may always
|
||||
* use s6 as a variable.
|
||||
*/
|
||||
class Variable {
|
||||
public:
|
||||
enum class Mode : u8 {
|
||||
READ, // represents value of the variable at the beginning of the instruction
|
||||
WRITE // represents value of the variable at the end of the instruction
|
||||
};
|
||||
|
||||
Variable() = default;
|
||||
Variable(Mode mode, Register reg, int atomic_idx, bool allow_all = false);
|
||||
|
||||
enum class Print {
|
||||
AS_REG, // print as a PS2 register name
|
||||
FULL, // print as a register name, plus an index, plus read or write
|
||||
AS_VARIABLE, // print local variable name, error if impossible
|
||||
AUTOMATIC, // print as variable, but if that's not possible print as reg.
|
||||
};
|
||||
|
||||
std::string to_string(const Env* env, Print mode = Print::AUTOMATIC) const;
|
||||
|
||||
bool operator==(const Variable& other) const;
|
||||
bool operator!=(const Variable& other) const;
|
||||
|
||||
const Register& reg() const { return m_reg; }
|
||||
Mode mode() const { return m_mode; }
|
||||
int idx() const { return m_atomic_idx; }
|
||||
|
||||
private:
|
||||
Mode m_mode = Mode::READ; // do we represent a read or a write?
|
||||
Register m_reg; // the EE register
|
||||
int m_atomic_idx = -1; // the index in the function's list of AtomicOps
|
||||
};
|
||||
|
||||
/*!
|
||||
* An atomic operation represents a single operation from the point of view of the IR2 system.
|
||||
* Each IR2 op is one or more instructions.
|
||||
* Each function can be represented as a list of AtomicOps. These are stored in exactly the same
|
||||
* order as the instructions appear.
|
||||
*
|
||||
* The AtomicOps use SimpleAtom and SimpleExpression. These are extremely limited versions of
|
||||
* the full IR2 expression system, but are much easier to work with because they are less general
|
||||
* and can't be nested infinitely. They also have features specific to the AtomicOp system that are
|
||||
* not required for full expressions. The full expression system will later convert these into the
|
||||
* more complicated expressions.
|
||||
*
|
||||
* The types of AtomicOp are:
|
||||
* ConditionalMoveFalseOp
|
||||
* CallOp
|
||||
* SpecialOp
|
||||
* BranchOp
|
||||
* LoadVarOp
|
||||
* StoreOp
|
||||
* SetVarConditionOp
|
||||
* AsmOp
|
||||
* SetVarExprOp
|
||||
* AsmOp
|
||||
*/
|
||||
class AtomicOp {
|
||||
public:
|
||||
explicit AtomicOp(int my_idx);
|
||||
std::string to_string(const std::vector<DecompilerLabel>& labels, const Env* env);
|
||||
virtual goos::Object to_form(const std::vector<DecompilerLabel>& labels,
|
||||
const Env* env) const = 0;
|
||||
virtual bool operator==(const AtomicOp& other) const = 0;
|
||||
bool operator!=(const AtomicOp& other) const;
|
||||
|
||||
// determine if this is a (set! <var> thing) form. These will be handled differently in expression
|
||||
// building.
|
||||
virtual bool is_variable_set() const = 0;
|
||||
|
||||
// determine if this is a GOAL "sequence point".
|
||||
// non-sequence point instructions may be out of order from the point of view of the expression
|
||||
// stack.
|
||||
virtual bool is_sequence_point() const = 0;
|
||||
|
||||
// get the variable being set by this operation. Only call this if is_variable_set returns true.
|
||||
virtual Variable get_set_destination() const = 0;
|
||||
|
||||
// get the value of the variable being set, as an expression. Only call this if is_variable_set
|
||||
// returns true.
|
||||
virtual std::unique_ptr<Expr> get_set_source_as_expr() const = 0;
|
||||
|
||||
// convert me to an expression. If I'm a set!, this will produce a (set! x y), which may be
|
||||
// undesirable when expression stacking.
|
||||
virtual std::unique_ptr<Expr> get_as_expr() const = 0;
|
||||
|
||||
// figure out what registers are read and written in this AtomicOp and update read_regs,
|
||||
// write_regs, and clobber_regs. It's expected that these have duplicates if a register appears
|
||||
// in the original instructions multiple times. Ex: "and v0, v1, v1" would end up putting v1 in
|
||||
// read twice.
|
||||
virtual void update_register_info() = 0;
|
||||
|
||||
const std::vector<Register>& read_regs() { return m_read_regs; }
|
||||
const std::vector<Register>& write_regs() { return m_write_regs; }
|
||||
const std::vector<Register>& clobber_regs() { return m_clobber_regs; }
|
||||
|
||||
protected:
|
||||
int m_my_idx = -1;
|
||||
|
||||
// the register values that are read (at the start of this op)
|
||||
std::vector<Register> m_read_regs;
|
||||
// the registers that have actual values written into them (at the end of this op)
|
||||
std::vector<Register> m_write_regs;
|
||||
// the registers which have junk written into them.
|
||||
std::vector<Register> m_clobber_regs;
|
||||
};
|
||||
|
||||
/*!
|
||||
* The has a value. In some cases it can be set.
|
||||
*/
|
||||
class SimpleAtom {
|
||||
public:
|
||||
enum class Kind : u8 {
|
||||
VARIABLE,
|
||||
INTEGER_CONSTANT,
|
||||
SYMBOL_PTR,
|
||||
SYMBOL_VAL,
|
||||
EMPTY_LIST,
|
||||
STATIC_ADDRESS,
|
||||
INVALID
|
||||
};
|
||||
|
||||
SimpleAtom() = default;
|
||||
static SimpleAtom make_var(const Variable& var);
|
||||
static SimpleAtom make_sym_ptr(const std::string& name);
|
||||
static SimpleAtom make_sym_val(const std::string& name);
|
||||
static SimpleAtom make_empty_list();
|
||||
static SimpleAtom make_int_constant(s64 value);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const;
|
||||
|
||||
bool is_var() const { return m_kind == Kind::VARIABLE; }
|
||||
const Variable& var() const {
|
||||
assert(is_var());
|
||||
return m_variable;
|
||||
}
|
||||
bool is_int() const { return m_kind == Kind::INTEGER_CONSTANT; };
|
||||
bool is_sym_ptr() const { return m_kind == Kind::SYMBOL_PTR; };
|
||||
bool is_sym_val() const { return m_kind == Kind::SYMBOL_VAL; };
|
||||
bool is_empty_list() const { return m_kind == Kind::EMPTY_LIST; };
|
||||
bool is_static_addr() const { return m_kind == Kind::STATIC_ADDRESS; };
|
||||
bool operator==(const SimpleAtom& other) const;
|
||||
bool operator!=(const SimpleAtom& other) const { return !((*this) == other); }
|
||||
void get_regs(std::vector<Register>* out) const;
|
||||
|
||||
private:
|
||||
Kind m_kind = Kind::INVALID;
|
||||
std::string m_string; // for symbol ptr and symbol val
|
||||
s64 m_int = 0; // for integer constant and static address label id
|
||||
Variable m_variable;
|
||||
};
|
||||
|
||||
/*!
|
||||
* A "simple expression" can be used within an AtomicOp.
|
||||
* AtomicOps are often made up of very few instructions, so these expressions are quite simple and
|
||||
* can't nest. There is an "operation" and some arguments. There are no side effects of a
|
||||
* SimpleExpression. The side effects will be captured by the AtomicOp.
|
||||
*
|
||||
* Note - there is an expression kind called identity which takes one argument and uses that
|
||||
* argument as an expression.
|
||||
*/
|
||||
class SimpleExpression {
|
||||
public:
|
||||
enum class Kind : u8 {
|
||||
INVALID,
|
||||
IDENTITY,
|
||||
DIV_S,
|
||||
MUL_S,
|
||||
ADD_S,
|
||||
SUB_S,
|
||||
MIN_S,
|
||||
MAX_S,
|
||||
FLOAT_TO_INT,
|
||||
INT_TO_FLOAT,
|
||||
ABS_S,
|
||||
NEG_S,
|
||||
SQRT_S,
|
||||
ADD,
|
||||
SUB,
|
||||
MUL_SIGNED,
|
||||
DIV_SIGNED,
|
||||
MOD_SIGNED,
|
||||
DIV_UNSIGNED,
|
||||
MOD_UNSIGNED,
|
||||
OR,
|
||||
AND,
|
||||
NOR,
|
||||
XOR,
|
||||
LEFT_SHIFT,
|
||||
RIGHT_SHIFT_ARITH,
|
||||
RIGHT_SHIFT_LOGIC,
|
||||
MUL_UNSIGNED,
|
||||
NOT,
|
||||
NEG
|
||||
};
|
||||
|
||||
// how many arguments?
|
||||
int args() const { return n_args; }
|
||||
const SimpleAtom& get_arg(int idx) const {
|
||||
assert(idx < args());
|
||||
return m_args[idx];
|
||||
}
|
||||
Kind kind() const { return m_kind; }
|
||||
|
||||
SimpleExpression(Kind kind, const SimpleAtom& arg0);
|
||||
SimpleExpression(Kind kind, const SimpleAtom& arg0, const SimpleAtom& arg1);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const;
|
||||
bool operator==(const SimpleExpression& other) const;
|
||||
bool is_identity() const { return m_kind == Kind::IDENTITY; }
|
||||
void get_regs(std::vector<Register>* out) const;
|
||||
|
||||
private:
|
||||
Kind m_kind = Kind::INVALID;
|
||||
SimpleAtom m_args[2];
|
||||
s8 n_args = -1;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Set a variable equal to a Simple Expression
|
||||
*/
|
||||
class SetVarOp : public AtomicOp {
|
||||
public:
|
||||
SetVarOp(const Variable& dst, const SimpleExpression& src, int my_idx)
|
||||
: AtomicOp(my_idx), m_dst(dst), m_src(src) {
|
||||
assert(my_idx == dst.idx());
|
||||
}
|
||||
virtual goos::Object to_form(const std::vector<DecompilerLabel>& labels,
|
||||
const Env* env) const override;
|
||||
bool operator==(const AtomicOp& other) const override;
|
||||
bool is_variable_set() const override;
|
||||
bool is_sequence_point() const override;
|
||||
Variable get_set_destination() const override;
|
||||
std::unique_ptr<Expr> get_set_source_as_expr() const override;
|
||||
std::unique_ptr<Expr> get_as_expr() const override;
|
||||
void update_register_info() override;
|
||||
|
||||
private:
|
||||
Variable m_dst;
|
||||
SimpleExpression m_src;
|
||||
};
|
||||
|
||||
/*!
|
||||
* An AsmOp represents a single inline assembly instruction. This is used when the BasicOpBuilder
|
||||
* pass decides that an instruction could not have been generated from high-level GOAL code, and
|
||||
* instead must be due to inline assembly.
|
||||
*
|
||||
* Each AsmOp stores the instruction it uses, as well as "Variable"s for each register used.
|
||||
*/
|
||||
class AsmOp : public AtomicOp {
|
||||
public:
|
||||
AsmOp(Instruction instr, int my_idx);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override;
|
||||
bool operator==(const AtomicOp& other) const override;
|
||||
bool is_variable_set() const override;
|
||||
bool is_sequence_point() const override;
|
||||
Variable get_set_destination() const override;
|
||||
std::unique_ptr<Expr> get_set_source_as_expr() const override;
|
||||
std::unique_ptr<Expr> get_as_expr() const override;
|
||||
void update_register_info() override;
|
||||
|
||||
private:
|
||||
Instruction m_instr;
|
||||
std::optional<Variable> m_dst;
|
||||
std::optional<Variable> m_src[3];
|
||||
};
|
||||
|
||||
/*!
|
||||
* A condition represents something that can generate a 0 or 1 based on a check or comparison.
|
||||
* This can be used as a branch condition in BranchOp
|
||||
* This can be used as a condition in an SetVarConditionOp, which sets a variable to a GOAL boolean.
|
||||
* Sometimes a SetVarConditionOp gets spread across many many instructions, in which case it is
|
||||
* not correctly detected here.
|
||||
*/
|
||||
class IR2_Condition {
|
||||
public:
|
||||
enum class Kind {
|
||||
NOT_EQUAL,
|
||||
EQUAL,
|
||||
LESS_THAN_SIGNED,
|
||||
GREATER_THAN_SIGNED,
|
||||
LEQ_SIGNED,
|
||||
GEQ_SIGNED,
|
||||
GREATER_THAN_ZERO_SIGNED,
|
||||
LEQ_ZERO_SIGNED,
|
||||
LESS_THAN_ZERO,
|
||||
GEQ_ZERO_SIGNED,
|
||||
LESS_THAN_UNSIGNED,
|
||||
GREATER_THAN_UNSIGNED,
|
||||
LEQ_UNSIGNED,
|
||||
GEQ_UNSIGNED,
|
||||
ZERO,
|
||||
NONZERO,
|
||||
FALSE,
|
||||
TRUTHY,
|
||||
ALWAYS,
|
||||
NEVER,
|
||||
FLOAT_EQUAL,
|
||||
FLOAT_NOT_EQUAL,
|
||||
FLOAT_LESS_THAN,
|
||||
FLOAT_GEQ,
|
||||
FLOAT_LEQ,
|
||||
FLOAT_GREATER_THAN,
|
||||
INVALID
|
||||
};
|
||||
|
||||
explicit IR2_Condition(Kind kind);
|
||||
IR2_Condition(Kind kind, const Variable& src0);
|
||||
IR2_Condition(Kind kind, const Variable& src0, const Variable& src1);
|
||||
|
||||
void invert();
|
||||
bool operator==(const IR2_Condition& other) const;
|
||||
bool operator!=(const IR2_Condition& other) const { return !((*this) == other); }
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const;
|
||||
void get_regs(std::vector<Register>* out) const;
|
||||
|
||||
private:
|
||||
Kind m_kind = Kind::INVALID;
|
||||
Variable m_src[2];
|
||||
};
|
||||
|
||||
/*!
|
||||
* Set a variable to a GOAL boolean, based off of a condition.
|
||||
*/
|
||||
class SetVarConditionOp : public AtomicOp {
|
||||
public:
|
||||
SetVarConditionOp(Variable dst, IR2_Condition condition, int my_idx);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override;
|
||||
bool operator==(const AtomicOp& other) const override;
|
||||
bool is_variable_set() const override;
|
||||
bool is_sequence_point() const override;
|
||||
Variable get_set_destination() const override;
|
||||
std::unique_ptr<Expr> get_set_source_as_expr() const override;
|
||||
std::unique_ptr<Expr> get_as_expr() const override;
|
||||
void update_register_info() override;
|
||||
|
||||
private:
|
||||
Variable m_dst;
|
||||
IR2_Condition m_condition;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Store an Atom into a memory location.
|
||||
* Note - this is _not_ considered a set! form because you are not setting the value of a
|
||||
* register which can be expression-compacted.
|
||||
*/
|
||||
class StoreOp : public AtomicOp {
|
||||
public:
|
||||
StoreOp(SimpleExpression addr, SimpleAtom value, int my_idx);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override;
|
||||
bool operator==(const AtomicOp& other) const override;
|
||||
bool is_variable_set() const override;
|
||||
bool is_sequence_point() const override;
|
||||
Variable get_set_destination() const override;
|
||||
std::unique_ptr<Expr> get_set_source_as_expr() const override;
|
||||
std::unique_ptr<Expr> get_as_expr() const override;
|
||||
void update_register_info() override;
|
||||
|
||||
private:
|
||||
SimpleExpression m_addr;
|
||||
SimpleAtom m_value;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Load a value into a variable.
|
||||
* This is treated as a set! form.
|
||||
*/
|
||||
class LoadVarOp : public AtomicOp {
|
||||
public:
|
||||
LoadVarOp(Variable dst, SimpleExpression src, int my_idx);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override;
|
||||
bool operator==(const AtomicOp& other) const override;
|
||||
bool is_variable_set() const override;
|
||||
bool is_sequence_point() const override;
|
||||
Variable get_set_destination() const override;
|
||||
std::unique_ptr<Expr> get_set_source_as_expr() const override;
|
||||
std::unique_ptr<Expr> get_as_expr() const override;
|
||||
void update_register_info() override;
|
||||
|
||||
private:
|
||||
Variable m_dst;
|
||||
SimpleExpression m_src;
|
||||
};
|
||||
|
||||
/*!
|
||||
* This represents one of the possible instructions that can go in a branch delay slot.
|
||||
* These will be "absorbed" into higher level structures, but for the purpose of printing AtomicOps,
|
||||
* it will be nice to have these print like expressions.
|
||||
*
|
||||
* These are always part of the branch op.
|
||||
*/
|
||||
class IR2_BranchDelay {
|
||||
public:
|
||||
enum class Kind {
|
||||
NOP,
|
||||
SET_REG_FALSE,
|
||||
SET_REG_TRUE,
|
||||
SET_REG_REG,
|
||||
SET_BINTEGER,
|
||||
SET_PAIR,
|
||||
DSLLV,
|
||||
NEGATE
|
||||
};
|
||||
|
||||
explicit IR2_BranchDelay(Kind kind);
|
||||
IR2_BranchDelay(Kind kind, Variable var0);
|
||||
IR2_BranchDelay(Kind kind, Variable var0, Variable var1);
|
||||
IR2_BranchDelay(Kind kind, Variable var0, Variable var1, Variable var2);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const;
|
||||
bool operator==(const IR2_BranchDelay& other) const;
|
||||
void get_regs(std::vector<Register>* write, std::vector<Register>* read) const;
|
||||
|
||||
private:
|
||||
std::optional<Variable> m_var[3];
|
||||
Kind m_kind;
|
||||
};
|
||||
|
||||
/*!
|
||||
* This represents a combination of a condition + a branch + the branch delay slot.
|
||||
* This is considered as a single operation.
|
||||
*/
|
||||
class BranchOp : public AtomicOp {
|
||||
public:
|
||||
BranchOp(bool likely,
|
||||
IR2_Condition condition,
|
||||
int label,
|
||||
IR2_BranchDelay branch_delay,
|
||||
int my_idx);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override;
|
||||
bool operator==(const AtomicOp& other) const override;
|
||||
bool is_variable_set() const override;
|
||||
bool is_sequence_point() const override;
|
||||
Variable get_set_destination() const override;
|
||||
std::unique_ptr<Expr> get_set_source_as_expr() const override;
|
||||
std::unique_ptr<Expr> get_as_expr() const override;
|
||||
void update_register_info() override;
|
||||
|
||||
private:
|
||||
bool m_likely = false;
|
||||
IR2_Condition m_condition;
|
||||
int m_label = -1;
|
||||
IR2_BranchDelay m_branch_delay;
|
||||
};
|
||||
|
||||
/*!
|
||||
* A "special" op has no arguments.
|
||||
* NOP, BREAK, SUSPEND,
|
||||
*/
|
||||
class SpecialOp : public AtomicOp {
|
||||
public:
|
||||
enum class Kind {
|
||||
NOP,
|
||||
BREAK,
|
||||
SUSPEND,
|
||||
};
|
||||
|
||||
SpecialOp(Kind kind, int my_idx);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override;
|
||||
bool operator==(const AtomicOp& other) const override;
|
||||
bool is_variable_set() const override;
|
||||
bool is_sequence_point() const override;
|
||||
Variable get_set_destination() const override;
|
||||
std::unique_ptr<Expr> get_set_source_as_expr() const override;
|
||||
std::unique_ptr<Expr> get_as_expr() const override;
|
||||
void update_register_info() override;
|
||||
|
||||
private:
|
||||
Kind m_kind;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Represents a function call.
|
||||
* This has so many special cases and exceptions that it is separate from SpecialOp.
|
||||
*/
|
||||
class CallOp : public AtomicOp {
|
||||
public:
|
||||
CallOp(int my_idx);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override;
|
||||
bool operator==(const AtomicOp& other) const override;
|
||||
bool is_variable_set() const override;
|
||||
bool is_sequence_point() const override;
|
||||
Variable get_set_destination() const override;
|
||||
std::unique_ptr<Expr> get_set_source_as_expr() const override;
|
||||
std::unique_ptr<Expr> get_as_expr() const override;
|
||||
void update_register_info() override;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Unfortunately the original GOAL compiler does something weird when compiling (zero? x) or (not
|
||||
* (zero? x)) when the result needs to be stored in a GOAL boolean (not in a branch condition). It
|
||||
* first does a (set! result #t), then (possibly) a bunch of code to evaluate x, then does a
|
||||
* conditional move (movn/movz). As a result, we can't recognize this as a Condition in the
|
||||
* AtomicOp pass. Instead we'll recognize it as a (set! result #t) .... (cmove result flag) where
|
||||
* flag is checked to be 0 or not. It's weird because all of the other similar cases get this
|
||||
* right.
|
||||
*
|
||||
* Note - this isn't considered a variable set. It's "conditional set" so it needs to be
|
||||
* handled separately. Unfortunately.
|
||||
*/
|
||||
class ConditionalMoveFalseOp : public AtomicOp {
|
||||
public:
|
||||
ConditionalMoveFalseOp(Variable dst, Variable src, bool on_zero, int my_idx);
|
||||
goos::Object to_form(const std::vector<DecompilerLabel>& labels, const Env* env) const override;
|
||||
bool operator==(const AtomicOp& other) const override;
|
||||
bool is_variable_set() const override;
|
||||
bool is_sequence_point() const override;
|
||||
Variable get_set_destination() const override;
|
||||
std::unique_ptr<Expr> get_set_source_as_expr() const override;
|
||||
std::unique_ptr<Expr> get_as_expr() const override;
|
||||
void update_register_info() override;
|
||||
|
||||
private:
|
||||
Variable m_dst, m_src;
|
||||
bool m_on_zero;
|
||||
};
|
||||
} // namespace decompiler
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "AtomicOpBuilder.h"
|
||||
#include "common/log/log.h"
|
||||
#include "decompiler/Function/BasicBlocks.h"
|
||||
#include "decompiler/Function/Function.h"
|
||||
|
||||
namespace decompiler {
|
||||
|
||||
namespace {
|
||||
|
||||
Variable make_dst_var(Register reg, int idx) {
|
||||
return Variable(Variable::Mode::WRITE, reg, idx);
|
||||
}
|
||||
|
||||
Variable make_src_var(Register reg, int idx) {
|
||||
return Variable(Variable::Mode::READ, reg, idx);
|
||||
}
|
||||
|
||||
SimpleAtom make_src_atom(Register reg, int idx) {
|
||||
return SimpleAtom::make_var(make_src_var(reg, idx));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Convert a single instruction in the form instr dest_reg, src_reg, src_reg
|
||||
* to an atomic op of (set! dst_reg (op src_reg src_reg))
|
||||
* Like daddu a0, a1, a2
|
||||
*/
|
||||
void make_3reg_op(const Instruction& instr,
|
||||
SimpleExpression::Kind kind,
|
||||
int idx,
|
||||
std::unique_ptr<AtomicOp>& result) {
|
||||
auto dst = make_dst_var(instr.get_dst(0).get_reg(), idx);
|
||||
auto src0 = make_src_atom(instr.get_src(0).get_reg(), idx);
|
||||
auto src1 = make_src_atom(instr.get_src(1).get_reg(), idx);
|
||||
result = std::make_unique<SetVarOp>(dst, SimpleExpression(kind, src0, src1), idx);
|
||||
}
|
||||
|
||||
bool convert_and_1(const Instruction& i0, int idx, std::unique_ptr<AtomicOp>& result) {
|
||||
// or reg, reg, reg:
|
||||
make_3reg_op(i0, SimpleExpression::Kind::AND, idx, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool convert_1(const Instruction& i0, int idx, std::unique_ptr<AtomicOp>& result) {
|
||||
switch (i0.kind) {
|
||||
case InstructionKind::AND:
|
||||
return convert_and_1(i0, idx, result);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/*!
|
||||
* Convert an entire basic block and add the results to a FunctionAtomicOps
|
||||
* @param block_id : the index of the block
|
||||
* @param begin : the start of the instructions for the block
|
||||
* @param end : the end of the instructions for the block
|
||||
* @param container : the container to add to
|
||||
*/
|
||||
void convert_block_to_atomic_ops(int begin_idx,
|
||||
std::vector<Instruction>::const_iterator begin,
|
||||
std::vector<Instruction>::const_iterator end,
|
||||
const std::vector<DecompilerLabel>& labels,
|
||||
FunctionAtomicOps* container) {
|
||||
container->block_id_to_first_atomic_op.push_back(container->ops.size());
|
||||
for (auto& instr = begin; instr < end;) {
|
||||
// how many instructions can we look at, at most?
|
||||
int n_instr = end - instr;
|
||||
// how many instructions did we use?
|
||||
int length = 0;
|
||||
// what is the index of the atomic op we would add
|
||||
int op_idx = int(container->ops.size());
|
||||
|
||||
bool converted = false;
|
||||
std::unique_ptr<AtomicOp> op;
|
||||
|
||||
if (n_instr >= 4) {
|
||||
// try 4 instructions
|
||||
}
|
||||
|
||||
if (!converted && n_instr >= 3) {
|
||||
// try 3 instructions
|
||||
}
|
||||
|
||||
if (!converted && n_instr >= 2) {
|
||||
// try 2 instructions
|
||||
}
|
||||
|
||||
if (!converted) {
|
||||
// try 1 instruction
|
||||
if (convert_1(*instr, op_idx, op)) {
|
||||
converted = true;
|
||||
length = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!converted) {
|
||||
// try assembly fallback.
|
||||
}
|
||||
|
||||
if (!converted) {
|
||||
// failed!
|
||||
lg::die("Failed to convert instruction {} to an atomic op", instr->to_string(labels));
|
||||
}
|
||||
|
||||
assert(converted && length && op);
|
||||
// add mappings:
|
||||
container->atomic_op_to_instruction[container->ops.size()] = begin_idx;
|
||||
for (int i = 0; i < length; i++) {
|
||||
container->instruction_to_basic_op[begin_idx + i] = container->ops.size();
|
||||
}
|
||||
// add
|
||||
op->update_register_info();
|
||||
container->ops.emplace_back(std::move(op));
|
||||
instr += length;
|
||||
}
|
||||
container->block_id_to_end_atomic_op.push_back(container->ops.size());
|
||||
}
|
||||
|
||||
FunctionAtomicOps convert_function_to_atomic_ops(const Function& func,
|
||||
const std::vector<DecompilerLabel>& labels) {
|
||||
FunctionAtomicOps result;
|
||||
|
||||
for (const auto& block : func.basic_blocks) {
|
||||
// we should only consider the blocks which actually have instructions:
|
||||
if (block.end_word > block.start_word) {
|
||||
auto begin = func.instructions.begin() + block.start_word;
|
||||
auto end = func.instructions.begin() + block.end_word;
|
||||
convert_block_to_atomic_ops(block.start_word, begin, end, labels, &result);
|
||||
} else {
|
||||
result.block_id_to_first_atomic_op.push_back(-1);
|
||||
result.block_id_to_end_atomic_op.push_back(-1);
|
||||
}
|
||||
}
|
||||
|
||||
assert(func.basic_blocks.size() == result.block_id_to_end_atomic_op.size());
|
||||
assert(func.basic_blocks.size() == result.block_id_to_first_atomic_op.size());
|
||||
return result;
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include "AtomicOp.h"
|
||||
|
||||
namespace decompiler {
|
||||
class Function;
|
||||
struct BasicBlock;
|
||||
class LinkedObjectFile;
|
||||
|
||||
/*!
|
||||
* A collection of Atomic Ops in a function
|
||||
*/
|
||||
struct FunctionAtomicOps {
|
||||
// the actual ops, store in the correct order
|
||||
std::vector<std::unique_ptr<AtomicOp>> ops;
|
||||
|
||||
// mappings from instructions to atomic ops and back
|
||||
std::unordered_map<int, int> instruction_to_basic_op;
|
||||
std::unordered_map<int, int> atomic_op_to_instruction;
|
||||
|
||||
// map from basic block to the index of the first op
|
||||
std::vector<int> block_id_to_first_atomic_op;
|
||||
// map from basic block to the index of the last op + 1
|
||||
std::vector<int> block_id_to_end_atomic_op;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Convert an entire basic block and add the results to a FunctionAtomicOps.
|
||||
* Updates the mapping between blocks, instructions, and atomic ops as needed
|
||||
* @param begin idx : the index of the first instruction for the block
|
||||
* @param begin : the start of the instructions for the block
|
||||
* @param end : the end of the instructions for the block
|
||||
* @param labels : label names for the function, used for error prints on failed conversions
|
||||
* @param container : the container to add to
|
||||
*/
|
||||
void convert_block_to_atomic_ops(int begin_idx,
|
||||
std::vector<Instruction>::const_iterator begin,
|
||||
std::vector<Instruction>::const_iterator end,
|
||||
const std::vector<DecompilerLabel>& labels,
|
||||
FunctionAtomicOps* container);
|
||||
|
||||
/*!
|
||||
* Convert an entire function to AtomicOps
|
||||
*/
|
||||
FunctionAtomicOps convert_function_to_atomic_ops(const Function& func,
|
||||
const std::vector<DecompilerLabel>& labels);
|
||||
} // namespace decompiler
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <stdexcept>
|
||||
#include "Env.h"
|
||||
|
||||
namespace decompiler {
|
||||
std::string Env::get_variable_name(Register reg, int atomic_idx) const {
|
||||
(void)reg;
|
||||
(void)atomic_idx;
|
||||
throw std::runtime_error("Env::get_variable_name not yet implemented.");
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
* An "environment" for a single function.
|
||||
* This contains data for an entire function, like which registers are live when, the types of
|
||||
* values in registers, and local variable names. This does not actually store IR itself, just
|
||||
* shared data that all IR can look at. The concept is somewhat similar to Env in the compiler.
|
||||
*/
|
||||
class Env {
|
||||
public:
|
||||
bool has_local_vars() const { return m_has_local_vars; }
|
||||
std::string get_variable_name(Register reg, int atomic_idx) const;
|
||||
|
||||
private:
|
||||
bool m_has_local_vars = false;
|
||||
};
|
||||
} // namespace decompiler
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
namespace decompiler {
|
||||
class IR2 {
|
||||
public:
|
||||
private:
|
||||
};
|
||||
} // namespace decompiler
|
||||
@@ -13,9 +13,10 @@
|
||||
#include "decompiler/Disasm/InstructionDecode.h"
|
||||
#include "decompiler/config.h"
|
||||
#include "third-party/json.hpp"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "common/log/log.h"
|
||||
#include "common/goos/PrettyPrinter.h"
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
* Set the number of segments in this object file.
|
||||
* This can only be done once, and must be done before adding any words.
|
||||
@@ -45,7 +46,7 @@ int LinkedObjectFile::get_label_id_for(int seg, int offset) {
|
||||
if (kv == label_per_seg_by_offset.at(seg).end()) {
|
||||
// create a new label
|
||||
int id = labels.size();
|
||||
Label label;
|
||||
DecompilerLabel label;
|
||||
label.target_segment = seg;
|
||||
label.offset = offset;
|
||||
label.name = "L" + std::to_string(id);
|
||||
@@ -498,7 +499,7 @@ void LinkedObjectFile::process_fp_relative_links() {
|
||||
} break;
|
||||
|
||||
default:
|
||||
printf("unknown fp using op: %s\n", instr.to_string(*this).c_str());
|
||||
printf("unknown fp using op: %s\n", instr.to_string(labels).c_str());
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
@@ -518,7 +519,7 @@ std::string LinkedObjectFile::to_asm_json(const std::string& obj_file_name) {
|
||||
auto& func = functions_by_seg.at(seg).at(fi);
|
||||
auto fname = func.guessed_name.to_string();
|
||||
if (functions_seen.find(fname) != functions_seen.end()) {
|
||||
spdlog::warn(
|
||||
lg::warn(
|
||||
"Function {} appears multiple times in the same object file {} - it cannot be uniquely "
|
||||
"referenced from config",
|
||||
func.guessed_name.to_string(), obj_file_name);
|
||||
@@ -544,7 +545,7 @@ std::string LinkedObjectFile::to_asm_json(const std::string& obj_file_name) {
|
||||
}
|
||||
auto& instr = func.instructions.at(i);
|
||||
op["id"] = i;
|
||||
op["asm_op"] = instr.to_string(*this);
|
||||
op["asm_op"] = instr.to_string(labels);
|
||||
|
||||
if (func.has_basic_ops() && func.instr_starts_basic_op(i)) {
|
||||
op["basic_op"] = func.get_basic_op_at_instr(i)->print(*this);
|
||||
@@ -608,7 +609,7 @@ std::string LinkedObjectFile::print_function_disassembly(Function& func,
|
||||
}
|
||||
|
||||
auto& instr = func.instructions.at(i);
|
||||
std::string line = " " + instr.to_string(*this);
|
||||
std::string line = " " + instr.to_string(labels);
|
||||
|
||||
if (write_hex) {
|
||||
if (line.length() < 60) {
|
||||
@@ -1053,14 +1054,15 @@ goos::Object LinkedObjectFile::to_form_script_object(int seg,
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 LinkedObjectFile::read_data_word(const Label& label) {
|
||||
u32 LinkedObjectFile::read_data_word(const DecompilerLabel& label) {
|
||||
assert(0 == (label.offset % 4));
|
||||
auto& word = words_by_seg.at(label.target_segment).at(label.offset / 4);
|
||||
assert(word.kind == LinkedWord::Kind::PLAIN_DATA);
|
||||
return word.data;
|
||||
}
|
||||
|
||||
std::string LinkedObjectFile::get_goal_string_by_label(const Label& label) const {
|
||||
std::string LinkedObjectFile::get_goal_string_by_label(const DecompilerLabel& label) const {
|
||||
assert(0 == (label.offset % 4));
|
||||
return get_goal_string(label.target_segment, (label.offset / 4) - 1, false);
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -14,19 +14,11 @@
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include "LinkedWord.h"
|
||||
#include "decompiler/Disasm/DecompilerLabel.h"
|
||||
#include "decompiler/Function/Function.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
/*!
|
||||
* A label to a location in this object file.
|
||||
* Doesn't have to be word aligned.
|
||||
*/
|
||||
struct Label {
|
||||
std::string name;
|
||||
int target_segment;
|
||||
int offset; // in bytes
|
||||
};
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
* An object file's data with linking information included.
|
||||
*/
|
||||
@@ -69,8 +61,8 @@ class LinkedObjectFile {
|
||||
const std::string& extra_name);
|
||||
std::string print_asm_function_disassembly(const std::string& my_name);
|
||||
|
||||
u32 read_data_word(const Label& label);
|
||||
std::string get_goal_string_by_label(const Label& label) const;
|
||||
u32 read_data_word(const DecompilerLabel& label);
|
||||
std::string get_goal_string_by_label(const DecompilerLabel& label) const;
|
||||
|
||||
struct Stats {
|
||||
uint32_t total_code_bytes = 0;
|
||||
@@ -131,7 +123,7 @@ class LinkedObjectFile {
|
||||
std::vector<std::vector<LinkedWord>> words_by_seg;
|
||||
std::vector<uint32_t> offset_of_data_zone_by_seg;
|
||||
std::vector<std::vector<Function>> functions_by_seg;
|
||||
std::vector<Label> labels;
|
||||
std::vector<DecompilerLabel> labels;
|
||||
|
||||
private:
|
||||
goos::Object to_form_script(int seg, int word_idx, std::vector<bool>& seen);
|
||||
@@ -142,5 +134,6 @@ class LinkedObjectFile {
|
||||
|
||||
std::vector<std::unordered_map<int, int>> label_per_seg_by_offset;
|
||||
};
|
||||
} // namespace decompiler
|
||||
|
||||
#endif // NEXT_LINKEDOBJECTFILE_H
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
#include "common/link_types.h"
|
||||
|
||||
namespace decompiler {
|
||||
// There are three link versions:
|
||||
// V2 - not really in use anymore, but V4 will resue logic from it (and the game didn't rename the
|
||||
// functions) V3 - optimized for code and small stuff. Supports segments (main, debug, top-level) V4
|
||||
@@ -819,3 +820,4 @@ LinkedObjectFile to_linked_object_file(const std::vector<uint8_t>& data,
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -11,9 +11,11 @@
|
||||
|
||||
#include "LinkedObjectFile.h"
|
||||
|
||||
namespace decompiler {
|
||||
class DecompilerTypeSystem;
|
||||
LinkedObjectFile to_linked_object_file(const std::vector<uint8_t>& data,
|
||||
const std::string& name,
|
||||
DecompilerTypeSystem& dts);
|
||||
} // namespace decompiler
|
||||
|
||||
#endif // NEXT_LINKEDOBJECTFILECREATION_H
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace decompiler {
|
||||
class LinkedWord {
|
||||
public:
|
||||
explicit LinkedWord(uint32_t _data) : data(_data) {}
|
||||
@@ -31,5 +32,6 @@ class LinkedWord {
|
||||
int label_id = -1;
|
||||
std::string symbol_name;
|
||||
};
|
||||
} // namespace decompiler
|
||||
|
||||
#endif // JAK2_DISASSEMBLER_LINKEDWORD_H
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
#include "decompiler/IR/BasicOpBuilder.h"
|
||||
#include "decompiler/IR/CfgBuilder.h"
|
||||
#include "decompiler/Function/TypeInspector.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "common/log/log.h"
|
||||
#include "third-party/json.hpp"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
std::string strip_dgo_extension(const std::string& x) {
|
||||
auto ext = x.substr(x.length() - 4, 4);
|
||||
@@ -112,32 +113,32 @@ ObjectFileDB::ObjectFileDB(const std::vector<std::string>& _dgos,
|
||||
const std::vector<std::string>& str_files) {
|
||||
Timer timer;
|
||||
|
||||
spdlog::info("-Loading types...");
|
||||
lg::info("-Loading types...");
|
||||
dts.parse_type_defs({"decompiler", "config", "all-types.gc"});
|
||||
|
||||
if (!obj_file_name_map_file.empty()) {
|
||||
spdlog::info("-Loading obj name map file...");
|
||||
lg::info("-Loading obj name map file...");
|
||||
load_map_file(file_util::read_text_file(file_util::get_file_path({obj_file_name_map_file})));
|
||||
} else {
|
||||
spdlog::warn(
|
||||
lg::warn(
|
||||
"Not using an obj name map file! The decompiler will automatically generate object file "
|
||||
"names and write them to out/objs.txt. It is recommended to reuse this map file to get "
|
||||
"consistent naming when doing a partial decompilation.");
|
||||
}
|
||||
|
||||
spdlog::info("-Loading DGOs...");
|
||||
lg::info("-Loading DGOs...");
|
||||
for (auto& dgo : _dgos) {
|
||||
get_objs_from_dgo(dgo);
|
||||
}
|
||||
|
||||
spdlog::info("-Loading plain object files...");
|
||||
lg::info("-Loading plain object files...");
|
||||
for (auto& obj : object_files) {
|
||||
auto data = file_util::read_binary_file(obj);
|
||||
auto name = obj_filename_to_name(obj);
|
||||
add_obj_from_dgo(name, name, data.data(), data.size(), "NO-XGO");
|
||||
}
|
||||
|
||||
spdlog::info("-Loading streaming object files...");
|
||||
lg::info("-Loading streaming object files...");
|
||||
for (auto& obj : str_files) {
|
||||
StrFileReader reader(obj);
|
||||
// name from the file name
|
||||
@@ -152,15 +153,15 @@ ObjectFileDB::ObjectFileDB(const std::vector<std::string>& _dgos,
|
||||
}
|
||||
}
|
||||
|
||||
spdlog::info("ObjectFileDB Initialized:");
|
||||
spdlog::info("Total DGOs: {}", int(_dgos.size()));
|
||||
spdlog::info("Total data: {} bytes", stats.total_dgo_bytes);
|
||||
spdlog::info("Total objs: {}", stats.total_obj_files);
|
||||
spdlog::info("Unique objs: {}", stats.unique_obj_files);
|
||||
spdlog::info("Unique data: {} bytes", stats.unique_obj_bytes);
|
||||
spdlog::info("Total {:.2f} ms ({:.3f} MB/sec, {:.2f} obj/sec)", timer.getMs(),
|
||||
stats.total_dgo_bytes / ((1u << 20u) * timer.getSeconds()),
|
||||
stats.total_obj_files / timer.getSeconds());
|
||||
lg::info("ObjectFileDB Initialized:");
|
||||
lg::info("Total DGOs: {}", int(_dgos.size()));
|
||||
lg::info("Total data: {} bytes", stats.total_dgo_bytes);
|
||||
lg::info("Total objs: {}", stats.total_obj_files);
|
||||
lg::info("Unique objs: {}", stats.unique_obj_files);
|
||||
lg::info("Unique data: {} bytes", stats.unique_obj_bytes);
|
||||
lg::info("Total {:.2f} ms ({:.3f} MB/sec, {:.2f} obj/sec)", timer.getMs(),
|
||||
stats.total_dgo_bytes / ((1u << 20u) * timer.getSeconds()),
|
||||
stats.total_obj_files / timer.getSeconds());
|
||||
}
|
||||
|
||||
void ObjectFileDB::load_map_file(const std::string& map_data) {
|
||||
@@ -180,7 +181,7 @@ void ObjectFileDB::load_map_file(const std::string& map_data) {
|
||||
for (auto& dgo : dgo_names) {
|
||||
auto kv = dgo_obj_name_map[dgo].find(game_name_with_ag);
|
||||
if (kv != dgo_obj_name_map[dgo].end()) {
|
||||
spdlog::error("Object {} in dgo {} occurs more than one time.", game_name_with_ag, dgo);
|
||||
lg::error("Object {} in dgo {} occurs more than one time.", game_name_with_ag, dgo);
|
||||
assert(false);
|
||||
}
|
||||
dgo_obj_name_map[dgo][game_name_with_ag] = mapped_name;
|
||||
@@ -317,7 +318,7 @@ void ObjectFileDB::get_objs_from_dgo(const std::string& filename) {
|
||||
assert_string_empty_after(obj_header.name, 60);
|
||||
|
||||
if (std::string(obj_header.name).find("-ag") != std::string::npos) {
|
||||
spdlog::error(
|
||||
lg::error(
|
||||
"Object file {} has \"-ag\" in its name. This will break any tools which use this to "
|
||||
"detect an art group",
|
||||
obj_header.name);
|
||||
@@ -388,14 +389,13 @@ void ObjectFileDB::add_obj_from_dgo(const std::string& obj_name,
|
||||
if (!dgo_obj_name_map.empty()) {
|
||||
auto dgo_kv = dgo_obj_name_map.find(strip_dgo_extension(dgo_name));
|
||||
if (dgo_kv == dgo_obj_name_map.end()) {
|
||||
spdlog::error("Object {} is from DGO {}, but this DGO wasn't in the map.", obj_name,
|
||||
dgo_name);
|
||||
lg::error("Object {} is from DGO {}, but this DGO wasn't in the map.", obj_name, dgo_name);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
auto name_kv = dgo_kv->second.find(obj_name);
|
||||
if (name_kv == dgo_kv->second.end()) {
|
||||
spdlog::error("Object {} from DGO {} wasn't found in the name map.", obj_name, dgo_name);
|
||||
lg::error("Object {} from DGO {} wasn't found in the name map.", obj_name, dgo_name);
|
||||
assert(false);
|
||||
}
|
||||
data.name_from_map = name_kv->second;
|
||||
@@ -481,7 +481,7 @@ std::string ObjectFileDB::generate_obj_listing() {
|
||||
* Process all of the linking data of all objects.
|
||||
*/
|
||||
void ObjectFileDB::process_link_data() {
|
||||
spdlog::info("- Processing Link Data...");
|
||||
lg::info("- Processing Link Data...");
|
||||
Timer process_link_timer;
|
||||
|
||||
LinkedObjectFile::Stats combined_stats;
|
||||
@@ -491,26 +491,26 @@ void ObjectFileDB::process_link_data() {
|
||||
combined_stats.add(obj.linked_data.stats);
|
||||
});
|
||||
|
||||
spdlog::info("Processed Link Data:");
|
||||
spdlog::info(" Code {} bytes", combined_stats.total_code_bytes);
|
||||
spdlog::info(" v2 Code {} bytes", combined_stats.total_v2_code_bytes);
|
||||
spdlog::info(" v2 Link Data {} bytes", combined_stats.total_v2_link_bytes);
|
||||
spdlog::info(" v2 Pointers {}", combined_stats.total_v2_pointers);
|
||||
spdlog::info(" v2 Pointer Seeks {}", combined_stats.total_v2_pointer_seeks);
|
||||
spdlog::info(" v2 Symbols {}", combined_stats.total_v2_symbol_count);
|
||||
spdlog::info(" v2 Symbol Links {}", combined_stats.total_v2_symbol_links);
|
||||
lg::info("Processed Link Data:");
|
||||
lg::info(" Code {} bytes", combined_stats.total_code_bytes);
|
||||
lg::info(" v2 Code {} bytes", combined_stats.total_v2_code_bytes);
|
||||
lg::info(" v2 Link Data {} bytes", combined_stats.total_v2_link_bytes);
|
||||
lg::info(" v2 Pointers {}", combined_stats.total_v2_pointers);
|
||||
lg::info(" v2 Pointer Seeks {}", combined_stats.total_v2_pointer_seeks);
|
||||
lg::info(" v2 Symbols {}", combined_stats.total_v2_symbol_count);
|
||||
lg::info(" v2 Symbol Links {}", combined_stats.total_v2_symbol_links);
|
||||
|
||||
spdlog::info(" v3 Code {} bytes", combined_stats.v3_code_bytes);
|
||||
spdlog::info(" v3 Link Data {} bytes", combined_stats.v3_link_bytes);
|
||||
spdlog::info(" v3 Pointers {}", combined_stats.v3_pointers);
|
||||
spdlog::info(" Split {}", combined_stats.v3_split_pointers);
|
||||
spdlog::info(" Word {}", combined_stats.v3_word_pointers);
|
||||
spdlog::info(" v3 Pointer Seeks {}", combined_stats.v3_pointer_seeks);
|
||||
spdlog::info(" v3 Symbols {}", combined_stats.v3_symbol_count);
|
||||
spdlog::info(" v3 Offset Symbol Links {}", combined_stats.v3_symbol_link_offset);
|
||||
spdlog::info(" v3 Word Symbol Links {}", combined_stats.v3_symbol_link_word);
|
||||
lg::info(" v3 Code {} bytes", combined_stats.v3_code_bytes);
|
||||
lg::info(" v3 Link Data {} bytes", combined_stats.v3_link_bytes);
|
||||
lg::info(" v3 Pointers {}", combined_stats.v3_pointers);
|
||||
lg::info(" Split {}", combined_stats.v3_split_pointers);
|
||||
lg::info(" Word {}", combined_stats.v3_word_pointers);
|
||||
lg::info(" v3 Pointer Seeks {}", combined_stats.v3_pointer_seeks);
|
||||
lg::info(" v3 Symbols {}", combined_stats.v3_symbol_count);
|
||||
lg::info(" v3 Offset Symbol Links {}", combined_stats.v3_symbol_link_offset);
|
||||
lg::info(" v3 Word Symbol Links {}", combined_stats.v3_symbol_link_word);
|
||||
|
||||
spdlog::info(" Total {} ms\n", process_link_timer.getMs());
|
||||
lg::info(" Total {} ms\n", process_link_timer.getMs());
|
||||
// printf("\n");
|
||||
}
|
||||
|
||||
@@ -518,14 +518,14 @@ void ObjectFileDB::process_link_data() {
|
||||
* Process all of the labels generated from linking and give them reasonable names.
|
||||
*/
|
||||
void ObjectFileDB::process_labels() {
|
||||
spdlog::info("- Processing Labels...");
|
||||
lg::info("- Processing Labels...");
|
||||
Timer process_label_timer;
|
||||
uint32_t total = 0;
|
||||
for_each_obj([&](ObjectFileData& obj) { total += obj.linked_data.set_ordered_label_names(); });
|
||||
|
||||
spdlog::info("Processed Labels:");
|
||||
spdlog::info(" Total {} labels", total);
|
||||
spdlog::info(" Total {} ms", process_label_timer.getMs());
|
||||
lg::info("Processed Labels:");
|
||||
lg::info(" Total {} labels", total);
|
||||
lg::info(" Total {} ms", process_label_timer.getMs());
|
||||
// printf("\n");
|
||||
}
|
||||
|
||||
@@ -534,9 +534,9 @@ void ObjectFileDB::process_labels() {
|
||||
*/
|
||||
void ObjectFileDB::write_object_file_words(const std::string& output_dir, bool dump_v3_only) {
|
||||
if (dump_v3_only) {
|
||||
spdlog::info("- Writing object file dumps (v3 only)...");
|
||||
lg::info("- Writing object file dumps (v3 only)...");
|
||||
} else {
|
||||
spdlog::info("- Writing object file dumps (all)...");
|
||||
lg::info("- Writing object file dumps (all)...");
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
@@ -552,17 +552,17 @@ void ObjectFileDB::write_object_file_words(const std::string& output_dir, bool d
|
||||
}
|
||||
});
|
||||
|
||||
spdlog::info("Wrote object file dumps:");
|
||||
spdlog::info(" Total {} files", total_files);
|
||||
spdlog::info(" Total {:.3f} MB", total_bytes / ((float)(1u << 20u)));
|
||||
spdlog::info(" Total {} ms ({:.3f} MB/sec)", timer.getMs(),
|
||||
total_bytes / ((1u << 20u) * timer.getSeconds()));
|
||||
lg::info("Wrote object file dumps:");
|
||||
lg::info(" Total {} files", total_files);
|
||||
lg::info(" Total {:.3f} MB", total_bytes / ((float)(1u << 20u)));
|
||||
lg::info(" Total {} ms ({:.3f} MB/sec)", timer.getMs(),
|
||||
total_bytes / ((1u << 20u) * timer.getSeconds()));
|
||||
// printf("\n");
|
||||
}
|
||||
|
||||
void ObjectFileDB::write_debug_type_analysis(const std::string& output_dir,
|
||||
const std::string& suffix) {
|
||||
spdlog::info("- Writing debug type analysis...");
|
||||
lg::info("- Writing debug type analysis...");
|
||||
Timer timer;
|
||||
uint32_t total_bytes = 0, total_files = 0;
|
||||
|
||||
@@ -578,11 +578,11 @@ void ObjectFileDB::write_debug_type_analysis(const std::string& output_dir,
|
||||
}
|
||||
});
|
||||
|
||||
spdlog::info("Wrote functions dumps:");
|
||||
spdlog::info(" Total {} files", total_files);
|
||||
spdlog::info(" Total {} MB", total_bytes / ((float)(1u << 20u)));
|
||||
spdlog::info(" Total {} ms ({:.3f} MB/sec)", timer.getMs(),
|
||||
total_bytes / ((1u << 20u) * timer.getSeconds()));
|
||||
lg::info("Wrote functions dumps:");
|
||||
lg::info(" Total {} files", total_files);
|
||||
lg::info(" Total {} MB", total_bytes / ((float)(1u << 20u)));
|
||||
lg::info(" Total {} ms ({:.3f} MB/sec)", timer.getMs(),
|
||||
total_bytes / ((1u << 20u) * timer.getSeconds()));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -592,7 +592,7 @@ void ObjectFileDB::write_disassembly(const std::string& output_dir,
|
||||
bool disassemble_objects_without_functions,
|
||||
bool write_json,
|
||||
const std::string& file_suffix) {
|
||||
spdlog::info("- Writing functions...");
|
||||
lg::info("- Writing functions...");
|
||||
Timer timer;
|
||||
uint32_t total_bytes = 0, total_files = 0;
|
||||
|
||||
@@ -625,18 +625,18 @@ void ObjectFileDB::write_disassembly(const std::string& output_dir,
|
||||
file_util::write_text_file(file_util::combine_path(output_dir, "asm_functions.func"),
|
||||
asm_functions);
|
||||
|
||||
spdlog::info("Wrote functions dumps:");
|
||||
spdlog::info(" Total {} files", total_files);
|
||||
spdlog::info(" Total {} MB", total_bytes / ((float)(1u << 20u)));
|
||||
spdlog::info(" Total {} ms ({:.3f} MB/sec)", timer.getMs(),
|
||||
total_bytes / ((1u << 20u) * timer.getSeconds()));
|
||||
lg::info("Wrote functions dumps:");
|
||||
lg::info(" Total {} files", total_files);
|
||||
lg::info(" Total {} MB", total_bytes / ((float)(1u << 20u)));
|
||||
lg::info(" Total {} ms ({:.3f} MB/sec)", timer.getMs(),
|
||||
total_bytes / ((1u << 20u) * timer.getSeconds()));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Find code/data zones, identify functions, and disassemble
|
||||
*/
|
||||
void ObjectFileDB::find_code() {
|
||||
spdlog::info("- Finding code in object files...");
|
||||
lg::info("- Finding code in object files...");
|
||||
LinkedObjectFile::Stats combined_stats;
|
||||
Timer timer;
|
||||
|
||||
@@ -649,28 +649,28 @@ void ObjectFileDB::find_code() {
|
||||
if (get_config().game_version == 1 || obj.to_unique_name() != "effect-control-v0") {
|
||||
obj.linked_data.process_fp_relative_links();
|
||||
} else {
|
||||
spdlog::warn("Skipping process_fp_relative_links in {}", obj.to_unique_name().c_str());
|
||||
lg::warn("Skipping process_fp_relative_links in {}", obj.to_unique_name().c_str());
|
||||
}
|
||||
|
||||
auto& obj_stats = obj.linked_data.stats;
|
||||
if (obj_stats.code_bytes / 4 > obj_stats.decoded_ops) {
|
||||
spdlog::warn("Failed to decode all in {} ({} / {})", obj.to_unique_name().c_str(),
|
||||
obj_stats.decoded_ops, obj_stats.code_bytes / 4);
|
||||
lg::warn("Failed to decode all in {} ({} / {})", obj.to_unique_name().c_str(),
|
||||
obj_stats.decoded_ops, obj_stats.code_bytes / 4);
|
||||
}
|
||||
combined_stats.add(obj.linked_data.stats);
|
||||
});
|
||||
|
||||
spdlog::info("Found code:");
|
||||
spdlog::info(" Code {:.3f} MB", combined_stats.code_bytes / (float)(1 << 20));
|
||||
spdlog::info(" Data {:.3f} MB", combined_stats.data_bytes / (float)(1 << 20));
|
||||
spdlog::info(" Functions: {}", combined_stats.function_count);
|
||||
spdlog::info(" fp uses resolved: {} / {} ({:.3f} %)", combined_stats.n_fp_reg_use_resolved,
|
||||
combined_stats.n_fp_reg_use,
|
||||
100.f * (float)combined_stats.n_fp_reg_use_resolved / combined_stats.n_fp_reg_use);
|
||||
lg::info("Found code:");
|
||||
lg::info(" Code {:.3f} MB", combined_stats.code_bytes / (float)(1 << 20));
|
||||
lg::info(" Data {:.3f} MB", combined_stats.data_bytes / (float)(1 << 20));
|
||||
lg::info(" Functions: {}", combined_stats.function_count);
|
||||
lg::info(" fp uses resolved: {} / {} ({:.3f} %)", combined_stats.n_fp_reg_use_resolved,
|
||||
combined_stats.n_fp_reg_use,
|
||||
100.f * (float)combined_stats.n_fp_reg_use_resolved / combined_stats.n_fp_reg_use);
|
||||
auto total_ops = combined_stats.code_bytes / 4;
|
||||
spdlog::info(" Decoded {} / {} ({:.3f} %)", combined_stats.decoded_ops, total_ops,
|
||||
100.f * (float)combined_stats.decoded_ops / total_ops);
|
||||
spdlog::info(" Total {:.3f} ms", timer.getMs());
|
||||
lg::info(" Decoded {} / {} ({:.3f} %)", combined_stats.decoded_ops, total_ops,
|
||||
100.f * (float)combined_stats.decoded_ops / total_ops);
|
||||
lg::info(" Total {:.3f} ms", timer.getMs());
|
||||
// printf("\n");
|
||||
}
|
||||
|
||||
@@ -679,7 +679,7 @@ void ObjectFileDB::find_code() {
|
||||
* Doesn't change any state in ObjectFileDB.
|
||||
*/
|
||||
void ObjectFileDB::find_and_write_scripts(const std::string& output_dir) {
|
||||
spdlog::info("- Finding scripts in object files...");
|
||||
lg::info("- Finding scripts in object files...");
|
||||
Timer timer;
|
||||
std::string all_scripts;
|
||||
|
||||
@@ -696,12 +696,12 @@ void ObjectFileDB::find_and_write_scripts(const std::string& output_dir) {
|
||||
auto file_name = file_util::combine_path(output_dir, "all_scripts.lisp");
|
||||
file_util::write_text_file(file_name, all_scripts);
|
||||
|
||||
spdlog::info("Found scripts:");
|
||||
spdlog::info(" Total {:.3f} ms\n", timer.getMs());
|
||||
lg::info("Found scripts:");
|
||||
lg::info(" Total {:.3f} ms\n", timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::process_tpages() {
|
||||
spdlog::info("- Finding textures in tpages...");
|
||||
lg::info("- Finding textures in tpages...");
|
||||
std::string tpage_string = "tpage-";
|
||||
int total = 0, success = 0;
|
||||
Timer timer;
|
||||
@@ -712,12 +712,12 @@ void ObjectFileDB::process_tpages() {
|
||||
success += statistics.successful_textures;
|
||||
}
|
||||
});
|
||||
spdlog::info("Processed {} / {} textures {:.2f}% in {:.2f} ms", success, total,
|
||||
100.f * float(success) / float(total), timer.getMs());
|
||||
lg::info("Processed {} / {} textures {:.2f}% in {:.2f} ms", success, total,
|
||||
100.f * float(success) / float(total), timer.getMs());
|
||||
}
|
||||
|
||||
std::string ObjectFileDB::process_game_text() {
|
||||
spdlog::info("- Finding game text...");
|
||||
std::string ObjectFileDB::process_game_text_files() {
|
||||
lg::info("- Finding game text...");
|
||||
std::string text_string = "COMMON";
|
||||
Timer timer;
|
||||
int file_count = 0;
|
||||
@@ -728,7 +728,7 @@ std::string ObjectFileDB::process_game_text() {
|
||||
for_each_obj([&](ObjectFileData& data) {
|
||||
if (data.name_in_dgo.substr(1) == text_string) {
|
||||
file_count++;
|
||||
auto statistics = ::process_game_text(data);
|
||||
auto statistics = process_game_text(data);
|
||||
string_count += statistics.total_text;
|
||||
char_count += statistics.total_chars;
|
||||
if (text_by_language_by_id.find(statistics.language) != text_by_language_by_id.end()) {
|
||||
@@ -738,14 +738,14 @@ std::string ObjectFileDB::process_game_text() {
|
||||
}
|
||||
});
|
||||
|
||||
spdlog::info("Processed {} text files ({} strings, {} characters) in {:.2f} ms", file_count,
|
||||
string_count, char_count, timer.getMs());
|
||||
lg::info("Processed {} text files ({} strings, {} characters) in {:.2f} ms", file_count,
|
||||
string_count, char_count, timer.getMs());
|
||||
|
||||
return write_game_text(text_by_language_by_id);
|
||||
}
|
||||
|
||||
std::string ObjectFileDB::process_game_count() {
|
||||
spdlog::info("- Finding game count file...");
|
||||
std::string ObjectFileDB::process_game_count_file() {
|
||||
lg::info("- Finding game count file...");
|
||||
bool found = false;
|
||||
std::string result;
|
||||
|
||||
@@ -753,12 +753,12 @@ std::string ObjectFileDB::process_game_count() {
|
||||
if (data.name_in_dgo == "game-cnt") {
|
||||
assert(!found);
|
||||
found = true;
|
||||
result = write_game_count(::process_game_count(data));
|
||||
result = write_game_count(process_game_count(data));
|
||||
}
|
||||
});
|
||||
|
||||
if (!found) {
|
||||
spdlog::warn("did not find game-cnt file");
|
||||
lg::warn("did not find game-cnt file");
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -768,7 +768,7 @@ std::string ObjectFileDB::process_game_count() {
|
||||
* This is the main decompiler routine which runs after we've identified functions.
|
||||
*/
|
||||
void ObjectFileDB::analyze_functions() {
|
||||
spdlog::info("- Analyzing Functions...");
|
||||
lg::info("- Analyzing Functions...");
|
||||
Timer timer;
|
||||
|
||||
int total_functions = 0;
|
||||
@@ -777,7 +777,7 @@ void ObjectFileDB::analyze_functions() {
|
||||
|
||||
// Step 1 - analyze the "top level" or "login" code for each object file.
|
||||
// this will give us type definitions, method definitions, and function definitions...
|
||||
spdlog::info(" - Processing top levels...");
|
||||
lg::info(" - Processing top levels...");
|
||||
|
||||
timer.start();
|
||||
for_each_obj([&](ObjectFileData& data) {
|
||||
@@ -921,8 +921,8 @@ void ObjectFileDB::analyze_functions() {
|
||||
if (func.cfg->is_fully_resolved()) {
|
||||
resolved_cfg_functions++;
|
||||
} else {
|
||||
spdlog::warn("Function {} from {} failed cfg ir", func.guessed_name.to_string(),
|
||||
data.to_unique_name());
|
||||
lg::warn("Function {} from {} failed cfg ir", func.guessed_name.to_string(),
|
||||
data.to_unique_name());
|
||||
}
|
||||
|
||||
// type analysis
|
||||
@@ -936,15 +936,15 @@ void ObjectFileDB::analyze_functions() {
|
||||
auto kv = dts.symbol_types.find(func.guessed_name.function_name);
|
||||
if (kv != dts.symbol_types.end() && kv->second.arg_count() >= 1) {
|
||||
if (kv->second.base_type() != "function") {
|
||||
spdlog::error("Found a function named {} but the symbol has type {}",
|
||||
func.guessed_name.to_string(), kv->second.print());
|
||||
lg::error("Found a function named {} but the symbol has type {}",
|
||||
func.guessed_name.to_string(), kv->second.print());
|
||||
assert(false);
|
||||
}
|
||||
// GOOD!
|
||||
func.type = kv->second;
|
||||
func.attempted_type_analysis = true;
|
||||
attempted_type_analysis++;
|
||||
// spdlog::info("Type Analysis on {} {}", func.guessed_name.to_string(),
|
||||
// lg::info("Type Analysis on {} {}", func.guessed_name.to_string(),
|
||||
// kv->second.print());
|
||||
if (func.run_type_analysis(kv->second, dts, data.linked_data, hints)) {
|
||||
successful_type_analysis++;
|
||||
@@ -963,15 +963,15 @@ void ObjectFileDB::analyze_functions() {
|
||||
dts.ts.lookup_method(func.guessed_name.type_name, func.guessed_name.method_id);
|
||||
if (info.type.arg_count() >= 1) {
|
||||
if (info.type.base_type() != "function") {
|
||||
spdlog::error("Found a method named {} but the symbol has type {}",
|
||||
func.guessed_name.to_string(), info.type.print());
|
||||
lg::error("Found a method named {} but the symbol has type {}",
|
||||
func.guessed_name.to_string(), info.type.print());
|
||||
assert(false);
|
||||
}
|
||||
// GOOD!
|
||||
func.type = info.type.substitute_for_method_call(func.guessed_name.type_name);
|
||||
func.attempted_type_analysis = true;
|
||||
attempted_type_analysis++;
|
||||
// spdlog::info("Type Analysis on {} {}",
|
||||
// lg::info("Type Analysis on {} {}",
|
||||
// func.guessed_name.to_string(),
|
||||
// func.type.print());
|
||||
if (func.run_type_analysis(func.type, dts, data.linked_data, hints)) {
|
||||
@@ -1061,29 +1061,29 @@ void ObjectFileDB::analyze_functions() {
|
||||
// }
|
||||
});
|
||||
|
||||
spdlog::info("Found {} functions ({} with no control flow)", total_functions,
|
||||
total_trivial_cfg_functions);
|
||||
spdlog::info("Named {}/{} functions ({:.3f}%)", total_named_functions, total_functions,
|
||||
100.f * float(total_named_functions) / float(total_functions));
|
||||
spdlog::info("Excluding {} asm functions", asm_funcs);
|
||||
spdlog::info("Found {} basic blocks in {:.3f} ms", total_basic_blocks, timer.getMs());
|
||||
spdlog::info(" {}/{} functions passed cfg analysis stage ({:.3f}%)", resolved_cfg_functions,
|
||||
non_asm_funcs, 100.f * float(resolved_cfg_functions) / float(non_asm_funcs));
|
||||
lg::info("Found {} functions ({} with no control flow)", total_functions,
|
||||
total_trivial_cfg_functions);
|
||||
lg::info("Named {}/{} functions ({:.3f}%)", total_named_functions, total_functions,
|
||||
100.f * float(total_named_functions) / float(total_functions));
|
||||
lg::info("Excluding {} asm functions", asm_funcs);
|
||||
lg::info("Found {} basic blocks in {:.3f} ms", total_basic_blocks, timer.getMs());
|
||||
lg::info(" {}/{} functions passed cfg analysis stage ({:.3f}%)", resolved_cfg_functions,
|
||||
non_asm_funcs, 100.f * float(resolved_cfg_functions) / float(non_asm_funcs));
|
||||
int successful_basic_ops = total_basic_ops - total_failed_basic_ops;
|
||||
spdlog::info(" {}/{} basic ops converted successfully ({:.3f}%)", successful_basic_ops,
|
||||
total_basic_ops, 100.f * float(successful_basic_ops) / float(total_basic_ops));
|
||||
spdlog::info(" {}/{} basic ops with reginfo ({:.3f}%)", total_reginfo_ops, total_basic_ops,
|
||||
100.f * float(total_reginfo_ops) / float(total_basic_ops));
|
||||
spdlog::info(" {}/{} cfgs converted to ir ({:.3f}%)", successful_cfg_irs, non_asm_funcs,
|
||||
100.f * float(successful_cfg_irs) / float(non_asm_funcs));
|
||||
spdlog::info(" {}/{} functions attempted type analysis ({:.2f}%)", attempted_type_analysis,
|
||||
non_asm_funcs, 100.f * float(attempted_type_analysis) / float(non_asm_funcs));
|
||||
spdlog::info(" {}/{} functions that attempted type analysis succeeded ({:.2f}%)",
|
||||
successful_type_analysis, attempted_type_analysis,
|
||||
100.f * float(successful_type_analysis) / float(attempted_type_analysis));
|
||||
spdlog::info(" {}/{} functions passed type analysis ({:.2f}%)", successful_type_analysis,
|
||||
non_asm_funcs, 100.f * float(successful_type_analysis) / float(non_asm_funcs));
|
||||
spdlog::info(
|
||||
lg::info(" {}/{} basic ops converted successfully ({:.3f}%)", successful_basic_ops,
|
||||
total_basic_ops, 100.f * float(successful_basic_ops) / float(total_basic_ops));
|
||||
lg::info(" {}/{} basic ops with reginfo ({:.3f}%)", total_reginfo_ops, total_basic_ops,
|
||||
100.f * float(total_reginfo_ops) / float(total_basic_ops));
|
||||
lg::info(" {}/{} cfgs converted to ir ({:.3f}%)", successful_cfg_irs, non_asm_funcs,
|
||||
100.f * float(successful_cfg_irs) / float(non_asm_funcs));
|
||||
lg::info(" {}/{} functions attempted type analysis ({:.2f}%)", attempted_type_analysis,
|
||||
non_asm_funcs, 100.f * float(attempted_type_analysis) / float(non_asm_funcs));
|
||||
lg::info(" {}/{} functions that attempted type analysis succeeded ({:.2f}%)",
|
||||
successful_type_analysis, attempted_type_analysis,
|
||||
100.f * float(successful_type_analysis) / float(attempted_type_analysis));
|
||||
lg::info(" {}/{} functions passed type analysis ({:.2f}%)", successful_type_analysis,
|
||||
non_asm_funcs, 100.f * float(successful_type_analysis) / float(non_asm_funcs));
|
||||
lg::info(
|
||||
" {} functions were supposed to do type analysis but either failed or didn't know their "
|
||||
"types.\n",
|
||||
bad_type_analysis);
|
||||
@@ -1097,7 +1097,7 @@ void ObjectFileDB::analyze_functions() {
|
||||
}
|
||||
|
||||
void ObjectFileDB::analyze_expressions() {
|
||||
spdlog::info("- Analyzing Expressions...");
|
||||
lg::info("- Analyzing Expressions...");
|
||||
Timer timer;
|
||||
int attempts = 0;
|
||||
int success = 0;
|
||||
@@ -1107,7 +1107,7 @@ void ObjectFileDB::analyze_expressions() {
|
||||
|
||||
if (/*!had_failure &&*/ func.attempted_type_analysis) {
|
||||
attempts++;
|
||||
spdlog::info("Analyze {}", func.guessed_name.to_string());
|
||||
lg::info("Analyze {}", func.guessed_name.to_string());
|
||||
if (func.build_expression(data.linked_data)) {
|
||||
success++;
|
||||
} else {
|
||||
@@ -1117,8 +1117,8 @@ void ObjectFileDB::analyze_expressions() {
|
||||
}
|
||||
});
|
||||
|
||||
spdlog::info(" {}/{} functions passed expression building ({:.2f}%)\n", success, attempts,
|
||||
100.f * float(success) / float(attempts));
|
||||
lg::info(" {}/{} functions passed expression building ({:.2f}%)\n", success, attempts,
|
||||
100.f * float(success) / float(attempts));
|
||||
}
|
||||
|
||||
void ObjectFileDB::dump_raw_objects(const std::string& output_dir) {
|
||||
@@ -1126,4 +1126,5 @@ void ObjectFileDB::dump_raw_objects(const std::string& output_dir) {
|
||||
auto dest = output_dir + "/" + data.to_unique_name();
|
||||
file_util::write_binary_file(dest, data.data.data(), data.data.size());
|
||||
});
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
* A "record" which can be used to identify an object file.
|
||||
*/
|
||||
@@ -67,8 +68,8 @@ class ObjectFileDB {
|
||||
void analyze_functions();
|
||||
void process_tpages();
|
||||
void analyze_expressions();
|
||||
std::string process_game_count();
|
||||
std::string process_game_text();
|
||||
std::string process_game_count_file();
|
||||
std::string process_game_text_files();
|
||||
|
||||
ObjectFileData& lookup_record(const ObjectFileRecord& rec);
|
||||
DecompilerTypeSystem dts;
|
||||
@@ -148,5 +149,6 @@ class ObjectFileDB {
|
||||
uint32_t unique_obj_bytes = 0;
|
||||
} stats;
|
||||
};
|
||||
} // namespace decompiler
|
||||
|
||||
#endif // JAK2_DISASSEMBLER_OBJECTFILEDB_H
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "third-party/json.hpp"
|
||||
#include "common/util/FileUtil.h"
|
||||
|
||||
namespace decompiler {
|
||||
Config gConfig;
|
||||
|
||||
Config& get_config() {
|
||||
@@ -101,3 +102,4 @@ void set_config(const std::string& path_to_config_file) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <unordered_map>
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
|
||||
namespace decompiler {
|
||||
struct TypeHint {
|
||||
Register reg;
|
||||
std::string type_name;
|
||||
@@ -46,5 +47,6 @@ struct Config {
|
||||
|
||||
Config& get_config();
|
||||
void set_config(const std::string& path_to_config_file);
|
||||
} // namespace decompiler
|
||||
|
||||
#endif // JAK2_DISASSEMBLER_CONFIG_H
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "decompiler/ObjectFile/LinkedWord.h"
|
||||
|
||||
namespace decompiler {
|
||||
class LinkedWordReader {
|
||||
public:
|
||||
explicit LinkedWordReader(const std::vector<LinkedWord>* words) : m_words(words) {}
|
||||
@@ -37,4 +38,5 @@ class LinkedWordReader {
|
||||
private:
|
||||
const std::vector<LinkedWord>* m_words = nullptr;
|
||||
u32 m_offset = 0;
|
||||
};
|
||||
};
|
||||
} // namespace decompiler
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "game/common/str_rpc_types.h"
|
||||
#include "StrFileReader.h"
|
||||
|
||||
namespace decompiler {
|
||||
StrFileReader::StrFileReader(const std::string& file_path) {
|
||||
auto data = file_util::read_binary_file(file_path);
|
||||
assert(data.size() >= SECTOR_SIZE); // must have at least the header sector
|
||||
@@ -178,4 +179,5 @@ std::string StrFileReader::get_full_name(const std::string& short_name) const {
|
||||
assert(strcmp(iso_name_1, iso_name_2) == 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace decompiler {
|
||||
class StrFileReader {
|
||||
public:
|
||||
explicit StrFileReader(const std::string& file_path);
|
||||
@@ -19,3 +20,4 @@ class StrFileReader {
|
||||
private:
|
||||
std::vector<std::vector<u8>> m_chunks;
|
||||
};
|
||||
} // namespace decompiler
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "game_count.h"
|
||||
#include "LinkedWordReader.h"
|
||||
|
||||
namespace decompiler {
|
||||
GameCountResult process_game_count(ObjectFileData& data) {
|
||||
GameCountResult result;
|
||||
auto& words = data.linked_data.words_by_seg.at(0);
|
||||
@@ -37,4 +38,5 @@ std::string write_game_count(const GameCountResult& result) {
|
||||
str += fmt::format("(:unknown-1 {} :unknown-2 {})\n", result.mystery_data[0],
|
||||
result.mystery_data[1]);
|
||||
return str;
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace decompiler {
|
||||
struct GameCountResult {
|
||||
struct CountInfo {
|
||||
s32 money_count;
|
||||
@@ -15,4 +16,5 @@ struct GameCountResult {
|
||||
|
||||
struct ObjectFileData;
|
||||
GameCountResult process_game_count(ObjectFileData& data);
|
||||
std::string write_game_count(const GameCountResult& result);
|
||||
std::string write_game_count(const GameCountResult& result);
|
||||
} // namespace decompiler
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "decompiler/ObjectFile/ObjectFileDB.h"
|
||||
#include "common/goos/Reader.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
template <typename T>
|
||||
T get_word(const LinkedWord& word) {
|
||||
@@ -17,7 +18,7 @@ T get_word(const LinkedWord& word) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Label get_label(ObjectFileData& data, const LinkedWord& word) {
|
||||
DecompilerLabel get_label(ObjectFileData& data, const LinkedWord& word) {
|
||||
assert(word.kind == LinkedWord::PTR);
|
||||
return data.linked_data.labels.at(word.label_id);
|
||||
}
|
||||
@@ -159,4 +160,5 @@ std::string write_game_text(
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace decompiler {
|
||||
struct ObjectFileData;
|
||||
|
||||
struct GameTextResult {
|
||||
@@ -13,4 +14,5 @@ struct GameTextResult {
|
||||
|
||||
GameTextResult process_game_text(ObjectFileData& data);
|
||||
std::string write_game_text(
|
||||
const std::unordered_map<int, std::unordered_map<int, std::string>>& data);
|
||||
const std::unordered_map<int, std::unordered_map<int, std::string>>& data);
|
||||
} // namespace decompiler
|
||||
@@ -18,8 +18,9 @@
|
||||
#include "tpage.h"
|
||||
#include "common/versions.h"
|
||||
#include "decompiler/ObjectFile/ObjectFileDB.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
|
||||
/*!
|
||||
@@ -314,7 +315,7 @@ struct Texture {
|
||||
u32 packed_info_words[9];
|
||||
};
|
||||
|
||||
Label name_label;
|
||||
DecompilerLabel name_label;
|
||||
std::string name;
|
||||
u32 size;
|
||||
float uv_dist;
|
||||
@@ -342,7 +343,7 @@ struct Texture {
|
||||
* Unclear what the segments really are, maybe you could split up big tpages if needed?
|
||||
*/
|
||||
struct TexturePageSegment {
|
||||
Label block_data_label;
|
||||
DecompilerLabel block_data_label;
|
||||
u32 size = 0xffffffff;
|
||||
u32 dest = 0xffffffff;
|
||||
std::string print_debug() const {
|
||||
@@ -379,10 +380,10 @@ struct FileInfo {
|
||||
* GOAL texture-page type.
|
||||
*/
|
||||
struct TexturePage {
|
||||
Label info_label;
|
||||
DecompilerLabel info_label;
|
||||
FileInfo info;
|
||||
|
||||
Label name_label;
|
||||
DecompilerLabel name_label;
|
||||
std::string name;
|
||||
|
||||
u32 id = 0xffffffff;
|
||||
@@ -392,7 +393,7 @@ struct TexturePage {
|
||||
TexturePageSegment segments[3];
|
||||
u32 pad[16] = {};
|
||||
// data...
|
||||
std::vector<Label> data;
|
||||
std::vector<DecompilerLabel> data;
|
||||
std::vector<Texture> textures;
|
||||
|
||||
std::string print_debug() const {
|
||||
@@ -423,7 +424,7 @@ struct TexturePage {
|
||||
* Convert a label to the offset (words) in the object segment.
|
||||
* If basic is set, gives you a pointer to the beginning of the memory, if the thing is a basic.
|
||||
*/
|
||||
int label_to_word_offset(Label l, bool basic) {
|
||||
int label_to_word_offset(DecompilerLabel l, bool basic) {
|
||||
assert((l.offset & 3) == 0);
|
||||
int result = l.offset / 4;
|
||||
if (basic) {
|
||||
@@ -441,7 +442,7 @@ bool is_type_tag(const LinkedWord& word, const std::string& type) {
|
||||
return word.kind == LinkedWord::TYPE_PTR && word.symbol_name == type;
|
||||
}
|
||||
|
||||
Label get_label(ObjectFileData& data, const LinkedWord& word) {
|
||||
DecompilerLabel get_label(ObjectFileData& data, const LinkedWord& word) {
|
||||
assert(word.kind == LinkedWord::PTR);
|
||||
return data.linked_data.labels.at(word.label_id);
|
||||
}
|
||||
@@ -905,3 +906,4 @@ TPageResultStats process_tpage(ObjectFileData& data) {
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace decompiler {
|
||||
struct ObjectFileData;
|
||||
|
||||
struct TPageResultStats {
|
||||
@@ -7,4 +8,5 @@ struct TPageResultStats {
|
||||
int successful_textures = 0;
|
||||
};
|
||||
|
||||
TPageResultStats process_tpage(ObjectFileData& data);
|
||||
TPageResultStats process_tpage(ObjectFileData& data);
|
||||
} // namespace decompiler
|
||||
+10
-11
@@ -2,18 +2,17 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "ObjectFile/ObjectFileDB.h"
|
||||
#include "common/log/log.h"
|
||||
#include "config.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "third-party/spdlog/include/spdlog/sinks/basic_file_sink.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
spdlog::info("Beginning disassembly. This may take a few minutes...");
|
||||
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
// auto lu = spdlog::basic_logger_mt("GOAL Decompiler", "logs/decompiler.log");
|
||||
// spdlog::set_default_logger(lu);
|
||||
spdlog::flush_on(spdlog::level::info);
|
||||
using namespace decompiler;
|
||||
lg::set_file(file_util::get_file_path({"log/decompiler.txt"}));
|
||||
lg::set_file_level(lg::level::info);
|
||||
lg::set_stdout_level(lg::level::info);
|
||||
lg::set_flush_level(lg::level::info);
|
||||
lg::initialize();
|
||||
|
||||
file_util::init_crc();
|
||||
init_opcode_info();
|
||||
@@ -69,7 +68,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
if (get_config().process_game_text) {
|
||||
auto result = db.process_game_text();
|
||||
auto result = db.process_game_text_files();
|
||||
file_util::write_text_file(file_util::get_file_path({"assets", "game_text.txt"}), result);
|
||||
}
|
||||
|
||||
@@ -78,7 +77,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
if (get_config().process_game_count) {
|
||||
auto result = db.process_game_count();
|
||||
auto result = db.process_game_count_file();
|
||||
file_util::write_text_file(file_util::get_file_path({"assets", "game_count.txt"}), result);
|
||||
}
|
||||
|
||||
@@ -98,6 +97,6 @@ int main(int argc, char** argv) {
|
||||
|
||||
file_util::write_text_file(file_util::combine_path(out_folder, "all-syms.gc"),
|
||||
db.dts.dump_symbol_types());
|
||||
spdlog::info("Disassembly has completed successfully.");
|
||||
lg::info("Disassembly has completed successfully.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
#include "common/goos/Reader.h"
|
||||
#include "common/type_system/deftype.h"
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "common/log/log.h"
|
||||
#include "TP_Type.h"
|
||||
|
||||
namespace decompiler {
|
||||
DecompilerTypeSystem::DecompilerTypeSystem() {
|
||||
ts.add_builtin_types();
|
||||
}
|
||||
@@ -105,9 +106,9 @@ void DecompilerTypeSystem::add_type_flags(const std::string& name, u64 flags) {
|
||||
auto kv = type_flags.find(name);
|
||||
if (kv != type_flags.end()) {
|
||||
if (kv->second != flags) {
|
||||
spdlog::warn("duplicated type flags for {}, was 0x{:x}, now 0x{:x}", name.c_str(), kv->second,
|
||||
flags);
|
||||
spdlog::warn("duplicated type flags that are inconsistent!");
|
||||
lg::warn("duplicated type flags for {}, was 0x{:x}, now 0x{:x}", name.c_str(), kv->second,
|
||||
flags);
|
||||
lg::warn("duplicated type flags that are inconsistent!");
|
||||
}
|
||||
}
|
||||
type_flags[name] = flags;
|
||||
@@ -117,8 +118,8 @@ void DecompilerTypeSystem::add_type_parent(const std::string& child, const std::
|
||||
auto kv = type_parents.find(child);
|
||||
if (kv != type_parents.end()) {
|
||||
if (kv->second != parent) {
|
||||
spdlog::warn("duplicated type parents for {} was {} now {}", child.c_str(),
|
||||
kv->second.c_str(), parent.c_str());
|
||||
lg::warn("duplicated type parents for {} was {} now {}", child.c_str(), kv->second.c_str(),
|
||||
parent.c_str());
|
||||
throw std::runtime_error("duplicated type parents that are inconsistent!");
|
||||
}
|
||||
}
|
||||
@@ -151,8 +152,8 @@ void DecompilerTypeSystem::add_symbol(const std::string& name, const TypeSpec& t
|
||||
} else {
|
||||
if (ts.typecheck(type_spec, skv->second, "", false, false)) {
|
||||
} else {
|
||||
spdlog::warn("Attempting to redefine type of symbol {} from {} to {}\n", name,
|
||||
skv->second.print(), type_spec.print());
|
||||
lg::warn("Attempting to redefine type of symbol {} from {} to {}\n", name,
|
||||
skv->second.print(), type_spec.print());
|
||||
throw std::runtime_error("Type redefinition");
|
||||
}
|
||||
}
|
||||
@@ -322,4 +323,5 @@ int DecompilerTypeSystem::get_format_arg_count(const TP_Type& type) {
|
||||
} else {
|
||||
return type.get_format_string_arg_count();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -5,7 +5,8 @@
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
#include "common/goos/Reader.h"
|
||||
|
||||
struct TP_Type;
|
||||
namespace decompiler {
|
||||
class TP_Type;
|
||||
struct TypeState;
|
||||
|
||||
class DecompilerTypeSystem {
|
||||
@@ -54,5 +55,6 @@ class DecompilerTypeSystem {
|
||||
private:
|
||||
goos::Reader m_reader;
|
||||
};
|
||||
} // namespace decompiler
|
||||
|
||||
#endif // JAK_DECOMPILERTYPESYSTEM_H
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace decompiler {
|
||||
template <typename T>
|
||||
struct MatchParam {
|
||||
MatchParam() { is_wildcard = true; }
|
||||
@@ -15,4 +16,5 @@ struct MatchParam {
|
||||
|
||||
bool operator==(const T& other) const { return is_wildcard || (value == other); }
|
||||
bool operator!=(const T& other) const { return !(*this == other); }
|
||||
};
|
||||
};
|
||||
} // namespace decompiler
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "TP_Type.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
namespace decompiler {
|
||||
std::string TypeState::print_gpr_masked(u32 mask) const {
|
||||
std::string result;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
@@ -117,3 +118,4 @@ TypeSpec TP_Type::typespec() const {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -5,83 +5,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
|
||||
// struct TP_Type {
|
||||
// enum Kind {
|
||||
// OBJECT_OF_TYPE,
|
||||
// TYPE_OBJECT,
|
||||
// FALSE,
|
||||
// NONE,
|
||||
// PRODUCT,
|
||||
// OBJ_PLUS_PRODUCT,
|
||||
// PARTIAL_METHOD_TABLE_ACCESS, // type + method_number * 4
|
||||
// METHOD_NEW_OF_OBJECT,
|
||||
// STRING
|
||||
// } kind = NONE;
|
||||
// // in the case that we are type_object, just store the type name in a single arg ts.
|
||||
// TypeSpec ts;
|
||||
// int multiplier;
|
||||
// std::string str_data;
|
||||
//
|
||||
// TP_Type() = default;
|
||||
// explicit TP_Type(const TypeSpec& _ts) {
|
||||
// kind = OBJECT_OF_TYPE;
|
||||
// ts = _ts;
|
||||
// }
|
||||
//
|
||||
// TP_Type simplify() const;
|
||||
// std::string print() const;
|
||||
//
|
||||
// bool is_object_of_type() const { return kind == TYPE_OBJECT || ts == TypeSpec("type"); }
|
||||
//
|
||||
// TypeSpec as_typespec() const {
|
||||
// switch (kind) {
|
||||
// case OBJECT_OF_TYPE:
|
||||
// return ts;
|
||||
// case TYPE_OBJECT:
|
||||
// return TypeSpec("type");
|
||||
// case FALSE:
|
||||
// return TypeSpec("symbol");
|
||||
// case NONE:
|
||||
// return TypeSpec("none");
|
||||
// case PRODUCT:
|
||||
// case METHOD_NEW_OF_OBJECT:
|
||||
// return ts;
|
||||
// default:
|
||||
// assert(false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// static TP_Type make_partial_method_table_access(TypeSpec ts) {
|
||||
// TP_Type result;
|
||||
// result.kind = PARTIAL_METHOD_TABLE_ACCESS;
|
||||
// result.ts = std::move(ts);
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// static TP_Type make_type_object(const std::string& name) {
|
||||
// TP_Type result;
|
||||
// result.kind = TYPE_OBJECT;
|
||||
// result.ts = TypeSpec(name);
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// static TP_Type make_string_object(const std::string& str) {
|
||||
// TP_Type result;
|
||||
// result.kind = STRING;
|
||||
// result.ts = TypeSpec("string");
|
||||
// result.str_data = str;
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// static TP_Type make_none() {
|
||||
// TP_Type result;
|
||||
// result.kind = NONE;
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// bool operator==(const TP_Type& other) const;
|
||||
//};
|
||||
|
||||
namespace decompiler {
|
||||
/*!
|
||||
* A TP_Type is a specialized typespec used in the type propagation algorithm.
|
||||
* It is basically a normal typespec plus some optional information.
|
||||
@@ -267,4 +191,5 @@ struct TypeState {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
} // namespace decompiler
|
||||
+7
-14
@@ -69,24 +69,17 @@ set(RUNTIME_SOURCE
|
||||
overlord/ssound.cpp
|
||||
overlord/stream.cpp)
|
||||
|
||||
# the runtime should be built without any static/dynamic libraries.
|
||||
#add_executable(gk ${RUNTIME_SOURCE} main.cpp)
|
||||
|
||||
|
||||
# we also build a runtime library for testing. This version is likely unable to call GOAL code correctly, but
|
||||
# can be used to test other things.
|
||||
# we build the runtime as a static library.
|
||||
add_library(runtime ${RUNTIME_SOURCE})
|
||||
|
||||
add_executable(gk main.cpp)
|
||||
|
||||
target_link_libraries(runtime common fmt)
|
||||
IF (WIN32)
|
||||
# set stuff for windows
|
||||
target_link_libraries(runtime mman cross_sockets common_util spdlog cross_os_debug)
|
||||
target_link_libraries(gk cross_sockets mman common_util runtime spdlog cross_os_debug)
|
||||
target_link_libraries(runtime mman)
|
||||
ELSE()
|
||||
# set stuff for other systems
|
||||
target_link_libraries(runtime pthread cross_sockets common_util spdlog cross_os_debug)
|
||||
target_link_libraries(gk cross_sockets pthread common_util runtime spdlog cross_os_debug)
|
||||
|
||||
target_link_libraries(runtime pthread)
|
||||
ENDIF()
|
||||
|
||||
add_executable(gk main.cpp)
|
||||
target_link_libraries(gk runtime)
|
||||
|
||||
|
||||
@@ -187,15 +187,25 @@ _call_goal_asm_win32:
|
||||
push r13 ; 88
|
||||
push r14 ; 96
|
||||
push r15 ; 104
|
||||
|
||||
sub rsp, 16
|
||||
movups [rsp], xmm6
|
||||
sub rsp, 16
|
||||
movups [rsp], xmm7
|
||||
|
||||
mov rdi, rcx ;; rdi is GOAL first argument, rcx is windows first argument
|
||||
mov rsi, rdx ;; rsi is GOAL second argument, rdx is windows second argument
|
||||
mov rdx, r8 ;; rdx is GOAL third argument, r8 is windows third argument
|
||||
mov r13, r9 ;; r13 is GOAL fp, r9 is windows fourth argument
|
||||
mov r15, [rsp + 152] ;; symbol table
|
||||
mov r14, [rsp + 144] ;; offset
|
||||
mov r15, [rsp + 184] ;; symbol table
|
||||
mov r14, [rsp + 176] ;; offset
|
||||
|
||||
call r13
|
||||
|
||||
movups xmm7, [rsp]
|
||||
add rsp, 16
|
||||
movups xmm6, [rsp]
|
||||
add rsp, 16
|
||||
|
||||
pop r15
|
||||
pop r14
|
||||
@@ -234,6 +244,11 @@ _call_goal_on_stack_asm_win32:
|
||||
push r14 ; 96
|
||||
push r15 ; 104
|
||||
|
||||
sub rsp, 16
|
||||
movups [rsp], xmm6
|
||||
sub rsp, 16
|
||||
movups [rsp], xmm7
|
||||
|
||||
;; stack swap
|
||||
mov rsi, rsp
|
||||
mov rsp, rcx
|
||||
@@ -249,6 +264,11 @@ _call_goal_on_stack_asm_win32:
|
||||
pop rsi
|
||||
mov rsp, rsi
|
||||
|
||||
movups xmm7, [rsp]
|
||||
add rsp, 16
|
||||
movups xmm6, [rsp]
|
||||
add rsp, 16
|
||||
|
||||
pop r15
|
||||
pop r14
|
||||
pop r13
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <cstring>
|
||||
#include <chrono>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <thread>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include "game/common/loader_rpc_types.h"
|
||||
#include "game/common/play_rpc_types.h"
|
||||
#include "game/common/str_rpc_types.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "common/log/log.h"
|
||||
|
||||
using namespace ee;
|
||||
|
||||
@@ -192,8 +192,8 @@ void BeginLoadingDGO(const char* name, Ptr<u8> buffer1, Ptr<u8> buffer2, Ptr<u8>
|
||||
|
||||
// file name
|
||||
strcpy(sMsg[msgID].name, name);
|
||||
spdlog::debug("[Begin Loading DGO RPC] {}, 0x{:x}, 0x{:x}, 0x{:x}", name, buffer1.offset,
|
||||
buffer2.offset, currentHeap.offset);
|
||||
lg::debug("[Begin Loading DGO RPC] {}, 0x{:x}, 0x{:x}, 0x{:x}", name, buffer1.offset,
|
||||
buffer2.offset, currentHeap.offset);
|
||||
// this RPC will return once we have loaded the first object file.
|
||||
// but we call async, so we don't block here.
|
||||
RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess,
|
||||
@@ -315,7 +315,7 @@ void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size)
|
||||
* This does not use the mutli-threaded linker and will block until the entire file is done.e
|
||||
*/
|
||||
void load_and_link_dgo_from_c(const char* name, Ptr<kheapinfo> heap, u32 linkFlag, s32 bufferSize) {
|
||||
spdlog::debug("[Load and Link DGO From C] {}", name);
|
||||
lg::debug("[Load and Link DGO From C] {}", name);
|
||||
u32 oldShowStall = sShowStallMsg;
|
||||
|
||||
// remember where the heap top point is so we can clear temporary allocations
|
||||
@@ -364,7 +364,7 @@ void load_and_link_dgo_from_c(const char* name, Ptr<kheapinfo> heap, u32 linkFla
|
||||
|
||||
char objName[64];
|
||||
strcpy(objName, (dgoObj + 4).cast<char>().c()); // name from dgo object header
|
||||
spdlog::debug("[link and exec] {} {}", objName, lastObjectLoaded);
|
||||
lg::debug("[link and exec] {} {}", objName, lastObjectLoaded);
|
||||
link_and_exec(obj, objName, objSize, heap, linkFlag); // link now!
|
||||
|
||||
// inform IOP we are done
|
||||
|
||||
+22
-22
@@ -25,7 +25,7 @@
|
||||
#include "game/sce/libcdvd_ee.h"
|
||||
#include "game/sce/stubs.h"
|
||||
#include "common/symbols.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "common/log/log.h"
|
||||
using namespace ee;
|
||||
|
||||
/*!
|
||||
@@ -148,13 +148,13 @@ void InitParms(int argc, const char* const* argv) {
|
||||
* DONE, EXACT
|
||||
*/
|
||||
void InitCD() {
|
||||
spdlog::info("Initializing CD drive. This may take a while...");
|
||||
lg::info("Initializing CD drive. This may take a while...");
|
||||
sceCdInit(SCECdINIT);
|
||||
sceCdMmode(SCECdDVD);
|
||||
while (sceCdDiskReady(0) == SCECdNotReady) {
|
||||
spdlog::debug("Drive not ready... insert a disk!");
|
||||
lg::debug("Drive not ready... insert a disk!");
|
||||
}
|
||||
spdlog::debug("Disk type {}\n", sceCdGetDiskType());
|
||||
lg::debug("Disk type {}\n", sceCdGetDiskType());
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -172,22 +172,22 @@ void InitIOP() {
|
||||
|
||||
if (!reboot) {
|
||||
// reboot with development IOP kernel
|
||||
spdlog::debug("Rebooting IOP...");
|
||||
lg::debug("Rebooting IOP...");
|
||||
while (!sceSifRebootIop("host0:/usr/local/sce/iop/modules/ioprp221.img")) {
|
||||
spdlog::debug("Failed, retrying");
|
||||
lg::debug("Failed, retrying");
|
||||
}
|
||||
while (!sceSifSyncIop()) {
|
||||
spdlog::debug("Syncing...");
|
||||
lg::debug("Syncing...");
|
||||
}
|
||||
} else {
|
||||
// reboot with IOP kernel off of the disk
|
||||
// reboot with development IOP kernel
|
||||
spdlog::debug("Rebooting IOP...");
|
||||
lg::debug("Rebooting IOP...");
|
||||
while (!sceSifRebootIop("cdrom0:\\DRIVERS\\IOPRP221.IMG;1")) {
|
||||
spdlog::debug("Failed, retrying");
|
||||
lg::debug("Failed, retrying");
|
||||
}
|
||||
while (!sceSifSyncIop()) {
|
||||
spdlog::debug("Syncing...");
|
||||
lg::debug("Syncing...");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ void InitIOP() {
|
||||
|
||||
sceSifLoadModule("host0:/usr/home/src/989snd10/iop/989ERR.IRX", 0, nullptr);
|
||||
|
||||
spdlog::debug("Initializing CD library...");
|
||||
lg::debug("Initializing CD library...");
|
||||
auto rv = sceSifLoadModule("host0:binee/overlord.irx", cmd + len + 1 - overlord_boot_command,
|
||||
overlord_boot_command);
|
||||
if (rv < 0) {
|
||||
@@ -270,7 +270,7 @@ void InitIOP() {
|
||||
MsgErr("loading 989snd.irx failed\n");
|
||||
}
|
||||
|
||||
spdlog::debug("Initializing CD library in ISO_CD mode...");
|
||||
lg::debug("Initializing CD library in ISO_CD mode...");
|
||||
auto rv = sceSifLoadModule("cdrom0:\\\\DRIVERS\\\\OVERLORD.IRX;1",
|
||||
cmd + len + 1 - overlord_boot_command, overlord_boot_command);
|
||||
if (rv < 0) {
|
||||
@@ -281,7 +281,7 @@ void InitIOP() {
|
||||
if (rv < 0) {
|
||||
MsgErr("MC driver init failed %d\n", rv);
|
||||
} else {
|
||||
spdlog::info("InitIOP OK");
|
||||
lg::info("InitIOP OK");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,8 +302,8 @@ int InitMachine() {
|
||||
// initialize the global heap
|
||||
u32 global_heap_size = GLOBAL_HEAP_END - HEAP_START;
|
||||
float size_mb = ((float)global_heap_size) / (float)(1 << 20);
|
||||
spdlog::info("gkernel: global heap 0x{:08x} to 0x{:08x} (size {:.3f} MB)", HEAP_START,
|
||||
GLOBAL_HEAP_END, size_mb);
|
||||
lg::info("gkernel: global heap 0x{:08x} to 0x{:08x} (size {:.3f} MB)", HEAP_START,
|
||||
GLOBAL_HEAP_END, size_mb);
|
||||
kinitheap(kglobalheap, Ptr<u8>(HEAP_START), global_heap_size);
|
||||
|
||||
// initialize the debug heap, if appropriate
|
||||
@@ -312,8 +312,8 @@ int InitMachine() {
|
||||
kinitheap(kdebugheap, Ptr<u8>(DEBUG_HEAP_START), debug_heap_size);
|
||||
float debug_size_mb = ((float)debug_heap_size) / (float)(1 << 20);
|
||||
float gap_size_mb = ((float)DEBUG_HEAP_START - GLOBAL_HEAP_END) / (float)(1 << 20);
|
||||
spdlog::info("gkernel: debug heap 0x{:08x} to 0x{:08x} (size {:.3f} MB, gap {:.3f} MB)",
|
||||
DEBUG_HEAP_START, debug_heap_end, debug_size_mb, gap_size_mb);
|
||||
lg::info("gkernel: debug heap 0x{:08x} to 0x{:08x} (size {:.3f} MB, gap {:.3f} MB)",
|
||||
DEBUG_HEAP_START, debug_heap_end, debug_size_mb, gap_size_mb);
|
||||
} else {
|
||||
// if no debug, we make the kheapinfo structure NULL so GOAL knows not to use it.
|
||||
kdebugheap.offset = 0;
|
||||
@@ -338,9 +338,9 @@ int InitMachine() {
|
||||
InitGoalProto();
|
||||
}
|
||||
|
||||
spdlog::info("InitSound");
|
||||
lg::info("InitSound");
|
||||
InitSound(); // do nothing!
|
||||
spdlog::info("InitRPC");
|
||||
lg::info("InitRPC");
|
||||
InitRPC(); // connect to IOP
|
||||
reset_output(); // reset output buffers
|
||||
clear_print();
|
||||
@@ -350,9 +350,9 @@ int InitMachine() {
|
||||
return goal_status;
|
||||
}
|
||||
|
||||
spdlog::info("InitListenerConnect");
|
||||
lg::info("InitListenerConnect");
|
||||
InitListenerConnect();
|
||||
spdlog::info("InitCheckListener");
|
||||
lg::info("InitCheckListener");
|
||||
InitCheckListener();
|
||||
Msg(6, "kernel: machine started\n");
|
||||
return 0;
|
||||
@@ -619,7 +619,7 @@ void InitMachineScheme() {
|
||||
new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE).cast<u32>()),
|
||||
make_string_from_c("common"), kernel_packages->value);
|
||||
|
||||
spdlog::info("calling fake play~");
|
||||
lg::info("calling fake play~");
|
||||
call_goal_function_by_name("play");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ s32 cvt_float(float x, s32 precision, s32* lead_char, char* buff_start, char* bu
|
||||
value = (char)rounder;
|
||||
} else if (!(ru32 >> 31)) { // sign bit
|
||||
value = 0;
|
||||
assert(false); // not sure what happens here.
|
||||
// assert(false); // not sure what happens here.
|
||||
} else {
|
||||
value = -1; // happens on NaN's
|
||||
}
|
||||
@@ -396,7 +396,7 @@ s32 cvt_float(float x, s32 precision, s32* lead_char, char* buff_start, char* bu
|
||||
value = (char)next_int;
|
||||
} else if (!(ru32 >> 0x1f)) {
|
||||
value = 0;
|
||||
assert(false); // not sure what happens here.
|
||||
// assert(false); // not sure what happens here.
|
||||
} else {
|
||||
value = -1; // happens on NaN's
|
||||
}
|
||||
@@ -1085,4 +1085,4 @@ s32 format_impl(uint64_t* args) {
|
||||
|
||||
assert(false); // ??????
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "common/symbols.h"
|
||||
#include "common/versions.h"
|
||||
#include "common/goal_constants.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "common/log/log.h"
|
||||
|
||||
//! Controls link mode when EnableMethodSet = 0, MasterDebug = 1, DiskBoot = 0. Will enable a
|
||||
//! warning message if EnableMethodSet = 1
|
||||
@@ -1979,10 +1979,8 @@ s32 InitHeapAndSymbol() {
|
||||
(kernel_version >> 3) & 0xffff);
|
||||
return -1;
|
||||
} else {
|
||||
spdlog::info("Got correct kernel version {}.{}", kernel_version >> 0x13,
|
||||
(kernel_version >> 3) & 0xffff);
|
||||
// printf("Got correct kernel version %d.%d\n", kernel_version >> 0x13,
|
||||
// (kernel_version >> 3) & 0xffff);
|
||||
lg::info("Got correct kernel version {}.{}", kernel_version >> 0x13,
|
||||
(kernel_version >> 3) & 0xffff);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-14
@@ -6,23 +6,21 @@
|
||||
#include <string>
|
||||
#include "runtime.h"
|
||||
#include "common/versions.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "third-party/spdlog/include/spdlog/sinks/basic_file_sink.h"
|
||||
#include "third-party/spdlog/include/spdlog/sinks/stdout_color_sinks.h"
|
||||
#include "common/log/log.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
|
||||
void setup_logging(bool verbose) {
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
lg::set_file(file_util::get_file_path({"log/game.txt"}));
|
||||
if (verbose) {
|
||||
auto game_logger = spdlog::stdout_color_mt("GOAL Runtime");
|
||||
spdlog::set_default_logger(game_logger);
|
||||
spdlog::flush_on(spdlog::level::info);
|
||||
spdlog::info("Verbose logging enabled");
|
||||
lg::set_file_level(lg::level::info);
|
||||
lg::set_stdout_level(lg::level::info);
|
||||
lg::set_flush_level(lg::level::info);
|
||||
} else {
|
||||
auto game_logger = spdlog::basic_logger_mt("GOAL Runtime", "logs/runtime.log");
|
||||
spdlog::set_default_logger(game_logger);
|
||||
spdlog::flush_on(spdlog::level::debug);
|
||||
printf("OpenGOAL Runtime %d.%d\n", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR);
|
||||
lg::set_file_level(lg::level::warn);
|
||||
lg::set_stdout_level(lg::level::warn);
|
||||
lg::set_flush_level(lg::level::warn);
|
||||
}
|
||||
lg::initialize();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -38,8 +36,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
while (true) {
|
||||
// run the runtime in a loop so we can reset the game and have it restart cleanly
|
||||
spdlog::info("OpenGOAL Runtime {}.{}", versions::GOAL_VERSION_MAJOR,
|
||||
versions::GOAL_VERSION_MINOR);
|
||||
lg::info("OpenGOAL Runtime {}.{}", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR);
|
||||
|
||||
if (exec_runtime(argc, argv) == 2) {
|
||||
return 0;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "isocommon.h"
|
||||
#include "overlord.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "common/log/log.h"
|
||||
|
||||
using namespace iop;
|
||||
|
||||
@@ -199,7 +199,7 @@ uint32_t FS_GetLength(FileRecord* fr) {
|
||||
* This is an ISO FS API Function
|
||||
*/
|
||||
LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset) {
|
||||
spdlog::debug("[OVERLORD] FS Open {}", fr->name);
|
||||
lg::debug("[OVERLORD] FS Open {}", fr->name);
|
||||
LoadStackEntry* selected = nullptr;
|
||||
// find first unused spot on load stack.
|
||||
for (uint32_t i = 0; i < MAX_OPEN_FILES; i++) {
|
||||
@@ -213,7 +213,7 @@ LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset) {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
spdlog::warn("[OVERLORD] Failed to FS Open {}", fr->name);
|
||||
lg::warn("[OVERLORD] Failed to FS Open {}", fr->name);
|
||||
ExitIOP();
|
||||
return nullptr;
|
||||
}
|
||||
@@ -224,7 +224,7 @@ LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset) {
|
||||
* This is an ISO FS API Function
|
||||
*/
|
||||
LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset) {
|
||||
spdlog::debug("[OVERLORD] FS_OpenWad {}", fr->name);
|
||||
lg::debug("[OVERLORD] FS_OpenWad {}", fr->name);
|
||||
LoadStackEntry* selected = nullptr;
|
||||
for (uint32_t i = 0; i < MAX_OPEN_FILES; i++) {
|
||||
if (!sLoadStack[i].fr) {
|
||||
@@ -234,7 +234,7 @@ LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset) {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
spdlog::warn("[OVERLORD] Failed to FS_OpenWad {}", fr->name);
|
||||
lg::warn("[OVERLORD] Failed to FS_OpenWad {}", fr->name);
|
||||
ExitIOP();
|
||||
return nullptr;
|
||||
}
|
||||
@@ -244,7 +244,7 @@ LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset) {
|
||||
* This is an ISO FS API Function
|
||||
*/
|
||||
void FS_Close(LoadStackEntry* fd) {
|
||||
spdlog::debug("[OVERLORD] FS_Close {}", fd->fr->name);
|
||||
lg::debug("[OVERLORD] FS_Close {}", fd->fr->name);
|
||||
|
||||
// close the FD
|
||||
fd->fr = nullptr;
|
||||
@@ -263,7 +263,7 @@ uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len) {
|
||||
int32_t real_size = len;
|
||||
if (len < 0) {
|
||||
// not sure what this is about...
|
||||
spdlog::warn("[OVERLORD ISO CD] Negative length warning!");
|
||||
lg::warn("[OVERLORD ISO CD] Negative length warning!");
|
||||
real_size = len + 0x7ff;
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len) {
|
||||
const char* path = get_file_path(fd->fr);
|
||||
FILE* fp = fopen(path, "rb");
|
||||
if (!fp) {
|
||||
spdlog::error("[OVERLORD] fake iso could not open the file \"{}\"", path);
|
||||
lg::error("[OVERLORD] fake iso could not open the file \"{}\"", path);
|
||||
}
|
||||
assert(fp);
|
||||
fseek(fp, 0, SEEK_END);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* This is a huge mess
|
||||
*/
|
||||
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "common/log/log.h"
|
||||
#include <assert.h>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
@@ -496,8 +496,8 @@ u32 RunDGOStateMachine(IsoMessage* _cmd, IsoBufferHeader* buffer) {
|
||||
// printf("[Overlord DGO] Got DGO file header for %s with %d objects\n",
|
||||
// cmd->dgo_header.name,
|
||||
// cmd->dgo_header.object_count); // added
|
||||
spdlog::info("[Overlord DGO] Got DGO file header for {} with {} objects",
|
||||
cmd->dgo_header.name, cmd->dgo_header.object_count);
|
||||
lg::info("[Overlord DGO] Got DGO file header for {} with {} objects",
|
||||
cmd->dgo_header.name, cmd->dgo_header.object_count);
|
||||
cmd->bytes_processed = 0;
|
||||
cmd->objects_loaded = 0;
|
||||
if (cmd->dgo_header.object_count == 1) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "iso_api.h"
|
||||
#include "game/sce/iop.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
#include "common/log/log.h"
|
||||
|
||||
using namespace iop;
|
||||
|
||||
@@ -8,8 +8,7 @@ using namespace iop;
|
||||
* Load a File to IOP memory (blocking)
|
||||
*/
|
||||
s32 LoadISOFileToIOP(FileRecord* file, void* addr, uint32_t length) {
|
||||
// printf("[OVERLORD] LoadISOFileToIOP %s, %d/%d bytes\n", file->name, length, file->size);
|
||||
spdlog::debug("[OVERLORD] LoadISOFileToIOP {}, {}/{} bytes", file->name, length, file->size);
|
||||
lg::debug("[OVERLORD] LoadISOFileToIOP {}, {}/{} bytes", file->name, length, file->size);
|
||||
IsoCommandLoadSingle cmd;
|
||||
cmd.cmd_id = LOAD_TO_IOP_CMD_ID;
|
||||
cmd.messagebox_to_reply = 0;
|
||||
@@ -31,8 +30,7 @@ s32 LoadISOFileToIOP(FileRecord* file, void* addr, uint32_t length) {
|
||||
* Load a File to IOP memory (blocking)
|
||||
*/
|
||||
s32 LoadISOFileToEE(FileRecord* file, uint32_t addr, uint32_t length) {
|
||||
// printf("[OVERLORD] LoadISOFileToEE %s, %d/%d bytes\n", file->name, length, file->size);
|
||||
spdlog::debug("[OVERLORD] LoadISOFileToEE {}, {}/{} bytes", file->name, length, file->size);
|
||||
lg::debug("[OVERLORD] LoadISOFileToEE {}, {}/{} bytes", file->name, length, file->size);
|
||||
IsoCommandLoadSingle cmd;
|
||||
cmd.cmd_id = LOAD_TO_EE_CMD_ID;
|
||||
cmd.messagebox_to_reply = 0;
|
||||
@@ -51,7 +49,7 @@ s32 LoadISOFileToEE(FileRecord* file, uint32_t addr, uint32_t length) {
|
||||
}
|
||||
|
||||
s32 LoadISOFileChunkToEE(FileRecord* file, uint32_t dest_addr, uint32_t length, uint32_t offset) {
|
||||
spdlog::debug("[OVERLORD] LoadISOFileChunkToEE {} : {} offset {}\n", file->name, length, offset);
|
||||
lg::debug("[OVERLORD] LoadISOFileChunkToEE {} : {} offset {}\n", file->name, length, offset);
|
||||
IsoCommandLoadSingle cmd;
|
||||
cmd.cmd_id = LOAD_TO_EE_OFFSET_CMD_ID;
|
||||
cmd.messagebox_to_reply = 0;
|
||||
|
||||
+21
-20
@@ -11,6 +11,7 @@
|
||||
#include "overlord.h"
|
||||
#include "soundcommon.h"
|
||||
#include "srpc.h"
|
||||
#include "common/log/log.h"
|
||||
|
||||
// iso_cd is an implementation of the IsoFs API for loading files from a CD/DVD with an ISO and/or
|
||||
// DUP filesystem.
|
||||
@@ -231,7 +232,7 @@ u32 ReadDirectory(uint32_t sector, uint32_t size, uint32_t secBufID) {
|
||||
while (lsize > 0) {
|
||||
// ISO low-level read
|
||||
if (!ReadSectorsNow(lsector, 1, buffer)) {
|
||||
spdlog::info("[OVERLORD ISO CD] Failed to read sector in ReadDirectory!");
|
||||
lg::info("[OVERLORD ISO CD] Failed to read sector in ReadDirectory!");
|
||||
return 0;
|
||||
}
|
||||
u8* lbuffer = buffer;
|
||||
@@ -264,7 +265,7 @@ u32 ReadDirectory(uint32_t sector, uint32_t size, uint32_t secBufID) {
|
||||
}
|
||||
} else {
|
||||
if (sNumFiles == MAX_ISO_FILES) {
|
||||
spdlog::info("[OVERLORD ISO CD] There are too many files on the disc!");
|
||||
lg::info("[OVERLORD ISO CD] There are too many files on the disc!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -283,7 +284,7 @@ u32 ReadDirectory(uint32_t sector, uint32_t size, uint32_t secBufID) {
|
||||
lsize -= 0x800;
|
||||
}
|
||||
} else {
|
||||
spdlog::info("[OVERLORD ISO CD] ReadDirectory ran out of sector buffers!");
|
||||
lg::info("[OVERLORD ISO CD] ReadDirectory ran out of sector buffers!");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -354,7 +355,7 @@ void LoadMusicTweaks(u8* buffer) {
|
||||
FileRecord* fr = FS_FindIN(iso_name);
|
||||
if (!fr || !ReadSectorsNow(fr->location, 1, buffer)) {
|
||||
*(s32*)gMusicTweakInfo = 0;
|
||||
spdlog::warn("[OVERLORD ISO CD] Failed to load music tweaks!");
|
||||
lg::warn("[OVERLORD ISO CD] Failed to load music tweaks!");
|
||||
} else {
|
||||
memcpy(gMusicTweakInfo, buffer, MUSIC_TWEAK_SIZE);
|
||||
}
|
||||
@@ -373,7 +374,7 @@ void LoadDiscID() {
|
||||
MakeISOName(iso_name, "DISK_ID.DIZ");
|
||||
FileRecord* fr = FS_FindIN(iso_name);
|
||||
if (!fr) {
|
||||
spdlog::warn(
|
||||
lg::warn(
|
||||
"[OVERLORD ISO CD] LoadDiscID failed to find DISK_ID.DIZ, using sector 0x400 instead!");
|
||||
CD_ID_SectorNum = 0x400;
|
||||
} else {
|
||||
@@ -385,7 +386,7 @@ void LoadDiscID() {
|
||||
for (uint32_t i = 0; i < SECTOR_SIZE / 4; i++) {
|
||||
CD_ID_SectorSum += CD_ID_Sector[i];
|
||||
}
|
||||
spdlog::info("[OVERLORD] DISK_ID.DIZ OK 0x{:x}\n", CD_ID_SectorSum);
|
||||
lg::info("[OVERLORD] DISK_ID.DIZ OK 0x{:x}\n", CD_ID_SectorSum);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -416,13 +417,13 @@ void SetRealSector() {
|
||||
} else {
|
||||
// it's a duplicated file, and duplicate read is enabled, so get the area 2 sector.
|
||||
_real_sector = _sector + sAreaDiff;
|
||||
spdlog::warn("[OVERLORD] Warning, adjusting real sector in SetRealSector");
|
||||
lg::warn("[OVERLORD] Warning, adjusting real sector in SetRealSector");
|
||||
}
|
||||
|
||||
// we suspect the game is pirated, load the wrong sector.
|
||||
if (pirated) {
|
||||
_real_sector += 3;
|
||||
spdlog::warn("Pirated!");
|
||||
lg::warn("Pirated!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,13 +467,13 @@ int FS_Init(u8* buffer) {
|
||||
|
||||
// read primary volume descriptor into buffer
|
||||
if (!ReadSectorsNow(0x10, 1, sSecBuffer[0])) {
|
||||
spdlog::warn("[OVERLORD ISO CD] Failed to read primary volume descriptor");
|
||||
lg::warn("[OVERLORD ISO CD] Failed to read primary volume descriptor");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// check volume descriptor identifier
|
||||
if (memcmp(sSecBuffer[0] + 1, "CD001", 5)) {
|
||||
spdlog::warn("[OVERLORD ISO CD] Got the wrong volume descriptor identifier");
|
||||
lg::warn("[OVERLORD ISO CD] Got the wrong volume descriptor identifier");
|
||||
char* cptr = (char*)sSecBuffer[0] + 1;
|
||||
printf("%c%c%c%c%c\n", cptr[0], cptr[1], cptr[2], cptr[3], cptr[4]);
|
||||
return 1;
|
||||
@@ -482,7 +483,7 @@ int FS_Init(u8* buffer) {
|
||||
uint32_t path_table_sector = ReadU32(sSecBuffer[0] + 0x8c);
|
||||
|
||||
if (!ReadSectorsNow(path_table_sector, 1, sSecBuffer[0])) {
|
||||
spdlog::warn("[OVERLORD ISO CD] Failed to read path");
|
||||
lg::warn("[OVERLORD ISO CD] Failed to read path");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -490,14 +491,14 @@ int FS_Init(u8* buffer) {
|
||||
uint32_t path_table_extent = ReadU32(sSecBuffer[0] + 2);
|
||||
|
||||
if (!ReadSectorsNow(path_table_extent, 1, sSecBuffer[0])) {
|
||||
spdlog::warn("[OVERLORD ISO CD] Failed to read path table extent");
|
||||
lg::warn("[OVERLORD ISO CD] Failed to read path table extent");
|
||||
}
|
||||
|
||||
// read root directory
|
||||
add_files = true;
|
||||
uint32_t dir_size = ReadU32(sSecBuffer[0] + 10);
|
||||
if (!ReadDirectory(path_table_extent, dir_size, 0)) {
|
||||
spdlog::warn("[OVERLORD ISO CD] Failed to ReadDirectory");
|
||||
lg::warn("[OVERLORD ISO CD] Failed to ReadDirectory");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -520,7 +521,7 @@ int FS_Init(u8* buffer) {
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
spdlog::warn("[OVERLORD ISO CD] Bad Media Type!");
|
||||
lg::warn("[OVERLORD ISO CD] Bad Media Type!");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -567,7 +568,7 @@ FileRecord* FS_FindIN(const char* iso_name) {
|
||||
}
|
||||
|
||||
// we didn't get 1 GB of files, you're a pirate.
|
||||
spdlog::warn("Pirated!");
|
||||
lg::warn("Pirated!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,7 +586,7 @@ uint32_t FS_GetLength(FileRecord* fr) {
|
||||
* This is an ISO FS API Function
|
||||
*/
|
||||
LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset) {
|
||||
spdlog::info("[OVERLORD] FS Open {}", fr->name);
|
||||
lg::info("[OVERLORD] FS Open {}", fr->name);
|
||||
LoadStackEntry* selected = nullptr;
|
||||
// find first unused spot on load stack.
|
||||
for (uint32_t i = 0; i < MAX_OPEN_FILES; i++) {
|
||||
@@ -599,7 +600,7 @@ LoadStackEntry* FS_Open(FileRecord* fr, int32_t offset) {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
spdlog::warn("[OVERLORD ISO CD] Failed to FS_Open {}", fr->name);
|
||||
lg::warn("[OVERLORD ISO CD] Failed to FS_Open {}", fr->name);
|
||||
ExitIOP();
|
||||
return nullptr;
|
||||
}
|
||||
@@ -620,7 +621,7 @@ LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset) {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
spdlog::warn("[OVERLORD ISO CD] Failed to use FS_OpenWad {}", fr->name);
|
||||
lg::warn("[OVERLORD ISO CD] Failed to use FS_OpenWad {}", fr->name);
|
||||
ExitIOP();
|
||||
return nullptr;
|
||||
}
|
||||
@@ -630,7 +631,7 @@ LoadStackEntry* FS_OpenWad(FileRecord* fr, int32_t offset) {
|
||||
* This is an ISO FS API Function
|
||||
*/
|
||||
void FS_Close(LoadStackEntry* fd) {
|
||||
spdlog::info("[OVERLORD] FS Close {}", fd->fr->name);
|
||||
lg::info("[OVERLORD] FS Close {}", fd->fr->name);
|
||||
if (fd == sReadInfo) {
|
||||
// the file is currently being read, so lets try to finish out the read, if possible.
|
||||
int count = 0;
|
||||
@@ -663,7 +664,7 @@ uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len) {
|
||||
int32_t real_size = len;
|
||||
if (len < 0) {
|
||||
// not sure what this is about...
|
||||
spdlog::warn("[OVERLORD ISO CD] Negative length warning!");
|
||||
lg::warn("[OVERLORD ISO CD] Negative length warning!");
|
||||
real_size = len + 0x7ff;
|
||||
}
|
||||
_sectors = real_size >> 11;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
#include "common/log/log.h"
|
||||
#include "game/sce/iop.h"
|
||||
#include "iso_queue.h"
|
||||
#include "isocommon.h"
|
||||
@@ -179,11 +180,11 @@ u32 QueueMessage(IsoMessage* cmd, int32_t priority, const char* name) {
|
||||
gPriStack[priority].cmds[gPriStack[priority].n] = cmd;
|
||||
gPriStack[priority].names[gPriStack[priority].n] = name;
|
||||
gPriStack[priority].n++;
|
||||
spdlog::debug("[OVERLORD] Queue {} ({}/{}), {}", priority, gPriStack[priority].n,
|
||||
PRI_STACK_LENGTH, gPriStack[priority].names[gPriStack[priority].n - 1].c_str());
|
||||
lg::debug("[OVERLORD] Queue {} ({}/{}), {}", priority, gPriStack[priority].n, PRI_STACK_LENGTH,
|
||||
gPriStack[priority].names[gPriStack[priority].n - 1].c_str());
|
||||
DisplayQueue();
|
||||
} else {
|
||||
spdlog::warn("[OVERLORD ISO QUEUE] Failed to queue!");
|
||||
lg::warn("[OVERLORD ISO QUEUE] Failed to queue!");
|
||||
cmd->status = CMD_STATUS_FAILED_TO_QUEUE;
|
||||
ReturnMessage(cmd);
|
||||
}
|
||||
@@ -209,7 +210,7 @@ void UnqueueMessage(IsoMessage* cmd) {
|
||||
}
|
||||
}
|
||||
}
|
||||
spdlog::warn("[OVERLORD ISO QUEUE] Failed to unqueue!");
|
||||
lg::warn("[OVERLORD ISO QUEUE] Failed to unqueue!");
|
||||
|
||||
found:
|
||||
assert(gPriStack[pri].cmds[idx] == cmd);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include "stream.h"
|
||||
#include "game/sce/iop.h"
|
||||
#include "game/common/str_rpc_types.h"
|
||||
|
||||
+17
-16
@@ -16,6 +16,7 @@
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
#include "common/log/log.h"
|
||||
#include "runtime.h"
|
||||
#include "system/SystemThread.h"
|
||||
#include "sce/libcdvd_ee.h"
|
||||
@@ -72,21 +73,21 @@ void deci2_runner(SystemThreadInterface& iface) {
|
||||
iface.initialization_complete();
|
||||
|
||||
// in our own thread, wait for the EE to register the first protocol driver
|
||||
spdlog::debug("[DECI2] Waiting for EE to register protos");
|
||||
lg::debug("[DECI2] Waiting for EE to register protos");
|
||||
server.wait_for_protos_ready();
|
||||
// then allow the server to accept connections
|
||||
if (!server.init()) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
spdlog::debug("[DECI2] Waiting for listener...");
|
||||
// spdlog::debug("[DECI2] Waiting for listener..."); --> disabled temporarily, some weird race
|
||||
lg::debug("[DECI2] Waiting for listener...");
|
||||
// lg::debug("[DECI2] Waiting for listener..."); --> disabled temporarily, some weird race
|
||||
// condition?
|
||||
bool saw_listener = false;
|
||||
while (!iface.get_want_exit()) {
|
||||
if (server.check_for_listener()) {
|
||||
if (!saw_listener) {
|
||||
spdlog::debug("[DECI2] Connected!");
|
||||
lg::debug("[DECI2] Connected!");
|
||||
}
|
||||
saw_listener = true;
|
||||
// we have a listener, run!
|
||||
@@ -116,19 +117,19 @@ void ee_runner(SystemThreadInterface& iface) {
|
||||
}
|
||||
|
||||
if (g_ee_main_mem == (u8*)(-1)) {
|
||||
spdlog::debug("Failed to initialize main memory! {}", strerror(errno));
|
||||
lg::debug("Failed to initialize main memory! {}", strerror(errno));
|
||||
iface.initialization_complete();
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::debug("Main memory mapped at 0x{:016x}", (u64)(g_ee_main_mem));
|
||||
spdlog::debug("Main memory size 0x{:x} bytes ({:.3f} MB)", EE_MAIN_MEM_SIZE,
|
||||
(double)EE_MAIN_MEM_SIZE / (1 << 20));
|
||||
lg::debug("Main memory mapped at 0x{:016x}", (u64)(g_ee_main_mem));
|
||||
lg::debug("Main memory size 0x{:x} bytes ({:.3f} MB)", EE_MAIN_MEM_SIZE,
|
||||
(double)EE_MAIN_MEM_SIZE / (1 << 20));
|
||||
|
||||
spdlog::debug("[EE] Initialization complete!");
|
||||
lg::debug("[EE] Initialization complete!");
|
||||
iface.initialization_complete();
|
||||
|
||||
spdlog::debug("[EE] Run!");
|
||||
lg::debug("[EE] Run!");
|
||||
memset((void*)g_ee_main_mem, 0, EE_MAIN_MEM_SIZE);
|
||||
|
||||
// prevent access to the first 1 MB of memory.
|
||||
@@ -153,7 +154,7 @@ void ee_runner(SystemThreadInterface& iface) {
|
||||
xdbg::allow_debugging();
|
||||
|
||||
goal_main(g_argc, g_argv);
|
||||
spdlog::debug("[EE] Done!");
|
||||
lg::debug("[EE] Done!");
|
||||
|
||||
// // kill the IOP todo
|
||||
iop::LIBRARY_kill();
|
||||
@@ -169,7 +170,7 @@ void ee_runner(SystemThreadInterface& iface) {
|
||||
*/
|
||||
void iop_runner(SystemThreadInterface& iface) {
|
||||
IOP iop;
|
||||
spdlog::debug("[IOP] Restart!");
|
||||
lg::debug("[IOP] Restart!");
|
||||
iop.reset_allocator();
|
||||
ee::LIBRARY_sceSif_register(&iop);
|
||||
iop::LIBRARY_register(&iop);
|
||||
@@ -192,12 +193,12 @@ void iop_runner(SystemThreadInterface& iface) {
|
||||
|
||||
iface.initialization_complete();
|
||||
|
||||
spdlog::debug("[IOP] Wait for OVERLORD to start...");
|
||||
lg::debug("[IOP] Wait for OVERLORD to start...");
|
||||
iop.wait_for_overlord_start_cmd();
|
||||
if (iop.status == IOP_OVERLORD_INIT) {
|
||||
spdlog::debug("[IOP] Run!");
|
||||
lg::debug("[IOP] Run!");
|
||||
} else {
|
||||
spdlog::debug("[IOP] Shutdown!");
|
||||
lg::debug("[IOP] Shutdown!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -259,6 +260,6 @@ u32 exec_runtime(int argc, char** argv) {
|
||||
|
||||
// join and exit
|
||||
tm.join();
|
||||
spdlog::info("GOAL Runtime Shutdown (code {})", MasterExit);
|
||||
lg::info("GOAL Runtime Shutdown (code {})", MasterExit);
|
||||
return MasterExit;
|
||||
}
|
||||
|
||||
+2
-2
@@ -6,6 +6,7 @@
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include "common/log/log.h"
|
||||
#include "deci2.h"
|
||||
#include "game/system/Deci2Server.h"
|
||||
|
||||
@@ -69,8 +70,7 @@ s32 sceDeci2Open(u16 protocol, void* opt, void (*handler)(s32 event, s32 param,
|
||||
drv.id = protocol_count + 1;
|
||||
drv.active = true;
|
||||
protocols[protocol_count++] = drv;
|
||||
// printf("[DECI2] Add new protocol driver %d for 0x%x\n", drv.id, drv.protocol);
|
||||
spdlog::info("[DECI2] Add new protocol driver {} for 0x{:x}", drv.id, drv.protocol);
|
||||
lg::info("[DECI2] Add new protocol driver {} for 0x{:x}", drv.id, drv.protocol);
|
||||
server->unlock();
|
||||
|
||||
if (protocol_count == 1) {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#define JAK1_IOP_H
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
|
||||
#define SMEM_Low (0)
|
||||
#define SMEM_High (1)
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include "game/system/deci_common.h"
|
||||
#include "third-party/spdlog/include/spdlog/spdlog.h"
|
||||
|
||||
class Deci2Server {
|
||||
public:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user