├── .gitignore ├── src ├── c-rez-config.cmake ├── c-rez-cpack.cmake ├── c-rez-add-c-rez.cmake └── c-rez.c ├── CMakeLists.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *build*/ 2 | .idea/* 3 | -------------------------------------------------------------------------------- /src/c-rez-config.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/c-rez-targets.cmake") 2 | include("${CMAKE_CURRENT_LIST_DIR}/c-rez-add-c-rez.cmake") 3 | 4 | -------------------------------------------------------------------------------- /src/c-rez-cpack.cmake: -------------------------------------------------------------------------------- 1 | set(C_REZ_ARCH "x86") 2 | if (CMAKE_SIZEOF_VOID_P EQUAL 8) 3 | set(C_REZ_ARCH "x64") 4 | endif () 5 | 6 | string(TOLOWER ${CMAKE_SYSTEM_NAME} C_REZ_SYSTEM_NAME) 7 | 8 | set(CPACK_PACKAGE_VENDOR "Leonardo G. L. de Freitas") 9 | set(CPACK_PACKAGE_DESCRIPTION 10 | "c-rez is a small tool to generate `C` arrays of data from a list of 11 | input files. You can then compile them in your project and reference 12 | them just as regular variables.") 13 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${CMAKE_PROJECT_DESCRIPTION}") 14 | set(CPACK_PACKAGE_FILE_NAME 15 | "${PROJECT_NAME}-${PROJECT_VERSION}-${C_REZ_SYSTEM_NAME}-${C_REZ_ARCH}") 16 | set(CPACK_SOURCE_PACKAGE_FILE_NAME 17 | "${PROJECT_NAME}-${PROJECT_VERSION}") 18 | 19 | set(CPACK_SOURCE_IGNORE_FILES .*build.*/ .idea/) 20 | 21 | if (WIN32) 22 | set(CPACK_SOURCE_GENERATOR "ZIP") 23 | set(CPACK_GENERATOR "ZIP") 24 | else() 25 | set(CPACK_SOURCE_GENERATOR "TGZ") 26 | set(CPACK_GENERATOR "TGZ") 27 | endif() 28 | 29 | include(CPack) -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(c-rez 3 | VERSION 1.0.1 4 | LANGUAGES C 5 | DESCRIPTION "c-rez is a small tool to generate C arrays of data from files." 6 | ) 7 | 8 | # Honor C_VISIBILITY_PRESET and CXX_VISIBILITY_PRESET 9 | cmake_policy(PUSH) 10 | cmake_policy(SET CMP0063 NEW) 11 | 12 | if (NOT TARGET c-rez) 13 | add_executable(c-rez src/c-rez.c) 14 | add_executable(c-rez::c-rez ALIAS c-rez) 15 | if (WIN32) 16 | target_compile_definitions(c-rez PRIVATE -D_CRT_SECURE_NO_WARNINGS) 17 | endif() 18 | 19 | install(TARGETS c-rez EXPORT c-rez-targets RUNTIME DESTINATION bin) 20 | endif() 21 | 22 | include(src/c-rez-add-c-rez.cmake) 23 | 24 | # Export c-rez-config 25 | include(CMakePackageConfigHelpers) 26 | write_basic_package_version_file("${CMAKE_BINARY_DIR}/c-rez-config-version.cmake" VERSION "${PROJECT_VERSION}" COMPATIBILITY AnyNewerVersion) 27 | install(EXPORT c-rez-targets FILE c-rez-targets.cmake NAMESPACE c-rez:: DESTINATION lib/cmake/c-rez) 28 | install(FILES 29 | src/c-rez-config.cmake 30 | src/c-rez-add-c-rez.cmake 31 | ${CMAKE_BINARY_DIR}/c-rez-config-version.cmake 32 | DESTINATION lib/cmake/c-rez) 33 | 34 | include(src/c-rez-cpack.cmake) 35 | cmake_policy(POP) 36 | -------------------------------------------------------------------------------- /src/c-rez-add-c-rez.cmake: -------------------------------------------------------------------------------- 1 | set(C_REZ_FN_DIR ${CMAKE_CURRENT_LIST_DIR} CACHE INTERNAL "") 2 | 3 | function(add_c_rez) 4 | set(C_REZ_NAME "${ARGV0}") 5 | set(C_REZ_DIR "${CMAKE_CURRENT_BINARY_DIR}/_c_rez_${C_REZ_NAME}") 6 | 7 | file(MAKE_DIRECTORY "${C_REZ_DIR}") 8 | file(MAKE_DIRECTORY "${C_REZ_DIR}/src/") 9 | file(MAKE_DIRECTORY "${C_REZ_DIR}/include/c-rez/") 10 | 11 | get_target_property(C_REZ_IS_IMPORTED c-rez::c-rez IMPORTED) 12 | if (C_REZ_IS_IMPORTED) 13 | get_target_property(C_REZ_EXECUTABLE c-rez::c-rez LOCATION) 14 | else() 15 | set(C_REZ_EXECUTABLE "${CMAKE_BINARY_DIR}/c-rez/c-rez${CMAKE_EXECUTABLE_SUFFIX}") 16 | try_compile(C_REZ_COMPILE_RESULT ${CMAKE_CURRENT_BINARY_DIR} ${C_REZ_FN_DIR}/c-rez.c COPY_FILE "${C_REZ_EXECUTABLE}") 17 | endif() 18 | 19 | list(REMOVE_AT ARGN 0) 20 | 21 | set(C_REZ_LIBRARY_NAME _c_rez_${C_REZ_NAME}) 22 | set(C_REZ_C_FILE ${C_REZ_DIR}/src/${C_REZ_NAME}.c) 23 | set(C_REZ_H_FILE ${C_REZ_DIR}/include/c-rez/${C_REZ_NAME}.h) 24 | 25 | 26 | string(REPLACE ";TEXT;" ";--text;" C_REZ_INPUTS "${ARGN}") 27 | 28 | execute_process( 29 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 30 | COMMAND 31 | ${C_REZ_EXECUTABLE} 32 | -h ${C_REZ_H_FILE} 33 | -c ${C_REZ_C_FILE} 34 | -k ${C_REZ_NAME} 35 | ${C_REZ_INPUTS} 36 | RESULT_VARIABLE C_REZ_RESULT 37 | ) 38 | if (C_REZ_RESULT EQUAL 1) 39 | message(FATAL_ERROR "c-rez failed for resource ${C_REZ_NAME}") 40 | endif() 41 | 42 | string(REPLACE ";--text;" "" C_REZ_FILES_ONLY "${C_REZ_INPUTS}") 43 | 44 | add_custom_command( 45 | OUTPUT ${C_REZ_H_FILE} ${C_REZ_C_FILE} 46 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 47 | DEPENDS ${C_REZ_FILES_ONLY} 48 | COMMAND 49 | ${C_REZ_EXECUTABLE} 50 | -h ${C_REZ_H_FILE} 51 | -c ${C_REZ_C_FILE} 52 | -k ${C_REZ_NAME} 53 | ${C_REZ_INPUTS} 54 | ) 55 | 56 | add_library(${C_REZ_LIBRARY_NAME} STATIC ${C_REZ_H_FILE} ${C_REZ_C_FILE}) 57 | add_library(c-rez::${C_REZ_NAME} ALIAS ${C_REZ_LIBRARY_NAME}) 58 | target_include_directories(${C_REZ_LIBRARY_NAME} INTERFACE ${C_REZ_DIR}/include) 59 | endfunction() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C-Rez 2 | 3 | **c-rez** is a small tool to generate `C` arrays of data from a list of 4 | input files. You can then compile them in your project and reference 5 | them just as regular variables. 6 | 7 | It features: 8 | 9 | - An easy to use command-line tool 10 | - A neat CMake interface for CMake users 11 | - Allows you to reference assets both as strings 12 | (`c_rez_resource("sprites.png")`) and as a variables (`sprites_png`) 13 | - Written in portable C89 code 14 | - Generates portable C89 code 15 | - Its MIT licensed 16 | 17 | You might be wondering why would anyone ever need this tool since there 18 | are many ways of opening and reading a file, below are some of my reasons: 19 | 20 | - Cross-platform resource loading is a pain in the arse (specially when 21 | targeting mobile devices (I'm looking at you, Android Assets)) 22 | - Cross-platform resource bundling is even worse 23 | - No loading time, your assets are available as soon as `main` starts 24 | (even before!) 25 | - No loose files, you can distribute a single executable file 26 | - Asset bundling is now part of the compile and link process. 27 | 28 | Of course, there are downsides: 29 | 30 | - Makes your executable very very big depending on the size of your assets 31 | - Which in turn makes it slow to load 32 | - Depending on the final size of your app, the device might decide not to 33 | load it 34 | - You have no way of freeing up the loaded memory once you're done using 35 | the asset 36 | - No way of deciding what *not* to load at runtime. It is always all or 37 | nothing (you can go around this by making your assets a shared library 38 | and then loading them with `dlopen`, but if you're doing this you might 39 | as well use plain old `fopen`) 40 | 41 | ## Output 42 | 43 | A struct type is declared and used to represent each processed file. 44 | This is how the struct looks: 45 | 46 | ```cpp 47 | typedef struct c_rez_resource { 48 | unsigned char const * const data; 49 | unsigned int const length; 50 | } c_rez_resource; 51 | ``` 52 | 53 | The path of input files given to the **c-rez** tool will be used to generate 54 | an identifier. This identifier will be the variable name declared 55 | in the `.h` file and defined it in the `.c`file. **c-rez** also accepts a `key` 56 | that will be part of the generated identifiers. 57 | 58 | For instance, if you pass a resource file `img/sprites.png` and key 59 | `resources`, the `.h` file will have a variable called 60 | `resources_img_sprites_png` that will have a `data` member pointing to its 61 | bytes and a `length` member with the byte count. 62 | 63 | You can also use the function `c_rez_locate_resources("img/sprites.png")`, it 64 | will return `&resources_img_sprites_png` 65 | 66 | Suppose you generate an `img.h` header file, it would look like this: 67 | ```cpp 68 | #ifndef c_rez_resources_img_h 69 | #define c_rez_resources_img_h 70 | 71 | #ifdef __cplusplus 72 | extern "C" { 73 | #endif 74 | 75 | #ifndef c_rez_resource_struct 76 | #define c_rez_resource_struct 77 | typedef struct c_rez_resource { 78 | unsigned char const * const data; 79 | unsigned int const length; 80 | } c_rez_resource; 81 | #endif /* c_rez_resource_struct */ 82 | 83 | extern c_rez_resource const resources_img_sprites_png; 84 | struct c_rez_resource const * c_rez_locate_resources(const char name[]); 85 | 86 | #ifdef __cplusplus 87 | } 88 | #endif 89 | 90 | #endif /* c_rez_resources_img_h */ 91 | ``` 92 | 93 | Aftwewards you can `#include` it in your project and use the generated structs 94 | to get to your data: 95 | 96 | ```cpp 97 | #include "resources/img.h" 98 | 99 | int main() { 100 | printf("sprites.png length: %u\n", resources_img_sprites_png.length); 101 | printf("address of sprites.png: %p\n", c_rez_locate_resources("img/sprites.png"); 102 | return 0; 103 | } 104 | ``` 105 | 106 | You can then either commit these files to your project or make it part of your 107 | build process. 108 | 109 | Refer to either [CMake usage](#cmake-usage) or [Command-line 110 | tool](#command-line-tool) on how to generate and use these files in your 111 | project. 112 | 113 | ## CMake usage 114 | 115 | ### As a subproject 116 | 117 | **c-rez** is ready to be used as a sub-project. Simply download the 118 | source archive and use `add_subdirectory` before adding targets using 119 | **c-rez** 120 | 121 | ```cmake 122 | add_subdirectory(path/to/c-rez) 123 | ``` 124 | 125 | ### As a CMake package 126 | 127 | You can either compile from source or install a release archive. 128 | 129 | **Compile from source** 130 | 131 | 1. Have a compiler environment ready (GCC, LLVM, MSVC, MinGW, etc); 132 | 2. Have [CMake](http://cmake.org) 3.0 (minimum) installed; 133 | 3. Download this repository; 134 | 4. Create a `build` folder inside it; 135 | 5. Run `cmake` (or `cmake-gui`), set the binary dir to the newly created 136 | build folder and the source dir to the repository folder; 137 | 6. build it with `cmake --build . --target c-rez` 138 | - Alternatively, you can open the generated project files and build them in 139 | your IDE 140 | 7. Install it with `cmake --build . --target install` (you might need to use 141 | **sudo** here or to adjust `CMAKE_INSTALL_PREFIX`) 142 | - If using it inside an IDE, run the *INSTALL* target 143 | 144 | **Install a release** 145 | 146 | 1. Download a suitable archive from the releases section 147 | 2. Unzip it somewhere you can later find with CMake (you may need to 148 | adjust the `CMAKE_PREFIX_PATH` of your project) 149 | 150 | **Add package** 151 | 152 | Use `find_package` to add it to your project: 153 | 154 | ```cmake 155 | list(APPEND CMAKE_PREFIX_PATH /path/to/c-rez/prefix) 156 | find_package(c-rez REQUIRED) 157 | ``` 158 | 159 | ### Create a resource target 160 | 161 | After using either `find_package` or `add_subdirectory`, a new CMake 162 | function will be available: `add_c_rez`. This function will create a new 163 | `C` *STATIC* library under the `c-rez` namespace that you can link your 164 | targets to: 165 | 166 | ```cmake 167 | add_executable(mygame mygame.c engine.c) 168 | add_c_rez(images 169 | assets/tileset.png 170 | assets/sprites.png 171 | assets/gui.png 172 | ) 173 | target_link_libraries(mygame c-rez::images) 174 | ``` 175 | 176 | If you place the word `TEXT` before a path, it will be treated as text and a 177 | `\0` terminating byte will be appended to its data: 178 | 179 | ```cmake 180 | add_c_rez(texts 181 | TEXT texts/chapter01.txt 182 | TEXT texts/chapter02.txt 183 | TEXT texts/chapter03.txt 184 | ) 185 | ``` 186 | 187 | The header files for your target will be under a `c-rez` folder in your build 188 | path (*different* targets might not be in the *same* path, though) 189 | 190 | ```c 191 | #include "c-rez/images.h" 192 | #include "c-rez/texts.h" 193 | ``` 194 | 195 | ## Command-line tool 196 | If added to your `PATH`, you can call **c-rez** as a command line tool to 197 | generate a header 198 | and/or source files: 199 | 200 | ``` 201 | c-rez -k [-h ] [-c ] [--text] [[--text] ] [[--text] ]* 202 | ``` 203 | 204 | - **-h \**: specifies the header output file. If omitted, only 205 | source gets generated. 206 | - **-c \**: specifies the source output file. If omitted, only 207 | header gets generated. 208 | - **-k \**: specifies a key to identify this resource. It will 209 | be used in header guards and resource functions. 210 | - **--text**: appends `\0` when processing the next *\* 211 | file. This helps when using its data as a string resource. 212 | - **\**: space separated list of files to read from. 213 | Declarations and definitions will be generated based on the file name. If 214 | `--text` is specified before the file name, `\0` will be appended after 215 | processing. 216 | 217 | # License 218 | 219 | **c-rez** is MIT-licensed. 220 | -------------------------------------------------------------------------------- /src/c-rez.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c-rez is a portable C90 tool that reads from a list of input files and 3 | * outputs them to a .h/.c pair so that you can reference them at compile/link 4 | * time. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | #define c_rez_xstr(value) c_rez_str(value) 15 | #define c_rez_str(value) #value 16 | 17 | #ifndef C_REZ_MAX_FILE_COUNT 18 | #define C_REZ_MAX_FILE_COUNT 1024 19 | #endif 20 | 21 | 22 | #define C_REZ_MAX_ARG_COUNT (C_REZ_MAX_FILE_COUNT +1 +2 +2 +2) 23 | 24 | /** 25 | * help text for the program 26 | */ 27 | static const char * help_text = 28 | "c-rez: a resource to c tool\n" 29 | "\n" 30 | "usage: c-rez -k [-h ] " 31 | "[-c ] [--text] " 32 | "[[--text] ] [[--text] ]*\n" 33 | "\n" 34 | " -h : specifies the header output file. If omitted,\n" 35 | " * if -c is specified, only source gets generated.\n" 36 | " * if -c is not specified, .h is generated.\n" 37 | " -c : specifies the source output file. When omitted,\n" 38 | " * if -h is specified, only header gets generated.\n" 39 | " * if -h is not specified, .c is generated.\n" 40 | " -k : specifies an key to identify this resource. It will be " 41 | "used in header guards and resource functions.\n" 42 | " --text: appends \\0 when processing the next file. " 43 | "This helps when using its data as a string resource.\n" 44 | " : space separated list of files to read from. " 45 | "Declarations and definitions will be generated based on the file name.\n" 46 | " If --text is specified before the file name, '\\0' " 47 | "will be appended after processing.\n"; 48 | 49 | /** 50 | * parsed options for the program execution 51 | */ 52 | struct crez_opts { 53 | const char * key; 54 | const char * h_output; 55 | const char * c_output; 56 | const char * files[C_REZ_MAX_FILE_COUNT]; 57 | unsigned file_count; 58 | unsigned char generated_output_name; 59 | }; 60 | 61 | /** 62 | * the crez_arg struct helps process the program arguments. you can see it as an 63 | * argument iterator. see arg_construct, arg_next and arg_finished. 64 | */ 65 | struct crez_arg { 66 | int argc; 67 | const char ** argv; 68 | int i; 69 | }; 70 | 71 | /** 72 | * a resource node to later transform in a series of switch/cases 73 | * to locate resources by name 74 | */ 75 | struct crez_node { 76 | char * symbol; 77 | struct crez_node * chilren[256]; 78 | }; 79 | 80 | /** 81 | * parses options or exit if problematic. fills in opts with options and files. 82 | * 83 | * @param opts the options struct to fill 84 | * @param argc count of arguments in argv 85 | * @param argv argument array 86 | * @return 1 if arguments were parsed, or 0 if not. 87 | */ 88 | int opts_parse_or_exit(struct crez_opts * opts, int argc, const char * argv[]); 89 | 90 | /** 91 | * prints help and exists the program, optionally printing an error message 92 | * before exiting. 93 | * 94 | * @param error the error message to print. can be NULL. 95 | * @return 0 96 | */ 97 | int print_help_and_exit(const char * error); 98 | 99 | /** 100 | * constructs an crez_arg type 101 | * 102 | * @param arg the crez_arg instance to construct 103 | * @param argc count of arguments in argv 104 | * @param argv the argument array 105 | */ 106 | void arg_construct(struct crez_arg * arg, int argc, const char * argv[]); 107 | 108 | /** 109 | * returns the next argument to be processed 110 | * 111 | * @param arg the crez_arg instance to use 112 | * @return pointer to the next argument 113 | */ 114 | const char * arg_next(struct crez_arg * arg); 115 | 116 | /** 117 | * tells if all arguments in arg were processed 118 | * 119 | * @param arg the crez_arg instance to use 120 | * @return 1 if finished, 0 if not. 121 | */ 122 | int arg_finished(struct crez_arg * arg); 123 | 124 | /** 125 | * creates a C-compatible identifier by replacing non-ascii characters with _ 126 | * 127 | * @param input the input text 128 | * @param prefix the prefix 129 | * @return a pointer to a null-terminated char array that must be free'd by the 130 | * user 131 | */ 132 | char * make_identifier(const char input[], const char prefix[]); 133 | 134 | /** 135 | * allocates/creates a node with an specified symbol. 136 | * @param symbol the symbol to add. might be NULL 137 | * @return a node instance that needs to be destroyed with node_destroy 138 | */ 139 | struct crez_node * node_create(const char * symbol); 140 | 141 | /** 142 | * destroys a node and all its children node with it. 143 | * @param node the node to destroy and free 144 | */ 145 | void node_destroy(struct crez_node * node); 146 | 147 | /** 148 | * adds a new symbol to the tree, creating intermediary nodes if needed. 149 | * @param node the root node 150 | * @param name the name to parse 151 | * @param symbol the symbol to add 152 | * @returns 1 if the symbol was added or 0 if it existed already 153 | * @example node_add_symbol(root, "assets/sprites.png", "assets_sprites_png"); 154 | */ 155 | int node_add_symbol(struct crez_node * node, const char * name, 156 | const char * symbol); 157 | 158 | /** 159 | * creates and write .h/.c files based on opts, 160 | * also creates the symbol index and writes it as a series of switch/cases 161 | * for the locate function. 162 | * 163 | * @param opts the options for the invocation 164 | */ 165 | void write_files(struct crez_opts * opts); 166 | 167 | /** 168 | * writes the content of file `filename` to h_file and c_file, prefixing names 169 | * by `prefix` 170 | * 171 | * @param file_name the file to open, read and write to h_file and c_file 172 | * @param identifier the identifier to add 173 | * @param h_file the header file to write declarations 174 | * @param c_file the c file to write definitions 175 | * @param is_text if an additional `\0` should be appended to the data 176 | */ 177 | void write_file(const char * file_name, const char * identifier, FILE * h_file, 178 | FILE * c_file, int is_text); 179 | 180 | /** 181 | * writes a byte with padding and indentation based on `current_column` 182 | * 183 | * @param file the file pointer to write to 184 | * @param byte the byte to write 185 | * @param current_column used to do formatting and comma placement 186 | * @return the new current_column that should be passed in the next invocation 187 | * of this function 188 | */ 189 | size_t write_byte(FILE * file, unsigned char byte, size_t current_column); 190 | 191 | /** 192 | * writes to `file` a declaration of the struct `c_rez_resource` 193 | * 194 | * @param file the file pointer to write to 195 | */ 196 | void write_resource_struct_declaration(FILE * file); 197 | 198 | /** 199 | * writes the `extern "C" {` guards if __cplusplus is defined 200 | * 201 | * @param file the file to write to 202 | */ 203 | void write_cplusplus_extern_guard_opening(FILE * file); 204 | 205 | /** 206 | * writes the closing brace of the `extern "C" {` guard 207 | * 208 | * @param file the file to write to 209 | */ 210 | void write_cplusplus_extern_guard_closing(FILE * file); 211 | 212 | /** 213 | * writes the opening of the include guards based on identifier 214 | * 215 | * @param file the file to write to 216 | * @param identifier identifier to use in #ifndefs 217 | */ 218 | void write_include_guard_opening(FILE * file, const char * identifier); 219 | 220 | /** 221 | * writes the closing of the include guards based on identifier 222 | * 223 | * @param file the file to write to 224 | * @param identifier identifier to use in #ifndefs 225 | */ 226 | void write_include_guard_closing(FILE * file, const char * identifier); 227 | 228 | /** 229 | * writes the resource_locate function with switch-cases 230 | * 231 | * @param h_file the header file to write to 232 | * @param c_file the source file to write to 233 | * @param key the key of this rez group 234 | * @param root the root of the nodes 235 | */ 236 | void write_locate_function(FILE * h_file, FILE * c_file, const char * key, 237 | struct crez_node * root); 238 | 239 | 240 | /** 241 | * writes a node and its children recursively 242 | * 243 | * @param c_file the file to write 244 | * @param node the node to write 245 | * @param level which level to write the switch/case 246 | */ 247 | void write_node(FILE * c_file, struct crez_node * node, int level); 248 | 249 | /* main function ------------------------------------------------------------ */ 250 | 251 | int main(int argc, const char * argv[]) { 252 | struct crez_opts opts; 253 | int ok = 0; 254 | ok = opts_parse_or_exit(&opts, argc, argv); 255 | if (!ok) return 1; 256 | 257 | write_files(&opts); 258 | if (opts.generated_output_name) { 259 | free((void *) opts.c_output); 260 | free((void *) opts.h_output); 261 | } 262 | return 0; 263 | } 264 | 265 | /* arguments and opts parser ------------------------------------------------ */ 266 | 267 | int opts_parse_or_exit(struct crez_opts * opts, 268 | int argc, 269 | const char * argv[]) { 270 | struct crez_arg state; 271 | const char * opt; 272 | 273 | if (argc >= C_REZ_MAX_ARG_COUNT) { 274 | return print_help_and_exit("unexcpected file count. c-rez expects at max " 275 | c_rez_xstr(C_REZ_MAX_FILE_COUNT) 276 | " files" 277 | ); 278 | } 279 | 280 | arg_construct(&state, argc, argv); 281 | arg_next(&state); 282 | opts->c_output = opts->h_output = (void *) 0; 283 | opts->file_count = 0; 284 | opts->key = (void *) 0; 285 | opts->generated_output_name = 0; 286 | 287 | if (arg_finished(&state)) return print_help_and_exit((void *) 0); 288 | 289 | while (!arg_finished(&state)) { 290 | opt = arg_next(&state); 291 | switch (opt[0]) { 292 | case '-': 293 | switch (opt[1]) { 294 | case 'c': 295 | opts->c_output = arg_next(&state); 296 | if (!opts->c_output) 297 | return print_help_and_exit("-c: no file specified."); 298 | break; 299 | case 'h': 300 | opts->h_output = arg_next(&state); 301 | if (!opts->h_output) 302 | return print_help_and_exit("-h: no file specified."); 303 | break; 304 | case 'k': 305 | opts->key = arg_next(&state); 306 | if (!opts->key) 307 | return print_help_and_exit("-k: no resource key specified."); 308 | break; 309 | case '-': 310 | opts->files[opts->file_count++] = opt; 311 | break; 312 | case '\0': 313 | default: 314 | return print_help_and_exit("unknown option."); 315 | } 316 | break; 317 | default: 318 | opts->files[opts->file_count++] = opt; 319 | break; 320 | } 321 | } 322 | 323 | if (!opts->key) 324 | return print_help_and_exit("no resource key specified (use -k)."); 325 | 326 | if (!opts->c_output && !opts->h_output) { 327 | size_t len = strlen(opts->key) + 3 /* .c\0 */; 328 | char * c_output = calloc(len, sizeof(char)); 329 | char * h_output = calloc(len, sizeof(char)); 330 | snprintf(c_output, len, "%s.%c", opts->key, 'c'); 331 | snprintf(h_output, len, "%s.%c", opts->key, 'h'); 332 | opts->h_output = h_output; 333 | opts->c_output = c_output; 334 | } 335 | 336 | return 1; 337 | } 338 | 339 | void arg_construct(struct crez_arg * arg, int argc, const char ** argv) { 340 | arg->argc = argc; 341 | arg->argv = argv; 342 | arg->i = 0; 343 | } 344 | 345 | const char * arg_next(struct crez_arg * arg) { 346 | if (arg->i >= arg->argc) return (void *)0; 347 | return arg->argv[arg->i++]; 348 | } 349 | 350 | int print_help_and_exit(const char * error) { 351 | if (error) printf("\n%s\n\n%s", error, help_text); 352 | else printf("%s\n", help_text); 353 | exit(1); 354 | return 0; 355 | } 356 | 357 | int arg_finished(struct crez_arg * arg) { 358 | return arg->i >= arg->argc; 359 | } 360 | 361 | /* identifier maker ---------------------------------------------- */ 362 | 363 | char * make_identifier(const char input[], const char prefix[]) { 364 | size_t input_len = strlen(input), 365 | prefix_len = prefix ? strlen(prefix) : 0, 366 | identifier_size = input_len + prefix_len + sizeof '_' + 1; 367 | char * identifier = calloc(identifier_size, sizeof(char)); 368 | size_t src_i = 0, dst_i = 0; 369 | 370 | /* copies prefix */ 371 | for (dst_i = 0, src_i = 0; src_i < prefix_len; src_i++, dst_i++) { 372 | identifier[dst_i] = isalnum(prefix[src_i]) ? prefix[src_i] : (char) '_'; 373 | } 374 | 375 | identifier[dst_i++] = '_'; 376 | 377 | /* copies input */ 378 | for (src_i = 0; src_i < input_len; src_i++, dst_i++) { 379 | identifier[dst_i] = isalnum(input[src_i]) ? input[src_i] : (char) '_'; 380 | } 381 | identifier[identifier_size - 1] = 0; 382 | 383 | return identifier; 384 | } 385 | 386 | /* writers ------------------------------------------------------------------ */ 387 | 388 | void write_files(struct crez_opts * opts) { 389 | unsigned i = 0; 390 | FILE * h_file, * c_file; 391 | c_file = h_file = (void *)0; 392 | char * h_identifier = (void *)0, 393 | * res_identifier = (void *)0; 394 | char const * basename = (void *)0; 395 | int wants_text = 0; 396 | struct crez_node * root = node_create((void *)0); 397 | char separator = '/'; 398 | #if _WIN32 || WIN32 399 | separator = '\\'; 400 | #endif 401 | 402 | if (opts->file_count == 0) { 403 | print_help_and_exit("no input files specified."); 404 | return; 405 | } 406 | 407 | if (opts->h_output) h_file = fopen(opts->h_output, "w+"); 408 | if (opts->c_output) c_file = fopen(opts->c_output, "w+"); 409 | 410 | if (!c_file && opts->c_output) { 411 | fprintf(stderr, "Cannot open output file %s: %s\n", opts->c_output, 412 | strerror(errno)); 413 | exit(1); 414 | } 415 | 416 | if (!h_file && opts->h_output) { 417 | fprintf(stderr, "Cannot open output file %s: %s\n", opts->h_output, 418 | strerror(errno)); 419 | exit(1); 420 | } 421 | 422 | if (h_file) { 423 | basename = strrchr(opts->h_output, separator); 424 | if (!basename) basename = opts->h_output; 425 | h_identifier = make_identifier(basename, opts->key); 426 | write_include_guard_opening(h_file, h_identifier); 427 | fprintf(h_file, "\n"); 428 | 429 | write_cplusplus_extern_guard_opening(h_file); 430 | fprintf(h_file, "\n"); 431 | 432 | write_resource_struct_declaration(h_file); 433 | fprintf(h_file, "\n"); 434 | } 435 | 436 | if (c_file) { 437 | write_cplusplus_extern_guard_opening(c_file); 438 | fprintf(c_file, "\n"); 439 | 440 | write_resource_struct_declaration(c_file); 441 | fprintf(c_file, "\n"); 442 | } 443 | 444 | for (i = 0; i < opts->file_count; i++) { 445 | if (strcmp(opts->files[i], "--text") == 0) { 446 | wants_text = 1; 447 | continue; 448 | } 449 | 450 | res_identifier = make_identifier(opts->files[i], opts->key); 451 | 452 | /* do not write again if symbol exists already */ 453 | if (node_add_symbol(root, opts->files[i], res_identifier)) { 454 | write_file(opts->files[i], res_identifier, h_file, c_file, wants_text); 455 | } 456 | fprintf(c_file, "\n"); 457 | free(res_identifier); 458 | wants_text = 0; 459 | } 460 | 461 | write_locate_function(h_file, c_file, opts->key, root); 462 | 463 | if (h_file) { 464 | fprintf(h_file, "\n"); 465 | 466 | write_cplusplus_extern_guard_closing(h_file); 467 | fprintf(h_file, "\n"); 468 | 469 | write_include_guard_closing(h_file, h_identifier); 470 | fprintf(h_file, "\n"); 471 | } 472 | 473 | node_destroy(root); 474 | free(h_identifier); 475 | fclose(c_file); 476 | fclose(h_file); 477 | } 478 | 479 | void write_file(const char * file_name, const char * identifier, FILE * h_file, 480 | FILE * c_file, int is_text) { 481 | FILE * in_file = fopen(file_name, "rb"); 482 | unsigned char c = 0; 483 | size_t n = 0, column = 0, total = 0; 484 | if (!h_file && !c_file) { return; } 485 | if (!in_file) { 486 | fprintf(stderr, "Cannot open input file %s: %s\n", file_name, 487 | strerror(errno)); 488 | return; 489 | } 490 | 491 | /* prints struct declaration */ 492 | if (h_file) { 493 | fprintf(h_file, "extern c_rez_resource const %s;\n", identifier); 494 | } 495 | 496 | if (c_file) { 497 | /* prints data */ 498 | fprintf(c_file, "unsigned char const %s_data[] = {\n", identifier); 499 | n = fread(&c, sizeof(c), 1, in_file); 500 | total = n; 501 | while (n) { 502 | column = write_byte(c_file, c, column); 503 | n = fread(&c, sizeof(c), 1, in_file); 504 | total += n; 505 | } 506 | 507 | if (is_text) { 508 | write_byte(c_file, 0, column); 509 | total++; 510 | } 511 | fprintf(c_file, "\n};\n"); 512 | 513 | /* prints struct definition */ 514 | fprintf(c_file, "struct c_rez_resource const %s = { %s_data, %lu };\n", 515 | identifier, identifier, total); 516 | } 517 | } 518 | 519 | void write_include_guard_opening(FILE * file, const char * identifier) { 520 | fprintf(file, 521 | "#ifndef c_rez_%s\n" 522 | "#define c_rez_%s\n", 523 | identifier, identifier); 524 | } 525 | 526 | void write_include_guard_closing(FILE * file, const char * identifier) { 527 | fprintf(file, "#endif /* c_rez_%s */\n", identifier); 528 | } 529 | 530 | void write_cplusplus_extern_guard_opening(FILE * file) { 531 | fprintf(file, "#ifdef __cplusplus\n" 532 | "extern \"C\" {\n" 533 | "#endif\n"); 534 | } 535 | 536 | void write_cplusplus_extern_guard_closing(FILE * file) { 537 | fprintf(file, "#ifdef __cplusplus\n" 538 | "}\n" 539 | "#endif\n"); 540 | } 541 | 542 | 543 | void write_space(FILE * file, int amount) { 544 | while (amount--) putc(' ', file); 545 | } 546 | 547 | void write_resource_struct_declaration(FILE * file) { 548 | fprintf(file, 549 | "#ifndef c_rez_resource_struct\n" 550 | "#define c_rez_resource_struct\n" 551 | "typedef struct c_rez_resource {\n" 552 | " unsigned char const * const data;\n" 553 | " unsigned int const length;\n" 554 | "} c_rez_resource;\n" 555 | "#endif /* c_rez_resource_struct */\n" 556 | ); 557 | } 558 | 559 | size_t 560 | write_byte(FILE * file, unsigned char byte, size_t current_column) { 561 | if (current_column > 0) { 562 | fprintf(file, ","); 563 | } 564 | if (current_column == 15) { 565 | fprintf(file, "\n"); 566 | current_column = 0; 567 | } 568 | if (current_column == 0) { fprintf(file, " "); } 569 | fprintf(file, " %3d", byte); 570 | return current_column + 1; 571 | } 572 | 573 | void write_locate_function(FILE * h_file, FILE * c_file, const char * key, 574 | struct crez_node * root) { 575 | if (h_file) { 576 | fprintf(h_file, "struct c_rez_resource const * " 577 | "c_rez_locate_%s(const char name[]);", key); 578 | } 579 | if (c_file) { 580 | fprintf(c_file, "struct c_rez_resource const * " 581 | "c_rez_locate_%s(const char name[]) {\n", key); 582 | write_node(c_file, root, 0); 583 | fprintf(c_file, "}\n"); 584 | } 585 | } 586 | 587 | void write_node(FILE * c_file, struct crez_node * node, int level) { 588 | int i = 0, indent = level * 2 + 2; 589 | const char * symbol = node->chilren[0] && node->chilren[0]->symbol ? node->chilren[0]->symbol : (void *)0; 590 | write_space(c_file, indent); 591 | fprintf(c_file, "switch (name[%d]) {\n", level); 592 | 593 | /* writes the 0th case, the symbol itself */ 594 | write_space(c_file, indent); 595 | fprintf(c_file, " case 0: return%s%s;\n", symbol ? " &" : " ", symbol ? symbol : "(void *) 0"); 596 | 597 | /* writes children nodes */ 598 | for (i = 1; i < 256; i++) { 599 | if (node->chilren[i]) { 600 | write_space(c_file, indent); 601 | fprintf(c_file, " case '%c':\n", i); 602 | write_node(c_file, node->chilren[i], level + 1); 603 | } 604 | } 605 | 606 | /* writes default case */ 607 | write_space(c_file, indent); 608 | fprintf(c_file, " default: return (void *) 0;\n"); 609 | 610 | /* writes end of swich/case */ 611 | write_space(c_file, indent); 612 | fprintf(c_file, "}\n"); 613 | } 614 | 615 | /* tree builders ------------------------------------------------------------ */ 616 | 617 | struct crez_node * node_create(const char * symbol) { 618 | struct crez_node * node = calloc(1, sizeof(*node)); 619 | size_t len = 0, i = 0; 620 | if (symbol) { 621 | len = strlen(symbol); 622 | node->symbol = calloc(len +1, sizeof(symbol)); 623 | for (i = 0; i < len; i++) node->symbol[i] = symbol[i]; 624 | } 625 | return node; 626 | } 627 | 628 | void node_destroy(struct crez_node * node) { 629 | int i = 0; 630 | if (node->symbol) free(node->symbol); 631 | for (i = 0; i < 256; i++) { 632 | if (node->chilren[0]) node_destroy(node->chilren[0]); 633 | } 634 | free(node); 635 | } 636 | 637 | int node_add_symbol(struct crez_node * node, const char * name, 638 | const char * symbol) { 639 | size_t name_len = strlen(name), i = 0; 640 | char key; 641 | 642 | /* this will add all nodes up until what we need */ 643 | for (key = name[0], i = 0; i < name_len; i++, key = name[i]) { 644 | if (!node->chilren[key]) { 645 | node->chilren[key] = node_create((void *)0); 646 | } 647 | node = node->chilren[key]; 648 | } 649 | 650 | /* only add a new node if old wasn't found */ 651 | if (node->chilren[0] == (void *)0) { 652 | node->chilren[0] = node_create(symbol); 653 | return 1; 654 | } 655 | return 0; 656 | } 657 | --------------------------------------------------------------------------------