GameLib is a collection of libraries for creating applications in Cakelisp.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

307 lines
14 KiB

;; DataBundle.cake: Bundle data into the executable to avoid loose files
;; Important caveat: The bundled data is not null-terminated, so don't just go printing it as a
;; string or you'll get garbage at the end
(import "CHelpers.cake" "CppHelpers.cake" "ComptimeHelpers.cake" "BuildTools.cake")
;; I don't want to expose this until I've determined a clean way to do this.
(defgenerator declare-extern (statement-token (arg-index array))
(var statement (const ([] CStatementOperation))
(array Keyword "extern" -1)
(array Statement null statement-token)))
(return (c-statement-out statement))
(return true))
;; Do not change these arguments without also changing usage of them in data-bundle-invocations
(defmacro bundle-file (start-var-name symbol end-var-name symbol var-base-type array
filename-token string)
(when (and (= TokenType_Symbol (field (at 1 var-base-type) type))
(std-str-equals (field (at 1 var-base-type) contents) "*"))
(ErrorAtToken (at 1 var-base-type)
"bundled file variable type must not be a pointer. It will be automatically pointerized")
(return false))
(var filename (* (const char)) (call-on c_str (path filename-token > contents)))
(get-or-create-comptime-var files-to-bundle (<> (in std vector) (in std string)))
(when (= (call-on-ptr end files-to-bundle)
(FindInContainer (deref files-to-bundle) filename))
(call-on-ptr push_back files-to-bundle filename))
(var data-var-name ([] 1024 char) (array 0))
(var data-var-name-write (* char) data-var-name)
(strncpy data-var-name "_binary_" (array-size data-var-name))
(set data-var-name-write
(+ data-var-name-write (strlen data-var-name)))
(each-char-in-string-const filename current-char
(if (isalnum (deref current-char))
(set (deref data-var-name-write) (deref current-char))
(set (deref data-var-name-write) '_'))
(incr data-var-name-write))
(set (deref data-var-name-write) 0)
(var data-var-name-start-token Token (deref start-var-name))
(token-contents-snprintf data-var-name-start-token "%s_start" data-var-name)
(var data-var-name-end-token Token (deref end-var-name))
(token-contents-snprintf data-var-name-end-token "%s_end" data-var-name)
(tokenize-push output
(declare-extern (var (token-splice-addr data-var-name-start-token)
(token-splice var-base-type)))
(declare-extern (var (token-splice-addr data-var-name-end-token)
(token-splice var-base-type)))
(var (token-splice start-var-name) (* (token-splice var-base-type))
(addr (token-splice-addr data-var-name-start-token)))
(var (token-splice end-var-name) (* (token-splice var-base-type))
(addr (token-splice-addr data-var-name-end-token))))
(return true))
;; We need to find the variable address at runtime
(tokenize-push output
(var-global (token-splice start-var-name) (* (token-splice var-base-type)) null)
(var-global (token-splice end-var-name) (* (token-splice var-base-type)) null))
;; Bundle more information to use later
(get-or-create-comptime-var data-bundle-invocations (<> (in std vector) (* (const Token))))
(when (= (call-on-ptr end data-bundle-invocations)
(FindInContainer (deref data-bundle-invocations) start-var-name))
(call-on-ptr push_back data-bundle-invocations start-var-name))
(return true)))
(return false))
(defun-comptime data-bundle-create-safe-resource-name (resource-filename (* (const char))
out-buffer (* char) out-buffer-size size_t)
(var buffer-write (* char) out-buffer)
(each-char-in-string-const resource-filename current-char
(when (isalnum (deref current-char))
(set (deref buffer-write) (deref current-char))
(incr buffer-write)
(when (>= (- buffer-write out-buffer) (- out-buffer-size 1))
(set (deref buffer-write) 0))
(defun-comptime convert-all-bundle-files (manager (& ModuleManager) module (* Module) &return bool)
(var environment (& EvaluatorEnvironment) (field manager environment))
(get-or-create-comptime-var data-bundle-load-all-called bool false)
(unless (deref data-bundle-load-all-called)
(Log "error: call data-bundle-load-all-resources somewhere in your program initialization in " \
"order to properly load bundled data on all platforms\n")
(return false))
(get-or-create-comptime-var files-to-bundle (<> (in std vector) (in std string)))
(var results (<> (in std vector) int))
(call-on resize results (call-on-ptr size files-to-bundle))
(var current-result (* int) (addr (at 0 results)))
(each-in-range (call-on-ptr size files-to-bundle) i
(var filename (& (const (in std string))) (at i (deref files-to-bundle)))
(var cache-filename ([] 1024 char) (array 0))
(unless (outputFilenameFromSourceFilename
(call-on c_str (field manager buildOutputDir))
(call-on c_str filename)
;; Add to end of file for type. Add bundle in case it's a cpp that we're also building,
;; to prevent name collisions in the cache
(comptime-cond ('Unix "data.o") ('Windows "data.res"))
cache-filename (sizeof cache-filename))
(return false))
(call-on push_back (field manager environment additionalStaticLinkObjects) cache-filename)
;; Don't re-package the resource if unchanged
(unless (fileIsMoreRecentlyModified (call-on c_str filename) cache-filename)
(Logf "Bundle filename to: %s => %s\n" (call-on c_str filename) cache-filename)
(run-process-start-or current-result
("objcopy" "--input" "binary" "--output" "elf64-x86-64" "--binary-architecture" "i386"
(call-on c_str filename) cache-filename)
(return false)))
;; I'm not actually sure how long these can be!
(var safe-resource-name ([] 1024 char) (array 0))
(call-on c_str filename) safe-resource-name (array-size safe-resource-name))
;; Note that while we could bundle all our resources into a single RC file, we'd rather build
;; them separately so they don't all have to be recompiled if one changes
(var resource-definition-file ([] 1024 char) (array 0))
(unless (outputFilenameFromSourceFilename
(call-on c_str (field manager buildOutputDir))
(call-on c_str filename)
;; Add to end of file for type. Add bundle in case it's a cpp that we're also building,
;; to prevent name collisions in the cache
resource-definition-file (sizeof resource-definition-file))
(return false))
(scope ;; Write the resource definition
(var resource-file-ptr (* FILE) (fopen resource-definition-file "w"))
(unless resource-file-ptr
(Logf "error: failed to open resource file %s for writing\n" resource-definition-file)
(return false))
;; Arbitrary: "CUSTOMDATA" userdata type
(fprintf resource-file-ptr "%s CUSTOMDATA \"%s\"" safe-resource-name (call-on c_str filename))
(fclose resource-file-ptr))
('Cross-Compiling-From-Linux ;; Cross compiling
(run-process-start-or current-result
("wrc" "-m64" "-o" cache-filename "-i" resource-definition-file)
(Log "error: failed to run Wine Resource Compiler, a.k.a. wrc. Please install Wine to " \
"cross-compile resource files.\n")
(return false)))
(run-process-start-or current-result
;; TODO: Programmatically find this
("C:/Program Files (x86)/Windows Kits/10/bin/10.0.19041.0/x86/rc.exe"
"/nologo" "/fo" cache-filename resource-definition-file)
(Log "error: failed to run Resource Compiler\n")
(return false))))))
(incr current-result))
(waitForAllProcessesClosed null)
(var result-index int 0)
(for-in result bool results
(unless (= 0 result)
(Logf "error: failed to bundle file %s\n" (call-on c_str (at result-index (deref files-to-bundle))))
(return false))
(incr result-index))
(return true))
(add-compile-time-hook-module pre-build convert-all-bundle-files)
(defun-comptime bundle-generate-runtime-load (environment (& EvaluatorEnvironment) &return bool)
(get-or-create-comptime-var bundle-files-processed bool false)
(when (deref bundle-files-processed)
(return true))
(set (deref bundle-files-processed) true))
(get-or-create-comptime-var files-to-bundle (<> (in std vector) (in std string)))
(get-or-create-comptime-var data-bundle-invocations (<> (in std vector) (* (const Token))))
(var load-resources-body (* (<> std::vector Token)) (new (<> std::vector Token)))
(call-on push_back (field environment comptimeTokens) load-resources-body)
(each-in-range (call-on-ptr size data-bundle-invocations) i
;; Generate the runtime code for finding the resource
(var start-var-name-token (* (const Token)) (at i (deref data-bundle-invocations)))
(var end-var-name-token (* (const Token)) (+ start-var-name-token 1))
(var base-type-start-token (* (const Token)) (+ start-var-name-token 2))
(var type-token (* (const Token)) (+ start-var-name-token 3))
(var resource-name-token Token (deref start-var-name-token))
(set (field resource-name-token type) TokenType_String)
(var safe-resource-name ([] 1024 char) (array 0))
(var filename (& (const (in std string))) (at i (deref files-to-bundle)))
(call-on c_str filename) safe-resource-name (array-size safe-resource-name)))
(set (field resource-name-token contents) safe-resource-name)
(tokenize-push (deref load-resources-body)
(var resource data-bundle-resource
(data-bundle-get-resource (token-splice-addr resource-name-token)))
(unless (field resource start-resource)
(return false))
(set (token-splice start-var-name-token)
(type-cast (field resource start-resource) (* (token-splice base-type-start-token))))
(set (token-splice end-var-name-token)
(type-cast (field resource end-resource) (* (token-splice base-type-start-token)))))))
(var load-all-resources-function (* (<> std::vector Token)) (new (<> std::vector Token)))
(call-on push_back (field environment comptimeTokens) load-all-resources-function)
(tokenize-push (deref load-all-resources-function)
(defun data-bundle-load-all-resources-implementation (&return bool)
(token-splice-array (deref load-resources-body))
(return true)))
(unless (ReplaceAndEvaluateDefinition environment
(deref load-all-resources-function))
(return false))
(return true))
(add-compile-time-hook post-references-resolved bundle-generate-runtime-load)))
(defstruct-local data-bundle-resource
start-resource (* void)
end-resource (* void))
(import "WindowsHeader.cake")
(c-import "<stdio.h>")
(defun-local data-bundle-get-resource (resource-name (* (const char))
&return data-bundle-resource)
;; TODO: This will break if we're loading from a DLL!
(var h-module HMODULE null)
(var final-resource data-bundle-resource (array 0))
(var resource-info HRSRC
(FindResourceA h-module resource-name "CUSTOMDATA"))
(unless resource-info
(fprintf stderr "Could not find resource %s\n" resource-name)
(return final-resource))
(var resource HGLOBAL (LoadResource h-module resource-info))
(unless resource
(fprintf stderr "Could not load resource %s\n" resource-name)
(return final-resource))
(var resource-address LPVOID (LockResource resource))
(unless resource-address
(fprintf stderr "Could not lock resource %s\n" resource-name)
(return final-resource))
(var resource-size DWORD (SizeofResource h-module resource-info))
(set (field final-resource start-resource) (type-cast resource-address (* void)))
(set (field final-resource end-resource) (+ (type-cast resource-address (* char))
(return final-resource))))
(defun-local enumerate-resource (h-module HMODULE type LPCSTR name LPSTR userdata LONG_PTR)
(fprintf stderr "%s %s\n" name type))
(defun-local data-bundle-list-resources ()
(fprintf stderr "Available resources:\n")
;; TODO: This will break if we're loading from a DLL!
(var h-module HMODULE null)
(EnumResourceNamesA h-module "CUSTOMDATA" (type-cast enumerate-resource ENUMRESNAMEPROCA)
(type-cast null LONG_PTR)))))
;; Replaced at compile-time on Windows. Use data-bundle-load-all-resources to invoke this so the
;; system can easily know it is called
(defun data-bundle-load-all-resources-implementation (&return bool)
(return true))
(defmacro data-bundle-load-all-resources ()
(get-or-create-comptime-var data-bundle-load-all-called bool false)
(set (deref data-bundle-load-all-called) true)
(tokenize-push output (data-bundle-load-all-resources-implementation))
(return true))
(c-import "<stdio.h>")
;; Bundle our own source code
(bundle-file data-start data-end (const char)
(defun-nodecl test--bundle-file (&return int)
(unless (data-bundle-load-all-resources)
(fprintf stderr "error: Could not load bundled data\n")
(comptime-cond ;; List resources if possible
('Windows (data-bundle-list-resources)))
(return 1))
(unless (and data-start data-end)
(fprintf stderr "error: Bundled data pointers null\n")
(return 1))
(fprintf stderr "Bundled data:\n")
;; We can't simply fprintf it because it's not null-terminated
(fwrite data-start (- data-end data-start) 1 stderr)
(return 0))))