mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
build: embed the addon and bindings as RCDATA resources
The embed step hex-encoded every file in a CMake while loop at build time (7.6 s, a 2.4 MB generated header). Configure now writes an .rc that points rc.exe at the files directly, plus a path-to-ID header. Embed::Resource reads the bytes from the DLL image at runtime. The .toc version stamp moves to a configure-time copy. Editing the toc reconfigures, and editing any other file recompiles only the .rc.
This commit is contained in:
@@ -71,7 +71,7 @@ jobs:
|
||||
# is_release=true when a vX.Y.Z tag is in play (pushed tag,
|
||||
# dispatched-from-tag, or a `tag` input); the embedded addon's
|
||||
# `## Version:` and the compiled CLASSIC_API_VERSION are both
|
||||
# stamped from it at embed/compile time (see cmake/embed_addon.cmake)
|
||||
# stamped from it at embed/compile time (see cmake/embed_resources.cmake)
|
||||
# — no source edit.
|
||||
- name: Resolve version
|
||||
id: ver
|
||||
|
||||
+24
-49
@@ -45,7 +45,7 @@ set(TINYCBOR_SOURCES
|
||||
# always pass locally. CI release builds pass `-DCLASSICAPI_TAG=vX.Y.Z`,
|
||||
# which drives BOTH the compiled `CLASSIC_API_VERSION` int AND the
|
||||
# embedded addon's `## Version:` toc line (substituted in
|
||||
# embed_addon.cmake). The on-disk source toc stays `## Version: DEV`;
|
||||
# embed_resources.cmake). The on-disk source toc stays `## Version: DEV`;
|
||||
# only the embedded copy is stamped, so a release build needs nothing
|
||||
# but the flag — no source edit.
|
||||
set(CLASSICAPI_VERSION_VALUE 99999999)
|
||||
@@ -73,55 +73,30 @@ elseif(CLASSICAPI_TAG)
|
||||
)
|
||||
endif()
|
||||
|
||||
# Bundle `AddOns/!!!ClassicAPI/` into a generated header so the DLL can
|
||||
# surface the addon to the engine even when the user doesn't have it
|
||||
# installed on disk. The file-read hook in `src/addons/Embedded.cpp`
|
||||
# returns these buffers when the engine asks for paths under
|
||||
# `Interface\AddOns\!!!ClassicAPI\`.
|
||||
set(EMBEDDED_CLASSICAPI_SRC "${CMAKE_CURRENT_SOURCE_DIR}/AddOns/!!!ClassicAPI")
|
||||
set(EMBEDDED_CLASSICAPI_HEADER "${CMAKE_BINARY_DIR}/generated/embedded_classicapi.h")
|
||||
file(GLOB_RECURSE EMBEDDED_CLASSICAPI_FILES CONFIGURE_DEPENDS
|
||||
"${EMBEDDED_CLASSICAPI_SRC}/*")
|
||||
# The `.classicapi-dev` marker is a disk-only developer signal — keep it
|
||||
# out of the embed inputs (embed_addon.cmake filters it too).
|
||||
list(FILTER EMBEDDED_CLASSICAPI_FILES EXCLUDE REGEX "/\\.classicapi-dev$")
|
||||
add_custom_command(
|
||||
OUTPUT "${EMBEDDED_CLASSICAPI_HEADER}"
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSRC_DIR=${EMBEDDED_CLASSICAPI_SRC}
|
||||
-DOUT_HEADER=${EMBEDDED_CLASSICAPI_HEADER}
|
||||
-DADDON_NAME=ClassicAPI
|
||||
-DADDON_VERSION=${CLASSICAPI_TOC_VERSION}
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/embed_addon.cmake"
|
||||
DEPENDS
|
||||
${EMBEDDED_CLASSICAPI_FILES}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/embed_addon.cmake"
|
||||
COMMENT "Embedding !!!ClassicAPI addon into ${EMBEDDED_CLASSICAPI_HEADER}"
|
||||
VERBATIM)
|
||||
|
||||
# Bundle `src/bindings/Bindings.xml` (our TARGETING-group additions)
|
||||
# into a generated header. `src/bindings/Inject.cpp` extracts the
|
||||
# inner `<Binding>` elements from the embedded buffer and splices them
|
||||
# into the engine's `Interface\FrameXML\Bindings.xml` at load time.
|
||||
set(EMBEDDED_BINDINGS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/bindings/Bindings.xml")
|
||||
set(EMBEDDED_BINDINGS_HEADER "${CMAKE_BINARY_DIR}/generated/embedded_bindings.h")
|
||||
add_custom_command(
|
||||
OUTPUT "${EMBEDDED_BINDINGS_HEADER}"
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DINPUT_FILE=${EMBEDDED_BINDINGS_SRC}
|
||||
-DOUT_HEADER=${EMBEDDED_BINDINGS_HEADER}
|
||||
-DSYMBOL=kEmbeddedBindingsXml
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/embed_file.cmake"
|
||||
DEPENDS
|
||||
"${EMBEDDED_BINDINGS_SRC}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/embed_file.cmake"
|
||||
COMMENT "Embedding bindings XML into ${EMBEDDED_BINDINGS_HEADER}"
|
||||
VERBATIM)
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED
|
||||
${SOURCES} ${HEADERS} ${TINYCBOR_SOURCES}
|
||||
${EMBEDDED_CLASSICAPI_HEADER}
|
||||
${EMBEDDED_BINDINGS_HEADER})
|
||||
${SOURCES} ${HEADERS} ${TINYCBOR_SOURCES})
|
||||
|
||||
# Embed `AddOns/!!!ClassicAPI/` and `src/bindings/Bindings.xml` as RCDATA
|
||||
# resources. The file-read hook in `src/addons/Embedded.cpp` serves the
|
||||
# addon files when the engine asks for paths under
|
||||
# `Interface\AddOns\!!!ClassicAPI\`, so the addon loads even when the user
|
||||
# doesn't have it on disk. `src/bindings/Inject.cpp` splices the bindings
|
||||
# XML (our TARGETING-group additions) into the engine's
|
||||
# `Interface\FrameXML\Bindings.xml` at load time.
|
||||
include(cmake/embed_resources.cmake)
|
||||
set(EMBEDDED_CLASSICAPI_SRC "${CMAKE_CURRENT_SOURCE_DIR}/AddOns/!!!ClassicAPI")
|
||||
file(GLOB_RECURSE EMBEDDED_CLASSICAPI_FILES CONFIGURE_DEPENDS RELATIVE
|
||||
"${EMBEDDED_CLASSICAPI_SRC}" "${EMBEDDED_CLASSICAPI_SRC}/*")
|
||||
# Never embed the developer marker: it must reflect on-disk presence only
|
||||
# (see DiskHasDevMarker in src/addons/Embedded.cpp).
|
||||
list(FILTER EMBEDDED_CLASSICAPI_FILES EXCLUDE REGEX "(^|/)\\.classicapi-dev$")
|
||||
embed_resources(
|
||||
TARGET ${PROJECT_NAME}
|
||||
ADDON_DIR "${EMBEDDED_CLASSICAPI_SRC}"
|
||||
ADDON_FILES ${EMBEDDED_CLASSICAPI_FILES}
|
||||
ADDON_VERSION "${CLASSICAPI_TOC_VERSION}"
|
||||
BINDINGS_XML "${CMAKE_CURRENT_SOURCE_DIR}/src/bindings/Bindings.xml"
|
||||
OUT_DIR "${CMAKE_BINARY_DIR}/generated")
|
||||
|
||||
target_compile_definitions(
|
||||
${PROJECT_NAME}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
# embed_addon.cmake — bundle an addon directory into a generated C++ header.
|
||||
#
|
||||
# Invoked from CMakeLists.txt via `cmake -P` as a custom command. Reads every
|
||||
# file under SRC_DIR recursively and emits OUT_HEADER containing:
|
||||
#
|
||||
# namespace Addons::Embedded {
|
||||
# struct File {
|
||||
# const char *path;
|
||||
# const unsigned char *data;
|
||||
# size_t size;
|
||||
# };
|
||||
# extern const File kFiles[];
|
||||
# extern const size_t kFileCount;
|
||||
# }
|
||||
#
|
||||
# Paths are normalized to lowercase backslash-form relative to
|
||||
# `Interface\AddOns\` so they match what the engine's TOC parser
|
||||
# constructs when it asks for the addon's files (see
|
||||
# `s_Interface_AddOns__s__s_toc_00853840` in the binary). The
|
||||
# manifest is hash-table-friendly for fast lookup but the lookup
|
||||
# walks the array linearly — fine for 25 files.
|
||||
#
|
||||
# Usage:
|
||||
# cmake -DSRC_DIR=... -DOUT_HEADER=... -DADDON_NAME=... -P embed_addon.cmake
|
||||
|
||||
if(NOT SRC_DIR)
|
||||
message(FATAL_ERROR "embed_addon.cmake: SRC_DIR required")
|
||||
endif()
|
||||
if(NOT OUT_HEADER)
|
||||
message(FATAL_ERROR "embed_addon.cmake: OUT_HEADER required")
|
||||
endif()
|
||||
if(NOT ADDON_NAME)
|
||||
message(FATAL_ERROR "embed_addon.cmake: ADDON_NAME required")
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE INPUT_FILES RELATIVE "${SRC_DIR}" "${SRC_DIR}/*")
|
||||
# Never embed the developer marker — it must reflect on-disk presence
|
||||
# only (see src/addons/Embedded.cpp DiskHasDevMarker), and it is
|
||||
# gitignored so it only exists in a developer working tree anyway.
|
||||
list(FILTER INPUT_FILES EXCLUDE REGEX "(^|/)\\.classicapi-dev$")
|
||||
list(SORT INPUT_FILES) # deterministic codegen
|
||||
|
||||
set(BODY "")
|
||||
set(MANIFEST "")
|
||||
set(INDEX 0)
|
||||
foreach(REL_PATH ${INPUT_FILES})
|
||||
set(ABS_PATH "${SRC_DIR}/${REL_PATH}")
|
||||
|
||||
# Stamp the addon's `## Version:` line from the build flag rather
|
||||
# than the committed source (which stays `DEV`). This is what makes
|
||||
# a release build a pure function of `-DCLASSICAPI_TAG` — the source
|
||||
# toc is never edited. Only the `.toc` file is rewritten, in-memory,
|
||||
# before hex-encoding; every other file is embedded verbatim.
|
||||
if(ADDON_VERSION AND REL_PATH MATCHES "\\.toc$")
|
||||
file(READ "${ABS_PATH}" TOC_TEXT)
|
||||
string(REGEX REPLACE "## Version:[^\n\r]*"
|
||||
"## Version: ${ADDON_VERSION}" TOC_TEXT "${TOC_TEXT}")
|
||||
# Round-trip through a temp file to hex-encode the modified text
|
||||
# (portable to CMake 3.10; avoids requiring string(HEX), 3.18+).
|
||||
set(TOC_TMP "${OUT_HEADER}.toc.tmp")
|
||||
file(WRITE "${TOC_TMP}" "${TOC_TEXT}")
|
||||
file(READ "${TOC_TMP}" CONTENT_HEX HEX)
|
||||
file(REMOVE "${TOC_TMP}")
|
||||
else()
|
||||
file(READ "${ABS_PATH}" CONTENT_HEX HEX)
|
||||
endif()
|
||||
|
||||
string(LENGTH "${CONTENT_HEX}" HEX_LEN)
|
||||
math(EXPR FILE_SIZE "${HEX_LEN} / 2")
|
||||
|
||||
# Convert hex string to comma-separated byte literals. 16 bytes per line
|
||||
# keeps the generated header readable.
|
||||
set(BYTES "")
|
||||
set(COL 0)
|
||||
set(POS 0)
|
||||
while(POS LESS HEX_LEN)
|
||||
string(SUBSTRING "${CONTENT_HEX}" ${POS} 2 BYTE)
|
||||
if(COL EQUAL 0)
|
||||
string(APPEND BYTES " ")
|
||||
endif()
|
||||
string(APPEND BYTES "0x${BYTE},")
|
||||
math(EXPR POS "${POS} + 2")
|
||||
math(EXPR COL "${COL} + 1")
|
||||
if(COL EQUAL 16)
|
||||
string(APPEND BYTES "\n")
|
||||
set(COL 0)
|
||||
else()
|
||||
string(APPEND BYTES " ")
|
||||
endif()
|
||||
endwhile()
|
||||
if(NOT COL EQUAL 0)
|
||||
string(APPEND BYTES "\n")
|
||||
endif()
|
||||
|
||||
# Normalize path to engine-style: `<AddonName>\Sub\Path.lua` with
|
||||
# backslashes. Engine constructs full path as
|
||||
# `Interface\AddOns\<AddonName>\<file>` — we strip the
|
||||
# `Interface\AddOns\<AddonName>\` prefix at lookup time so storing
|
||||
# just the relative form keeps the manifest small.
|
||||
string(REPLACE "/" "\\\\" REL_PATH_BS "${REL_PATH}")
|
||||
|
||||
string(APPEND BODY "static const unsigned char kData_${INDEX}[] = {\n${BYTES}};\n\n")
|
||||
string(APPEND MANIFEST " { \"${REL_PATH_BS}\", kData_${INDEX}, ${FILE_SIZE} },\n")
|
||||
|
||||
math(EXPR INDEX "${INDEX} + 1")
|
||||
endforeach()
|
||||
|
||||
set(HEADER_CONTENT "// Auto-generated by embed_addon.cmake — do not edit.
|
||||
// Source: ${SRC_DIR}
|
||||
//
|
||||
// Addon \"${ADDON_NAME}\" embedded as a fallback for users who don't have
|
||||
// it installed on disk. See src/addons/Embedded.cpp for the file-read
|
||||
// hook that surfaces these buffers to the engine.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Addons::Embedded {
|
||||
|
||||
namespace ${ADDON_NAME}Files {
|
||||
|
||||
${BODY}static const struct File {
|
||||
const char *path;
|
||||
const unsigned char *data;
|
||||
size_t size;
|
||||
} kFiles[] = {
|
||||
${MANIFEST}};
|
||||
|
||||
static constexpr size_t kFileCount = sizeof(kFiles) / sizeof(kFiles[0]);
|
||||
|
||||
} // namespace ${ADDON_NAME}Files
|
||||
|
||||
} // namespace Addons::Embedded
|
||||
")
|
||||
|
||||
file(WRITE "${OUT_HEADER}" "${HEADER_CONTENT}")
|
||||
message(STATUS "embed_addon: wrote ${OUT_HEADER} (${INDEX} files)")
|
||||
@@ -1,62 +0,0 @@
|
||||
# embed_file.cmake — bundle a single file into a generated C++ header.
|
||||
#
|
||||
# Invoked from CMakeLists.txt via `cmake -P` as a custom command. Reads
|
||||
# INPUT_FILE and emits OUT_HEADER containing a constexpr byte array
|
||||
# named SYMBOL plus a `SYMBOL ## Size` size constant. Both live at file
|
||||
# scope; the header is meant to be included exactly once.
|
||||
#
|
||||
# Usage:
|
||||
# cmake -DINPUT_FILE=... -DOUT_HEADER=... -DSYMBOL=... -P embed_file.cmake
|
||||
|
||||
if(NOT INPUT_FILE)
|
||||
message(FATAL_ERROR "embed_file.cmake: INPUT_FILE required")
|
||||
endif()
|
||||
if(NOT OUT_HEADER)
|
||||
message(FATAL_ERROR "embed_file.cmake: OUT_HEADER required")
|
||||
endif()
|
||||
if(NOT SYMBOL)
|
||||
message(FATAL_ERROR "embed_file.cmake: SYMBOL required")
|
||||
endif()
|
||||
|
||||
file(READ "${INPUT_FILE}" CONTENT_HEX HEX)
|
||||
string(LENGTH "${CONTENT_HEX}" HEX_LEN)
|
||||
math(EXPR FILE_SIZE "${HEX_LEN} / 2")
|
||||
|
||||
# Convert hex string to comma-separated byte literals. 16 bytes per
|
||||
# line keeps the generated header readable.
|
||||
set(BYTES "")
|
||||
set(COL 0)
|
||||
set(POS 0)
|
||||
while(POS LESS HEX_LEN)
|
||||
string(SUBSTRING "${CONTENT_HEX}" ${POS} 2 BYTE)
|
||||
if(COL EQUAL 0)
|
||||
string(APPEND BYTES " ")
|
||||
endif()
|
||||
string(APPEND BYTES "0x${BYTE},")
|
||||
math(EXPR POS "${POS} + 2")
|
||||
math(EXPR COL "${COL} + 1")
|
||||
if(COL EQUAL 16)
|
||||
string(APPEND BYTES "\n")
|
||||
set(COL 0)
|
||||
else()
|
||||
string(APPEND BYTES " ")
|
||||
endif()
|
||||
endwhile()
|
||||
if(NOT COL EQUAL 0)
|
||||
string(APPEND BYTES "\n")
|
||||
endif()
|
||||
|
||||
set(HEADER_CONTENT "// Auto-generated by embed_file.cmake — do not edit.
|
||||
// Source: ${INPUT_FILE}
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
static constexpr unsigned char ${SYMBOL}[] = {
|
||||
${BYTES}};
|
||||
static constexpr size_t ${SYMBOL}Size = sizeof(${SYMBOL});
|
||||
")
|
||||
|
||||
file(WRITE "${OUT_HEADER}" "${HEADER_CONTENT}")
|
||||
message(STATUS "embed_file: wrote ${OUT_HEADER} (${FILE_SIZE} bytes)")
|
||||
@@ -0,0 +1,111 @@
|
||||
# embed_resources.cmake — embed the addon and the bindings XML as Win32
|
||||
# RCDATA resources.
|
||||
#
|
||||
# Runs at configure time. It writes two files into OUT_DIR:
|
||||
#
|
||||
# embedded.rc one `<id> RCDATA "<path>"` line per file; rc.exe
|
||||
# reads each file straight from disk.
|
||||
# embedded_resources.h the IDs the DLL looks up at runtime through
|
||||
# src/embed/Resource.h:
|
||||
#
|
||||
# namespace Embed::Ids { constexpr int kBindingsXml = ...; }
|
||||
# namespace Addons::Embedded::ClassicAPIFiles {
|
||||
# struct File { const char *path; int resourceId; };
|
||||
# constexpr File kFiles[] = { ... };
|
||||
# constexpr size_t kFileCount = ...;
|
||||
# }
|
||||
#
|
||||
# Addon paths are relative to the addon folder, in backslash form
|
||||
# (`Util\Print.lua`) — the suffix the engine builds after
|
||||
# `Interface\AddOns\!!!ClassicAPI\`. The DLL compares them ignoring case.
|
||||
#
|
||||
# The `.toc` is the one file that is not embedded verbatim: its
|
||||
# `## Version:` line is stamped with ADDON_VERSION into a copy under
|
||||
# OUT_DIR, so a release build is a pure function of `-DCLASSICAPI_TAG` and
|
||||
# the source toc stays `DEV`.
|
||||
#
|
||||
# Rebuild triggers: adding or removing a file reconfigures through the
|
||||
# caller's CONFIGURE_DEPENDS glob; editing the toc reconfigures through
|
||||
# CMAKE_CONFIGURE_DEPENDS; editing any other file recompiles the .rc
|
||||
# through its OBJECT_DEPENDS.
|
||||
#
|
||||
# Usage:
|
||||
# embed_resources(TARGET <t> ADDON_DIR <dir> ADDON_FILES <rel paths...>
|
||||
# ADDON_VERSION <v> BINDINGS_XML <file> OUT_DIR <dir>)
|
||||
|
||||
# Write `content` to `path` only when it differs, so an unchanged
|
||||
# reconfigure does not touch the file and trigger a rebuild.
|
||||
function(_embed_write_if_changed path content)
|
||||
set(tmp "${path}.tmp")
|
||||
file(WRITE "${tmp}" "${content}")
|
||||
configure_file("${tmp}" "${path}" COPYONLY)
|
||||
file(REMOVE "${tmp}")
|
||||
endfunction()
|
||||
|
||||
function(embed_resources)
|
||||
cmake_parse_arguments(E "" "TARGET;ADDON_DIR;ADDON_VERSION;BINDINGS_XML;OUT_DIR"
|
||||
"ADDON_FILES" ${ARGN})
|
||||
|
||||
set(rc "// Generated by cmake/embed_resources.cmake - do not edit.\n\n")
|
||||
set(manifest "")
|
||||
set(inputs "")
|
||||
set(id 1)
|
||||
|
||||
string(APPEND rc "${id} RCDATA \"${E_BINDINGS_XML}\"\n")
|
||||
list(APPEND inputs "${E_BINDINGS_XML}")
|
||||
set(bindings_id ${id})
|
||||
math(EXPR id "${id} + 1")
|
||||
|
||||
set(files ${E_ADDON_FILES})
|
||||
list(SORT files) # deterministic IDs
|
||||
foreach(rel ${files})
|
||||
set(abs "${E_ADDON_DIR}/${rel}")
|
||||
if(rel MATCHES "\\.toc$")
|
||||
file(READ "${abs}" toc)
|
||||
string(REGEX REPLACE "## Version:[^\n\r]*"
|
||||
"## Version: ${E_ADDON_VERSION}" toc "${toc}")
|
||||
set(stamped "${E_OUT_DIR}/embed/${rel}")
|
||||
_embed_write_if_changed("${stamped}" "${toc}")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${abs}")
|
||||
set(abs "${stamped}")
|
||||
endif()
|
||||
string(APPEND rc "${id} RCDATA \"${abs}\"\n")
|
||||
list(APPEND inputs "${abs}")
|
||||
string(REPLACE "/" "\\\\" rel_bs "${rel}")
|
||||
string(APPEND manifest " { \"${rel_bs}\", ${id} },\n")
|
||||
math(EXPR id "${id} + 1")
|
||||
endforeach()
|
||||
|
||||
_embed_write_if_changed("${E_OUT_DIR}/embedded.rc" "${rc}")
|
||||
_embed_write_if_changed("${E_OUT_DIR}/embedded_resources.h"
|
||||
"// Generated by cmake/embed_resources.cmake — do not edit.
|
||||
// Resource IDs for the RCDATA entries in embedded.rc. Load them through
|
||||
// src/embed/Resource.h.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Embed::Ids {
|
||||
constexpr int kBindingsXml = ${bindings_id};
|
||||
} // namespace Embed::Ids
|
||||
|
||||
namespace Addons::Embedded::ClassicAPIFiles {
|
||||
|
||||
struct File {
|
||||
const char *path;
|
||||
int resourceId;
|
||||
};
|
||||
|
||||
constexpr File kFiles[] = {
|
||||
${manifest}};
|
||||
|
||||
constexpr size_t kFileCount = sizeof(kFiles) / sizeof(kFiles[0]);
|
||||
|
||||
} // namespace Addons::Embedded::ClassicAPIFiles
|
||||
")
|
||||
|
||||
target_sources(${E_TARGET} PRIVATE "${E_OUT_DIR}/embedded.rc")
|
||||
set_source_files_properties("${E_OUT_DIR}/embedded.rc" PROPERTIES
|
||||
OBJECT_DEPENDS "${inputs}")
|
||||
endfunction()
|
||||
+19
-22
@@ -18,8 +18,8 @@
|
||||
// How:
|
||||
//
|
||||
// 1. The CMake build embeds every file under `AddOns/!!!ClassicAPI/`
|
||||
// into a generated header (`embedded_classicapi.h`) as a byte
|
||||
// array per file plus a `{path, data, size}` manifest.
|
||||
// as an RCDATA resource, plus a generated `{path, resourceId}`
|
||||
// manifest (`embedded_resources.h`).
|
||||
//
|
||||
// 2. We hook the engine's file-read function at `FUN_FILE_READ`.
|
||||
// When the engine asks for a path under
|
||||
@@ -49,7 +49,7 @@
|
||||
// `Compat.lua`, `Util\Print.lua`, etc. all surface from
|
||||
// embedded buffers.
|
||||
|
||||
#include "embedded_classicapi.h"
|
||||
#include "embedded_resources.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
@@ -60,6 +60,7 @@
|
||||
#include "addons/Toc.h"
|
||||
#include "addons/TocRewrite.h"
|
||||
#include "bindings/Inject.h"
|
||||
#include "embed/Resource.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
@@ -115,12 +116,13 @@ const char *StripAddonPrefix(const char *path) {
|
||||
return p;
|
||||
}
|
||||
|
||||
const ClassicAPIFiles::File *LookupEmbedded(const char *suffix) {
|
||||
// The embedded bytes for `suffix`; `data` is NULL when it is not embedded.
|
||||
Embed::Bytes LookupEmbedded(const char *suffix) {
|
||||
for (size_t i = 0; i < ClassicAPIFiles::kFileCount; ++i) {
|
||||
if (PathEqualsCI(suffix, ClassicAPIFiles::kFiles[i].path))
|
||||
return &ClassicAPIFiles::kFiles[i];
|
||||
return Embed::Resource(ClassicAPIFiles::kFiles[i].resourceId);
|
||||
}
|
||||
return nullptr;
|
||||
return {nullptr, 0};
|
||||
}
|
||||
|
||||
// Engine file I/O + Storm allocator — see addons/EngineIO.h for the shapes and
|
||||
@@ -196,15 +198,10 @@ Source g_source = Source::Undecided;
|
||||
|
||||
void EnsureEmbeddedVersionExtracted() {
|
||||
if (g_embeddedVersion[0] != '\0') return;
|
||||
for (size_t i = 0; i < ClassicAPIFiles::kFileCount; ++i) {
|
||||
if (PathEqualsCI(kAddonTocFile, ClassicAPIFiles::kFiles[i].path)) {
|
||||
ExtractTocVersion(
|
||||
reinterpret_cast<const char *>(ClassicAPIFiles::kFiles[i].data),
|
||||
ClassicAPIFiles::kFiles[i].size,
|
||||
g_embeddedVersion, sizeof(g_embeddedVersion));
|
||||
return;
|
||||
}
|
||||
}
|
||||
const Embed::Bytes toc = LookupEmbedded(kAddonTocFile);
|
||||
if (toc.data != nullptr)
|
||||
ExtractTocVersion(reinterpret_cast<const char *>(toc.data), toc.size,
|
||||
g_embeddedVersion, sizeof(g_embeddedVersion));
|
||||
}
|
||||
|
||||
// True iff the on-disk addon folder contains the `.classicapi-dev`
|
||||
@@ -331,8 +328,8 @@ int __stdcall FileRead_h(int unused, const char *path, void **outBuf,
|
||||
// Source::Embedded path, OR Source::Disk-but-file-missing fallback.
|
||||
// Look up the embedded version and synthesize a Storm buffer the
|
||||
// caller can free normally.
|
||||
const auto *entry = LookupEmbedded(suffix);
|
||||
if (entry == nullptr) {
|
||||
const Embed::Bytes entry = LookupEmbedded(suffix);
|
||||
if (entry.data == nullptr) {
|
||||
// Not in our embedded set either — let the engine try disk
|
||||
// one more time (returns 0 if that also fails). Covers
|
||||
// pathological cases like an empty disk install + embedded
|
||||
@@ -343,15 +340,15 @@ int __stdcall FileRead_h(int unused, const char *path, void **outBuf,
|
||||
|
||||
auto SMemAlloc = reinterpret_cast<SMemAllocFn>(
|
||||
Offsets::FUN_STORM_SMEM_ALLOC);
|
||||
const size_t totalSize = entry->size + extraBytes;
|
||||
const size_t totalSize = entry.size + extraBytes;
|
||||
void *buf = SMemAlloc(totalSize, __FILE__, __LINE__, 0);
|
||||
if (buf == nullptr) return 0;
|
||||
std::memcpy(buf, entry->data, entry->size);
|
||||
std::memcpy(buf, entry.data, entry.size);
|
||||
if (extraBytes > 0) {
|
||||
std::memset(static_cast<uint8_t *>(buf) + entry->size, 0, extraBytes);
|
||||
std::memset(static_cast<uint8_t *>(buf) + entry.size, 0, extraBytes);
|
||||
}
|
||||
if (outBuf != nullptr) *outBuf = buf;
|
||||
if (outSize != nullptr) *outSize = entry->size;
|
||||
if (outSize != nullptr) *outSize = entry.size;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -370,7 +367,7 @@ int __stdcall FileExists_h(const char *path, int mode) {
|
||||
DecideSource();
|
||||
if (g_source == Source::Disk && FileExists_o(path, mode) != 0)
|
||||
return 1;
|
||||
if (LookupEmbedded(suffix) != nullptr)
|
||||
if (LookupEmbedded(suffix).data != nullptr)
|
||||
return 1;
|
||||
return FileExists_o(path, mode);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
#include "Inject.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
#include "embedded_bindings.h"
|
||||
#include "embed/Resource.h"
|
||||
#include "embedded_resources.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -132,8 +133,10 @@ bool ExtractHeaderAttr(const char *tagStart, const char *tagEnd,
|
||||
|
||||
EmbeddedConfig ResolveEmbeddedConfig() {
|
||||
EmbeddedConfig out = {};
|
||||
const char *content = reinterpret_cast<const char *>(kEmbeddedBindingsXml);
|
||||
const char *end = content + kEmbeddedBindingsXmlSize;
|
||||
const Embed::Bytes xml = Embed::Resource(Embed::Ids::kBindingsXml);
|
||||
if (xml.data == nullptr) return out;
|
||||
const char *content = reinterpret_cast<const char *>(xml.data);
|
||||
const char *end = content + xml.size;
|
||||
|
||||
// Locate `<Bindings` opening tag (skipping any comment blocks
|
||||
// that mention the tag literally for documentation purposes).
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "embed/Resource.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// The linker-defined base of this module — our HMODULE without a lookup.
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
namespace Embed {
|
||||
|
||||
Bytes Resource(int id) {
|
||||
const HMODULE self = reinterpret_cast<HMODULE>(&__ImageBase);
|
||||
HRSRC info = FindResourceW(self, MAKEINTRESOURCEW(id), MAKEINTRESOURCEW(10)); // RT_RCDATA
|
||||
if (info == nullptr)
|
||||
return {nullptr, 0};
|
||||
HGLOBAL handle = LoadResource(self, info);
|
||||
if (handle == nullptr)
|
||||
return {nullptr, 0};
|
||||
return {static_cast<const unsigned char *>(LockResource(handle)),
|
||||
static_cast<size_t>(SizeofResource(self, info))};
|
||||
}
|
||||
|
||||
} // namespace Embed
|
||||
@@ -0,0 +1,31 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Embed {
|
||||
|
||||
struct Bytes {
|
||||
const unsigned char *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
// The bytes of RCDATA resource `id` in this DLL (IDs come from the generated
|
||||
// `embedded_resources.h`). The memory is part of the mapped image, so it stays
|
||||
// valid for the life of the DLL and must not be freed. `{nullptr, 0}` when the
|
||||
// resource is missing.
|
||||
Bytes Resource(int id);
|
||||
|
||||
} // namespace Embed
|
||||
Reference in New Issue
Block a user