mirror of
https://github.com/acidicoala/SmokeAPI.git
synced 2025-12-05 21:15:39 -05:00
Reformat project
This commit is contained in:
@@ -2,11 +2,6 @@ cmake_minimum_required(VERSION 3.24)
|
||||
|
||||
project(smoke-api-tools LANGUAGES CXX)
|
||||
|
||||
### C++ preprocessor library
|
||||
|
||||
CPMAddPackage("gh:danmar/simplecpp#1.5.2")
|
||||
target_include_directories(simplecpp_obj INTERFACE ${simplecpp_SOURCE_DIR})
|
||||
|
||||
### Thread pool library
|
||||
|
||||
CPMAddPackage(
|
||||
@@ -34,5 +29,4 @@ add_executable(steamworks_parser steamworks_parser.cpp)
|
||||
target_link_libraries(steamworks_parser PRIVATE
|
||||
KoalaBox
|
||||
BS_thread_pool
|
||||
simplecpp_obj
|
||||
)
|
||||
|
||||
@@ -15,8 +15,8 @@ namespace {
|
||||
|
||||
std::string generate_random_string() {
|
||||
static constexpr char charset[] = "0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
thread_local std::mt19937_64 rng{std::random_device{}()};
|
||||
thread_local std::uniform_int_distribution<std::size_t> dist(0, sizeof(charset) - 2);
|
||||
@@ -24,7 +24,7 @@ namespace {
|
||||
constexpr auto length = 16;
|
||||
std::string result;
|
||||
result.reserve(length);
|
||||
for (std::size_t i = 0; i < length; ++i) {
|
||||
for(std::size_t i = 0; i < length; ++i) {
|
||||
result += charset[dist(rng)];
|
||||
}
|
||||
|
||||
@@ -33,26 +33,29 @@ namespace {
|
||||
|
||||
void print_help() {
|
||||
std::cout << "Steamworks SDK downloader for SmokeAPI v1.0" << std::endl
|
||||
<< "Usage: steamworks_downloader version1 version2 ... versionN" << std::endl
|
||||
<< "Example: steamworks_downloader 100 158a 162" << std::endl
|
||||
<< "Alternative usage: steamworks_downloader C:/path/to/downloaded_sdk/"
|
||||
<< "SDK version list available at: "
|
||||
<< "https://partner.steamgames.com/downloads/list" << std::endl;
|
||||
<< "Usage: steamworks_downloader version1 version2 ... versionN" << std::endl
|
||||
<< "Example: steamworks_downloader 100 158a 162" << std::endl
|
||||
<< "Alternative usage: steamworks_downloader C:/path/to/downloaded_sdk/"
|
||||
<< "SDK version list available at: "
|
||||
<< "https://partner.steamgames.com/downloads/list" << std::endl;
|
||||
}
|
||||
|
||||
void unzip_sdk(const fs::path& zip_file_path, const fs::path& unzip_dir) {
|
||||
kb::zip::extract_files(zip_file_path, [&](const std::string& name, const bool) {
|
||||
if (name.starts_with("sdk/public/steam/") && name.ends_with(".h")) {
|
||||
return unzip_dir / "headers/steam" / fs::path(name).filename();
|
||||
}
|
||||
kb::zip::extract_files(
|
||||
zip_file_path,
|
||||
[&](const std::string& name, const bool) {
|
||||
if(name.starts_with("sdk/public/steam/") && name.ends_with(".h")) {
|
||||
return unzip_dir / "headers/steam" / fs::path(name).filename();
|
||||
}
|
||||
|
||||
if (name.starts_with("sdk/redistributable_bin/") && name.ends_with(".dll") &&
|
||||
name.find("steam_api") != std::string::npos) {
|
||||
return unzip_dir / "binaries" / fs::path(name).filename();
|
||||
}
|
||||
if(name.starts_with("sdk/redistributable_bin/") && name.ends_with(".dll") &&
|
||||
name.find("steam_api") != std::string::npos) {
|
||||
return unzip_dir / "binaries" / fs::path(name).filename();
|
||||
}
|
||||
|
||||
return fs::path();
|
||||
});
|
||||
return fs::path();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void download_sdk(const fs::path& steamworks_dir, const std::string_view& version) {
|
||||
@@ -68,13 +71,12 @@ namespace {
|
||||
try {
|
||||
const auto unzip_dir = steamworks_dir / version;
|
||||
unzip_sdk(zip_file_path, unzip_dir);
|
||||
} catch (std::exception& e) {
|
||||
} catch(std::exception& e) {
|
||||
std::cerr << "Unzip error: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
fs::remove(zip_file_path);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
@@ -82,21 +84,21 @@ namespace {
|
||||
* for further processing by other tools.
|
||||
*/
|
||||
int wmain(const int argc, const wchar_t** argv) { // NOLINT(*-use-internal-linkage)
|
||||
if (argc == 1) {
|
||||
if(argc == 1) {
|
||||
print_help();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto steamworks_dir = std::filesystem::current_path() / "steamworks";
|
||||
|
||||
if (argc == 2) {
|
||||
if (const auto cdn_dir = kb::str::to_str(argv[1]); fs::is_directory(cdn_dir)) {
|
||||
for (const auto& entry : fs::directory_iterator(cdn_dir)) {
|
||||
if(argc == 2) {
|
||||
if(const auto cdn_dir = kb::str::to_str(argv[1]); fs::is_directory(cdn_dir)) {
|
||||
for(const auto& entry : fs::directory_iterator(cdn_dir)) {
|
||||
const auto filename = entry.path().filename().string();
|
||||
const std::regex re(R"(steamworks_sdk_(.+)\.zip)");
|
||||
|
||||
if (std::smatch match; std::regex_match(filename, match, re)) {
|
||||
if (match.size() > 1) {
|
||||
if(std::smatch match; std::regex_match(filename, match, re)) {
|
||||
if(match.size() > 1) {
|
||||
const auto& version = match[1].str();
|
||||
unzip_sdk(entry.path(), steamworks_dir / version);
|
||||
}
|
||||
@@ -107,13 +109,13 @@ int wmain(const int argc, const wchar_t** argv) { // NOLINT(*-use-internal-linka
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = 1; i < argc; i++) {
|
||||
for(auto i = 1; i < argc; i++) {
|
||||
const auto version = kb::str::to_str(argv[i]);
|
||||
|
||||
try {
|
||||
download_sdk(steamworks_dir, version);
|
||||
} catch (const std::exception& e) {
|
||||
} catch(const std::exception& e) {
|
||||
LOG_ERROR("Error downloading SDK '{}'. Reason: {}", version, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
|
||||
#include <BS_thread_pool.hpp>
|
||||
#include <cpp-tree-sitter.h>
|
||||
#undef ERROR // Workaround for the ERROR enum defined in simplecpp
|
||||
#include <simplecpp.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <koalabox/io.hpp>
|
||||
#include <koalabox/logger.hpp>
|
||||
@@ -20,7 +20,7 @@ namespace {
|
||||
namespace kb = koalabox;
|
||||
|
||||
std::string_view unquote_if_quoted(const std::string_view& s) {
|
||||
if (s.size() >= 2 && s.front() == '"' && s.back() == '"') {
|
||||
if(s.size() >= 2 && s.front() == '"' && s.back() == '"') {
|
||||
return s.substr(1, s.size() - 2);
|
||||
}
|
||||
|
||||
@@ -35,132 +35,101 @@ namespace {
|
||||
nlohmann::ordered_json current_lookup = {};
|
||||
std::string interface_version;
|
||||
|
||||
kb::parser::walk(root, [&](const auto& current_node) {
|
||||
const auto current_type = current_node.getType();
|
||||
const auto current_value = current_node.getSourceRange(source);
|
||||
const auto current_sexpr = current_node.getSExpr();
|
||||
kb::parser::walk(
|
||||
root,
|
||||
[&](const auto& current_node) {
|
||||
const auto current_type = current_node.getType();
|
||||
const auto current_value = current_node.getSourceRange(source);
|
||||
const auto current_sexpr = current_node.getSExpr();
|
||||
|
||||
if (current_type == "class_specifier") {
|
||||
std::string interface_name;
|
||||
[[maybe_unused]] int vt_idx = 0;
|
||||
if(current_type == "class_specifier") {
|
||||
std::string interface_name;
|
||||
[[maybe_unused]] int vt_idx = 0;
|
||||
|
||||
kb::parser::walk(current_node, [&](const ts::Node& class_node) {
|
||||
const auto type = class_node.getType();
|
||||
const auto value = class_node.getSourceRange(source);
|
||||
kb::parser::walk(
|
||||
current_node,
|
||||
[&](const ts::Node& class_node) {
|
||||
const auto type = class_node.getType();
|
||||
const auto value = class_node.getSourceRange(source);
|
||||
|
||||
if (type == "type_identifier" && interface_name.empty()) {
|
||||
interface_name = value;
|
||||
LOG_DEBUG("Found interface: {}", interface_name);
|
||||
if(type == "type_identifier" && interface_name.empty()) {
|
||||
interface_name = value;
|
||||
LOG_DEBUG("Found interface: {}", interface_name);
|
||||
|
||||
return kb::parser::visit_result::Continue;
|
||||
}
|
||||
|
||||
if (type == "field_declaration" && value.starts_with("virtual ")) {
|
||||
if (value.starts_with("virtual ")) {
|
||||
kb::parser::walk(class_node, [&](const ts::Node& decl_node) {
|
||||
if (decl_node.getType() == "field_identifier") {
|
||||
const auto function_name = decl_node.getSourceRange(source);
|
||||
|
||||
// Note: This doesn't take into account overloaded functions.
|
||||
// However, so far this project hasn't had any need to hook such
|
||||
// functions. Hence, no fixes have been implemented so far.
|
||||
|
||||
current_lookup[function_name] = vt_idx++;
|
||||
return kb::parser::visit_result::Stop;
|
||||
}
|
||||
return kb::parser::visit_result::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
if(type == "field_declaration" && value.starts_with("virtual ")) {
|
||||
if(value.starts_with("virtual ")) {
|
||||
kb::parser::walk(
|
||||
class_node,
|
||||
[&](const ts::Node& decl_node) {
|
||||
if(decl_node.getType() == "field_identifier") {
|
||||
const auto function_name = decl_node.getSourceRange(
|
||||
source
|
||||
);
|
||||
|
||||
// Note: This doesn't take into account overloaded functions.
|
||||
// However, so far this project hasn't had any need to hook such
|
||||
// functions. Hence, no fixes have been implemented so far.
|
||||
|
||||
current_lookup[function_name] = vt_idx++;
|
||||
return kb::parser::visit_result::Stop;
|
||||
}
|
||||
return kb::parser::visit_result::Continue;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return kb::parser::visit_result::SkipChildren;
|
||||
}
|
||||
|
||||
return kb::parser::visit_result::Continue;
|
||||
}
|
||||
);
|
||||
} else if(current_type == "preproc_def") {
|
||||
kb::parser::walk(
|
||||
current_node,
|
||||
[&](const ts::Node& preproc_node) {
|
||||
if(preproc_node.getType() == "identifier") {
|
||||
const auto identifier = preproc_node.getSourceRange(source);
|
||||
|
||||
return kb::parser::visit_result::SkipChildren;
|
||||
}
|
||||
return identifier.ends_with("INTERFACE_VERSION")
|
||||
? kb::parser::visit_result::Continue
|
||||
: kb::parser::visit_result::Stop;
|
||||
}
|
||||
|
||||
if(preproc_node.getType() == "preproc_arg") {
|
||||
const auto quoted_version = preproc_node.getSourceRange(source);
|
||||
const auto trimmed_version = koalabox::str::trim(quoted_version);
|
||||
interface_version = unquote_if_quoted(trimmed_version);
|
||||
LOG_DEBUG("Interface version: {}", interface_version);
|
||||
|
||||
return kb::parser::visit_result::Stop;
|
||||
}
|
||||
|
||||
return kb::parser::visit_result::Continue;
|
||||
}
|
||||
);
|
||||
} else if(current_type == "translation_unit" || current_type == "preproc_ifdef") {
|
||||
return kb::parser::visit_result::Continue;
|
||||
});
|
||||
} else if (current_type == "preproc_def") {
|
||||
kb::parser::walk(current_node, [&](const ts::Node& preproc_node) {
|
||||
if (preproc_node.getType() == "identifier") {
|
||||
const auto identifier = preproc_node.getSourceRange(source);
|
||||
}
|
||||
|
||||
return identifier.ends_with("INTERFACE_VERSION")
|
||||
? kb::parser::visit_result::Continue
|
||||
: kb::parser::visit_result::Stop;
|
||||
}
|
||||
|
||||
if (preproc_node.getType() == "preproc_arg") {
|
||||
const auto quoted_version = preproc_node.getSourceRange(source);
|
||||
const auto trimmed_version = koalabox::str::trim(quoted_version);
|
||||
interface_version = unquote_if_quoted(trimmed_version);
|
||||
LOG_DEBUG("Interface version: {}", interface_version);
|
||||
|
||||
return kb::parser::visit_result::Stop;
|
||||
}
|
||||
|
||||
return kb::parser::visit_result::Continue;
|
||||
});
|
||||
} else if (current_type == "translation_unit" || current_type == "preproc_ifdef") {
|
||||
return kb::parser::visit_result::Continue;
|
||||
return kb::parser::visit_result::SkipChildren;
|
||||
}
|
||||
|
||||
return kb::parser::visit_result::SkipChildren;
|
||||
});
|
||||
);
|
||||
|
||||
// Save the findings
|
||||
static std::mutex mutex;
|
||||
if (not interface_version.empty()) {
|
||||
const std::lock_guard lock(mutex);
|
||||
static std::mutex section;
|
||||
if(not
|
||||
interface_version.empty()
|
||||
)
|
||||
{
|
||||
const std::lock_guard lock(section);
|
||||
lookup[interface_version] = current_lookup;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns out that preprocessing headers using a proper C++ preprocessor
|
||||
* significantly complicates AST parsing down the line.
|
||||
* To make matters worse, it also removes macro definitions with interface version string.
|
||||
* Hence, this function should not be used in practice.
|
||||
* It remains here for reference purposes only.
|
||||
*/
|
||||
[[maybe_unused]] std::string preprocess_header(const fs::path& header_path) {
|
||||
auto files = std::vector<std::string>();
|
||||
|
||||
static simplecpp::DUI dui{};
|
||||
dui.removeComments = true;
|
||||
// For stdlib headers
|
||||
dui.includePaths.emplace_back("dummy/");
|
||||
// For headers includes via "steam/*" path
|
||||
dui.includePaths.emplace_back(header_path.parent_path().parent_path().string());
|
||||
dui.defines.emplace_back("VALVE_CALLBACK_PACK_LARGE");
|
||||
|
||||
simplecpp::OutputList output_list;
|
||||
simplecpp::TokenList raw_token_list(header_path.string(), files, &output_list);
|
||||
raw_token_list.removeComments();
|
||||
|
||||
simplecpp::FileDataCache cache;
|
||||
simplecpp::TokenList output_token_list(files);
|
||||
simplecpp::preprocess(output_token_list, raw_token_list, files, cache, dui, &output_list);
|
||||
simplecpp::cleanup(cache);
|
||||
|
||||
for (const simplecpp::Output& output : output_list) {
|
||||
if (output.type == simplecpp::Output::MISSING_HEADER) {
|
||||
LOG_WARN(
|
||||
"Place missing empty header at: " + fs::absolute(fs::path("dummy")).string()
|
||||
);
|
||||
}
|
||||
|
||||
const auto msg = std::format(
|
||||
"msg = {}, line={}, col={}, type = {}",
|
||||
output.msg,
|
||||
output.location.line,
|
||||
output.location.col,
|
||||
static_cast<int>(output.type)
|
||||
);
|
||||
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
return output_token_list.stringify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Certain Steam macros break C++ AST parser, if left unprocessed.
|
||||
* This function does that in a very naive manner. Stupid, but works.
|
||||
@@ -175,11 +144,14 @@ namespace {
|
||||
return processed_contents;
|
||||
}
|
||||
|
||||
void
|
||||
parse_sdk(const fs::path& sdk_path, nlohmann::ordered_json& lookup, BS::thread_pool<>& pool) {
|
||||
void parse_sdk(
|
||||
const fs::path& sdk_path,
|
||||
nlohmann::ordered_json& lookup,
|
||||
BS::thread_pool<>& pool
|
||||
) {
|
||||
const auto headers_dir = sdk_path / "headers\\steam";
|
||||
|
||||
if (not fs::exists(headers_dir)) {
|
||||
if(not fs::exists(headers_dir)) {
|
||||
LOG_WARN("Warning: SDK missing 'headers' directory: {}", headers_dir.string());
|
||||
return;
|
||||
}
|
||||
@@ -187,25 +159,28 @@ namespace {
|
||||
LOG_INFO("Parsing SDK: {}", headers_dir.string());
|
||||
|
||||
// Go over each file in headers directory
|
||||
for (const auto& entry : fs::directory_iterator(headers_dir)) {
|
||||
if (const auto& header_path = entry.path(); header_path.extension() == ".h") {
|
||||
const auto task = pool.submit_task([&, header_path] {
|
||||
try {
|
||||
LOG_DEBUG("Parsing header: {}", header_path.string());
|
||||
for(const auto& entry : fs::directory_iterator(headers_dir)) {
|
||||
if(const auto& header_path = entry.path(); header_path.extension() == ".h") {
|
||||
const auto task = pool.submit_task(
|
||||
[&, header_path] {
|
||||
try {
|
||||
LOG_DEBUG("Parsing header: {}", header_path.string());
|
||||
|
||||
const auto processed_header = manually_preprocess_header(header_path);
|
||||
parse_header(processed_header, lookup);
|
||||
} catch (std::exception& e) {
|
||||
LOG_CRITICAL(e.what());
|
||||
exit(-1);
|
||||
const auto processed_header = manually_preprocess_header(header_path);
|
||||
parse_header(processed_header, lookup);
|
||||
} catch(std::exception& e) {
|
||||
LOG_CRITICAL(e.what());
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generate_lookup_json(
|
||||
const fs::path& steamworks_dir, //
|
||||
const fs::path& steamworks_dir,
|
||||
//
|
||||
const std::set<std::string>& sdk_filter
|
||||
) {
|
||||
nlohmann::ordered_json lookup;
|
||||
@@ -217,13 +192,22 @@ namespace {
|
||||
BS::thread_pool pool(thread_count);
|
||||
|
||||
// Go over each steamworks sdk version
|
||||
for (const auto& entry : fs::directory_iterator(steamworks_dir)) {
|
||||
if (not entry.is_directory()) {
|
||||
for(const auto& entry : fs::directory_iterator(steamworks_dir)) {
|
||||
if(not
|
||||
entry.is_directory()
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not sdk_filter.empty() and
|
||||
not sdk_filter.contains(entry.path().filename().string())) {
|
||||
if(not
|
||||
sdk_filter.empty()
|
||||
and
|
||||
not sdk_filter
|
||||
.
|
||||
contains(entry.path().filename().string())
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -252,8 +236,8 @@ int wmain(const int argc, const wchar_t* argv[]) { // NOLINT(*-use-internal-link
|
||||
koalabox::logger::init_console_logger();
|
||||
|
||||
std::set<std::string> sdk_filter;
|
||||
if (argc > 1) {
|
||||
for (auto i = 1; i < argc; i++) {
|
||||
if(argc > 1) {
|
||||
for(auto i = 1; i < argc; i++) {
|
||||
const auto version = koalabox::str::to_str(argv[i]);
|
||||
sdk_filter.insert(version);
|
||||
}
|
||||
@@ -261,7 +245,7 @@ int wmain(const int argc, const wchar_t* argv[]) { // NOLINT(*-use-internal-link
|
||||
|
||||
const auto steamworks_dir = fs::path("steamworks");
|
||||
|
||||
if (!fs::exists(steamworks_dir)) {
|
||||
if(!fs::exists(steamworks_dir)) {
|
||||
throw std::exception("Expected to find 'steamworks' in current working directory.");
|
||||
}
|
||||
|
||||
@@ -271,7 +255,7 @@ int wmain(const int argc, const wchar_t* argv[]) { // NOLINT(*-use-internal-link
|
||||
const auto elapsed = duration_cast<std::chrono::seconds>(end - start);
|
||||
|
||||
LOG_INFO("Finished parsing steamworks in {} seconds", elapsed.count());
|
||||
} catch (std::exception& e) {
|
||||
} catch(std::exception& e) {
|
||||
LOG_CRITICAL("Error: {}", e.what());
|
||||
return 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user