Removed store mode

This commit is contained in:
acidicoala
2025-08-22 05:35:47 +05:00
parent 3978006e12
commit 28650491b2
2129 changed files with 6991 additions and 8112 deletions

View File

@@ -2,42 +2,12 @@ cmake_minimum_required(VERSION 3.24)
project(smoke-api-tools LANGUAGES CXX)
### Install parser library
### C++ preprocessor library
CPMAddPackage(
NAME cpp-tree-sitter
GIT_REPOSITORY https://github.com/nsumner/cpp-tree-sitter.git
# The latest commit includes a fix for MSVC, which is not present in the latest release
GIT_TAG 9cd7f3c9de9fc6d19bc982c6772fcd7a39e5797a
)
CPMAddPackage("gh:danmar/simplecpp#1.5.2")
target_include_directories(simplecpp_obj INTERFACE ${simplecpp_SOURCE_DIR})
# Downloads a tree-sitter grammar from github
# and makes it available as a cmake library target.
add_grammar_from_repo(tree-sitter-cpp # Library name
https://github.com/tree-sitter/tree-sitter-cpp.git # Repository URL
0.23.4 # Version tag
)
### Install JSON library
#CPMAddPackage(
# NAME nlohmann_json
# GIT_REPOSITORY https://github.com/nlohmann/json.git
# GIT_TAG v3.12.0
#)
### Install HTTP client library
set(CURL_USE_LIBPSL OFF)
set(CPR_USE_SYSTEM_LIB_PSL ON)
set(ENABLE_CURL_MANUAL OFF)
set(BUILD_LIBCURL_DOCS OFF)
set(BUILD_EXAMPLES OFF)
CPMAddPackage(
NAME cpr
GIT_REPOSITORY https://github.com/libcpr/cpr.git
GIT_TAG 1.12.0
)
### Thread pool library
CPMAddPackage(
NAME BS_thread_pool
@@ -64,7 +34,5 @@ add_executable(steamworks_parser steamworks_parser.cpp)
target_link_libraries(steamworks_parser PRIVATE
KoalaBox
BS_thread_pool
# nlohmann_json # JSON parser
# tree-sitter-cpp # C++ grammar
# cpp-tree-sitter # C++ bindings for tree-sitter
simplecpp_obj
)

View File

@@ -1,16 +1,17 @@
#include <filesystem>
#include <iostream>
#include <random>
#include <regex>
#include <string>
#include <ranges>
#include <cpr/cpr.h>
#include <koalabox/http_client.hpp>
#include <koalabox/logger.hpp>
#include <koalabox/str.hpp>
#include <koalabox/zip.hpp>
namespace {
namespace fs = std::filesystem;
namespace zip = koalabox::zip;
namespace kb = koalabox;
std::string generate_random_string() {
static constexpr char charset[] = "0123456789"
@@ -34,14 +35,15 @@ namespace {
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;
}
void unzip_sdk(const fs::path& zip_file_path, const fs::path& unzip_dir) {
zip::extract_files(zip_file_path, [&](const std::string& name, const bool) {
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" / fs::path(name).filename();
return unzip_dir / "headers/steam" / fs::path(name).filename();
}
if (name.starts_with("sdk/redistributable_bin/") && name.ends_with(".dll") &&
@@ -59,18 +61,9 @@ namespace {
version
);
const auto zip_file_path =
fs::temp_directory_path() / (generate_random_string() + ".zip");
const auto zip_file_path = fs::temp_directory_path() / (generate_random_string() + ".zip");
std::cout << "Downloading " << download_url << " to " << zip_file_path << std::endl;
std::ofstream of(zip_file_path, std::ios::binary);
if (const auto res = cpr::Download(of, cpr::Url{download_url});
res.error.code != cpr::ErrorCode::OK) {
std::cerr << "Download error: " << res.error.message << std::endl;
return;
}
of.close();
kb::http_client::download_file(download_url, zip_file_path);
try {
const auto unzip_dir = steamworks_dir / version;
@@ -88,20 +81,39 @@ namespace {
* A tool for downloading Steamworks SDK and unpacking its headers and binaries
* for further processing by other tools.
*/
int main(const int argc, const char** argv) { // NOLINT(*-exception-escape)
int wmain(const int argc, const wchar_t** argv) { // NOLINT(*-use-internal-linkage)
if (argc == 1) {
print_help();
return 0;
}
const auto streamworks_dir = std::filesystem::current_path() / "steamworks";
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)) {
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) {
const auto& version = match[1].str();
unzip_sdk(entry.path(), steamworks_dir / version);
}
}
}
return 0;
}
}
for (auto i = 1; i < argc; i++) {
const auto version = kb::str::to_str(argv[i]);
try {
download_sdk(streamworks_dir, argv[i]);
download_sdk(steamworks_dir, version);
} catch (const std::exception& e) {
std::cerr << std::format("Error downloading SDK '{}'. Reason: {}", argv[i], e.what())
<< std::endl;
LOG_ERROR("Error downloading SDK '{}'. Reason: {}", version, e.what());
}
}
}

View File

@@ -1,7 +1,3 @@
#include "koalabox/crypto.hpp"
#include "koalabox/io.hpp"
#include "koalabox/util.hpp"
#include <chrono>
#include <deque>
#include <filesystem>
@@ -10,16 +6,18 @@
#include <BS_thread_pool.hpp>
#include <cpp-tree-sitter.h>
#include <nlohmann/json.hpp>
#undef ERROR // Workaround for the ERROR enum defined in simplecpp
#include <simplecpp.h>
#include <koalabox/io.hpp>
#include <koalabox/logger.hpp>
#include <koalabox/parser.hpp>
#include <koalabox/str.hpp>
#include <koalabox/util.hpp>
namespace {
using json = nlohmann::json;
namespace fs = std::filesystem;
namespace parser = koalabox::parser;
namespace kb = koalabox;
std::string_view unquote_if_quoted(const std::string_view& s) {
if (s.size() >= 2 && s.front() == '"' && s.back() == '"') {
@@ -30,20 +28,23 @@ namespace {
return s;
}
void parse_header(const std::string_view& source, json& lookup) {
const auto tree = parser::parse_source(source);
void parse_header(const std::string_view& source, nlohmann::ordered_json& lookup) {
const auto tree = kb::parser::parse_source(source);
const auto root = tree.getRootNode();
json current_lookup = {};
nlohmann::ordered_json current_lookup = {};
std::string interface_version;
parser::walk(root, [&](const auto& current_node) {
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;
koalabox::parser::walk(current_node, [&](const ts::Node& class_node) {
kb::parser::walk(current_node, [&](const ts::Node& class_node) {
const auto type = class_node.getType();
const auto value = class_node.getSourceRange(source);
@@ -51,35 +52,39 @@ namespace {
interface_name = value;
LOG_DEBUG("Found interface: {}", interface_name);
return parser::visit_result::Continue;
return kb::parser::visit_result::Continue;
}
if (type == "field_declaration" && value.starts_with("virtual ")) {
if (value.starts_with("virtual ")) {
koalabox::parser::walk(class_node, [&](const ts::Node& decl_node) {
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 parser::visit_result::Stop;
return kb::parser::visit_result::Stop;
}
return parser::visit_result::Continue;
return kb::parser::visit_result::Continue;
});
}
return parser::visit_result::SkipChildren;
return kb::parser::visit_result::SkipChildren;
}
return parser::visit_result::Continue;
return kb::parser::visit_result::Continue;
});
} else if (current_type == "preproc_def") {
koalabox::parser::walk(current_node, [&](const ts::Node& preproc_node) {
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")
? parser::visit_result::Continue
: parser::visit_result::Stop;
? kb::parser::visit_result::Continue
: kb::parser::visit_result::Stop;
}
if (preproc_node.getType() == "preproc_arg") {
@@ -88,16 +93,16 @@ namespace {
interface_version = unquote_if_quoted(trimmed_version);
LOG_DEBUG("Interface version: {}", interface_version);
return parser::visit_result::Stop;
return kb::parser::visit_result::Stop;
}
return parser::visit_result::Continue;
return kb::parser::visit_result::Continue;
});
} else if (current_type == "translation_unit" || current_type == "preproc_ifdef") {
return parser::visit_result::Continue;
return kb::parser::visit_result::Continue;
}
return parser::visit_result::SkipChildren;
return kb::parser::visit_result::SkipChildren;
});
// Save the findings
@@ -108,8 +113,71 @@ namespace {
}
}
void parse_sdk(const fs::path& sdk_path, json& lookup, BS::thread_pool<>& pool) {
const auto headers_dir = sdk_path / "headers";
/**
* 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.
*/
std::string manually_preprocess_header(const fs::path& header_path) {
const auto header_contents = kb::io::read_file(header_path);
// language=RegExp
const std::regex re(R"(STEAM_PRIVATE_API\s*\(\s*([^)]+)\s*\))");
const auto processed_contents = std::regex_replace(header_contents, re, "$1");
return processed_contents;
}
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)) {
LOG_WARN("Warning: SDK missing 'headers' directory: {}", headers_dir.string());
@@ -125,23 +193,26 @@ namespace {
try {
LOG_DEBUG("Parsing header: {}", header_path.string());
// Read file as text
const auto header_contents = koalabox::io::read_file(header_path);
parse_header(header_contents, lookup);
const auto processed_header = manually_preprocess_header(header_path);
parse_header(processed_header, lookup);
} catch (std::exception& e) {
koalabox::util::panic(std::format("Error parsing header: {}", e.what()));
LOG_CRITICAL(e.what());
exit(-1);
}
});
}
}
}
void generate_lookup_json(const fs::path& steamworks_dir) {
json lookup;
void generate_lookup_json(
const fs::path& steamworks_dir, //
const std::set<std::string>& sdk_filter
) {
nlohmann::ordered_json lookup;
// Create the thread pool
const auto thread_count = std::max(2U, std::thread::hardware_concurrency() / 2);
// The thread pool noticeably speeds up the overall parsing.
// Thread count of 4 seems to yield most optimal performance benefits.
constexpr auto thread_count = 4;
LOG_INFO("Creating task pool with {} threads", thread_count);
BS::thread_pool pool(thread_count);
@@ -151,24 +222,43 @@ namespace {
continue;
}
if (not sdk_filter.empty() and
not sdk_filter.contains(entry.path().filename().string())) {
continue;
}
parse_sdk(entry.path(), lookup, pool);
}
// Wait for all tasks to finish
pool.wait();
std::ofstream lookup_output("interface_lookup.json");
lookup_output << std::setw(4) << lookup << std::endl;
const auto interface_lookup_path = fs::path("interface_lookup.json");
std::ofstream lookup_output(interface_lookup_path);
lookup_output << std::setw(4) << lookup;
LOG_INFO("Interface lookup generated at: {}", fs::absolute(interface_lookup_path).string());
}
}
/**
* A tool for parsing Steamworks headers and generating a lookup map of its interfaces.
* Optionally accepts a list of folder names that filters which sdk versions will be parsed.
* No list means all versions will be parsed.
*/
int main() {
int wmain(const int argc, const wchar_t* argv[]) { // NOLINT(*-use-internal-linkage)
try {
koalabox::logger::init_console_logger();
std::set<std::string> sdk_filter;
if (argc > 1) {
for (auto i = 1; i < argc; i++) {
const auto version = koalabox::str::to_str(argv[i]);
sdk_filter.insert(version);
}
}
const auto steamworks_dir = fs::path("steamworks");
if (!fs::exists(steamworks_dir)) {
@@ -176,7 +266,7 @@ int main() {
}
const auto start = std::chrono::steady_clock::now();
generate_lookup_json(steamworks_dir);
generate_lookup_json(steamworks_dir, sdk_filter);
const auto end = std::chrono::steady_clock::now();
const auto elapsed = duration_cast<std::chrono::seconds>(end - start);