mirror of
https://github.com/acidicoala/SmokeAPI.git
synced 2025-12-05 21:15:39 -05:00
Added steamworks downloader & parser
This commit is contained in:
7
.idea/runConfigurations/steamworks_downloader__prompt_.xml
generated
Normal file
7
.idea/runConfigurations/steamworks_downloader__prompt_.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="steamworks_downloader [prompt]" type="CMakeRunConfiguration" factoryName="Application" PROGRAM_PARAMS="$Prompt$" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/res" PASS_PARENT_ENVS_2="true" PROJECT_NAME="SmokeAPI" TARGET_NAME="steamworks_downloader" CONFIG_NAME="Debug [64]" RUN_TARGET_PROJECT_NAME="SmokeAPI" RUN_TARGET_NAME="steamworks_downloader">
|
||||||
|
<method v="2">
|
||||||
|
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
7
.idea/runConfigurations/steamworks_parser.xml
generated
Normal file
7
.idea/runConfigurations/steamworks_parser.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="steamworks_parser" type="CMakeRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" EMULATE_TERMINAL="false" WORKING_DIR="file://$ProjectFileDir$/res/" PASS_PARENT_ENVS_2="true" PROJECT_NAME="SmokeAPI" TARGET_NAME="steamworks_parser" CONFIG_NAME="Debug [64]" RUN_TARGET_PROJECT_NAME="SmokeAPI" RUN_TARGET_NAME="steamworks_parser">
|
||||||
|
<method v="2">
|
||||||
|
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
74
tools/CMakeLists.txt
Normal file
74
tools/CMakeLists.txt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.24)
|
||||||
|
|
||||||
|
project(smoke-api-tools LANGUAGES CXX)
|
||||||
|
|
||||||
|
### Install CPM package manager
|
||||||
|
|
||||||
|
file(
|
||||||
|
DOWNLOAD
|
||||||
|
https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.42.0/CPM.cmake
|
||||||
|
${CMAKE_BINARY_DIR}/cmake/CPM.cmake
|
||||||
|
EXPECTED_HASH SHA256=2020b4fc42dba44817983e06342e682ecfc3d2f484a581f11cc5731fbe4dce8a
|
||||||
|
)
|
||||||
|
include(${CMAKE_BINARY_DIR}/cmake/CPM.cmake)
|
||||||
|
|
||||||
|
### Install parser 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
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
)
|
||||||
|
|
||||||
|
### Install ZIP library https://github.com/richgel999/miniz
|
||||||
|
CPMAddPackage(
|
||||||
|
NAME miniz
|
||||||
|
GIT_REPOSITORY https://github.com/richgel999/miniz.git
|
||||||
|
GIT_TAG c883286f1a6443720e7705450f59e579a4bbb8e2
|
||||||
|
)
|
||||||
|
|
||||||
|
### Steamworks Downloader executable
|
||||||
|
|
||||||
|
add_executable(steamworks_downloader steamworks_downloader.cpp)
|
||||||
|
target_link_libraries(steamworks_downloader PRIVATE
|
||||||
|
cpr # HTTP client
|
||||||
|
miniz # ZIP library
|
||||||
|
)
|
||||||
|
|
||||||
|
### Steamworks Parser executable
|
||||||
|
|
||||||
|
add_executable(steamworks_parser steamworks_parser.cpp)
|
||||||
|
target_link_libraries(steamworks_parser PRIVATE
|
||||||
|
nlohmann_json # JSON parser
|
||||||
|
tree-sitter-cpp # C++ grammar
|
||||||
|
cpp-tree-sitter # C++ bindings for tree-sitter
|
||||||
|
)
|
||||||
206
tools/steamworks_downloader.cpp
Normal file
206
tools/steamworks_downloader.cpp
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <cpr/cpr.h>
|
||||||
|
#include <miniz.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
// Function to extract files from a ZIP archive that match multiple regex patterns
|
||||||
|
void extract_files(
|
||||||
|
const fs::path& zip_path,
|
||||||
|
const std::function<fs::path(const std::string& name, bool is_dir)>& predicate
|
||||||
|
) {
|
||||||
|
mz_zip_archive zip = {};
|
||||||
|
|
||||||
|
if (!mz_zip_reader_init_file(&zip, zip_path.string().c_str(), 0)) {
|
||||||
|
throw std::runtime_error("mz_zip_reader_init_file() failed for: " + zip_path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] auto guard = [&zip] { mz_zip_reader_end(&zip); };
|
||||||
|
try {
|
||||||
|
std::error_code ec;
|
||||||
|
const mz_uint num_files = mz_zip_reader_get_num_files(&zip);
|
||||||
|
|
||||||
|
for (mz_uint i = 0; i < num_files; ++i) {
|
||||||
|
mz_zip_archive_file_stat st;
|
||||||
|
if (!mz_zip_reader_file_stat(&zip, i, &st)) {
|
||||||
|
mz_zip_reader_end(&zip);
|
||||||
|
throw std::runtime_error(
|
||||||
|
"mz_zip_reader_file_stat() failed at index " + std::to_string(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string name = st.m_filename;
|
||||||
|
|
||||||
|
// Skip dangerous names early
|
||||||
|
if (name.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool is_dir = mz_zip_reader_is_file_a_directory(&zip, i) != 0;
|
||||||
|
|
||||||
|
const auto out_path = predicate(name, is_dir);
|
||||||
|
if (out_path.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_dir) {
|
||||||
|
fs::create_directories(out_path, ec);
|
||||||
|
if (ec) {
|
||||||
|
mz_zip_reader_end(&zip);
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Failed to create directory: " + out_path.string() + " (" + ec.message()
|
||||||
|
+ ")");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure parent directories exist
|
||||||
|
fs::create_directories(out_path.parent_path(), ec);
|
||||||
|
if (ec) {
|
||||||
|
mz_zip_reader_end(&zip);
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Failed to create parent directories for: " + out_path.string() + " (" + ec.
|
||||||
|
message() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract to heap then write to file
|
||||||
|
size_t uncomp_size = 0;
|
||||||
|
void* p = mz_zip_reader_extract_to_heap(&zip, i, &uncomp_size, 0);
|
||||||
|
if (!p) {
|
||||||
|
mz_zip_reader_end(&zip);
|
||||||
|
throw std::runtime_error("Extraction failed for entry: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream ofs(out_path, std::ios::binary);
|
||||||
|
if (!ofs) {
|
||||||
|
mz_free(p);
|
||||||
|
mz_zip_reader_end(&zip);
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Failed to open output file for writing: " + out_path.string());
|
||||||
|
}
|
||||||
|
ofs.write(static_cast<const char*>(p),
|
||||||
|
static_cast<std::streamsize>(uncomp_size));
|
||||||
|
ofs.close();
|
||||||
|
mz_free(p);
|
||||||
|
|
||||||
|
if (!ofs) {
|
||||||
|
mz_zip_reader_end(&zip);
|
||||||
|
throw std::runtime_error("Failed to write output file: " + out_path.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mz_zip_reader_end(&zip);
|
||||||
|
} catch (...) {
|
||||||
|
// Ensure cleanup on exceptions
|
||||||
|
guard();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string generate_random_string(const size_t length) {
|
||||||
|
static constexpr char charset[] =
|
||||||
|
"0123456789"
|
||||||
|
"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);
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
result.reserve(length);
|
||||||
|
for (std::size_t i = 0; i < length; ++i) {
|
||||||
|
result += charset[dist(rng)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
<< "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) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void download_sdk(
|
||||||
|
const fs::path& steamworks_dir,
|
||||||
|
const std::string_view& version
|
||||||
|
) {
|
||||||
|
const auto download_url = std::format(
|
||||||
|
"https://github.com/acidicoala/cdn/raw/refs/heads/main/valve/steamworks_sdk_{}.zip",
|
||||||
|
version
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto zip_file_path = fs::temp_directory_path()
|
||||||
|
/ (generate_random_string(16) + ".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();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auto unzip_dir = steamworks_dir / version;
|
||||||
|
unzip_sdk(zip_file_path, unzip_dir);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
std::cerr << "Unzip error: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::remove(zip_file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
if (argc == 1) {
|
||||||
|
print_help();
|
||||||
|
} else {
|
||||||
|
const auto streamworks_dir = std::filesystem::current_path() / "steamworks";
|
||||||
|
|
||||||
|
for (auto i = 1; i < argc; i++) {
|
||||||
|
try {
|
||||||
|
download_sdk(streamworks_dir, argv[i]);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr
|
||||||
|
<< std::format("Error downloading SDK '{}'. Reason: {}", argv[i], e.what())
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
212
tools/steamworks_parser.cpp
Normal file
212
tools/steamworks_parser.cpp
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <functional>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <deque>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <cpp-tree-sitter.h>
|
||||||
|
|
||||||
|
extern "C" const TSLanguage* tree_sitter_cpp();
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
std::string_view trim(const std::string_view& s) {
|
||||||
|
const auto start =
|
||||||
|
std::ranges::find_if_not(s, [](const unsigned char ch) { return std::isspace(ch); });
|
||||||
|
const auto end =
|
||||||
|
std::find_if_not(
|
||||||
|
s.rbegin(), s.rend(),
|
||||||
|
[](const unsigned char ch) { return std::isspace(ch); }
|
||||||
|
).base();
|
||||||
|
return start < end ? std::string_view(start, end) : std::string_view();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view unquote_if_quoted(const std::string_view& s) {
|
||||||
|
if (s.size() >= 2 && ((s.front() == '"' && s.back() == '"'))) {
|
||||||
|
return s.substr(1, s.size() - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a plain quoted string (might be raw/user-defined). Return as-is.
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class VisitResult {
|
||||||
|
Continue,
|
||||||
|
SkipChildren,
|
||||||
|
Stop
|
||||||
|
};
|
||||||
|
|
||||||
|
void walk(
|
||||||
|
const ts::Node& root,
|
||||||
|
const std::function<VisitResult(ts::Node)>& visit
|
||||||
|
) {
|
||||||
|
// DFS traversal
|
||||||
|
std::deque<ts::Node> queue;
|
||||||
|
queue.push_back(root);
|
||||||
|
auto first_visit = true;
|
||||||
|
|
||||||
|
while (!queue.empty()) {
|
||||||
|
const auto node = queue.front();
|
||||||
|
queue.pop_front();
|
||||||
|
|
||||||
|
switch (first_visit ? VisitResult::Continue : visit(node)) {
|
||||||
|
case VisitResult::Continue:
|
||||||
|
break;
|
||||||
|
case VisitResult::SkipChildren:
|
||||||
|
continue;
|
||||||
|
case VisitResult::Stop:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0, count = node.getNumNamedChildren(); i < count; ++i) {
|
||||||
|
if (const auto child = node.getNamedChild(i); !child.isNull()) {
|
||||||
|
queue.push_back(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
first_visit = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_header(const std::string_view& source, json& lookup) {
|
||||||
|
const auto language = ts::Language(tree_sitter_cpp());
|
||||||
|
auto parser = ts::Parser(language);
|
||||||
|
const auto tree = parser.parseString(source);
|
||||||
|
const auto root = tree.getRootNode();
|
||||||
|
|
||||||
|
json current_lookup = {};
|
||||||
|
std::string interface_version;
|
||||||
|
|
||||||
|
walk(root, [&](const auto& current_node) {
|
||||||
|
const auto current_type = current_node.getType();
|
||||||
|
if (current_type == "class_specifier") {
|
||||||
|
|
||||||
|
std::string interface_name;
|
||||||
|
[[maybe_unused]] int vt_idx = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
std::cout << " Parsing interface: " << interface_name << std::endl;
|
||||||
|
|
||||||
|
return VisitResult::Continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == "field_declaration" && value.starts_with("virtual ")) {
|
||||||
|
if (value.starts_with("virtual ")) {
|
||||||
|
walk(class_node, [&](const ts::Node& decl_node) {
|
||||||
|
if (decl_node.getType() == "field_identifier") {
|
||||||
|
const auto function_name =
|
||||||
|
decl_node.getSourceRange(source);
|
||||||
|
|
||||||
|
current_lookup[function_name]["vt_idx"] = vt_idx++;
|
||||||
|
return VisitResult::Stop;
|
||||||
|
}
|
||||||
|
return VisitResult::Continue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return VisitResult::SkipChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
return VisitResult::Continue;
|
||||||
|
});
|
||||||
|
} else if (current_type == "preproc_def") {
|
||||||
|
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")
|
||||||
|
? VisitResult::Continue
|
||||||
|
: VisitResult::Stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preproc_node.getType() == "preproc_arg") {
|
||||||
|
const auto quoted_version = preproc_node.getSourceRange(source);
|
||||||
|
interface_version = unquote_if_quoted(trim(quoted_version));
|
||||||
|
std::cout << " Interface version: " << interface_version << std::endl;
|
||||||
|
|
||||||
|
return VisitResult::Stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
return VisitResult::Continue;
|
||||||
|
});
|
||||||
|
} else if (current_type == "translation_unit" || current_type == "preproc_ifdef") {
|
||||||
|
return VisitResult::Continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return VisitResult::SkipChildren;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the findings
|
||||||
|
if (!interface_version.empty()) {
|
||||||
|
lookup[interface_version] = current_lookup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_sdk(const fs::path& sdk_path, json& lookup) {
|
||||||
|
const auto headers_dir = sdk_path / "headers";
|
||||||
|
|
||||||
|
if (!fs::exists(headers_dir)) {
|
||||||
|
std::cout << "Warning: SDK missing 'headers' directory: " << headers_dir << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
) {
|
||||||
|
std::cout << "Parsing header: " << header_path << std::endl;
|
||||||
|
|
||||||
|
// Read file as text
|
||||||
|
std::ifstream in(header_path, std::ios::binary);
|
||||||
|
const std::string header_contents(std::istreambuf_iterator{in}, {});
|
||||||
|
|
||||||
|
// Parse it
|
||||||
|
parse_header(header_contents, lookup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void generate_lookup_json(const fs::path& steamworks_dir) {
|
||||||
|
json lookup;
|
||||||
|
|
||||||
|
// Go over each steamworks sdk version
|
||||||
|
for (const auto& entry : fs::directory_iterator(steamworks_dir)) {
|
||||||
|
if (!entry.is_directory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_sdk(entry.path(), lookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream lookup_output("interface_lookup.json");
|
||||||
|
lookup_output << std::setw(4) << lookup << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tool for parsing Steamworks headers and generating a lookup map of its interfaces.
|
||||||
|
*/
|
||||||
|
int main() {
|
||||||
|
try {
|
||||||
|
const auto steamworks_dir = fs::path("steamworks");
|
||||||
|
|
||||||
|
if (!fs::exists(steamworks_dir)) {
|
||||||
|
throw std::exception("Expected to find 'steamworks' in current working directory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_lookup_json(steamworks_dir);
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
std::cerr << "Error: " << e.what() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user