extractor: cleanup, support unicode properly, and add multi-game support (#1609)

* extractor: refactor and cleanup for multi-game support

* deps: switch to `ghc::filesystem` as it is utf-8 everywhere by default

* extractor: finally working with unicode

* unicode: fix unicode cli args on windows in all `main` functions
This commit is contained in:
Tyler Wilding 2022-07-05 20:38:13 -04:00 committed by GitHub
parent ac1e620161
commit 6446389263
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 7010 additions and 765 deletions

View File

@ -1,151 +1,180 @@
{
"version" : "0.2.1", "defaults" : {}, "configurations" : [
"version": "0.2.1",
"defaults": {},
"configurations": [
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "goalc-test.exe (bin\\goalc-test.exe)",
"name" : "Tests - Unit-Tests - Summary",
"args" : ["--gtest_brief=1"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - Unit-Tests - Summary",
"args": ["--gtest_brief=1"]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "goalc-test.exe (bin\\goalc-test.exe)",
"name" : "Tests - Unit-Tests - Verbose",
"args" : ["--gtest_brief=0"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - Unit-Tests - Verbose",
"args": ["--gtest_brief=0"]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "goalc-test.exe (bin\\goalc-test.exe)",
"name" : "Tests - Draft Tests - Verbose",
"args" : ["--gtest_brief=0", "--gtest_filter=\"*Draft*\""]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - Draft Tests - Verbose",
"args": ["--gtest_brief=0", "--gtest_filter=\"*Draft*\""]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "goalc-test.exe (bin\\goalc-test.exe)",
"name" : "Tests - TypeConsistency - Verbose",
"args" : ["--gtest_brief=0", "--gtest_filter=\"*TypeConsistency*\""]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - TypeConsistency - Verbose",
"args": ["--gtest_brief=0", "--gtest_filter=\"*TypeConsistency*\""]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "goalc-test.exe (bin\\goalc-test.exe)",
"name" : "Tests - WithGameTests - Verbose",
"args" : ["--gtest_brief=0", "--gtest_filter=\"*WithGameTests*\""]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc-test.exe (bin\\goalc-test.exe)",
"name": "Tests - WithGameTests - Verbose",
"args": ["--gtest_brief=0", "--gtest_filter=\"*WithGameTests*\""]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "offline-test.exe (bin\\offline-test.exe)",
"name" : "Tests - Offline Tests",
"args" : ["${workspaceRoot}/iso_data/jak1"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "offline-test.exe (bin\\offline-test.exe)",
"name": "Tests - Offline Tests",
"args": ["${workspaceRoot}/iso_data/jak1"]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "gk.exe (bin\\gk.exe)",
"name" : "Run - Runtime (no kernel)",
"args" : [ "-fakeiso", "-debug", "-nokernel", "-v", "-nodisplay" ]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Run - Runtime (no kernel)",
"args": ["-fakeiso", "-debug", "-nokernel", "-v", "-nodisplay"]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "gk.exe (bin\\gk.exe)",
"name" : "Run - Runtime (with kernel)",
"args" : [ "-fakeiso", "-debug", "-v" ]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Run - Runtime (with kernel)",
"args": ["-fakeiso", "-debug", "-v"]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "gk.exe (bin\\gk.exe)",
"name" : "Run - Runtime (boot)",
"args" : [ "-boot", "-fakeiso", "-debug", "-v" ]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Run - Runtime (boot)",
"args": ["-boot", "-fakeiso", "-debug", "-v"]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "gk.exe (bin\\gk.exe)",
"name" : "Run - Runtime (boot no debug)",
"args" : [ "-boot", "-fakeiso", "-v" ]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "gk.exe (bin\\gk.exe)",
"name": "Run - Runtime (boot no debug)",
"args": ["-boot", "-fakeiso", "-v"]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "goalc.exe (bin\\goalc.exe)",
"name" : "Run - REPL",
"args" : [ "--user-auto" ]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc.exe (bin\\goalc.exe)",
"name": "Run - REPL",
"args": ["--user-auto"]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "goalc.exe (bin\\goalc.exe)",
"name" : "Run - REPL - Auto Listen",
"args" : [ "--user-auto", "--auto-lt" ]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "goalc.exe (bin\\goalc.exe)",
"name": "Run - REPL - Auto Listen",
"args": ["--user-auto", "--auto-lt"]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "decompiler.exe (bin\\decompiler.exe)",
"name" : "Run - Decompiler - Jak 1",
"args" : [ "${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "decompiler.exe (bin\\decompiler.exe)",
"name": "Run - Decompiler - Jak 1",
"args": [
"${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc",
"${workspaceRoot}/iso_data",
"${workspaceRoot}/decompiler_out"
]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "decompiler.exe (bin\\decompiler.exe)",
"name" : "Run - Decompiler - Jak 1 - Data Only",
"args" : [ "${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out", "decompile_code=false"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "decompiler.exe (bin\\decompiler.exe)",
"name": "Run - Decompiler - Jak 1 - Data Only",
"args": [
"${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc",
"${workspaceRoot}/iso_data",
"${workspaceRoot}/decompiler_out",
"decompile_code=false"
]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "decompiler.exe (bin\\decompiler.exe)",
"name" : "Run - Decompiler - Jak 1 PAL",
"args" : [ "${workspaceRoot}/decompiler/config/jak1_pal.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "decompiler.exe (bin\\decompiler.exe)",
"name": "Run - Decompiler - Jak 1 PAL",
"args": [
"${workspaceRoot}/decompiler/config/jak1_pal.jsonc",
"${workspaceRoot}/iso_data",
"${workspaceRoot}/decompiler_out"
]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "decompiler.exe (bin\\decompiler.exe)",
"name" : "Run - Disassembler - Jak 1",
"args" : [ "${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "decompiler.exe (bin\\decompiler.exe)",
"name": "Run - Disassembler - Jak 1",
"args": [
"${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc",
"${workspaceRoot}/iso_data",
"${workspaceRoot}/decompiler_out"
]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "decompiler.exe (bin\\decompiler.exe)",
"name" : "Run - Decompiler - Jak 2",
"args" : [ "${workspaceRoot}/decompiler/config/jak2_ntsc_v1.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "decompiler.exe (bin\\decompiler.exe)",
"name": "Run - Decompiler - Jak 2",
"args": [
"${workspaceRoot}/decompiler/config/jak2_ntsc_v1.jsonc",
"${workspaceRoot}/iso_data",
"${workspaceRoot}/decompiler_out"
]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "memory_dump_tool.exe (bin\\memory_dump_tool.exe)",
"name" : "Run - EE Memory Analyze",
"args" : [ "${workspaceRoot}/eeMemory.bin", "${workspaceRoot}"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "memory_dump_tool.exe (bin\\memory_dump_tool.exe)",
"name": "Run - EE Memory Analyze",
"args": ["${workspaceRoot}/eeMemory.bin", "${workspaceRoot}"]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "memory_dump_tool.exe (bin\\memory_dump_tool.exe)",
"name" : "Run - EE Memory Analyze - Test",
"args" : [ "\"C:\\Users\\xtvas\\Repositories\\pcsx2\\128mb\\sstates\\SCUS-97124 (1B3976AB).00.p2s\"", "${workspaceRoot}"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "memory_dump_tool.exe (bin\\memory_dump_tool.exe)",
"name": "Run - EE Memory Analyze - Test",
"args": [
"\"C:\\Users\\xtvas\\Repositories\\pcsx2\\128mb\\sstates\\SCUS-97124 (1B3976AB).00.p2s\"",
"${workspaceRoot}"
]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "dgo_unpacker.exe (bin\\dgo_unpacker.exe)",
"name" : "Run - DGO Unpacker (test)",
"args" : [ "C:\\GameData\\Jak1\\Backup\\DGO-PAL\\GAME", "C:\\GameData\\Jak1\\Backup\\DISC-PAL\\CGO\\GAME.CGO"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "dgo_unpacker.exe (bin\\dgo_unpacker.exe)",
"name": "Run - DGO Unpacker (test)",
"args": [
"C:\\GameData\\Jak1\\Backup\\DGO-PAL\\GAME",
"C:\\GameData\\Jak1\\Backup\\DISC-PAL\\CGO\\GAME.CGO"
]
},
{
"type" : "default",
"project" : "CMakeLists.txt",
"projectTarget" : "extractor.exe (bin\\extractor.exe)",
"name" : "Run - Extractor - Extract",
"args" : ["E:\\ISOs\\Jak\\Jak 1.iso"]
"type": "default",
"project": "CMakeLists.txt",
"projectTarget": "extractor.exe (bin\\extractor.exe)",
"name": "Run - Extractor - Extract",
"args": ["\"E:\\ISOs\\Jak\\дWTF平仮名WTF\\Jak 1.iso\""]
}
]
}

View File

@ -48,7 +48,7 @@ add_library(common
util/os.cpp
util/print_float.cpp
util/FontUtils.cpp
util/FrameLimiter.cpp)
util/FrameLimiter.cpp "util/unicode_util.h" "util/unicode_util.cpp")
target_link_libraries(common fmt lzokay replxx libzstd_static)

View File

@ -7,9 +7,7 @@
/*!
* Write a wave file from a vector of samples.
*/
void write_wave_file_mono(const std::vector<s16>& samples,
s32 sample_rate,
const std::filesystem::path& name) {
void write_wave_file_mono(const std::vector<s16>& samples, s32 sample_rate, const fs::path& name) {
WaveFileHeader header;
memcpy(header.chunk_id, "RIFF", 4);
header.chunk_size = 36 + samples.size() * sizeof(s16);

View File

@ -1,11 +1,11 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
#include "common/common_types.h"
#include "common/util/BinaryReader.h"
#include "common/util/FileUtil.h"
// The header data for a simple wave file
struct WaveFileHeader {
@ -29,10 +29,8 @@ struct WaveFileHeader {
s32 subchunk2_size;
};
void write_wave_file_mono(const std::vector<s16>& samples,
s32 sample_rate,
const std::filesystem::path& name);
void write_wave_file_mono(const std::vector<s16>& samples, s32 sample_rate, const fs::path& name);
std::vector<s16> decode_adpcm(BinaryReader& reader);
std::vector<u8> encode_adpcm(const std::vector<s16>& samples);
std::vector<u8> encode_adpcm(const std::vector<s16>& samples);

View File

@ -1051,7 +1051,7 @@ Object Interpreter::eval_try_load_file(const Object& form,
vararg_check(form, args, {ObjectType::STRING}, {});
auto path = {args.unnamed.at(0).as_string()->data};
if (!std::filesystem::exists(file_util::get_file_path(path))) {
if (!fs::exists(file_util::get_file_path(path))) {
return SymbolObject::make_new(reader.symbolTable, "#f");
}

View File

@ -11,8 +11,6 @@
#include "Reader.h"
#include <filesystem>
#include "ReplUtils.h"
#include "common/util/FileUtil.h"

View File

@ -57,14 +57,14 @@ void ReplWrapper::add_to_history(const std::string& line) {
}
void ReplWrapper::save_history() {
std::filesystem::path path = file_util::get_user_config_dir() / ".opengoal.repl.history";
fs::path path = file_util::get_user_config_dir() / ".opengoal.repl.history";
file_util::create_dir_if_needed_for_file(path.string());
repl.history_save(path.string());
}
void ReplWrapper::load_history() {
std::filesystem::path path = file_util::get_user_config_dir() / ".opengoal.repl.history";
if (std::filesystem::exists(path)) {
fs::path path = file_util::get_user_config_dir() / ".opengoal.repl.history";
if (fs::exists(path)) {
repl.history_load(path.string());
} else {
fmt::print("Couldn't locate REPL history file at '{}'\n", path.string());

View File

@ -83,7 +83,7 @@ void log_message(level log_level, LogTime& now, const char* message) {
void set_file(const std::string& filename) {
ASSERT(!gLogger.fp);
file_util::create_dir_if_needed_for_file(filename);
gLogger.fp = fopen(filename.c_str(), "w");
gLogger.fp = file_util::open_file(filename.c_str(), "w");
ASSERT(gLogger.fp);
}

View File

@ -117,8 +117,7 @@ bool write_subtitle_db_to_files(const GameSubtitleDB& db) {
}
// Commit it to the file
std::string full_path =
(file_util::get_jak_project_dir() / std::filesystem::path(bank->file_path)).string();
std::string full_path = (file_util::get_jak_project_dir() / fs::path(bank->file_path)).string();
file_util::write_text_file(full_path, file_contents);
}

View File

@ -7,11 +7,11 @@
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <stdexcept>
#include <vector>
#include "common/util/Assert.h"
#include "common/util/FileUtil.h"
struct BinaryWriterRef {
size_t offset;
@ -64,8 +64,8 @@ class BinaryWriter {
void* get_data() { return data.data(); }
void write_to_file(const std::filesystem::path& filename) {
auto fp = fopen(filename.string().c_str(), "wb");
void write_to_file(const fs::path& filename) {
auto fp = file_util::open_file(filename.string().c_str(), "wb");
if (!fp)
throw std::runtime_error("failed to open " + filename.string());
if (fwrite(get_data(), get_size(), 1, fp) != 1)

View File

@ -5,7 +5,6 @@
* Create a DGO from existing files.
*/
#include <filesystem>
#include <string>
#include <vector>

View File

@ -7,7 +7,6 @@
#include <cstdio> /* defines FILENAME_MAX */
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
@ -37,22 +36,22 @@
#include <common/log/log.h>
namespace file_util {
std::filesystem::path get_user_home_dir() {
fs::path get_user_home_dir() {
#ifdef _WIN32
// NOTE - on older systems, this may case issues if it cannot be found!
std::string home_dir = std::getenv("USERPROFILE");
return std::filesystem::path(home_dir);
return fs::path(home_dir);
#else
std::string home_dir = std::getenv("HOME");
return std::filesystem::path(home_dir);
return fs::path(home_dir);
#endif
}
std::filesystem::path get_user_config_dir() {
std::filesystem::path config_base_path;
fs::path get_user_config_dir() {
fs::path config_base_path;
#ifdef _WIN32
auto config_base_dir = std::getenv("APPDATA");
config_base_path = std::filesystem::path(std::string(config_base_dir));
config_base_path = fs::path(std::string(config_base_dir));
#elif __linux
// Docs - https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
// Prefer XDG_CONFIG_HOME if available
@ -66,19 +65,19 @@ std::filesystem::path get_user_config_dir() {
return config_base_path / "OpenGOAL";
}
std::filesystem::path get_user_settings_dir() {
fs::path get_user_settings_dir() {
// TODO - jak2
return get_user_config_dir() / "jak1" / "settings";
}
std::filesystem::path get_user_memcard_dir() {
fs::path get_user_memcard_dir() {
// TODO - jak2
return get_user_config_dir() / "jak1" / "saves";
}
struct {
bool initialized = false;
std::filesystem::path path_to_data;
fs::path path_to_data;
} gFilePathInfo;
/*!
@ -120,17 +119,17 @@ std::optional<std::string> try_get_jak_project_path() {
0, pos + 11)); // + 12 to include "/jak-project" in the returned filepath
}
std::optional<std::filesystem::path> try_get_data_dir() {
std::filesystem::path my_path = get_current_executable_path();
std::optional<fs::path> try_get_data_dir() {
fs::path my_path = get_current_executable_path();
auto data_dir = my_path.parent_path() / "data";
if (std::filesystem::exists(data_dir) && std::filesystem::is_directory(data_dir)) {
if (fs::exists(data_dir) && fs::is_directory(data_dir)) {
return std::make_optional(data_dir);
} else {
return {};
}
}
bool setup_project_path(std::optional<std::filesystem::path> project_path_override) {
bool setup_project_path(std::optional<fs::path> project_path_override) {
if (gFilePathInfo.initialized) {
return true;
}
@ -162,7 +161,7 @@ bool setup_project_path(std::optional<std::filesystem::path> project_path_overri
return false;
}
std::filesystem::path get_jak_project_dir() {
fs::path get_jak_project_dir() {
ASSERT(gFilePathInfo.initialized);
return gFilePathInfo.path_to_data;
}
@ -172,7 +171,7 @@ std::string get_file_path(const std::vector<std::string>& input) {
// the project path should be explicitly provided by whatever if needed
// TEMP HACK
// - if the provided path is absolute, don't add the project path
if (input.size() == 1 && std::filesystem::path(input.at(0)).is_absolute()) {
if (input.size() == 1 && fs::path(input.at(0)).is_absolute()) {
return input.at(0);
}
@ -184,24 +183,24 @@ std::string get_file_path(const std::vector<std::string>& input) {
return current_path.string();
}
bool create_dir_if_needed(const std::filesystem::path& path) {
if (!std::filesystem::is_directory(path)) {
std::filesystem::create_directories(path);
bool create_dir_if_needed(const fs::path& path) {
if (!fs::is_directory(path)) {
fs::create_directories(path);
return true;
}
return false;
}
bool create_dir_if_needed_for_file(const std::string& path) {
return create_dir_if_needed_for_file(std::filesystem::path(path));
return create_dir_if_needed_for_file(fs::path(path));
}
bool create_dir_if_needed_for_file(const std::filesystem::path& path) {
return std::filesystem::create_directories(path.parent_path());
bool create_dir_if_needed_for_file(const fs::path& path) {
return fs::create_directories(path.parent_path());
}
void write_binary_file(const std::filesystem::path& name, const void* data, size_t size) {
FILE* fp = fopen(name.string().c_str(), "wb");
void write_binary_file(const fs::path& name, const void* data, size_t size) {
FILE* fp = file_util::open_file(name.string().c_str(), "wb");
if (!fp) {
throw std::runtime_error("couldn't open file " + name.string());
}
@ -215,10 +214,10 @@ void write_binary_file(const std::filesystem::path& name, const void* data, size
}
void write_binary_file(const std::string& name, const void* data, size_t size) {
write_binary_file(std::filesystem::path(name), data, size);
write_binary_file(fs::path(name), data, size);
}
void write_rgba_png(const std::filesystem::path& name, void* data, int w, int h) {
void write_rgba_png(const fs::path& name, void* data, int w, int h) {
auto flags = 0;
auto ok = fpng::fpng_encode_image_to_file(name.string().c_str(), data, w, h, 4, flags);
@ -229,11 +228,11 @@ void write_rgba_png(const std::filesystem::path& name, void* data, int w, int h)
}
void write_text_file(const std::string& file_name, const std::string& text) {
write_text_file(std::filesystem::path(file_name), text);
write_text_file(fs::path(file_name), text);
}
void write_text_file(const std::filesystem::path& file_name, const std::string& text) {
FILE* fp = fopen(file_name.string().c_str(), "w");
void write_text_file(const fs::path& file_name, const std::string& text) {
FILE* fp = file_util::open_file(file_name.string().c_str(), "w");
if (!fp) {
lg::error("Failed to fopen {}\n", file_name.string());
throw std::runtime_error("Failed to open file");
@ -242,26 +241,25 @@ void write_text_file(const std::filesystem::path& file_name, const std::string&
fclose(fp);
}
std::vector<uint8_t> read_binary_file(const std::string& filename) {
return read_binary_file(std::filesystem::path(filename));
return read_binary_file(fs::path(filename));
}
std::vector<uint8_t> read_binary_file(const std::filesystem::path& path) {
std::vector<uint8_t> read_binary_file(const fs::path& path) {
// make sure file exists and isn't a directory
auto status = std::filesystem::status(path);
auto status = fs::status(path);
if (!std::filesystem::exists(status)) {
if (!fs::exists(status)) {
throw std::runtime_error(
fmt::format("File {} cannot be opened: does not exist.", path.string()));
}
if (status.type() != std::filesystem::file_type::regular &&
status.type() != std::filesystem::file_type::symlink) {
if (status.type() != fs::file_type::regular && status.type() != fs::file_type::symlink) {
throw std::runtime_error(
fmt::format("File {} cannot be opened: not a regular file or symlink.", path.string()));
}
auto fp = fopen(path.string().c_str(), "rb");
auto fp = file_util::open_file(path.string().c_str(), "rb");
if (!fp)
throw std::runtime_error("File " + path.string() +
" cannot be opened: " + std::string(strerror(errno)));
@ -281,7 +279,7 @@ std::vector<uint8_t> read_binary_file(const std::filesystem::path& path) {
return data;
}
std::string read_text_file(const std::filesystem::path& path) {
std::string read_text_file(const fs::path& path) {
std::ifstream file(path.string());
if (!file.good()) {
throw std::runtime_error("couldn't open " + path.string());
@ -292,7 +290,7 @@ std::string read_text_file(const std::filesystem::path& path) {
}
std::string read_text_file(const std::string& path) {
return read_text_file(std::filesystem::path(path));
return read_text_file(fs::path(path));
}
bool is_printable_char(char c) {
@ -304,7 +302,7 @@ std::string combine_path(const std::string& parent, const std::string& child) {
}
bool file_exists(const std::string& path) {
return std::filesystem::exists(path);
return fs::exists(path);
}
std::string base_name(const std::string& filename) {
@ -448,7 +446,7 @@ void MakeISOName(char* dst, const char* src) {
}
void assert_file_exists(const char* path, const char* error_message) {
if (!std::filesystem::exists(path)) {
if (!fs::exists(path)) {
ASSERT_MSG(false, fmt::format("File {} was not found: {}", path, error_message));
}
}
@ -512,4 +510,12 @@ std::vector<u8> decompress_dgo(const std::vector<u8>& data_in) {
return decompressed_data;
}
FILE* open_file(const fs::path& path, std::string mode) {
#ifdef _WIN32
return _wfopen(path.wstring().c_str(), std::wstring(mode.begin(), mode.end()).c_str());
#else
return fopen(path.string().c_str(), mode.c_str());
#endif
}
} // namespace file_util

View File

@ -5,36 +5,46 @@
* Utility functions for reading and writing files.
*/
#include <filesystem>
#ifdef _WIN32
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#endif
#include "third-party/filesystem.hpp"
#ifdef _WIN32
#undef FALSE
#endif
#include <optional>
#include <string>
#include <vector>
#include "common/common_types.h"
namespace fs = std::filesystem;
namespace fs = ghc::filesystem;
namespace file_util {
std::filesystem::path get_user_home_dir();
std::filesystem::path get_user_config_dir();
std::filesystem::path get_user_settings_dir();
std::filesystem::path get_user_memcard_dir();
std::filesystem::path get_jak_project_dir();
fs::path get_user_home_dir();
fs::path get_user_config_dir();
fs::path get_user_settings_dir();
fs::path get_user_memcard_dir();
fs::path get_jak_project_dir();
bool create_dir_if_needed(const std::filesystem::path& path);
bool create_dir_if_needed(const fs::path& path);
bool create_dir_if_needed_for_file(const std::string& path);
bool create_dir_if_needed_for_file(const std::filesystem::path& path);
bool setup_project_path(std::optional<std::filesystem::path> project_path_override);
bool create_dir_if_needed_for_file(const fs::path& path);
bool setup_project_path(std::optional<fs::path> project_path_override);
std::string get_file_path(const std::vector<std::string>& path);
void write_binary_file(const std::string& name, const void* data, size_t size);
void write_binary_file(const std::filesystem::path& name, const void* data, size_t size);
void write_rgba_png(const std::filesystem::path& name, void* data, int w, int h);
void write_binary_file(const fs::path& name, const void* data, size_t size);
void write_rgba_png(const fs::path& name, void* data, int w, int h);
void write_text_file(const std::string& file_name, const std::string& text);
void write_text_file(const std::filesystem::path& file_name, const std::string& text);
void write_text_file(const fs::path& file_name, const std::string& text);
std::vector<uint8_t> read_binary_file(const std::string& filename);
std::vector<uint8_t> read_binary_file(const std::filesystem::path& filename);
std::vector<uint8_t> read_binary_file(const fs::path& filename);
std::string read_text_file(const std::string& path);
std::string read_text_file(const std::filesystem::path& path);
std::string read_text_file(const fs::path& path);
bool is_printable_char(char c);
std::string combine_path(const std::string& parent, const std::string& child);
bool file_exists(const std::string& path);
@ -44,4 +54,6 @@ void ISONameFromAnimationName(char* dst, const char* src);
void assert_file_exists(const char* path, const char* error_message);
bool dgo_header_is_compressed(const std::vector<u8>& data);
std::vector<u8> decompress_dgo(const std::vector<u8>& data_in);
FILE* open_file(const fs::path& path, std::string mode);
} // namespace file_util

View File

@ -80,11 +80,11 @@ void add_from_dir(FILE* fp, u32 sector, u32 size, IsoFile::Entry* parent) {
void unpack_entry(FILE* fp,
IsoFile& iso,
const IsoFile::Entry& entry,
const std::filesystem::path& dest,
const fs::path& dest,
bool print_progress) {
std::filesystem::path path_to_entry = dest / entry.name;
fs::path path_to_entry = dest / entry.name;
if (entry.is_dir) {
std::filesystem::create_directory(path_to_entry);
fs::create_directory(path_to_entry);
for (const auto& child : entry.children) {
unpack_entry(fp, iso, child, path_to_entry, print_progress);
}
@ -118,15 +118,12 @@ IsoFile find_files_in_iso(FILE* fp) {
return result;
}
void unpack_iso_files(FILE* fp,
IsoFile& layout,
const std::filesystem::path& dest,
bool print_progress) {
void unpack_iso_files(FILE* fp, IsoFile& layout, const fs::path& dest, bool print_progress) {
unpack_entry(fp, layout, layout.root, dest, print_progress);
}
IsoFile unpack_iso_files(FILE* fp,
const std::filesystem::path& dest,
const fs::path& dest,
bool print_progress,
const bool hashFiles) {
auto file = find_files_in_iso(fp);

View File

@ -1,9 +1,10 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
#include "common/util/FileUtil.h"
#include "third-party/xxhash.hpp"
struct IsoFile {
@ -34,8 +35,8 @@ struct IsoFile {
};
IsoFile find_files_in_iso(FILE* fp);
void unpack_iso_files(FILE* fp, IsoFile& layout, const std::filesystem::path& dest);
void unpack_iso_files(FILE* fp, IsoFile& layout, const fs::path& dest);
IsoFile unpack_iso_files(FILE* fp,
const std::filesystem::path& dest,
const fs::path& dest,
bool print_progress,
const bool hashFiles = false);

View File

@ -0,0 +1,61 @@
#include "unicode_util.h"
// clang-format off
#ifdef _WIN32
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <Stringapiset.h>
#include <processenv.h>
#include <winbase.h>
#include <shellapi.h>
#undef FALSE
#endif
// clang-format on
std::string utf8_from_utf16(const wchar_t* utf16_string) {
#ifdef _WIN32
if (utf16_string == nullptr) {
return std::string();
}
int target_length = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr,
0, nullptr, nullptr);
if (target_length == 0) {
return std::string();
}
std::string utf8_string;
utf8_string.resize(target_length);
int converted_length = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1,
utf8_string.data(), target_length, nullptr, nullptr);
if (converted_length == 0) {
return std::string();
}
return utf8_string;
#else
return "don't call this on linux";
#endif
}
std::vector<std::string> get_widechar_cli_args() {
#ifdef _WIN32
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
int argc;
wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (argv == nullptr) {
return {};
}
std::vector<std::string> command_line_arguments;
for (int i = 0; i < argc; i++) {
command_line_arguments.push_back(utf8_from_utf16(argv[i]));
}
LocalFree(argv);
return command_line_arguments;
#else
return {"don't call this on linux"};
#endif
}

View File

@ -0,0 +1,7 @@
#pragma once
#include <string>
#include <vector>
std::string utf8_from_utf16(const wchar_t* utf16_string);
std::vector<std::string> get_widechar_cli_args();

View File

@ -100,7 +100,7 @@ target_link_libraries(decompiler
add_executable(extractor
extractor/main.cpp)
extractor/main.cpp extractor/extractor_util.hpp )
target_link_libraries(extractor
decomp

View File

@ -113,10 +113,10 @@ const ObjectFileData& ObjectFileDB::lookup_record(const ObjectFileRecord& rec) c
/*!
* Build an object file DB for the given list of DGOs.
*/
ObjectFileDB::ObjectFileDB(const std::vector<std::filesystem::path>& _dgos,
const std::filesystem::path& obj_file_name_map_file,
const std::vector<std::filesystem::path>& object_files,
const std::vector<std::filesystem::path>& str_files,
ObjectFileDB::ObjectFileDB(const std::vector<fs::path>& _dgos,
const fs::path& obj_file_name_map_file,
const std::vector<fs::path>& object_files,
const std::vector<fs::path>& str_files,
const Config& config) {
Timer timer;
@ -258,7 +258,7 @@ void ObjectFileDB::load_map_file(const std::string& map_data) {
/*!
* Load the objects stored in the given DGO into the ObjectFileDB
*/
void ObjectFileDB::get_objs_from_dgo(const std::filesystem::path& filename, const Config& config) {
void ObjectFileDB::get_objs_from_dgo(const fs::path& filename, const Config& config) {
auto dgo_data = file_util::read_binary_file(filename);
stats.total_dgo_bytes += dgo_data.size();
@ -516,7 +516,7 @@ void ObjectFileDB::process_labels() {
/*!
* Dump object files and their linking data to text files for debugging
*/
void ObjectFileDB::write_object_file_words(const std::filesystem::path& output_dir,
void ObjectFileDB::write_object_file_words(const fs::path& output_dir,
bool dump_data,
bool dump_code) {
lg::info("- Writing object file dumps (code? {} data? {})...", dump_code, dump_data);
@ -545,7 +545,7 @@ void ObjectFileDB::write_object_file_words(const std::filesystem::path& output_d
/*!
* Dump disassembly for object files containing code. Data zones will also be dumped.
*/
void ObjectFileDB::write_disassembly(const std::filesystem::path& output_dir,
void ObjectFileDB::write_disassembly(const fs::path& output_dir,
bool disassemble_data,
bool disassemble_code,
bool print_hex) {
@ -623,7 +623,7 @@ void ObjectFileDB::find_code(const Config& config) {
* Finds and writes all scripts into a file named all_scripts.lisp.
* Doesn't change any state in ObjectFileDB.
*/
void ObjectFileDB::find_and_write_scripts(const std::filesystem::path& output_dir) {
void ObjectFileDB::find_and_write_scripts(const fs::path& output_dir) {
lg::info("- Finding scripts in object files...");
Timer timer;
std::string all_scripts;
@ -645,8 +645,7 @@ void ObjectFileDB::find_and_write_scripts(const std::filesystem::path& output_di
lg::info(" Total {:.3f} ms\n", timer.getMs());
}
std::string ObjectFileDB::process_tpages(TextureDB& tex_db,
const std::filesystem::path& output_path) {
std::string ObjectFileDB::process_tpages(TextureDB& tex_db, const fs::path& output_path) {
lg::info("- Finding textures in tpages...");
std::string tpage_string = "tpage-";
int total = 0, success = 0;
@ -796,7 +795,7 @@ void ObjectFileDB::extract_art_info() {
/*!
* Write out the art group information.
*/
void ObjectFileDB::dump_art_info(const std::filesystem::path& output_dir) {
void ObjectFileDB::dump_art_info(const fs::path& output_dir) {
lg::info("Writing art group info...");
Timer timer;
@ -820,7 +819,7 @@ void ObjectFileDB::dump_art_info(const std::filesystem::path& output_dir) {
lg::info("Written art group info: in {:.2f} ms\n", timer.getMs());
}
void ObjectFileDB::dump_raw_objects(const std::filesystem::path& output_dir) {
void ObjectFileDB::dump_raw_objects(const fs::path& output_dir) {
for_each_obj([&](ObjectFileData& data) {
auto dest = output_dir / data.to_unique_name();
if (data.obj_version != 3) {

View File

@ -15,6 +15,7 @@
#include "common/common_types.h"
#include "common/util/Assert.h"
#include "common/util/FileUtil.h"
#include "decompiler/analysis/symbol_def_map.h"
#include "decompiler/data/TextureDB.h"
@ -144,30 +145,28 @@ struct LetRewriteStats {
class ObjectFileDB {
public:
ObjectFileDB(const std::vector<std::filesystem::path>& _dgos,
const std::filesystem::path& obj_file_name_map_file,
const std::vector<std::filesystem::path>& object_files,
const std::vector<std::filesystem::path>& str_files,
ObjectFileDB(const std::vector<fs::path>& _dgos,
const fs::path& obj_file_name_map_file,
const std::vector<fs::path>& object_files,
const std::vector<fs::path>& str_files,
const Config& config);
std::string generate_dgo_listing();
std::string generate_obj_listing(const std::unordered_set<std::string>& merged_objs);
void process_link_data(const Config& config);
void process_labels();
void find_code(const Config& config);
void find_and_write_scripts(const std::filesystem::path& output_dir);
void find_and_write_scripts(const fs::path& output_dir);
void extract_art_info();
void dump_art_info(const std::filesystem::path& output_dir);
void dump_raw_objects(const std::filesystem::path& output_dir);
void write_object_file_words(const std::filesystem::path& output_dir,
bool dump_data,
bool dump_code);
void write_disassembly(const std::filesystem::path& output_dir,
void dump_art_info(const fs::path& output_dir);
void dump_raw_objects(const fs::path& output_dir);
void write_object_file_words(const fs::path& output_dir, bool dump_data, bool dump_code);
void write_disassembly(const fs::path& output_dir,
bool disassemble_data,
bool disassemble_code,
bool print_hex);
void analyze_functions_ir2(
const std::filesystem::path& output_dir,
const fs::path& output_dir,
const Config& config,
const std::unordered_set<std::string>& skip_functions,
const std::unordered_map<std::string, std::unordered_set<std::string>>& skip_states = {});
@ -185,7 +184,7 @@ class ObjectFileDB {
void ir2_rewrite_inline_asm_instructions(int seg, ObjectFileData& data);
void ir2_insert_anonymous_functions(int seg, ObjectFileData& data);
void ir2_symbol_definition_map(ObjectFileData& data);
void ir2_write_results(const std::filesystem::path& output_dir,
void ir2_write_results(const fs::path& output_dir,
const Config& config,
const std::vector<std::string>& imports,
ObjectFileData& data);
@ -193,7 +192,7 @@ class ObjectFileDB {
void ir2_do_segment_analysis_phase2(int seg, const Config& config, ObjectFileData& data);
void ir2_setup_labels(const Config& config, ObjectFileData& data);
void ir2_run_mips2c(const Config& config, ObjectFileData& data);
void ir2_analyze_all_types(const std::filesystem::path& output_file,
void ir2_analyze_all_types(const fs::path& output_file,
const std::optional<std::string>& previous_game_types,
const std::unordered_set<std::string>& bad_types);
std::string ir2_to_file(ObjectFileData& data, const Config& config);
@ -202,7 +201,7 @@ class ObjectFileDB {
const std::vector<std::string>& imports,
const std::unordered_set<std::string>& skip_functions);
std::string process_tpages(TextureDB& tex_db, const std::filesystem::path& output_path);
std::string process_tpages(TextureDB& tex_db, const fs::path& output_path);
std::string process_game_count_file();
std::string process_game_text_files(const Config& cfg);
@ -216,7 +215,7 @@ class ObjectFileDB {
public:
void load_map_file(const std::string& map_data);
void get_objs_from_dgo(const std::filesystem::path& filename, const Config& config);
void get_objs_from_dgo(const fs::path& filename, const Config& config);
void add_obj_from_dgo(const std::string& obj_name,
const std::string& name_in_dgo,
const uint8_t* obj_data,

View File

@ -38,7 +38,7 @@ namespace decompiler {
* functions, but nothing else.
*/
void ObjectFileDB::analyze_functions_ir2(
const std::filesystem::path& output_dir,
const fs::path& output_dir,
const Config& config,
const std::unordered_set<std::string>& skip_functions,
const std::unordered_map<std::string, std::unordered_set<std::string>>& skip_states) {
@ -300,7 +300,7 @@ void ObjectFileDB::ir2_top_level_pass(const Config& config) {
lg::info("{:4d} logins {:.2f}%\n", total_top_levels, 100.f * total_top_levels / total_functions);
}
void ObjectFileDB::ir2_analyze_all_types(const std::filesystem::path& output_file,
void ObjectFileDB::ir2_analyze_all_types(const fs::path& output_file,
const std::optional<std::string>& previous_game_types,
const std::unordered_set<std::string>& bad_types) {
struct PerObject {
@ -690,7 +690,7 @@ void ObjectFileDB::ir2_insert_anonymous_functions(int seg, ObjectFileData& data)
});
}
void ObjectFileDB::ir2_write_results(const std::filesystem::path& output_dir,
void ObjectFileDB::ir2_write_results(const fs::path& output_dir,
const Config& config,
const std::vector<std::string>& imports,
ObjectFileData& obj) {

View File

@ -14,7 +14,7 @@
#include "game/common/str_rpc_types.h"
namespace decompiler {
StrFileReader::StrFileReader(const std::filesystem::path& file_path) {
StrFileReader::StrFileReader(const fs::path& file_path) {
auto data = file_util::read_binary_file(file_path);
ASSERT(data.size() >= SECTOR_SIZE); // must have at least the header sector
ASSERT(data.size() % SECTOR_SIZE == 0); // should be multiple of the sector size.

View File

@ -5,16 +5,16 @@
* Utility class to read a .STR file and extract the full file name.
*/
#include <filesystem>
#include <string>
#include <vector>
#include "common/common_types.h"
#include "common/util/FileUtil.h"
namespace decompiler {
class StrFileReader {
public:
explicit StrFileReader(const std::filesystem::path& file_path);
explicit StrFileReader(const fs::path& file_path);
int chunk_count() const;
const std::vector<u8>& get_chunk(int idx) const;
std::string get_full_name(const std::string& short_name) const;
@ -22,4 +22,4 @@ class StrFileReader {
private:
std::vector<std::vector<u8>> m_chunks;
};
} // namespace decompiler
} // namespace decompiler

View File

@ -1,7 +1,5 @@
#include "TextureDB.h"
#include <filesystem>
#include "common/util/Assert.h"
#include "third-party/fmt/core.h"
@ -45,12 +43,11 @@ void TextureDB::add_texture(u32 tpage,
}
}
void TextureDB::replace_textures(const std::filesystem::path& path) {
std::filesystem::path base_path(path);
void TextureDB::replace_textures(const fs::path& path) {
fs::path base_path(path);
for (auto& tex : textures) {
std::filesystem::path full_path =
base_path / tpage_names.at(tex.second.page) / (tex.second.name + ".png");
if (std::filesystem::exists(full_path)) {
fs::path full_path = base_path / tpage_names.at(tex.second.page) / (tex.second.name + ".png");
if (fs::exists(full_path)) {
fmt::print("Replacing {}\n", full_path.string().c_str());
int w, h;
auto data = stbi_load(full_path.string().c_str(), &w, &h, 0, 4); // rgba channels

View File

@ -1,12 +1,12 @@
#pragma once
#include <filesystem>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
#include "common/util/FileUtil.h"
namespace decompiler {
struct TextureDB {
@ -30,6 +30,6 @@ struct TextureDB {
const std::string& tpage_name,
const std::vector<std::string>& level_names);
void replace_textures(const std::filesystem::path& path);
void replace_textures(const fs::path& path);
};
} // namespace decompiler

View File

@ -89,7 +89,7 @@ struct VagFileHeader {
/*!
* Read the DIR file into an AudioDir
*/
AudioDir read_audio_dir(const std::filesystem::path& path) {
AudioDir read_audio_dir(const fs::path& path) {
// matches the format in file.
struct DirEntry {
char name[8];
@ -145,7 +145,7 @@ struct AudioFileInfo {
double length_seconds;
};
AudioFileInfo process_audio_file(const std::filesystem::path& output_folder,
AudioFileInfo process_audio_file(const fs::path& output_folder,
const std::vector<u8>& data,
const std::string& name,
const std::string& suffix) {
@ -182,8 +182,8 @@ AudioFileInfo process_audio_file(const std::filesystem::path& output_folder,
return {vag_filename, (double)decoded_samples.size() / header.sample_rate};
}
void process_streamed_audio(const std::filesystem::path& output_path,
const std::filesystem::path& input_dir,
void process_streamed_audio(const fs::path& output_path,
const fs::path& input_dir,
const std::vector<std::string>& audio_files) {
auto dir_data = read_audio_dir(input_dir / "VAG" / "VAGDIR.AYB");
double audio_len = 0.f;
@ -201,7 +201,7 @@ void process_streamed_audio(const std::filesystem::path& output_path,
for (size_t lang_id = 0; lang_id < audio_files.size(); lang_id++) {
auto& file = audio_files[lang_id];
auto wad_data = file_util::read_binary_file(input_dir / "VAG" / file);
auto suffix = std::filesystem::path(file).extension().u8string().substr(1);
auto suffix = fs::path(file).extension().u8string().substr(1);
langs.push_back(suffix);
dir_data.set_file_size(wad_data.size());
for (int i = 0; i < dir_data.entry_count(); i++) {

View File

@ -1,11 +1,12 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
#include "common/util/FileUtil.h"
namespace decompiler {
void process_streamed_audio(const std::filesystem::path& output_path,
const std::filesystem::path& input_dir,
void process_streamed_audio(const fs::path& output_path,
const fs::path& input_dir,
const std::vector<std::string>& audio_files);
}
}

View File

@ -421,7 +421,7 @@ TexturePage read_texture_page(ObjectFileData& data,
*/
TPageResultStats process_tpage(ObjectFileData& data,
TextureDB& texture_db,
const std::filesystem::path& output_path) {
const fs::path& output_path) {
TPageResultStats stats;
auto& words = data.linked_data.words_by_seg.at(0);
const auto& level_names = data.dgo_names;

View File

@ -1,7 +1,5 @@
#pragma once
#include <filesystem>
#include "decompiler/data/TextureDB.h"
namespace decompiler {
@ -15,5 +13,5 @@ struct TPageResultStats {
TPageResultStats process_tpage(ObjectFileData& data,
TextureDB& texture_db,
const std::filesystem::path& output_path);
} // namespace decompiler
const fs::path& output_path);
} // namespace decompiler

View File

@ -0,0 +1,251 @@
#pragma once
#include <optional>
#include <regex>
#include <unordered_map>
#include "common/log/log.h"
#include "common/util/FileUtil.h"
#include <common/util/json_util.h>
#include <common/util/read_iso_file.h>
#include <third-party/json.hpp>
#include "third-party/xxhash.hpp"
enum class ExtractorErrorCode {
SUCCESS = 0,
INVALID_CLI_INPUT = 3990,
VALIDATION_CANT_LOCATE_ELF = 4000,
VALIDATION_SERIAL_MISSING_FROM_DB = 4001,
VALIDATION_ELF_MISSING_FROM_DB = 4002,
VALIDATION_BAD_ISO_CONTENTS = 4010,
VALIDATION_INCORRECT_EXTRACTION_COUNT = 4011,
VALIDATION_FILE_CONTENTS_UNEXPECTED = 4012,
VALIDATION_BAD_EXTRACTION = 4020,
DECOMPILATION_GENERIC_ERROR = 4030,
EXTRACTION_INVALID_ISO_PATH = 4040,
EXTRACTION_ISO_UNEXPECTED_SIZE = 4041,
COMPILATION_BAD_PROJECT_PATH = 4050,
};
enum GameIsoFlags { FLAG_JAK1_BLACK_LABEL = (1 << 0) };
static const std::unordered_map<std::string, GameIsoFlags> sGameIsoFlagNames = {
{"jak1-black-label", FLAG_JAK1_BLACK_LABEL}};
// used for - decompiler_out/<jak1> and iso_data/<jak1>
std::unordered_map<std::string, std::string> data_subfolders = {{"jak1", "jak1"}};
struct ISOMetadata {
std::string canonical_name;
std::string region;
int num_files;
xxh::hash64_t contents_hash;
std::string decomp_config;
std::string game_name;
std::vector<std::string> flags;
};
// This is all we need to re-fetch info from the database
// - if this changes such that we have a collision in the future,
// then the database isn't adequate and everything must change
struct BuildInfo {
std::string serial = "";
xxh::hash64_t elf_hash = 0;
};
void to_json(nlohmann::json& j, const BuildInfo& info) {
j = nlohmann::json{{"serial", info.serial}, {"elf_hash", info.elf_hash}};
}
void from_json(const nlohmann::json& j, BuildInfo& info) {
j[0].at("serial").get_to(info.serial);
j[0].at("elf_hash").get_to(info.elf_hash);
}
std::optional<BuildInfo> get_buildinfo_from_path(fs::path iso_data_path) {
if (!fs::exists(iso_data_path / "buildinfo.json")) {
return {};
}
auto buildinfo_path = (iso_data_path / "buildinfo.json").string();
try {
return parse_commented_json(file_util::read_text_file(buildinfo_path), buildinfo_path)
.get<BuildInfo>();
} catch (std::exception& e) {
lg::error("JSON parsing error on buildinfo.json - {}", e.what());
return {};
}
}
static const ISOMetadata jak1_ntsc_black_label_info = {
"Jak & Daxter™: The Precursor Legacy (Black Label)",
"NTSC-U",
337,
11363853835861842434U,
"jak1_ntsc_black_label",
"jak1",
{"jak1-black-label"}};
// { SERIAL : { ELF_HASH : ISOMetadataDatabase } }
static const std::unordered_map<std::string, std::unordered_map<xxh::hash64_t, ISOMetadata>>
isoDatabase{{"SCUS-97124",
{{7280758013604870207U, jak1_ntsc_black_label_info},
{744661860962747854,
{"Jak & Daxter™: The Precursor Legacy",
"NTSC-U",
338,
8538304367812415885U,
"jak1_jp",
"jak1",
{}}}}},
{"SCES-50361",
{{12150718117852276522U,
{"Jak & Daxter™: The Precursor Legacy",
"PAL",
338,
16850370297611763875U,
"jak1_pal",
"jak1",
{}}}}},
{"SCPS-15021",
{{16909372048085114219U,
{"ジャックXダクスター ~ 旧世界の遺産",
"NTSC-J",
338,
1262350561338887717,
"jak1_jp",
"jak1",
{}}}}}};
std::optional<ISOMetadata> get_version_info_from_build_info(const BuildInfo& build_info) {
if (build_info.serial.empty() || build_info.elf_hash == 0) {
return {};
}
auto dbEntry = isoDatabase.find(build_info.serial);
if (dbEntry == isoDatabase.end()) {
return {};
}
auto& metaMap = dbEntry->second;
auto meta_entry = metaMap.find(build_info.elf_hash);
if (meta_entry == metaMap.end()) {
return {};
}
return std::make_optional(meta_entry->second);
}
ISOMetadata get_version_info_or_default(const fs::path& iso_data_path) {
ISOMetadata version_info = jak1_ntsc_black_label_info;
const auto build_info = get_buildinfo_from_path(iso_data_path);
if (!build_info) {
lg::warn(
"unable locate buildinfo.json file in iso data path, defaulting to Jak 1 - NTSC "
"Black Label");
} else {
auto maybe_version_info = get_version_info_from_build_info(build_info.value());
if (!maybe_version_info) {
lg::warn(
"unable to determine game version from buildinfo.json file, defaulting to Jak 1 - NTSC "
"Black Label");
} else {
version_info = maybe_version_info.value();
}
}
return version_info;
}
std::tuple<std::optional<std::string>, std::optional<xxh::hash64_t>> findElfFile(
const fs::path& extracted_iso_path) {
std::optional<std::string> serial = std::nullopt;
std::optional<xxh::hash64_t> elf_hash = std::nullopt;
for (const auto& entry : fs::directory_iterator(extracted_iso_path)) {
auto as_str = entry.path().filename().string();
if (std::regex_match(as_str, std::regex(".{4}_.{3}\\..{2}"))) {
serial = std::make_optional(
fmt::format("{}-{}", as_str.substr(0, 4), as_str.substr(5, 3) + as_str.substr(9, 2)));
// We already found the path, so hash it while we're here
auto fp = file_util::open_file(entry.path().string().c_str(), "rb");
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
std::vector<u8> buffer(size);
rewind(fp);
fread(&buffer[0], sizeof(std::vector<u8>::value_type), buffer.size(), fp);
elf_hash = std::make_optional(xxh::xxhash<64>(buffer));
fclose(fp);
break;
}
}
return {serial, elf_hash};
}
void log_potential_new_db_entry(ExtractorErrorCode error_code,
const std::string& serial,
const xxh::hash64_t elf_hash,
const int files_extracted,
const xxh::hash64_t contents_hash) {
// Finally, return the result
// Generate the map entry to make things simple, just convienance
if (error_code == ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB) {
lg::info(
"If this is a new release or version that should be supported, consider adding the "
"following serial entry to the database:");
lg::info(
"\t'{{\"{}\", {{{{{}U, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, "
"\"DECOMP_CONFIG_FILENAME_NO_EXTENSION\", \"jak1|jak2|jak3|jakx\", {}}}}}}}}}'",
serial, elf_hash, files_extracted, contents_hash);
} else if (error_code == ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB) {
lg::info(
"If this is a new release or version that should be supported, consider adding the "
"following ELF entry to the database under the '{}' serial:",
serial);
lg::info(
"\t'{{{}, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, "
"\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\", \"jak1|jak2|jak3|jakx\", {}}}}}'",
elf_hash, files_extracted, contents_hash);
}
}
std::tuple<bool, ExtractorErrorCode> is_iso_file(fs::path path_to_supposed_iso) {
// it's a file, normalize extension case and verify it's an ISO file
std::string ext = path_to_supposed_iso.extension().string();
if (!std::regex_match(ext, std::regex("\\.(iso|ISO)"))) {
lg::error("Provided game data path contains a file that isn't a .ISO!");
return {false, ExtractorErrorCode::EXTRACTION_INVALID_ISO_PATH};
}
// make sure the .iso is greater than 1GB in size
// to-do: verify game header data as well
if (fs::file_size(path_to_supposed_iso) < 1000000000) {
lg::error("Provided game data file appears to be too small or corrupted! Size is: {}",
fs::file_size(path_to_supposed_iso));
return {false, ExtractorErrorCode::EXTRACTION_ISO_UNEXPECTED_SIZE};
}
return {true, ExtractorErrorCode::SUCCESS};
}
std::tuple<xxh::hash64_t, int> calculate_extraction_hash(const IsoFile& iso_file) {
// - XOR all hashes together and hash the result. This makes the ordering of the hashes (aka
// files) irrelevant
xxh::hash64_t combined_hash = 0;
for (const auto& hash : iso_file.hashes) {
combined_hash ^= hash;
}
return {xxh::xxhash<64>({combined_hash}), iso_file.hashes.size()};
}
std::tuple<xxh::hash64_t, int> calculate_extraction_hash(const fs::path& extracted_iso_path) {
// - XOR all hashes together and hash the result. This makes the ordering of the hashes (aka
// files) irrelevant
xxh::hash64_t combined_hash = 0;
int filec = 0;
for (auto const& dir_entry : fs::recursive_directory_iterator(extracted_iso_path)) {
if (dir_entry.is_regular_file()) {
auto buffer = file_util::read_binary_file(dir_entry.path().string());
auto hash = xxh::xxhash<64>(buffer);
combined_hash ^= hash;
filec++;
}
}
return {xxh::xxhash<64>({combined_hash}), filec};
}

View File

@ -2,10 +2,13 @@
#include <regex>
#include <unordered_map>
#include "extractor_util.hpp"
#include "common/log/log.h"
#include "common/util/FileUtil.h"
#include "common/util/json_util.h"
#include "common/util/read_iso_file.h"
#include <common/util/unicode_util.h>
#include "decompiler/Disasm/OpcodeInfo.h"
#include "decompiler/ObjectFile/ObjectFileDB.h"
@ -15,369 +18,111 @@
#include "third-party/CLI11.hpp"
enum class ExtractorErrorCode {
SUCCESS = 0,
VALIDATION_CANT_LOCATE_ELF = 4000,
VALIDATION_SERIAL_MISSING_FROM_DB = 4001,
VALIDATION_ELF_MISSING_FROM_DB = 4002,
VALIDATION_BAD_ISO_CONTENTS = 4010,
VALIDATION_INCORRECT_EXTRACTION_COUNT = 4011,
VALIDATION_BAD_EXTRACTION = 4020,
DECOMPILATION_GENERIC_ERROR = 4030,
EXTRACTION_INVALID_ISO_PATH = 4040,
EXTRACTION_ISO_UNEXPECTED_SIZE = 4041
};
enum GameIsoFlags { FLAG_JAK1_BLACK_LABEL = (1 << 0) };
static const std::unordered_map<std::string, GameIsoFlags> sGameIsoFlagNames = {
{"jak1-black-label", FLAG_JAK1_BLACK_LABEL}};
struct ISOMetadata {
std::string canonical_name;
std::string region;
int num_files;
xxh::hash64_t contents_hash;
std::string decomp_config;
GameIsoFlags flags = (GameIsoFlags)0;
};
// TODO - when we support jak2 and beyond, add which game it's for as well
// this will let the installer reject (or gracefully handle) jak2 isos on the jak1 page, etc.
// { SERIAL : { ELF_HASH : ISOMetadataDatabase } }
static const std::map<std::string, std::map<xxh::hash64_t, ISOMetadata>> isoDatabase{
{"SCUS-97124",
{{7280758013604870207U,
{"Jak & Daxter™: The Precursor Legacy (Black Label)", "NTSC-U", 337, 11363853835861842434U,
"jak1_ntsc_black_label", FLAG_JAK1_BLACK_LABEL}},
{744661860962747854,
{"Jak & Daxter™: The Precursor Legacy", "NTSC-U", 338, 8538304367812415885U, "jak1_jp"}}}},
{"SCES-50361",
{{12150718117852276522U,
{"Jak & Daxter™: The Precursor Legacy", "PAL", 338, 16850370297611763875U, "jak1_pal"}}}},
{"SCPS-15021",
{{16909372048085114219U,
{"ジャックXダクスター ~ 旧世界の遺産", "NTSC-J", 338, 1262350561338887717,
"jak1_jp"}}}}};
void setup_global_decompiler_stuff(std::optional<std::filesystem::path> project_path_override) {
decompiler::init_opcode_info();
file_util::setup_project_path(project_path_override);
}
IsoFile extract_files(std::filesystem::path data_dir_path,
std::filesystem::path extracted_iso_path) {
fmt::print(
IsoFile extract_files(fs::path input_file_path, fs::path extracted_iso_path) {
lg::info(
"Note: Provided game data path '{}' points to a file, not a directory. Assuming it's an ISO "
"file and attempting to extract!\n",
data_dir_path.string());
"file and attempting to extract!",
input_file_path.string());
std::filesystem::create_directories(extracted_iso_path);
fs::create_directories(extracted_iso_path);
auto fp = fopen(data_dir_path.string().c_str(), "rb");
ASSERT_MSG(fp, "failed to open input ISO file\n");
auto fp = file_util::open_file(input_file_path, "rb");
ASSERT_MSG(fp, "failed to open input ISO file");
IsoFile iso = unpack_iso_files(fp, extracted_iso_path, true, true);
fclose(fp);
return iso;
}
std::pair<std::optional<std::string>, std::optional<xxh::hash64_t>> findElfFile(
const std::filesystem::path& extracted_iso_path) {
std::optional<std::string> serial = std::nullopt;
std::optional<xxh::hash64_t> elf_hash = std::nullopt;
for (const auto& entry : fs::directory_iterator(extracted_iso_path)) {
auto as_str = entry.path().filename().string();
if (std::regex_match(as_str, std::regex(".{4}_.{3}\\..{2}"))) {
serial = std::make_optional(
fmt::format("{}-{}", as_str.substr(0, 4), as_str.substr(5, 3) + as_str.substr(9, 2)));
// We already found the path, so hash it while we're here
auto fp = fopen(entry.path().string().c_str(), "rb");
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
std::vector<u8> buffer(size);
rewind(fp);
fread(&buffer[0], sizeof(std::vector<u8>::value_type), buffer.size(), fp);
elf_hash = std::make_optional(xxh::xxhash<64>(buffer));
break;
}
}
return {serial, elf_hash};
}
std::pair<ExtractorErrorCode, std::optional<ISOMetadata>> validate(
const std::filesystem::path& extracted_iso_path) {
if (!std::filesystem::exists(extracted_iso_path / "DGO")) {
fmt::print(stderr, "ERROR: input folder doesn't have a DGO folder. Is this the right input?\n");
return {ExtractorErrorCode::VALIDATION_BAD_EXTRACTION, std::nullopt};
std::tuple<std::optional<ISOMetadata>, ExtractorErrorCode> validate(
const fs::path& extracted_iso_path,
const xxh::hash64_t expected_hash,
const int expected_num_files) {
if (!fs::exists(extracted_iso_path / "DGO")) {
lg::error("input folder doesn't have a DGO folder. Is this the right input?");
return {std::nullopt, ExtractorErrorCode::VALIDATION_BAD_EXTRACTION};
}
std::optional<ExtractorErrorCode> error_code;
std::optional<std::string> serial = std::nullopt;
std::optional<xxh::hash64_t> elf_hash = std::nullopt;
std::tie(serial, elf_hash) = findElfFile(extracted_iso_path);
// - XOR all hashes together and hash the result. This makes the ordering of the hashes (aka
// files) irrelevant
xxh::hash64_t combined_hash = 0;
int filec = 0;
for (auto const& dir_entry : std::filesystem::recursive_directory_iterator(extracted_iso_path)) {
if (dir_entry.is_regular_file()) {
auto buffer = file_util::read_binary_file(dir_entry.path().string());
auto hash = xxh::xxhash<64>(buffer);
combined_hash ^= hash;
filec++;
}
}
xxh::hash64_t contents_hash = xxh::xxhash<64>({combined_hash});
const auto [serial, elf_hash] = findElfFile(extracted_iso_path);
if (!serial || !elf_hash) {
fmt::print(stderr, "ERROR: Unable to locate a Serial/ELF file!\n");
if (!error_code.has_value()) {
error_code = std::make_optional(ExtractorErrorCode::VALIDATION_CANT_LOCATE_ELF);
}
lg::error("Unable to locate a Serial/ELF file!");
// No point in continuing here
return {*error_code, std::nullopt};
}
// Find the game in our tracking database
std::optional<ISOMetadata> meta_res = std::nullopt;
if (auto dbEntry = isoDatabase.find(serial.value()); dbEntry == isoDatabase.end()) {
fmt::print(stderr, "ERROR: Serial '{}' not found in the validation database\n", serial.value());
if (!error_code.has_value()) {
error_code = std::make_optional(ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB);
}
} else {
auto& metaMap = dbEntry->second;
auto meta_entry = metaMap.find(elf_hash.value());
if (meta_entry == metaMap.end()) {
fmt::print(stderr,
"ERROR: ELF Hash '{}' not found in the validation database, is this a new or "
"modified version of the same game?\n",
elf_hash.value());
if (!error_code.has_value()) {
error_code = std::make_optional(ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB);
}
} else {
meta_res = std::make_optional<ISOMetadata>(meta_entry->second);
const auto& meta = *meta_res;
// Print out some information
fmt::print("Detected Game Metadata:\n");
fmt::print("\tDetected - {}\n", meta.canonical_name);
fmt::print("\tRegion - {}\n", meta.region);
fmt::print("\tSerial - {}\n", dbEntry->first);
fmt::print("\tUses Decompiler Config - {}\n", meta.decomp_config);
// - Number of Files
if (meta.num_files != filec) {
fmt::print(stderr,
"ERROR: Extracted an unexpected number of files. Expected '{}', Actual '{}'\n",
meta.num_files, filec);
if (!error_code.has_value()) {
error_code =
std::make_optional(ExtractorErrorCode::VALIDATION_INCORRECT_EXTRACTION_COUNT);
}
}
// Check the ISO Hash
if (meta.contents_hash != contents_hash) {
fmt::print(stderr,
"ERROR: Overall ISO content's hash does not match. Expected '{}', Actual '{}'\n",
meta.contents_hash, contents_hash);
}
}
}
// Finally, return the result
if (error_code.has_value()) {
// Generate the map entry to make things simple, just convienance
if (error_code.value() == ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB) {
fmt::print(
"If this is a new release or version that should be supported, consider adding the "
"following serial entry to the database:\n");
fmt::print(
"\t'{{\"{}\", {{{{{}U, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, "
"\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\"}}}}}}}}'\n",
serial.value(), elf_hash.value(), filec, contents_hash);
} else if (error_code.value() == ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB) {
fmt::print(
"If this is a new release or version that should be supported, consider adding the "
"following ELF entry to the database under the '{}' serial:\n",
serial.value());
fmt::print(
"\t'{{{}, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, "
"\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\"}}}}'\n",
elf_hash.value(), filec, contents_hash);
} else {
fmt::print(stderr,
"Validation has failed to match with expected values, see the above errors for "
"specifics. This may be an error in the validation database!\n");
}
return {*error_code, std::nullopt};
}
return {ExtractorErrorCode::SUCCESS, meta_res};
}
std::pair<ExtractorErrorCode, std::optional<ISOMetadata>> validate(
const IsoFile& iso_file,
const std::filesystem::path& extracted_iso_path) {
if (!std::filesystem::exists(extracted_iso_path / "DGO")) {
fmt::print(stderr, "ERROR: input folder doesn't have a DGO folder. Is this the right input?\n");
return {ExtractorErrorCode::VALIDATION_BAD_EXTRACTION, std::nullopt};
}
std::optional<ExtractorErrorCode> error_code;
std::optional<std::string> serial = std::nullopt;
std::optional<xxh::hash64_t> elf_hash = std::nullopt;
std::tie(serial, elf_hash) = findElfFile(extracted_iso_path);
// - XOR all hashes together and hash the result. This makes the ordering of the hashes (aka
// files) irrelevant
xxh::hash64_t combined_hash = 0;
for (const auto& hash : iso_file.hashes) {
combined_hash ^= hash;
}
xxh::hash64_t contents_hash = xxh::xxhash<64>({combined_hash});
if (!serial || !elf_hash) {
fmt::print(stderr, "ERROR: Unable to locate a Serial/ELF file!\n");
if (!error_code.has_value()) {
error_code = std::make_optional(ExtractorErrorCode::VALIDATION_CANT_LOCATE_ELF);
}
// No point in continuing here
return {*error_code, std::nullopt};
}
// Find the game in our tracking database
std::optional<ISOMetadata> meta_res = std::nullopt;
if (auto dbEntry = isoDatabase.find(serial.value()); dbEntry == isoDatabase.end()) {
fmt::print(stderr, "ERROR: Serial '{}' not found in the validation database\n", serial.value());
if (!error_code.has_value()) {
error_code = std::make_optional(ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB);
}
} else {
auto& metaMap = dbEntry->second;
auto meta_entry = metaMap.find(elf_hash.value());
if (meta_entry == metaMap.end()) {
fmt::print(stderr,
"ERROR: ELF Hash '{}' not found in the validation database, is this a new or "
"modified version of the same game?\n",
elf_hash.value());
if (!error_code.has_value()) {
error_code = std::make_optional(ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB);
}
} else {
meta_res = std::make_optional<ISOMetadata>(meta_entry->second);
const auto& meta = *meta_res;
// Print out some information
fmt::print("Detected Game Metadata:\n");
fmt::print("\tDetected - {}\n", meta.canonical_name);
fmt::print("\tRegion - {}\n", meta.region);
fmt::print("\tSerial - {}\n", dbEntry->first);
fmt::print("\tUses Decompiler Config - {}\n", meta.decomp_config);
// - Number of Files
if (meta.num_files != iso_file.files_extracted) {
fmt::print(stderr,
"ERROR: Extracted an unexpected number of files. Expected '{}', Actual '{}'\n",
meta.num_files, iso_file.files_extracted);
if (!error_code.has_value()) {
error_code =
std::make_optional(ExtractorErrorCode::VALIDATION_INCORRECT_EXTRACTION_COUNT);
}
}
// Check the ISO Hash
if (meta.contents_hash != contents_hash) {
fmt::print(stderr,
"ERROR: Overall ISO content's hash does not match. Expected '{}', Actual '{}'\n",
meta.contents_hash, contents_hash);
}
}
}
// Finally, return the result
if (error_code.has_value()) {
// Generate the map entry to make things simple, just convienance
if (error_code.value() == ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB) {
fmt::print(
"If this is a new release or version that should be supported, consider adding the "
"following serial entry to the database:\n");
fmt::print(
"\t'{{\"{}\", {{{{{}U, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, "
"\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\"}}}}}}}}'\n",
serial.value(), elf_hash.value(), iso_file.files_extracted, contents_hash);
} else if (error_code.value() == ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB) {
fmt::print(
"If this is a new release or version that should be supported, consider adding the "
"following ELF entry to the database under the '{}' serial:\n",
serial.value());
fmt::print(
"\t'{{{}, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, "
"\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\"}}}}'\n",
elf_hash.value(), iso_file.files_extracted, contents_hash);
} else {
fmt::print(stderr,
"Validation has failed to match with expected values, see the above errors for "
"specifics. This may be an error in the validation database!\n");
}
return {*error_code, std::nullopt};
}
return {ExtractorErrorCode::SUCCESS, meta_res};
}
std::optional<ISOMetadata> determineRelease(const std::filesystem::path& jak1_input_files) {
std::optional<std::string> serial = std::nullopt;
std::optional<xxh::hash64_t> elf_hash = std::nullopt;
std::tie(serial, elf_hash) = findElfFile(jak1_input_files);
if (!serial || !elf_hash) {
return std::nullopt;
return {std::nullopt, ExtractorErrorCode::VALIDATION_CANT_LOCATE_ELF};
}
// Find the game in our tracking database
auto dbEntry = isoDatabase.find(serial.value());
if (dbEntry == isoDatabase.end()) {
return std::nullopt;
} else {
auto& metaMap = dbEntry->second;
auto meta_entry = metaMap.find(elf_hash.value());
if (meta_entry == metaMap.end()) {
return std::nullopt;
} else {
return std::make_optional(meta_entry->second);
}
lg::error("Serial '{}' not found in the validation database", serial.value());
log_potential_new_db_entry(ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB,
serial.value(), elf_hash.value(), expected_num_files, expected_hash);
return {std::nullopt, ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB};
}
auto& metaMap = dbEntry->second;
auto meta_entry = metaMap.find(elf_hash.value());
if (meta_entry == metaMap.end()) {
lg::error(
"ELF Hash '{}' not found in the validation database, is this a new or "
"modified version of the same game?",
elf_hash.value());
log_potential_new_db_entry(ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB, serial.value(),
elf_hash.value(), expected_num_files, expected_hash);
return {std::nullopt, ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB};
}
auto version_info = meta_entry->second;
// Print out some information
lg::info("Detected Game Metadata:");
lg::info("\tDetected - {}", version_info.canonical_name);
lg::info("\tRegion - {}", version_info.region);
lg::info("\tSerial - {}", dbEntry->first);
lg::info("\tUses Decompiler Config - {}", version_info.decomp_config);
// - Number of Files
if (version_info.num_files != expected_num_files) {
lg::error("Extracted an unexpected number of files. Expected '{}', Actual '{}'",
version_info.num_files, expected_num_files);
return {std::nullopt, ExtractorErrorCode::VALIDATION_INCORRECT_EXTRACTION_COUNT};
}
// Check the ISO Hash
if (version_info.contents_hash != expected_hash) {
lg::error("Overall ISO content's hash does not match. Expected '{}', Actual '{}'",
version_info.contents_hash, expected_hash);
return {std::nullopt, ExtractorErrorCode::VALIDATION_FILE_CONTENTS_UNEXPECTED};
}
return {
std::make_optional(version_info),
ExtractorErrorCode::SUCCESS,
};
}
void decompile(std::filesystem::path jak1_input_files) {
void decompile(const fs::path& iso_data_path, const std::string& data_subfolder) {
using namespace decompiler;
// Determine which config to use from the database
auto meta = determineRelease(jak1_input_files);
std::string decomp_config = "jak1_ntsc_black_label";
if (meta.has_value()) {
decomp_config = meta.value().decomp_config;
fmt::print("INFO: Automatically detected decompiler config, using - {}\n", decomp_config);
}
const auto version_info = get_version_info_or_default(iso_data_path);
Config config = read_config_file((file_util::get_jak_project_dir() / "decompiler" / "config" /
fmt::format("{}.jsonc", decomp_config))
fmt::format("{}.jsonc", version_info.decomp_config))
.string(),
{});
std::vector<std::filesystem::path> dgos, objs;
std::vector<fs::path> dgos, objs;
// grab all DGOS we need (level + common)
// TODO - Jak 2 - jak 1 specific code?
for (const auto& dgo_name : config.dgo_names) {
std::string common_name = "GAME.CGO";
if (dgo_name.length() > 3 && dgo_name.substr(dgo_name.length() - 3) == "DGO") {
// ends in DGO, it's a level
dgos.push_back(jak1_input_files / dgo_name);
dgos.push_back(iso_data_path / dgo_name);
} else if (dgo_name.length() >= common_name.length() &&
dgo_name.substr(dgo_name.length() - common_name.length()) == common_name) {
// it's COMMON.CGO, we need that too.
dgos.push_back(jak1_input_files / dgo_name);
dgos.push_back(iso_data_path / dgo_name);
}
}
@ -385,15 +130,15 @@ void decompile(std::filesystem::path jak1_input_files) {
for (const auto& obj_name : config.object_file_names) {
if (obj_name.length() > 3 && obj_name.substr(obj_name.length() - 3) == "TXT") {
// ends in TXT
objs.push_back(jak1_input_files / obj_name);
objs.push_back(iso_data_path / obj_name);
}
}
// set up objects
ObjectFileDB db(dgos, std::filesystem::path(config.obj_file_name_map_file), objs, {}, config);
ObjectFileDB db(dgos, fs::path(config.obj_file_name_map_file), objs, {}, config);
// save object files
auto out_folder = file_util::get_jak_project_dir() / "decompiler_out" / "jak1";
auto out_folder = file_util::get_jak_project_dir() / "decompiler_out" / data_subfolder;
auto raw_obj_folder = out_folder / "raw_obj";
file_util::create_dir_if_needed(raw_obj_folder);
db.dump_raw_objects(raw_obj_folder);
@ -422,7 +167,7 @@ void decompile(std::filesystem::path jak1_input_files) {
db.process_tpages(tex_db, textures_out));
// texture replacements
auto replacements_path = file_util::get_jak_project_dir() / "texture_replacements";
if (std::filesystem::exists(replacements_path)) {
if (fs::exists(replacements_path)) {
tex_db.replace_textures(replacements_path);
}
@ -444,27 +189,34 @@ void decompile(std::filesystem::path jak1_input_files) {
}
}
void compile(std::filesystem::path extracted_iso_path) {
ExtractorErrorCode compile(const fs::path& iso_data_path, const std::string& data_subfolder) {
// Determine which config to use from the database
const auto version_info = get_version_info_or_default(iso_data_path);
Compiler compiler;
compiler.make_system().set_constant("*iso-data*", absolute(extracted_iso_path).string());
compiler.make_system().set_constant("*iso-data*", absolute(iso_data_path).string());
compiler.make_system().set_constant("*use-iso-data-path*", true);
auto buildinfo_path = (extracted_iso_path / "buildinfo.json").string();
auto bi = parse_commented_json(file_util::read_text_file(buildinfo_path), buildinfo_path);
auto all_flags = bi.at("flags").get<std::vector<std::string>>();
int flags = 0;
for (const auto& n : all_flags) {
if (auto it = sGameIsoFlagNames.find(n); it != sGameIsoFlagNames.end()) {
for (const auto& flag : version_info.flags) {
if (auto it = sGameIsoFlagNames.find(flag); it != sGameIsoFlagNames.end()) {
flags |= it->second;
}
}
compiler.make_system().set_constant("*jak1-full-game*", !(flags & FLAG_JAK1_BLACK_LABEL));
// TODO - jak2 - BAD!
// TODO - if this directory is failing, very bad (non-existant) error message
compiler.make_system().load_project_file(
(file_util::get_jak_project_dir() / "goal_src" / "jak1" / "game.gp").string());
if (version_info.game_name == "jak1") {
compiler.make_system().set_constant("*jak1-full-game*", !(flags & FLAG_JAK1_BLACK_LABEL));
}
auto project_path = file_util::get_jak_project_dir() / "goal_src" / data_subfolder / "game.gp";
if (!fs::exists(project_path)) {
return ExtractorErrorCode::COMPILATION_BAD_PROJECT_PATH;
}
compiler.make_system().load_project_file(project_path.string());
compiler.run_front_end_on_string("(mi)");
return ExtractorErrorCode::SUCCESS;
}
void launch_game() {
@ -472,8 +224,8 @@ void launch_game() {
}
int main(int argc, char** argv) {
std::filesystem::path data_dir_path;
std::filesystem::path project_path_override;
fs::path input_file_path;
fs::path project_path_override;
bool flag_runall = false;
bool flag_extract = false;
bool flag_fail_on_validation = false;
@ -481,20 +233,30 @@ int main(int argc, char** argv) {
bool flag_compile = false;
bool flag_play = false;
bool flag_folder = false;
std::string game_name = "jak1";
#ifdef _WIN32
auto args = get_widechar_cli_args();
std::vector<char*> string_ptrs;
for (auto& str : args) {
string_ptrs.push_back(str.data());
}
argv = string_ptrs.data();
#endif
lg::initialize();
CLI::App app{"OpenGOAL Level Extraction Tool"};
app.add_option("game-files-path", data_dir_path,
app.add_option("game-files-path", input_file_path,
"The path to the folder with the ISO extracted or the ISO itself")
->check(CLI::ExistingPath)
->required();
app.add_option("--proj-path", project_path_override,
"Explicitly set the location of the 'data/' folder")
->check(CLI::ExistingPath);
"Explicitly set the location of the 'data/' folder");
app.add_flag("-g,--game", game_name, "Specify the game name, defaults to 'jak1'");
app.add_flag("-a,--all", flag_runall, "Run all steps, from extraction to playing the game");
app.add_flag("-e,--extract", flag_extract, "Extract the ISO");
app.add_flag("-v,--validate", flag_fail_on_validation, "Fail on Validation Errors");
app.add_flag("-v,--validate", flag_fail_on_validation,
"Fail on validation errors during extraction");
app.add_flag("-d,--decompile", flag_decompile, "Decompile the game data");
app.add_flag("-c,--compile", flag_compile, "Compile the game");
app.add_flag("-p,--play", flag_play, "Play the game");
@ -502,7 +264,7 @@ int main(int argc, char** argv) {
app.validate_positionals();
CLI11_PARSE(app, argc, argv);
fmt::print("Working Directory - {}\n", std::filesystem::current_path().string());
lg::info("Working Directory - {}", fs::current_path().string());
// If no flag is set, we default to running everything
if (!flag_extract && !flag_decompile && !flag_compile && !flag_play) {
@ -516,90 +278,111 @@ int main(int argc, char** argv) {
flag_play = true;
}
// todo: print revision here.
// - SETUP
decompiler::init_opcode_info();
if (!project_path_override.empty()) {
setup_global_decompiler_stuff(std::make_optional(project_path_override));
if (!fs::exists(project_path_override)) {
lg::error("Error: project path override '{}' does not exist", project_path_override.string());
return static_cast<int>(ExtractorErrorCode::INVALID_CLI_INPUT);
}
file_util::setup_project_path(project_path_override);
} else {
setup_global_decompiler_stuff(std::nullopt);
file_util::setup_project_path({});
}
std::filesystem::path path_to_iso_files = file_util::get_jak_project_dir() / "iso_data" / "_temp";
fs::path iso_data_path;
// make sure the input looks right
if (!std::filesystem::exists(data_dir_path)) {
fmt::print("Error: input data path {} does not exist\n", data_dir_path.string());
return 1;
// - INPUT VALIDATION
if (!fs::exists(input_file_path)) {
lg::error("Error: input game file path '{}' does not exist", input_file_path.string());
return static_cast<int>(ExtractorErrorCode::INVALID_CLI_INPUT);
}
if (data_subfolders.count(game_name) == 0) {
lg::error("Error: input game name '{}' is not valid", game_name);
return static_cast<int>(ExtractorErrorCode::INVALID_CLI_INPUT);
}
std::string data_subfolder = data_subfolders[game_name];
if (flag_extract) {
if (data_dir_path != path_to_iso_files) {
// we extract to a temporary location because we don't know what we're extracting yet!
fs::path temp_iso_extract_location = file_util::get_jak_project_dir() / "iso_data" / "_temp";
if (input_file_path != temp_iso_extract_location) {
// in case input is also output, don't just wipe everything (weird)
std::filesystem::remove_all(path_to_iso_files);
fs::remove_all(temp_iso_extract_location);
}
std::filesystem::create_directories(path_to_iso_files);
fs::create_directories(temp_iso_extract_location);
int flags = 0;
if (std::filesystem::is_regular_file(data_dir_path)) {
// it's a file, normalize extension case and verify it's an ISO file
std::string ext = data_dir_path.extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(),
[](unsigned char c) { return std::tolower(c); });
if (ext != ".iso") {
fmt::print(stderr, "ERROR: Provided game data path contains a file that isn't a .ISO!");
return static_cast<int>(ExtractorErrorCode::EXTRACTION_INVALID_ISO_PATH);
if (fs::is_regular_file(input_file_path)) {
// If it's a file, then it better be an iso file
const auto [iso_ok, iso_code] = is_iso_file(input_file_path);
if (!iso_ok) {
return static_cast<int>(iso_code);
}
// make sure the .iso is greater than 1GB in size
// to-do: verify game header data as well
if (std::filesystem::file_size(data_dir_path) < 1000000000) {
fmt::print(
stderr,
"ERROR: Provided game data file appears to be too small or corrupted! Size is {}",
std::filesystem::file_size(data_dir_path));
return static_cast<int>(ExtractorErrorCode::EXTRACTION_ISO_UNEXPECTED_SIZE);
// Extract to the temporary location
const auto iso_file = extract_files(input_file_path, temp_iso_extract_location);
// Get hash and file count
const auto [hash, file_count] = calculate_extraction_hash(iso_file);
// Validate the result to determine the release
const auto [version_info, validate_code] =
validate(temp_iso_extract_location, hash, file_count);
if (validate_code == ExtractorErrorCode::VALIDATION_BAD_EXTRACTION ||
(flag_fail_on_validation && validate_code != ExtractorErrorCode::SUCCESS)) {
return static_cast<int>(validate_code);
}
// Finalize the folder name now that we know where it should go
if (!version_info) {
lg::error("could not verify release, so not finalizing iso_data, leaving in '_temp'");
iso_data_path = temp_iso_extract_location;
} else {
// We know the version since we just extracted it, so the user didn't need to provide this
// explicitly
data_subfolder = data_subfolders[version_info->game_name];
iso_data_path = file_util::get_jak_project_dir() / "iso_data" / data_subfolder;
if (fs::exists(iso_data_path)) {
fs::remove_all(iso_data_path);
}
auto iso_file = extract_files(data_dir_path, path_to_iso_files);
auto validation_res = validate(iso_file, path_to_iso_files);
flags = validation_res.second->flags;
if (validation_res.first == ExtractorErrorCode::VALIDATION_BAD_EXTRACTION) {
// We fail here regardless of the flag
return static_cast<int>(validation_res.first);
} else if (flag_fail_on_validation && validation_res.first != ExtractorErrorCode::SUCCESS) {
return static_cast<int>(validation_res.first);
// std::filesystem doesn't have a rename for dirs...
fs::copy(temp_iso_extract_location, iso_data_path, fs::copy_options::recursive);
fs::remove_all(temp_iso_extract_location);
}
} else if (std::filesystem::is_directory(data_dir_path)) {
} else if (fs::is_directory(input_file_path)) {
if (!flag_folder) {
// if we didn't request a folder explicitly, but we got one, assume something went wrong.
fmt::print("Error: got a folder, but didn't get folder flag\n");
return static_cast<int>(ExtractorErrorCode::VALIDATION_BAD_ISO_CONTENTS);
lg::error("got a folder, but didn't get folder flag");
return static_cast<int>(ExtractorErrorCode::INVALID_CLI_INPUT);
}
path_to_iso_files = data_dir_path;
if (std::filesystem::exists(path_to_iso_files / "buildinfo.json")) {
std::filesystem::remove(path_to_iso_files / "buildinfo.json");
}
auto validation_res = validate(path_to_iso_files);
flags = validation_res.second->flags;
iso_data_path = input_file_path;
// Get hash and file count
const auto [hash, file_count] = calculate_extraction_hash(iso_data_path);
// Validate
auto [version_info, code] = validate(iso_data_path, hash, file_count);
}
// write out a json file with some metadata for the game
nlohmann::json buildinfo_json;
auto flags_json = nlohmann::json::array();
for (const auto& [n, f] : sGameIsoFlagNames) {
if (flags & f) {
flags_json.push_back(n);
}
if (fs::exists(iso_data_path / "buildinfo.json")) {
fs::remove(iso_data_path / "buildinfo.json");
}
buildinfo_json["flags"] = flags_json;
// something tells me a ps2 game is unlikely to have a json file in root
file_util::write_text_file((path_to_iso_files / "buildinfo.json").string(),
buildinfo_json.dump(2));
const auto [serial, elf_hash] = findElfFile(iso_data_path);
BuildInfo build_info;
if (serial.has_value()) {
build_info.serial = serial.value();
}
if (elf_hash.has_value()) {
build_info.elf_hash = elf_hash.value();
}
const nlohmann::json json_data{build_info};
file_util::write_text_file((iso_data_path / "buildinfo.json").string(), json_data.dump(2));
} else {
// If we did not extract, we have no clue what game the user is trying to decompile / compile
// this is why the user has to specify this!
iso_data_path = file_util::get_jak_project_dir() / "iso_data" / data_subfolder;
}
if (flag_decompile) {
try {
decompile(path_to_iso_files);
decompile(iso_data_path, data_subfolder);
} catch (std::exception& e) {
lg::error("Error during decompile: {}", e.what());
return static_cast<int>(ExtractorErrorCode::DECOMPILATION_GENERIC_ERROR);
@ -607,7 +390,7 @@ int main(int argc, char** argv) {
}
if (flag_compile) {
compile(path_to_iso_files);
compile(iso_data_path, data_subfolder);
}
if (flag_play) {

View File

@ -203,7 +203,7 @@ void extract_common(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::string& dgo_name,
bool dump_levels,
const std::filesystem::path& output_folder) {
const fs::path& output_folder) {
if (db.obj_files_by_dgo.count(dgo_name) == 0) {
lg::warn("Skipping common extract for {} because the DGO was not part of the input", dgo_name);
return;
@ -239,7 +239,7 @@ void extract_from_level(const ObjectFileDB& db,
const DecompileHacks& hacks,
bool dump_level,
bool extract_collision,
const std::filesystem::path& output_folder) {
const fs::path& output_folder) {
if (db.obj_files_by_dgo.count(dgo_name) == 0) {
lg::warn("Skipping extract for {} because the DGO was not part of the input", dgo_name);
return;
@ -272,7 +272,7 @@ void extract_all_levels(const ObjectFileDB& db,
const DecompileHacks& hacks,
bool debug_dump_level,
bool extract_collision,
const std::filesystem::path& output_path) {
const fs::path& output_path) {
extract_common(db, tex_db, common_name, debug_dump_level, output_path);
SimpleThreadGroup threads;
threads.run(

View File

@ -1,6 +1,5 @@
#pragma once
#include <filesystem>
#include <vector>
#include "common/math/Vector.h"
@ -17,5 +16,5 @@ void extract_all_levels(const ObjectFileDB& db,
const DecompileHacks& hacks,
bool debug_dump_level,
bool extract_collision,
const std::filesystem::path& path);
const fs::path& path);
} // namespace decompiler

View File

@ -10,6 +10,7 @@
#include "common/util/diff.h"
#include "common/util/os.h"
#include "common/versions.h"
#include <common/util/unicode_util.h>
#include "ObjectFile/ObjectFileDB.h"
#include "decompiler/data/TextureDB.h"
@ -17,6 +18,15 @@
#include "decompiler/level_extractor/extract_level.h"
int main(int argc, char** argv) {
#ifdef _WIN32
auto args = get_widechar_cli_args();
std::vector<char*> string_ptrs;
for (auto& str : args) {
string_ptrs.push_back(str.data());
}
argv = string_ptrs.data();
#endif
Timer decomp_timer;
fmt::print("[Mem] Top of main: {} MB\n", get_peak_rss() / (1024 * 1024));
@ -87,8 +97,8 @@ int main(int argc, char** argv) {
}
// std::string in_folder = file_util::combine_path(argv[2], config.game_name);
std::filesystem::path in_folder = std::filesystem::path(argv[2]) / config.game_name;
std::filesystem::path out_folder = std::filesystem::path(argv[3]) / config.game_name;
fs::path in_folder = fs::path(argv[2]) / config.game_name;
fs::path out_folder = fs::path(argv[3]) / config.game_name;
// Verify the in_folder is correct
if (!exists(in_folder)) {
@ -105,7 +115,7 @@ int main(int argc, char** argv) {
in_folder.string(), config.expected_elf_name);
}
std::vector<std::filesystem::path> dgos, objs, strs;
std::vector<fs::path> dgos, objs, strs;
for (const auto& dgo_name : config.dgo_names) {
dgos.push_back(in_folder / dgo_name);
}
@ -129,7 +139,7 @@ int main(int argc, char** argv) {
// build file database
lg::info("Setting up object file DB...");
ObjectFileDB db(dgos, std::filesystem::path(config.obj_file_name_map_file), objs, strs, config);
ObjectFileDB db(dgos, fs::path(config.obj_file_name_map_file), objs, strs, config);
fmt::print("[Mem] After DB setup: {} MB\n", get_peak_rss() / (1024 * 1024));
@ -220,7 +230,7 @@ int main(int argc, char** argv) {
fmt::print("[Mem] After textures: {} MB\n", get_peak_rss() / (1024 * 1024));
auto replacements_path = file_util::get_jak_project_dir() / "texture_replacements";
if (std::filesystem::exists(replacements_path)) {
if (fs::exists(replacements_path)) {
tex_db.replace_textures(replacements_path);
}

View File

@ -6,7 +6,6 @@
#include "gfx.h"
#include <cstdio>
#include <filesystem>
#include <functional>
#include "display.h"
@ -57,9 +56,9 @@ GfxSettings g_settings;
// TODO serialize
void LoadSettings() {
const auto filename = file_util::get_file_path({GAME_CONFIG_DIR_NAME, SETTINGS_GFX_FILE_NAME});
if (std::filesystem::exists(filename)) {
if (fs::exists(filename)) {
// this is just wrong LOL
FILE* fp = fopen(filename.c_str(), "rb");
FILE* fp = file_util::open_file(filename.c_str(), "rb");
lg::info("Found graphics configuration file. Checking version.");
u64 version;
fread(&version, sizeof(u64), 1, fp);
@ -78,7 +77,7 @@ void LoadSettings() {
void SaveSettings() {
const auto filename = file_util::get_file_path({GAME_CONFIG_DIR_NAME, SETTINGS_GFX_FILE_NAME});
file_util::create_dir_if_needed(file_util::get_file_path({GAME_CONFIG_DIR_NAME}));
FILE* fp = fopen(filename.c_str(), "wb");
FILE* fp = file_util::open_file(filename.c_str(), "wb");
fwrite(&g_settings, sizeof(GfxSettings), 1, fp);
fclose(fp);
lg::info("Saved graphics configuration file.");

View File

@ -16,7 +16,7 @@ std::string uppercase_string(const std::string& s) {
}
} // namespace
Loader::Loader(const std::filesystem::path& base_path) : m_base_path(base_path) {
Loader::Loader(const fs::path& base_path) : m_base_path(base_path) {
m_loader_thread = std::thread(&Loader::loader_thread, this);
m_loader_stages = make_loader_stages();
}
@ -408,4 +408,4 @@ std::optional<MercRef> Loader::get_merc_model(const char* model_name) {
} else {
return std::nullopt;
}
}
}

View File

@ -1,11 +1,11 @@
#pragma once
#include <condition_variable>
#include <filesystem>
#include <mutex>
#include <thread>
#include "common/custom_data/Tfrag3Data.h"
#include "common/util/FileUtil.h"
#include "common/util/Timer.h"
#include "game/graphics/opengl_renderer/loader/common.h"
@ -16,7 +16,7 @@ class Loader {
public:
static constexpr float TIE_LOAD_BUDGET = 1.5f;
static constexpr float SHARED_TEXTURE_LOAD_BUDGET = 3.f;
Loader(const std::filesystem::path& base_path);
Loader(const fs::path& base_path);
~Loader();
void update(TexturePool& tex_pool);
void update_blocking(TexturePool& tex_pool);
@ -52,5 +52,5 @@ class Loader {
std::vector<std::string> m_desired_levels;
std::vector<std::unique_ptr<LoaderStage>> m_loader_stages;
std::filesystem::path m_base_path;
fs::path m_base_path;
};

View File

@ -411,7 +411,7 @@ void mkdir_path(u32 filepath) {
u64 filepath_exists(u32 filepath) {
auto filepath_str = std::string(Ptr<String>(filepath).c()->data());
if (std::filesystem::exists(filepath_str)) {
if (fs::exists(filepath_str)) {
return s7.offset + true_symbol_offset(g_game_version);
}
return s7.offset;
@ -470,4 +470,4 @@ void vif_interrupt_callback() {
*/
u32 offset_of_s7() {
return s7.offset;
}
}

View File

@ -117,8 +117,7 @@ u32 mc_checksum(Ptr<u8> data, s32 size) {
*/
bool file_is_present(int id, int bank = 0) {
auto bankname = file_util::get_user_memcard_dir() / filename[4 + id * 2 + bank];
if (!std::filesystem::exists(bankname) ||
std::filesystem::file_size(bankname) < BANK_TOTAL_SIZE) {
if (!fs::exists(bankname) || fs::file_size(bankname) < BANK_TOTAL_SIZE) {
// file doesn't exist, or size is bad. we do not want to open files that will crash on read!
return false;
}
@ -130,7 +129,7 @@ bool file_is_present(int id, int bank = 0) {
// file exists. but let's see if it's an empty one.
// this prevents the game from reading a bank but classifying it as corrupt data.
// which a file full of zeros logically is.
auto fp = fopen(bankname.c_str(), "rb");
auto fp = file_util::open_file(bankname.c_str(), "rb");
// we can actually just check if the save count is over zero...
u32 savecount = 0;
@ -214,7 +213,7 @@ void pc_game_save_synch() {
mc_print("open {} for saving", filename[op.param2 * 2 + 4 + p4]);
auto save_path = file_util::get_user_memcard_dir() / filename[op.param2 * 2 + 4 + p4];
file_util::create_dir_if_needed_for_file(save_path.string());
auto fd = fopen(save_path.string().c_str(), "wb");
auto fd = file_util::open_file(save_path.string().c_str(), "wb");
mc_print("synchronous save file open took {:.2f}ms\n", mc_timer.getMs());
if (fd) {
// cb_openedsave //
@ -282,12 +281,12 @@ void pc_game_load_open_file(FILE* fd) {
if (fclose(fd) == 0) {
// cb_closedload //
// added : check if aux bank exists
if (p2 < 1 && std::filesystem::exists(file_util::get_user_memcard_dir() /
filename[op.param2 * 2 + 4 + p2 + 1])) {
if (p2 < 1 &&
fs::exists(file_util::get_user_memcard_dir() / filename[op.param2 * 2 + 4 + p2 + 1])) {
p2++;
mc_print("reading next save bank {}", filename[op.param2 * 2 + 4 + p2]);
auto new_bankname = file_util::get_user_memcard_dir() / filename[op.param2 * 2 + 4 + p2];
auto new_fd = fopen(new_bankname.string().c_str(), "rb");
auto new_fd = file_util::open_file(new_bankname.string().c_str(), "rb");
pc_game_load_open_file(new_fd);
} else {
// let's verify the data.
@ -404,7 +403,7 @@ void pc_game_load_synch() {
mc_print("opening save file {}", filename[op.param2 * 2 + 4]);
auto path = file_util::get_user_memcard_dir() / filename[op.param2 * 2 + 4];
auto fd = fopen(path.string().c_str(), "rb");
auto fd = file_util::open_file(path.string().c_str(), "rb");
pc_game_load_open_file(fd);
mc_print("synchronous load took {:.2f}ms\n", mc_timer.getMs());

View File

@ -8,7 +8,6 @@
#include "kmachine.h"
#include <filesystem>
#include <string>
#include "common/log/log.h"

View File

@ -11,6 +11,7 @@
#include "common/util/FileUtil.h"
#include "common/util/os.h"
#include "common/versions.h"
#include <common/util/unicode_util.h>
#include "game/discord.h"
@ -39,6 +40,15 @@ void setup_logging(bool verbose) {
* Entry point for the game.
*/
int main(int argc, char** argv) {
#ifdef _WIN32
auto args = get_widechar_cli_args();
std::vector<char*> string_ptrs;
for (auto& str : args) {
string_ptrs.push_back(str.data());
}
argv = string_ptrs.data();
#endif
// Figure out if the CPU has AVX2 to enable higher performance AVX2 versions of functions.
setup_cpu_info();
// If the CPU doesn't have AVX, GOAL code won't work and we exit.
@ -50,7 +60,7 @@ int main(int argc, char** argv) {
// parse arguments
bool verbose = false;
bool disable_avx2 = false;
std::optional<std::filesystem::path> project_path_override = std::nullopt;
std::optional<fs::path> project_path_override = std::nullopt;
for (int i = 1; i < argc; i++) {
if (std::string("-v") == argv[i]) {
verbose = true;
@ -62,7 +72,7 @@ int main(int argc, char** argv) {
}
if (std::string("-proj-path") == argv[i] && i + 1 < argc) {
project_path_override = std::make_optional(std::filesystem::path(argv[i + 1]));
project_path_override = std::make_optional(fs::path(argv[i + 1]));
}
}

View File

@ -20,7 +20,6 @@
#include "fake_iso.h"
#include <cstring>
#include <filesystem>
#include "isocommon.h"
#include "overlord.h"
@ -98,8 +97,8 @@ void fake_iso_init_globals() {
int FS_Init(u8* buffer) {
(void)buffer;
for (const auto& f : std::filesystem::directory_iterator(
file_util::get_jak_project_dir() / "out" / game_version_names[g_game_version] / "iso")) {
for (const auto& f : fs::directory_iterator(file_util::get_jak_project_dir() / "out" /
game_version_names[g_game_version] / "iso")) {
if (f.is_regular_file()) {
ASSERT(fake_iso_entry_count < MAX_ISO_FILES);
FakeIsoEntry* e = &fake_iso_entries[fake_iso_entry_count];
@ -175,7 +174,7 @@ static const char* get_file_path(FileRecord* fr) {
uint32_t FS_GetLength(FileRecord* fr) {
const char* path = get_file_path(fr);
file_util::assert_file_exists(path, "fake_iso FS_GetLength");
FILE* fp = fopen(path, "rb");
FILE* fp = file_util::open_file(path, "rb");
ASSERT(fp);
fseek(fp, 0, SEEK_END);
uint32_t len = ftell(fp);
@ -265,7 +264,7 @@ uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len) {
u32 offset_into_file = SECTOR_SIZE * fd->location;
const char* path = get_file_path(fd->fr);
FILE* fp = fopen(path, "rb");
FILE* fp = file_util::open_file(path, "rb");
if (!fp) {
lg::error("[OVERLORD] fake iso could not open the file \"{}\"", path);
}
@ -354,7 +353,7 @@ uint32_t FS_LoadSoundBank(char* name, void* buffer) {
return 0;
}
auto fp = fopen(get_file_path(file), "rb");
auto fp = file_util::open_file(get_file_path(file), "rb");
fread(buffer, offset, 1, fp);
fclose(fp);
@ -370,7 +369,7 @@ void LoadMusicTweaks() {
MakeISOName(tweakname, "TWEAKVAL.MUS");
auto file = FS_FindIN(tweakname);
if (file) {
auto fp = fopen(get_file_path(file), "rb");
auto fp = file_util::open_file(get_file_path(file), "rb");
fread(&gMusicTweakInfo, sizeof(gMusicTweakInfo), 1, fp);
fclose(fp);
} else {

View File

@ -105,11 +105,11 @@ s32 sceOpen(const char* filename, s32 flag) {
auto name = file_util::get_file_path({filename});
switch (flag) {
case SCE_RDONLY: {
fp = fopen(name.c_str(), "rb");
fp = file_util::open_file(name.c_str(), "rb");
} break;
default: {
fp = fopen(name.c_str(), "w");
fp = file_util::open_file(name.c_str(), "w");
} break;
}
if (!fp) {

View File

@ -300,7 +300,7 @@ void flush_memory_card_to_file() {
}
void read_memory_card_from_file() {
if (std::filesystem::exists(get_memory_card_path())) {
if (fs::exists(get_memory_card_path())) {
g_mc_state.data.load_from_file(get_memory_card_path());
}
}

View File

@ -1,7 +1,7 @@
// Copyright: 2021 - 2022, Ziemas
// SPDX-License-Identifier: ISC
#pragma once
#include <filesystem>
#include <memory>
#include <vector>

View File

@ -7,6 +7,7 @@
#include <third-party/fmt/core.h>
#ifdef _WIN32
#include <combaseapi.h>
#include <windows.h>
#endif
@ -193,7 +194,7 @@ void player::set_master_volume(u32 group, s32 volume) {
}
}
u32 player::load_bank(std::filesystem::path& filepath, size_t offset) {
u32 player::load_bank(fs::path& filepath, size_t offset) {
std::scoped_lock lock(m_ticklock);
fmt::print("Loading bank {}\n", filepath.string());
std::fstream in(filepath, std::fstream::binary | std::fstream::in);

View File

@ -1,7 +1,7 @@
// Copyright: 2021 - 2022, Ziemas
// SPDX-License-Identifier: ISC
#pragma once
#include <filesystem>
#include <memory>
#include <mutex>
#include <unordered_map>
@ -14,6 +14,7 @@
#include "sound_handler.h"
#include "common/common_types.h"
#include "common/util/FileUtil.h"
#include "../common/synth.h"
#include "game/sound/989snd/vagvoice.h"
@ -32,7 +33,7 @@ class player {
// player(player&& other) noexcept = default;
// player& operator=(player&& other) noexcept = default;
u32 load_bank(std::filesystem::path& path, size_t offset);
u32 load_bank(fs::path& path, size_t offset);
u32 play_sound(u32 bank, u32 sound, s32 vol, s32 pan, s32 pm, s32 pb);
void set_midi_reg(u32 sound_id, u8 reg, u8 value);

View File

@ -1,12 +1,10 @@
#include <filesystem>
#include "player.h"
int main(int argc, char* argv[]) {
snd::player player;
unsigned bankid = 0;
std::filesystem::path file = argv[1];
fs::path file = argv[1];
if (argc > 2) {
bankid = player.load_bank(file, 0);

View File

@ -178,7 +178,7 @@ void snd_AutoPitchBend(s32, s32, s32, s32) {
s32 snd_BankLoadEx(const char* filename, s32 offset, s32, s32) {
// printf("snd_BankLoadEx\n");
if (player) {
std::filesystem::path path = filename;
fs::path path = filename;
return player->load_bank(path, offset);
} else {
return 0;

View File

@ -3,6 +3,7 @@
#include <cstring>
#include "common/util/Assert.h"
#include <common/util/FileUtil.h>
#include "game/sce/iop.h"
@ -299,7 +300,7 @@ void IOP_Kernel::rpc_loop(iop::sceSifQueueData* qd) {
void IOP_Kernel::read_disc_sectors(u32 sector, u32 sectors, void* buffer) {
if (!iso_disc_file) {
iso_disc_file = fopen("./disc.iso", "rb");
iso_disc_file = file_util::open_file("./disc.iso", "rb");
}
ASSERT(iso_disc_file);

View File

@ -16,7 +16,7 @@
void save_pc_data(const std::string& nickname,
tfrag3::Level& data,
const std::filesystem::path& fr3_output_dir) {
const fs::path& fr3_output_dir) {
Serializer ser;
data.serialize(ser);
auto compressed =
@ -55,7 +55,7 @@ bool run_build_level(const std::string& input_file,
gltf_mesh_extract::extract(mesh_extract_in, mesh_extract_out);
// add stuff to the GOAL level structure
file.info = make_file_info_for_level(std::filesystem::path(input_file).filename().string());
file.info = make_file_info_for_level(fs::path(input_file).filename().string());
// all vis
// drawable trees
// pat

View File

@ -1,10 +1,9 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
bool run_build_level(const std::string& input_file,
const std::string& bsp_output_file,
const std::string& output_prefix);
std::vector<std::string> get_build_level_deps(const std::string& input_file);
std::vector<std::string> get_build_level_deps(const std::string& input_file);

View File

@ -37,9 +37,8 @@ Compiler::Compiler(const std::string& user_profile, std::unique_ptr<ReplWrapper>
compile_object_file("goal-lib", library_code, false);
// user profile stuff
if (user_profile != "#f" &&
std::filesystem::exists(file_util::get_jak_project_dir() / "goal_src" / "user" /
user_profile / "user.gc")) {
if (user_profile != "#f" && fs::exists(file_util::get_jak_project_dir() / "goal_src" / "user" /
user_profile / "user.gc")) {
try {
Object user_code =
m_goos.reader.read_from_file({"goal_src", "user", user_profile, "user.gc"});

View File

@ -3,7 +3,6 @@
* Compiler implementation for forms which actually control the compiler.
*/
#include <filesystem>
#include <regex>
#include <stack>
@ -308,7 +307,7 @@ Val* Compiler::compile_build_dgo(const goos::Object& form, const goos::Object& r
desc.entries.push_back(o);
} else {
// allow data objects to be missing.
if (std::filesystem::exists(file_util::get_file_path(
if (fs::exists(file_util::get_file_path(
{"out", m_make.compiler_output_prefix(), "obj", o.file_name}))) {
desc.entries.push_back(o);
}

View File

@ -87,6 +87,7 @@ int main(int argc, char** argv) {
// the compiler may throw an exception if it fails to load its standard library.
try {
std::unique_ptr<Compiler> compiler;
// TODO - allow passing in an iso_data override
std::mutex compiler_mutex;
// if a command is provided on the command line, no REPL just run the compiler on it
if (!cmd.empty()) {

View File

@ -1,7 +1,5 @@
#include "MakeSystem.h"
#include <filesystem>
#include "common/goos/ParseHelpers.h"
#include "common/util/FileUtil.h"
#include "common/util/Timer.h"
@ -179,7 +177,7 @@ goos::Object MakeSystem::handle_basename(const goos::Object& form,
const std::shared_ptr<goos::EnvironmentObject>& env) {
m_goos.eval_args(&args, env);
va_check(form, args, {goos::ObjectType::STRING}, {});
std::filesystem::path input(args.unnamed.at(0).as_string()->data);
fs::path input(args.unnamed.at(0).as_string()->data);
return goos::StringObject::make_new(input.filename().u8string());
}
@ -189,7 +187,7 @@ goos::Object MakeSystem::handle_stem(const goos::Object& form,
const std::shared_ptr<goos::EnvironmentObject>& env) {
m_goos.eval_args(&args, env);
va_check(form, args, {goos::ObjectType::STRING}, {});
std::filesystem::path input(args.unnamed.at(0).as_string()->data);
fs::path input(args.unnamed.at(0).as_string()->data);
return goos::StringObject::make_new(input.stem().u8string());
}

View File

@ -2,7 +2,6 @@
#include "Tool.h"
#include <chrono>
#include <filesystem>
#include "common/util/FileUtil.h"
@ -14,17 +13,17 @@ bool Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
// for this to return false, all outputs need to be newer than all inputs.
for (auto& in : task.input) {
auto in_file = std::filesystem::path(file_util::get_file_path({in}));
auto in_file = fs::path(file_util::get_file_path({in}));
if (!std::filesystem::exists(in_file)) {
if (!fs::exists(in_file)) {
throw std::runtime_error(fmt::format("Input file {} does not exist.", in));
}
auto newest_input = std::filesystem::last_write_time(in_file);
auto newest_input = fs::last_write_time(in_file);
for (auto& dep : task.deps) {
auto dep_path = std::filesystem::path(file_util::get_file_path({dep}));
if (std::filesystem::exists(dep_path)) {
auto dep_time = std::filesystem::last_write_time(dep_path);
auto dep_path = fs::path(file_util::get_file_path({dep}));
if (fs::exists(dep_path)) {
auto dep_time = fs::last_write_time(dep_path);
if (dep_time > newest_input) {
newest_input = dep_time;
}
@ -34,9 +33,9 @@ bool Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
}
for (auto& dep : get_additional_dependencies(task, path_map)) {
auto dep_path = std::filesystem::path(file_util::get_file_path({dep}));
if (std::filesystem::exists(dep_path)) {
auto dep_time = std::filesystem::last_write_time(dep_path);
auto dep_path = fs::path(file_util::get_file_path({dep}));
if (fs::exists(dep_path)) {
auto dep_time = fs::last_write_time(dep_path);
if (dep_time > newest_input) {
newest_input = dep_time;
}
@ -46,9 +45,9 @@ bool Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
}
for (auto& out : task.output) {
auto out_path = std::filesystem::path(file_util::get_file_path({out}));
if (std::filesystem::exists(out_path)) {
auto out_time = std::filesystem::last_write_time(out_path);
auto out_path = fs::path(file_util::get_file_path({out}));
if (fs::exists(out_path)) {
auto out_time = fs::last_write_time(out_path);
if (out_time < newest_input) {
return true;
}
@ -87,4 +86,4 @@ std::string PathMap::apply_remaps(const std::string& input) const {
} else {
return input;
}
}
}

View File

@ -1,7 +1,5 @@
#include "Tools.h"
#include <filesystem>
#include "common/goos/ParseHelpers.h"
#include "common/util/DgoWriter.h"
#include "common/util/FileUtil.h"
@ -21,7 +19,7 @@ bool CompilerTool::needs_run(const ToolInput& task, const PathMap& path_map) {
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
}
if (!m_compiler->knows_object_file(std::filesystem::path(task.input.at(0)).stem().u8string())) {
if (!m_compiler->knows_object_file(fs::path(task.input.at(0)).stem().u8string())) {
return true;
}
return Tool::needs_run(task, path_map);
@ -113,9 +111,8 @@ bool CopyTool::run(const ToolInput& task, const PathMap& /*path_map*/) {
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
}
for (auto& out : task.output) {
std::filesystem::copy(std::filesystem::path(file_util::get_file_path({task.input.at(0)})),
std::filesystem::path(file_util::get_file_path({out})),
std::filesystem::copy_options::overwrite_existing);
fs::copy(fs::path(file_util::get_file_path({task.input.at(0)})),
fs::path(file_util::get_file_path({out})), fs::copy_options::overwrite_existing);
}
return true;
}

View File

@ -1,7 +1,6 @@
#include "test_runner.h"
#include <filesystem>
#include <string>
#include "inja.hpp"
@ -118,8 +117,8 @@ void runtime_with_kernel_no_debug_segment() {
}
void createDirIfAbsent(const std::string& path) {
if (!std::filesystem::is_directory(path) || !std::filesystem::exists(path)) {
std::filesystem::create_directory(path);
if (!fs::is_directory(path) || !fs::exists(path)) {
fs::create_directory(path);
}
}
std::string getTemplateDir(const std::string& category) {

View File

@ -1,6 +1,5 @@
#include <chrono>
#include <cstdio>
#include <filesystem>
#include <iostream>
#include <random>
#include <sstream>

View File

@ -1,6 +1,5 @@
#include <chrono>
#include <cstdio>
#include <filesystem>
#include <iostream>
#include <random>
#include <sstream>

View File

@ -1,6 +1,5 @@
#include <chrono>
#include <cstdio>
#include <filesystem>
#include <iostream>
#include <random>
#include <sstream>

View File

@ -1,6 +1,5 @@
#include <chrono>
#include <cstdio>
#include <filesystem>
#include <iostream>
#include <random>
#include <regex>
@ -380,8 +379,8 @@ TEST_F(WithGameTests, GameCount) {
shared_compiler->runner.run_static_test(env, testCategory, "test-game-count.gc",
get_test_pass_string("game-count", 4));
// don't leave behind a weird version of the game-count file.
std::filesystem::remove(file_util::get_file_path({"out", "jak1", "iso", "ENGINE.CGO"}));
std::filesystem::remove(file_util::get_file_path({"out", "jak1", "obj", "game-cnt.go"}));
fs::remove(file_util::get_file_path({"out", "jak1", "iso", "ENGINE.CGO"}));
fs::remove(file_util::get_file_path({"out", "jak1", "obj", "game-cnt.go"}));
}
TEST_F(WithGameTests, BitFieldAccess) {

View File

@ -9,13 +9,14 @@
#include "common/util/Timer.h"
#include "common/util/diff.h"
#include "common/util/json_util.h"
#include <common/util/unicode_util.h>
#include "decompiler/ObjectFile/ObjectFileDB.h"
#include "goalc/compiler/Compiler.h"
#include "third-party/fmt/format.h"
namespace fs = std::filesystem;
namespace fs = fs;
// command line arguments
struct OfflineTestArgs {
@ -82,7 +83,7 @@ OfflineTestConfig parse_config() {
}
struct DecompilerFile {
std::filesystem::path path;
fs::path path;
std::string name_in_dgo;
std::string unique_name;
std::string reference;
@ -264,20 +265,20 @@ Decompiler setup_decompiler(const std::vector<DecompilerFile>& files,
// don't try to do this because we can't write the file
dc.config->generate_symbol_definition_map = false;
std::vector<std::filesystem::path> dgo_paths;
std::vector<fs::path> dgo_paths;
if (args.iso_data_path.empty()) {
for (auto& x : offline_config.dgos) {
dgo_paths.push_back(file_util::get_jak_project_dir() / "iso_data" / "jak1" / x);
}
} else {
for (auto& x : offline_config.dgos) {
dgo_paths.push_back(std::filesystem::path(args.iso_data_path) / x);
dgo_paths.push_back(fs::path(args.iso_data_path) / x);
}
}
dc.db = std::make_unique<decompiler::ObjectFileDB>(
dgo_paths, dc.config->obj_file_name_map_file, std::vector<std::filesystem::path>{},
std::vector<std::filesystem::path>{}, *dc.config);
dc.db = std::make_unique<decompiler::ObjectFileDB>(dgo_paths, dc.config->obj_file_name_map_file,
std::vector<fs::path>{},
std::vector<fs::path>{}, *dc.config);
std::unordered_set<std::string> db_files;
for (auto& files_by_name : dc.db->obj_files_by_name) {
@ -420,6 +421,15 @@ bool compile(Decompiler& dc,
}
int main(int argc, char* argv[]) {
#ifdef _WIN32
auto utf8_args = get_widechar_cli_args();
std::vector<char*> string_ptrs;
for (auto& str : utf8_args) {
string_ptrs.push_back(str.data());
}
argv = string_ptrs.data();
#endif
fmt::print("Offline Decompiler Test 2\n");
lg::initialize();
if (!file_util::setup_project_path(std::nullopt)) {

View File

@ -1,8 +1,9 @@
#include <filesystem>
#include "common/log/log.h"
#include "common/util/FileUtil.h"
#include "common/util/os.h"
#include <common/util/unicode_util.h>
#include "gtest/gtest.h"
@ -18,6 +19,15 @@
// to make it easier to test a subset of tests
int main(int argc, char** argv) {
#ifdef _WIN32
auto args = get_widechar_cli_args();
std::vector<char*> string_ptrs;
for (auto& str : args) {
string_ptrs.push_back(str.data());
}
argv = string_ptrs.data();
#endif
// hopefully get a debug print on github actions
setup_cpu_info();
file_util::setup_project_path(std::nullopt);
@ -27,10 +37,10 @@ int main(int argc, char** argv) {
// Re-init failed folder
std::string failedFolder = file_util::get_file_path({"test/goalc/source_generated/failed/"});
if (std::filesystem::exists(failedFolder)) {
std::filesystem::remove_all(failedFolder);
if (fs::exists(failedFolder)) {
fs::remove_all(failedFolder);
}
std::filesystem::create_directory(failedFolder);
fs::create_directory(failedFolder);
return RUN_ALL_TESTS();
}

6049
third-party/filesystem.hpp generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -12,8 +12,9 @@
#include "decompiler/util/DecompilerTypeSystem.h"
#include "common/util/Assert.h"
#include <common/util/unicode_util.h>
namespace fs = std::filesystem;
namespace fs = fs;
struct Ram {
const u8* data = nullptr;
@ -554,6 +555,15 @@ void inspect_symbols(const Ram& ram,
}
int main(int argc, char** argv) {
#ifdef _WIN32
auto args = get_widechar_cli_args();
std::vector<char*> string_ptrs;
for (auto& str : args) {
string_ptrs.push_back(str.data());
}
argv = string_ptrs.data();
#endif
fmt::print("MemoryDumpTool\n");
if (argc != 2 && argc != 3) {

View File

@ -3,8 +3,18 @@
#include "common/util/FileUtil.h"
#include "common/util/BinaryWriter.h"
#include "third-party/json.hpp"
#include <common/util/unicode_util.h>
int main(int argc, char** argv) {
#ifdef _WIN32
auto args = get_widechar_cli_args();
std::vector<char*> string_ptrs;
for (auto& str : args) {
string_ptrs.push_back(str.data());
}
argv = string_ptrs.data();
#endif
printf("OpenGOAL version %d.%d\n", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR);
printf("DGO Packing Tool\n");

View File

@ -3,6 +3,7 @@
#include "common/versions.h"
#include "common/util/FileUtil.h"
#include "common/util/DgoReader.h"
#include <common/util/unicode_util.h>
namespace {
int run(int argc, char** argv) {
@ -48,6 +49,15 @@ int run(int argc, char** argv) {
} // namespace
int main(int argc, char** argv) {
#ifdef _WIN32
auto args = get_widechar_cli_args();
std::vector<char*> string_ptrs;
for (auto& str : args) {
string_ptrs.push_back(str.data());
}
argv = string_ptrs.data();
#endif
try {
return run(argc, argv);
} catch (const std::exception& e) {

View File

@ -8,6 +8,7 @@
#include "decompiler/level_extractor/BspHeader.h"
#include "common/util/Assert.h"
#include <common/util/unicode_util.h>
constexpr GameVersion kGameVersion = GameVersion::Jak1;
@ -55,6 +56,15 @@ bool is_valid_bsp(const decompiler::LinkedObjectFile& file) {
}
int main(int argc, char** argv) {
#ifdef _WIN32
auto args = get_widechar_cli_args();
std::vector<char*> string_ptrs;
for (auto& str : args) {
string_ptrs.push_back(str.data());
}
argv = string_ptrs.data();
#endif
try {
fmt::print("Level Dump Tool\n");
@ -92,4 +102,4 @@ int main(int argc, char** argv) {
}
return 0;
}
}