├── version.h ├── ags_cpu.c ├── StringEscape.h ├── sim-tests ├── loop.s └── hello.s ├── preproc.h ├── .gitignore ├── List.h ├── MemGrow.h ├── hsearch.h ├── kw_search.h ├── Script.h ├── List.c ├── Assembler.h ├── SpriteFile.h ├── lzw.c ├── Script_internal.h ├── File.h ├── .github └── workflows │ ├── cygwin.yml │ └── build.yml ├── Makefile.binary ├── RoomFile.h ├── debug.h ├── BitmapFuncs.h ├── agsalphainfo.c ├── agsdisas.c ├── MemGrow.c ├── agsalphahack.c ├── DataFile.h ├── agsex ├── StringEscape.c ├── tokenizer.h ├── File.c ├── agspack.c ├── Clib32.h ├── agssemble.c ├── RoomFile.c ├── Bitmap.h ├── agstract.c ├── asmlex.l ├── Makefile ├── hsearch.c ├── ByteArray.h ├── strstore.h ├── regusage.h ├── defpal.c ├── bmap.h ├── hbmap.h ├── agsinject.c ├── miniz.h ├── tglist.h ├── ags_cpu.h ├── endianness.h ├── agscriptxtract.c ├── README ├── minishilka.c ├── winfile.h └── Targa.h /version.h: -------------------------------------------------------------------------------- 1 | #define VERSION "1.0.1" 2 | -------------------------------------------------------------------------------- /ags_cpu.c: -------------------------------------------------------------------------------- 1 | #define AGS_CPU_IMPL 2 | #include "ags_cpu.h" 3 | 4 | -------------------------------------------------------------------------------- /StringEscape.h: -------------------------------------------------------------------------------- 1 | #ifndef STRINGESCAPE_H 2 | #define STRINGESCAPE_H 3 | #include 4 | size_t escape(char* in, char *out, size_t outsize); 5 | size_t unescape(char* in, char *out, size_t outsize); 6 | #pragma RcB2 DEP "StringEscape.c" 7 | #endif 8 | -------------------------------------------------------------------------------- /sim-tests/loop.s: -------------------------------------------------------------------------------- 1 | ; loop 10 million times 2 | ; this is 13x slower than native speed with -O3, and 3x slower than dino, 3 | ; but 4x faster than python 2.7.15 4 | li cx, 10000000 5 | li dx, 0 6 | loop: 7 | subi cx, 1 8 | mr ax, cx 9 | gt ax, dx 10 | jnzi loop 11 | 12 | 13 | -------------------------------------------------------------------------------- /preproc.h: -------------------------------------------------------------------------------- 1 | #ifndef PREPROC_H 2 | #define PREPROC_H 3 | 4 | #include 5 | 6 | struct cpp; 7 | 8 | struct cpp *cpp_new(void); 9 | void cpp_free(struct cpp*); 10 | void cpp_add_includedir(struct cpp *cpp, const char* includedir); 11 | int cpp_add_define(struct cpp *cpp, const char *mdecl); 12 | int cpp_run(struct cpp *cpp, FILE* in, FILE* out, const char* inname); 13 | 14 | #ifdef __GNUC__ 15 | #pragma GCC diagnostic ignored "-Wunknown-pragmas" 16 | #endif 17 | #pragma RcB2 DEP "preproc.c" 18 | 19 | #endif 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *~ 3 | *.a 4 | *.so 5 | *.lo 6 | *.la 7 | *.log 8 | *.out 9 | *.rcb 10 | *.pack 11 | *.exe 12 | *.s 13 | *.cfg 14 | *.dat 15 | *.i 16 | *.gz 17 | *.bz2 18 | *.xz 19 | agssave.* 20 | rcb.*.mak 21 | config.mak 22 | testcase* 23 | core 24 | foo* 25 | 26 | agsalphahack 27 | agscriptxtract 28 | agsdisas 29 | agsinject 30 | agspack 31 | agsprite 32 | agssemble 33 | agssim 34 | agstract 35 | 36 | scmd_tok.h 37 | scmd_tok.c 38 | scmd_tok.shilka 39 | regname_tok.h 40 | regname_tok.c 41 | regname_tok.shilk 42 | 43 | GPATH 44 | GRTAGS 45 | GTAGS 46 | 47 | -------------------------------------------------------------------------------- /List.h: -------------------------------------------------------------------------------- 1 | #ifndef LIST_H 2 | #define LIST_H 3 | 4 | #include "MemGrow.h" 5 | #include 6 | 7 | typedef struct List { 8 | MG mem; 9 | size_t count; 10 | size_t itemsize; 11 | } List; 12 | 13 | #define List_size(X) ((X)->count) 14 | 15 | void List_init(List *l, size_t itemsize); 16 | void List_free(List *l); 17 | int List_add(List *l, void* item); 18 | int List_get(List *l, size_t index, void* item); 19 | void* List_getptr(List *l, size_t index); 20 | void List_sort(List *l, int(*compar)(const void *, const void *)); 21 | 22 | #pragma RcB2 DEP "List.c" 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /MemGrow.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMGROW_H 2 | #define MEMGROW_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct MemGrow { 8 | void* mem; 9 | size_t used; 10 | size_t capa; 11 | } MG; 12 | 13 | void mem_init(MG* mem); 14 | void mem_free(MG* mem); 15 | int mem_grow_if_needed(MG *mem, size_t newsize); 16 | int mem_write(MG *mem, size_t offset, void* data, size_t size); 17 | int mem_append(MG *mem, void* data, size_t size); 18 | void* mem_getptr(MG* mem, size_t offset, size_t byteswanted); 19 | void mem_set(MG* mem, void* data, size_t used, size_t allocated); 20 | int mem_write_file(MG* mem, char* fn); 21 | int mem_write_stream(MG* mem, FILE* out); 22 | 23 | #pragma RcB2 DEP "MemGrow.c" 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /hsearch.h: -------------------------------------------------------------------------------- 1 | #ifndef HSEARCH_H 2 | #define HSEARCH_H 3 | 4 | #include 5 | 6 | typedef union htab_value { 7 | void *p; 8 | size_t n; 9 | } htab_value; 10 | 11 | #define HTV_N(N) (htab_value) {.n = N} 12 | #define HTV_P(P) (htab_value) {.p = P} 13 | 14 | struct htab * htab_create(size_t); 15 | void htab_destroy(struct htab *); 16 | htab_value* htab_find(struct htab *, char* key); 17 | /* same as htab_find, but can retrieve the saved key (for freeing) */ 18 | htab_value* htab_find2(struct htab *htab, char* key, char **saved_key); 19 | int htab_insert(struct htab *, char*, htab_value); 20 | int htab_delete(struct htab *htab, char* key); 21 | size_t htab_next(struct htab *, size_t iterator, char** key, htab_value **v); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /kw_search.h: -------------------------------------------------------------------------------- 1 | #include "ags_cpu.h" 2 | #include 3 | 4 | #ifndef KW_TOK_SCMD_BASE 5 | #define KW_TOK_SCMD_BASE 0 6 | #endif 7 | 8 | #define __KW_SCMD_DEBUG__ 9 | #include "scmd_tok.h" 10 | #include "scmd_tok.c" 11 | 12 | #ifndef RN_TOK_BASE 13 | #define RN_TOK_BASE 0 14 | #endif 15 | #include "regname_tok.h" 16 | #include "regname_tok.c" 17 | 18 | static void kw_init(void) { 19 | KW_SCMD_reset(); 20 | RN_reset(); 21 | } 22 | 23 | static void kw_finish(void) { 24 | KW_SCMD_output_statistics(); 25 | RN_output_statistics(); 26 | } 27 | 28 | static unsigned kw_find_insn(char* sym, size_t l) { 29 | return KW_SCMD_find_keyword(sym, l); 30 | } 31 | 32 | static unsigned kw_find_reg(char* sym, size_t l) { 33 | return RN_find_keyword(sym, l); 34 | } 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /sim-tests/hello.s: -------------------------------------------------------------------------------- 1 | jmpi main 2 | hello: 3 | ; set mar to sp 4 | ptrstack 0 5 | ; write 'H' to stack 6 | li ax, 72 7 | memwrite1 ax 8 | addi mar, 1 9 | ; write 'e' to stack 10 | li ax, 101 11 | memwrite1 ax 12 | addi mar, 1 13 | ; write 'l' to stack 14 | li ax, 108 15 | memwrite1 ax 16 | addi mar, 1 17 | ; write 'l' to stack 18 | li ax, 108 19 | memwrite1 ax 20 | addi mar, 1 21 | ; write 'o' to stack 22 | li ax, 111 23 | memwrite1 ax 24 | addi mar, 1 25 | ; write '\n' to stack 26 | li ax, 10 27 | memwrite1 ax 28 | addi mar, 1 29 | ;reset mar to point to the beginning of stack, i.e. our string 30 | ptrstack 0 31 | ; syscall nr 32 | li ax, 1 33 | ; fd 1: stdout 34 | li bx, 1 35 | ; buf 36 | mr cx, mar 37 | ; number of bytes to write 38 | li dx, 6 39 | callscr ax 40 | ret 41 | main: 42 | li ax, hello 43 | call ax 44 | ; SYS_exit 45 | li ax, 60 46 | li bx 0 47 | callscr ax 48 | -------------------------------------------------------------------------------- /Script.h: -------------------------------------------------------------------------------- 1 | #ifndef SCRIPT_H 2 | #define SCRIPT_H 3 | 4 | #include 5 | #include "File.h" 6 | 7 | typedef struct AgsScriptInfo { 8 | size_t start; 9 | size_t len; 10 | 11 | size_t globaldatasize; 12 | size_t codesize; 13 | size_t stringssize; 14 | 15 | size_t globaldatastart; 16 | size_t codestart; 17 | size_t stringsstart; 18 | 19 | size_t fixupcount; 20 | size_t fixupstart; 21 | 22 | size_t importcount; 23 | size_t importstart; 24 | 25 | size_t exportcount; 26 | size_t exportstart; 27 | 28 | size_t sectioncount; 29 | size_t sectionstart; 30 | 31 | int version; 32 | } ASI; 33 | 34 | enum DisasmFlags { 35 | DISAS_DEBUG_BYTECODE = 1 << 0, 36 | DISAS_DEBUG_OFFSETS = 1 << 1, 37 | DISAS_SKIP_LINENO = 1 << 2, 38 | DISAS_DEBUG_FIXUPS = 1 << 3, 39 | DISAS_VERBOSE = 1 << 4, 40 | }; 41 | 42 | int ASI_read_script(AF *a, ASI* s); 43 | int ASI_disassemble(AF* a, ASI* s, char *fn, int flags); 44 | 45 | #pragma RcB2 DEP "Script.c" 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /List.c: -------------------------------------------------------------------------------- 1 | #include "List.h" 2 | #include 3 | #include 4 | 5 | void List_init(List *l, size_t itemsize) { 6 | memset(l, 0, sizeof *l); 7 | mem_init(&l->mem); 8 | l->itemsize = itemsize; 9 | } 10 | 11 | void List_free(List *l) { 12 | mem_free(&l->mem); 13 | List_init(l, 0); 14 | } 15 | 16 | int List_add(List *l, void* item) { 17 | int ret = mem_write(&l->mem, l->count * l->itemsize, item, l->itemsize); 18 | if(ret) l->count++; 19 | return ret; 20 | } 21 | 22 | int List_get(List *l, size_t index, void* item) { 23 | if(index >= l->count) return 0; 24 | void* src = mem_getptr(&l->mem, index * l->itemsize, l->itemsize); 25 | if(!src) return 0; 26 | memcpy(item, src, l->itemsize); 27 | return 1; 28 | } 29 | 30 | void* List_getptr(List *l, size_t index) { 31 | return mem_getptr(&l->mem, index * l->itemsize, l->itemsize); 32 | } 33 | 34 | void List_sort(List *l, int(*compar)(const void *, const void *)) { 35 | qsort(mem_getptr(&l->mem, 0, l->itemsize * l->count), l->count, l->itemsize, compar); 36 | } 37 | -------------------------------------------------------------------------------- /Assembler.h: -------------------------------------------------------------------------------- 1 | #ifndef ASSEMBLER_H 2 | #define ASSEMBLER_H 3 | 4 | #include "ByteArray.h" 5 | #include "List.h" 6 | #include 7 | #include 8 | #include "hsearch.h" 9 | 10 | typedef struct AgsAssembler { 11 | struct ByteArray obj_b, *obj; 12 | struct ByteArray data_b, *data; 13 | struct ByteArray code_b, *code; 14 | List *export_list, export_list_b; 15 | List *fixup_list, fixup_list_b; 16 | List *string_list, string_list_b; 17 | List *label_ref_list, label_ref_list_b; 18 | List *function_ref_list, function_ref_list_b; 19 | List *variable_list, variable_list_b; 20 | List *import_list, import_list_b; 21 | List *sections_list, sections_list_b; 22 | struct htab* label_map; 23 | struct htab* import_map; 24 | struct htab* export_map; 25 | struct htab* string_offset_map; 26 | 27 | size_t string_section_length; 28 | 29 | FILE* in; 30 | } AS; 31 | 32 | void AS_open_stream(AS* a, FILE* f); 33 | int AS_open(AS* a, char* fn); 34 | void AS_close(AS* a); 35 | int AS_assemble(AS* a, char* out); 36 | 37 | #pragma RcB2 DEP "Assembler.c" 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /SpriteFile.h: -------------------------------------------------------------------------------- 1 | #ifndef SPRITEFILE_H 2 | #define SPRITEFILE_H 3 | 4 | 5 | /* Targa.h needs to be included to get ImageData typedef */ 6 | #include "Targa.h" 7 | 8 | #include 9 | #include "File.h" 10 | 11 | typedef struct SpriteFile { 12 | short version; 13 | unsigned num_sprites; 14 | int compressed; 15 | int id; 16 | 17 | unsigned *offsets; 18 | unsigned char *palette; 19 | 20 | /* private stuff */ 21 | unsigned sc_off; 22 | 23 | } SpriteFile; 24 | 25 | /* read interface */ 26 | 27 | /* read TOC */ 28 | int SpriteFile_read(AF* f, SpriteFile *sf); 29 | /* returns uncompressed sprite in data */ 30 | int SpriteFile_extract(AF* f, SpriteFile *sf, int spriteno, ImageData *data); 31 | 32 | 33 | /* write interface */ 34 | int SpriteFile_write_header(FILE *f, SpriteFile *sf); 35 | int SpriteFile_add(FILE *f, SpriteFile *sf, ImageData *data); 36 | int SpriteFile_finalize(FILE* f, SpriteFile *sf); 37 | 38 | /* sprindex.dat */ 39 | int SpriteFile_write_sprindex(AF* f, SpriteFile *sf, FILE *outf); 40 | 41 | #pragma RcB2 DEP "SpriteFile.c" 42 | 43 | #endif 44 | 45 | -------------------------------------------------------------------------------- /lzw.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define N 4096 4 | 5 | #define OUT(X) if(out < outend) *(out++) = X; else return 0; 6 | 7 | int lzwdecomp(unsigned char* in, unsigned long insz, 8 | unsigned char* out, unsigned long outsz) 9 | { 10 | unsigned char buf[N]; 11 | unsigned char *inend = in+insz, *outend = out+outsz; 12 | int bits, len, mask, i = N - 16; 13 | while (1) { 14 | if(in >= inend) return 0; 15 | bits = *(in++); 16 | for (mask = 1; mask & 0xFF; mask <<= 1) { 17 | if (bits & mask) { 18 | if(in+2>inend) return 0; 19 | short tmp; 20 | memcpy(&tmp, in, 2); 21 | in += 2; 22 | int j = tmp; 23 | 24 | len = ((j >> 12) & 15) + 3; 25 | j = (i - j - 1) & (N - 1); 26 | 27 | while (len--) { 28 | buf[i] = buf[j]; 29 | OUT(buf[i]); 30 | j = (j + 1) & (N - 1); 31 | i = (i + 1) & (N - 1); 32 | } 33 | } else { 34 | if(in >= inend) return 0; 35 | buf[i] = *(in++); 36 | OUT(buf[i]); 37 | i = (i + 1) & (N - 1); 38 | } 39 | if(out == outend) break; 40 | } 41 | if(out == outend) break; 42 | } 43 | return 1; 44 | } 45 | -------------------------------------------------------------------------------- /Script_internal.h: -------------------------------------------------------------------------------- 1 | #ifndef SCRIPT_INTERNAL_H 2 | #define SCRIPT_INTERNAL_H 3 | 4 | #include 5 | 6 | struct strings { 7 | size_t count; 8 | char ** strings; 9 | char* data; 10 | }; 11 | 12 | struct importlist { 13 | char** names; 14 | }; 15 | 16 | #define EXPORT_FUNCTION 1 17 | #define EXPORT_DATA 2 18 | struct export { 19 | char* fn; 20 | unsigned instr; 21 | unsigned type; 22 | }; 23 | 24 | #define FIXUP_GLOBALDATA 1 // code[fixup] += &globaldata[0] 25 | #define FIXUP_FUNCTION 2 // code[fixup] += &code[0] 26 | #define FIXUP_STRING 3 // code[fixup] += &strings[0] 27 | #define FIXUP_IMPORT 4 // code[fixup] = &imported_thing[code[fixup]] 28 | #define FIXUP_DATADATA 5 // globaldata[fixup] += &globaldata[0] 29 | #define FIXUP_STACK 6 // code[fixup] += &stack[0] 30 | #define FIXUP_MAX FIXUP_STACK 31 | struct fixup_data { 32 | unsigned count[FIXUP_MAX+1]; 33 | unsigned *codeindex; 34 | unsigned *codeindex_per[FIXUP_MAX+1]; 35 | unsigned char *types; 36 | }; 37 | 38 | struct varinfo {size_t numrefs; unsigned varsize;}; 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /File.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_H 2 | #define FILE_H 3 | #include 4 | #include 5 | #include "ByteArray.h" 6 | 7 | typedef struct AFile { 8 | struct ByteArray b_b; 9 | struct ByteArray *b; 10 | } AF; 11 | 12 | /* 0: error, 1: success */ 13 | int AF_open(AF *f, const char* fn); 14 | void AF_close(AF* f); 15 | 16 | int AF_is_eof(AF *f); 17 | off_t AF_get_pos(AF* f); 18 | int AF_set_pos(AF* f, off_t x); 19 | 20 | ssize_t AF_read(AF* f, void* buf, size_t len); 21 | long long AF_read_longlong(AF* f); 22 | int AF_read_int(AF* f); 23 | unsigned AF_read_uint(AF* f); 24 | short AF_read_short(AF* f); 25 | unsigned short AF_read_ushort(AF* f); 26 | int AF_read_string(AF* a, char* buf, size_t max); 27 | int AF_read_uchar(AF *f); 28 | 29 | int AF_search(AF *f, unsigned char* bytes, size_t len); 30 | /* dumps file contents between start and start+len into fn */ 31 | int AF_dump_chunk(AF* a, size_t start, size_t len, char* fn); 32 | int AF_dump_chunk_stream(AF* a, size_t start, size_t len, FILE* out); 33 | /* "swallows" or skips l bytes, i.e. advances the offset */ 34 | int AF_read_junk(AF* a, size_t l); 35 | 36 | #pragma RcB2 DEP "File.c" 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /.github/workflows/cygwin.yml: -------------------------------------------------------------------------------- 1 | name: compile_cygwin 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | cygwin_build: 9 | runs-on: windows-latest 10 | steps: 11 | - run: git config --global core.autocrlf false 12 | 13 | - uses: actions/checkout@v4 14 | 15 | - name: setup_cygwin 16 | uses: cygwin/cygwin-install-action@v5 17 | with: 18 | packages: | 19 | gcc-core, 20 | gcc-g++, 21 | make, 22 | bison, 23 | find-utils 24 | 25 | - name: compile 26 | shell: C:\cygwin\bin\bash.exe -o igncr '{0}' 27 | env: 28 | CYGWIN: winsymlinks:native 29 | run: | 30 | make --version 31 | DEST=agsutils-cygwin-git 32 | make CFLAGS="-O2 -g0 -fno-strict-aliasing" LDFLAGS=-s prefix= DESTDIR="$DEST" all install || exit 1 33 | cp /cygdrive/c/cygwin/bin/cygwin1.dll "$DEST"/bin/ && echo "copy OK" 34 | echo "current dir: $PWD" 35 | 36 | - name: upload_artifact 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: agsutils-cygwin-git 40 | path: agsutils-cygwin-git/bin/* 41 | 42 | -------------------------------------------------------------------------------- /Makefile.binary: -------------------------------------------------------------------------------- 1 | LINKLIBS="-lpthread" 2 | 3 | MAINFILE=$(FNAME).c 4 | OUTFILE=$(FNAME).out 5 | 6 | CFLAGS_OWN=-Wall -Wextra -static -std=c99 7 | CFLAGS_DBG=-g -O0 8 | CFLAGS_OPT=-Os -s 9 | CFLAGS_OPT_AGGRESSIVE=-O3 -s -flto -fwhole-program 10 | 11 | -include config.mak 12 | 13 | CFLAGS_RCB_OPT_AGGRESSIVE=$(DB_FLAGS) ${CFLAGS_OWN} ${CFLAGS_OPT_AGGRESSIVE} ${CFLAGS} 14 | CFLAGS_RCB_OPT=$(DB_FLAGS) ${CFLAGS_OWN} ${CFLAGS_OPT} ${CFLAGS} 15 | CFLAGS_RCB_DBG=$(DB_FLAGS) ${CFLAGS_OWN} ${CFLAGS_DBG} ${CFLAGS} 16 | 17 | RCB=rcb2 18 | 19 | all: debug 20 | 21 | clean: 22 | rm -f $(OUTFILE) 23 | rm -f *.o 24 | rm -f $(FNAME).rcb 25 | 26 | optimized: 27 | CFLAGS="${CFLAGS_RCB_OPT} -s" $(RCB) $(RCBFLAGS) ${MAINFILE} $(LINKLIBS) 28 | strip --remove-section .comment ${OUTFILE} 29 | 30 | optimized-aggressive: 31 | CFLAGS="${CFLAGS_RCB_OPT_AGGRESSIVE} -s" $(RCB) $(RCBFLAGS) ${MAINFILE} $(LINKLIBS) 32 | strip --remove-section .comment ${OUTFILE} 33 | 34 | odebug: 35 | CFLAGS="${CFLAGS_RCB_OPT} -g" $(RCB) $(RCBFLAGS) ${MAINFILE} $(LINKLIBS) 36 | debug-stripper.sh $(OUTFILE) 37 | 38 | debug: 39 | CFLAGS="${CFLAGS_RCB_DBG}" $(RCB) $(RCBFLAGS) ${MAINFILE} $(LINKLIBS) 40 | 41 | 42 | .PHONY: all optimized optimized-aggressive debug odebug 43 | -------------------------------------------------------------------------------- /RoomFile.h: -------------------------------------------------------------------------------- 1 | #ifndef ROOMFILE_H 2 | #define ROOMFILE_H 3 | 4 | #include 5 | #include 6 | #include "File.h" 7 | 8 | 9 | enum RoomFileBlockType { 10 | BLOCKTYPE_EXT = 0, 11 | BLOCKTYPE_MIN = BLOCKTYPE_EXT, 12 | BLOCKTYPE_MAIN = 1, 13 | BLOCKTYPE_SCRIPT = 2, 14 | BLOCKTYPE_COMPSCRIPT = 3, 15 | BLOCKTYPE_COMPSCRIPT2 = 4, 16 | BLOCKTYPE_OBJECTNAMES = 5, 17 | BLOCKTYPE_ANIMBKGRND = 6, 18 | BLOCKTYPE_COMPSCRIPT3 = 7, /* only bytecode script type supported by released engine code */ 19 | BLOCKTYPE_PROPERTIES = 8, 20 | BLOCKTYPE_OBJECTSCRIPTNAMES = 9, 21 | BLOCKTYPE_MAX = BLOCKTYPE_OBJECTSCRIPTNAMES, 22 | BLOCKTYPE_EOF = 0xFF 23 | }; 24 | 25 | struct RoomFile { 26 | short version; 27 | off_t blockpos[BLOCKTYPE_MAX+1]; 28 | unsigned blocklen[BLOCKTYPE_MAX+1]; 29 | }; 30 | 31 | /* 0: error, 1: succcess. expect pointer to zero-initialized struct */ 32 | int RoomFile_read(AF *f, struct RoomFile *r); 33 | char *RoomFile_extract_source(AF *f, struct RoomFile *r, size_t *sizep); 34 | 35 | /* this function actually isn't room specific at all, it works with all 36 | script containers as it looks out for the start signature. */ 37 | off_t ARF_find_code_start(AF* f, off_t start); 38 | 39 | 40 | #pragma RcB2 DEP "RoomFile.c" 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | cygwin_build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: setup_packages 14 | run: sudo apt update && sudo apt install gcc-mingw-w64-i686 musl-tools 15 | 16 | - name: compile_win32 17 | run: | 18 | DEST=agsutils-mingw-i686-git 19 | make CC=i686-w64-mingw32-gcc HOSTCC=gcc CFLAGS="-O2 -g0 -fno-strict-aliasing" LDFLAGS="-s -static" prefix= DESTDIR="$DEST" -j8 all install || exit 1 20 | 21 | - name: compile_musl_x86_64 22 | run: | 23 | DEST=agsutils-linux-x86_64-git 24 | make CC=musl-gcc HOSTCC=gcc CFLAGS="-O2 -g0 -fno-strict-aliasing" LDFLAGS="-s -static" prefix= DESTDIR="$DEST" -j8 all install || exit 1 25 | 26 | - name: upload_win32_artifact 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: agsutils-mingw-i686-git 30 | path: agsutils-mingw-i686-git/bin/* 31 | 32 | - name: upload_musl_artifact 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: agsutils-linux-x86_64-git 36 | path: agsutils-linux-x86_64-git/bin/* 37 | 38 | -------------------------------------------------------------------------------- /debug.h: -------------------------------------------------------------------------------- 1 | #ifndef DEBUG_H 2 | #define DEBUG_H 3 | 4 | #define empty_body do {} while(0) 5 | #if defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) 6 | # define INTEL_CPU 64 7 | #elif defined(__x86) || defined(__x86__) || defined(__i386) || defined(__i386__) || defined(_M_IX86) 8 | # define INTEL_CPU 32 9 | #endif 10 | 11 | //#define NO_BREAKPOINTS 12 | #ifdef NO_BREAKPOINTS 13 | # define breakpoint() empty_body 14 | #else 15 | # ifdef INTEL_CPU 16 | # ifdef __GNUC__ 17 | # define breakpoint() __asm__("int3") 18 | # elif defined(__POCC__) 19 | # define breakpoint() __asm { int3 } 20 | # else 21 | # define breakpoint() empty_body 22 | # endif 23 | # elif defined __unix__ 24 | # include 25 | # include 26 | # define breakpoint() kill(getpid(), SIGTRAP) 27 | # else 28 | # define breakpoint() empty_body 29 | # endif 30 | #endif 31 | 32 | #if !defined(__GNUC__) && (defined(__POCC__) || __STDC_VERSION__+0 >= 199901L) 33 | #define __FUNCTION__ __func__ 34 | #endif 35 | 36 | //#define NO_ASSERT 37 | #ifdef NO_ASSERT 38 | # define assert_dbg(exp) empty_body 39 | # define assert_lt(exp) empty_body 40 | # define assert_lte(exp) empty_body 41 | # define assert(x) empty_body 42 | #else 43 | # include 44 | # define assert_dbg(exp) do { if (!(exp)) breakpoint(); } while(0) 45 | # define assert_op(val, op, max) do { if(!((val) op (max))) { \ 46 | fprintf(stderr, "[%s:%d] %s: assert failed: %s < %s\n", \ 47 | __FILE__, __LINE__, __FUNCTION__, # val, # max); \ 48 | breakpoint();}} while(0) 49 | 50 | # define assert_lt (val, max) assert_op(val, <, max) 51 | # define assert_lte(val, max) assert_op(val, <= ,max) 52 | #endif 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /BitmapFuncs.h: -------------------------------------------------------------------------------- 1 | #ifndef BITMAPFUNCS_H 2 | #define BITMAPFUNCS_H 3 | 4 | #include "Bitmap.h" 5 | #include "ImageData.h" 6 | #include "endianness.h" 7 | #include 8 | #include 9 | 10 | static int pad_bmp(ImageData *d) { 11 | unsigned stride = ((((d->width * d->bytesperpixel*8) + 31) & ~31) >> 3); 12 | if(stride == d->width * d->bytesperpixel) return -1; 13 | unsigned x,y; 14 | unsigned outsz = d->height * stride; 15 | unsigned char *out = malloc(outsz), *p = d->data, *q = out; 16 | if(!out) return 0; 17 | for(y = 0; y < d->height; ++y) { 18 | for(x = 0; x < d->width*d->bytesperpixel; ++x) 19 | *(q++) = *(p++); 20 | for(; x < stride; ++x) 21 | *(q++) = 0; 22 | } 23 | free(d->data); 24 | d->data = out; 25 | d->data_size = outsz; 26 | return 1; 27 | } 28 | static void write_bmp(char *name, ImageData *d) { 29 | FILE *f = fopen(name, "wb"); 30 | if(f) { 31 | struct BITMAPINFOHEADER_X hdr = { 32 | .bfType = end_htole16(0x4D42), 33 | .bfSize = end_htole32(sizeof(hdr) + d->data_size), 34 | .bfOffsetBits = end_htole32(sizeof(hdr)), 35 | .biSize = end_htole32(sizeof(hdr)-14), 36 | .biWidth = end_htole32(d->width), 37 | /* negative height means bmp is stored from top to bottom */ 38 | .biHeight = end_htole32( - d->height), 39 | .biPlanes = end_htole32(1), 40 | .biBitCount = end_htole32(d->bytesperpixel * 8), 41 | .biCompression = 0, 42 | .biSizeImage = 0, 43 | .biXPelsPerMeter = end_htole32(0xb11), 44 | .biYPelsPerMeter = end_htole32(0xb11), 45 | }; 46 | fwrite(&hdr, 1, sizeof hdr, f); 47 | pad_bmp(d); 48 | fwrite(d->data, 1, d->data_size, f); 49 | fclose(f); 50 | } else { 51 | fprintf(stderr, "error opening %s\n", name); 52 | } 53 | } 54 | 55 | #endif 56 | 57 | -------------------------------------------------------------------------------- /agsalphainfo.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "DataFile.h" 3 | #include 4 | #include 5 | #include 6 | #include "version.h" 7 | #define ADS ":::AGSalphainfo " VERSION " by rofl0r:::" 8 | 9 | static int usage(char *argv0) { 10 | fprintf(stderr, ADS "\nusage:\n%s [-s spriteno] DIR\n" 11 | "prints alphachannel flags of all sprites, or specified sprite, in game file.\n" 12 | "gamefile is typically ac2game.dta or game28.dta inside DIR.\n" 13 | , argv0); 14 | return 1; 15 | } 16 | 17 | #define SPF_ALPHACHANNEL 0x10 18 | 19 | int main(int argc, char**argv) { 20 | int c, flags = 0, spriteno = -1; 21 | while ((c = getopt(argc, argv, "s:")) != EOF) switch(c) { 22 | case 's': spriteno = atoi(optarg); break; 23 | default: return usage(argv[0]); 24 | } 25 | if(!argv[optind]) return usage(argv[0]); 26 | char *dir = argv[optind]; 27 | 28 | ADF a_b, *a = &a_b; 29 | char fnbuf[512]; 30 | if(!ADF_find_datafile(dir, fnbuf, sizeof(fnbuf))) 31 | return 1; 32 | 33 | enum ADF_open_error aoe = ADF_open(a, fnbuf); 34 | if(aoe != AOE_success && aoe <= AOE_gamebase) { 35 | fprintf(stderr, "failed to open/process data file: %s\n", AOE2str(aoe)); 36 | return 1; 37 | } else if (aoe != AOE_success) { 38 | fprintf(stderr, "warning: failed to process some non-essential parts (%s) of gamefile, probably from a newer game format\n", AOE2str(aoe)); 39 | } 40 | 41 | off_t off = ADF_get_spriteflagsstart(a); 42 | unsigned nsprites = ADF_get_spritecount(a); 43 | ADF_close(a); 44 | 45 | FILE *f = fopen(fnbuf, "rb"); 46 | if(!f) return 1; 47 | fseeko(f, off, SEEK_SET); 48 | unsigned char *buf = malloc(nsprites); 49 | fread(buf, 1, nsprites, f); 50 | fclose(f); 51 | 52 | size_t i; 53 | static const char *offon[] = { "OFF", "ON" }; 54 | for(i=0;i 4 | #include 5 | #include 6 | #include "version.h" 7 | #define ADS ":::AGSdisas " VERSION " by rofl0r:::" 8 | 9 | static int usage(char *argv0) { 10 | fprintf(stderr, ADS "\nusage:\n%s [-oblf] file.o [file.s]\n" 11 | "pass input and optionally output filename.\n" 12 | "options:\n" 13 | "-v : verbose (show filenames)\n" 14 | "-o : dump offset comments in disassembly\n" 15 | "-b : dump hexadecimal bytecode comments in disassembly\n" 16 | "-f : dump informative original fixups section\n" 17 | "-l : remove linenumber debug assembly directives [produces smaller files]\n" 18 | , argv0); 19 | exit(1); 20 | } 21 | 22 | static int disas(char *o, char *s, int flags, int verbose) { 23 | //ARF_find_code_start 24 | AF f_b, *f = &f_b; 25 | ASI sc; 26 | int err = 0; 27 | if(AF_open(f, o)) { 28 | ASI *i = ASI_read_script(f, &sc) ? &sc : 0; 29 | if(verbose) printf("disassembling %s -> %s", o, s); 30 | if(!i || !ASI_disassemble(f, i, s, flags)) err = 1; 31 | if(verbose) { 32 | if(err) printf(" FAIL\n"); 33 | else printf(" OK\n"); 34 | } 35 | AF_close(f); 36 | } 37 | return err; 38 | } 39 | 40 | int main(int argc, char**argv) { 41 | int flags = 0, c, verbose = 0; 42 | while ((c = getopt(argc, argv, "voblf")) != EOF) switch(c) { 43 | case 'v': verbose = 1; break; 44 | case 'o': flags |= DISAS_DEBUG_OFFSETS; break; 45 | case 'b': flags |= DISAS_DEBUG_BYTECODE; break; 46 | case 'l': flags |= DISAS_SKIP_LINENO; break; 47 | case 'f': flags |= DISAS_DEBUG_FIXUPS; break; 48 | default: return usage(argv[0]); 49 | } 50 | if(!argv[optind]) return usage(argv[0]); 51 | char *o = argv[optind], *s, out[256]; 52 | if(!argv[optind+1]) { 53 | size_t l = strlen(o); 54 | snprintf(out, 256, "%s", o); 55 | out[l-1] = 's'; // overflow me! 56 | s = out; 57 | } else s = argv[optind+1]; 58 | if(!strcmp(o, s)) { 59 | fprintf(stderr, "error: input and output file (%s) identical!\n", o); 60 | return 1; 61 | } 62 | 63 | return disas(o, s, flags, verbose); 64 | } 65 | -------------------------------------------------------------------------------- /MemGrow.c: -------------------------------------------------------------------------------- 1 | #include "MemGrow.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #ifndef PAGE_SIZE 8 | #define PAGE_SIZE 4096 9 | #endif 10 | 11 | void mem_init(MG* mem) { 12 | memset(mem, 0, sizeof *mem); 13 | } 14 | 15 | void mem_free(MG* mem) { 16 | if(mem->mem) free(mem->mem); 17 | mem_init(mem); 18 | } 19 | 20 | /* returns 1 if realloc was necessary, -1 if no realloc was necessary, 0 when realloc failed */ 21 | int mem_grow_if_needed(MG *mem, size_t newsize) { 22 | if(newsize > mem->capa) { 23 | size_t nucap = mem->capa * 2; 24 | if(newsize > nucap) { 25 | nucap = newsize; 26 | if(nucap & (PAGE_SIZE -1)) { 27 | nucap += PAGE_SIZE; 28 | nucap &= ~(PAGE_SIZE -1); 29 | } 30 | } 31 | void *nu = realloc(mem->mem, nucap); 32 | if(!nu) return 0; 33 | mem->mem = nu; 34 | mem->capa = nucap; 35 | return 1; 36 | } 37 | return -1; 38 | } 39 | 40 | int mem_write(MG *mem, size_t offset, void* data, size_t size) { 41 | int ret; 42 | size_t needed = offset + size; 43 | if(needed < offset) return 0; /* overflow */ 44 | if((ret = mem_grow_if_needed(mem, needed))) { 45 | memcpy((char*) mem->mem + offset, data, size); 46 | if(needed > mem->used) mem->used = needed; 47 | } 48 | return ret; 49 | } 50 | 51 | int mem_append(MG *mem, void* data, size_t size) { 52 | return mem_write(mem, mem->used, data, size); 53 | } 54 | 55 | void* mem_getptr(MG* mem, size_t offset, size_t byteswanted) { 56 | if(!mem->mem || offset + byteswanted > mem->used) return 0; 57 | return (char*)mem->mem + offset; 58 | } 59 | 60 | void mem_set(MG* mem, void* data, size_t used, size_t allocated) { 61 | mem->mem = data; 62 | mem->used = used; 63 | mem->capa = allocated; 64 | } 65 | 66 | int mem_write_stream(MG* mem, FILE* out) { 67 | if(!mem->mem || !mem->used) return 0; 68 | int ret = (fwrite(mem->mem, 1, mem->used, out) == mem->used); 69 | return ret; 70 | } 71 | 72 | int mem_write_file(MG* mem, char* fn) { 73 | FILE *out = fopen(fn, "wb"); 74 | if(!out) return 0; 75 | int ret = mem_write_stream(mem, out); 76 | fclose(out); 77 | return ret; 78 | } 79 | -------------------------------------------------------------------------------- /agsalphahack.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "DataFile.h" 3 | #include 4 | #include 5 | #include 6 | #include "version.h" 7 | #define ADS ":::AGSalphahack " VERSION " by rofl0r:::" 8 | 9 | static int usage(char *argv0) { 10 | fprintf(stderr, ADS "\nusage:\n%s [-s spriteno] DIR\n" 11 | "removes alphachannel flag from specified sprite in game file.\n" 12 | "if spriteno is -1 (default), all flags are removed.\n" 13 | "gamefile is typically ac2game.dta or game28.dta inside DIR.\n" 14 | "this is useful when sprites have been hacked/converted to 16bit.\n" 15 | "you should make a backup of the file before doing this.\n" 16 | , argv0); 17 | return 1; 18 | } 19 | 20 | #define SPF_ALPHACHANNEL 0x10 21 | 22 | int main(int argc, char**argv) { 23 | int c, flags = 0, spriteno = -1; 24 | while ((c = getopt(argc, argv, "s:")) != EOF) switch(c) { 25 | case 's': spriteno = atoi(optarg); break; 26 | default: return usage(argv[0]); 27 | } 28 | if(!argv[optind]) return usage(argv[0]); 29 | char *dir = argv[optind]; 30 | 31 | ADF a_b, *a = &a_b; 32 | char fnbuf[512]; 33 | if(!ADF_find_datafile(dir, fnbuf, sizeof(fnbuf))) 34 | return 1; 35 | 36 | enum ADF_open_error aoe = ADF_open(a, fnbuf); 37 | if(aoe != AOE_success && aoe <= AOE_gamebase) { 38 | fprintf(stderr, "failed to open/process data file: %s\n", AOE2str(aoe)); 39 | return 1; 40 | } else if (aoe != AOE_success) { 41 | fprintf(stderr, "warning: failed to process some non-essential parts (%s) of gamefile, probably from a newer game format\n", AOE2str(aoe)); 42 | } 43 | 44 | off_t off = ADF_get_spriteflagsstart(a); 45 | unsigned nsprites = ADF_get_spritecount(a); 46 | ADF_close(a); 47 | 48 | printf("removing alpha of %u out of %u spriteflags.\n", spriteno==-1?nsprites:1, nsprites); 49 | 50 | FILE *f = fopen(fnbuf, "r+b"); 51 | if(!f) return 1; 52 | fseeko(f, off, SEEK_SET); 53 | unsigned char *buf = malloc(nsprites); 54 | fread(buf, 1, nsprites, f); 55 | 56 | size_t i; 57 | for(i=0;inumsprites 79 | #define ADF_get_spriteflagsstart(A) (A)->spriteflagsstart 80 | #define ADF_get_cursorcount(A) (A)->game.cursorcount 81 | #define ADF_get_cursorname(A, N) (A)->cursornames[N] 82 | #define ADF_get_charactercount(A) (A)->game.charactercount 83 | #define ADF_get_characterscriptname(A, N) (A)->characterscriptnames[N] 84 | #define ADF_get_guicount(A) (A)->guicount 85 | #define ADF_get_guiname(A, N) (A)->guinames[N] 86 | #define ADF_get_viewcount(A) (A)->game.viewcount 87 | #define ADF_get_viewname(A, N) (A)->viewnames[N] 88 | #define ADF_get_inventorycount(A) (A)->game.inventorycount 89 | #define ADF_get_inventoryname(A, N) (A)->inventorynames[N] 90 | 91 | 92 | #pragma RcB2 DEP "DataFile.c" 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /agsex: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | usage() { 4 | echo "$0 game.exe FILES OBJ" 5 | echo "extracts ags game files into FILES and script files into OBJ" 6 | echo "also a Makefile is created which automatically recompiles" 7 | echo "and reinjects changed asm (.s) files into 'game.ags'" 8 | exit 1 9 | } 10 | 11 | test $# -ne 3 && usage 12 | 13 | BINDIR="$(dirname "$(readlink -f "$0")")" 14 | 15 | GAME="$1" 16 | FILES="$2" 17 | OBJS="$3" 18 | 19 | set -e 20 | 21 | "$BINDIR"/agstract "$GAME" "$FILES" 22 | "$BINDIR"/agscriptxtract "$FILES" "$OBJS" 23 | 24 | DATAFILE="$FILES"/game28.dta 25 | test -e "$DATAFILE" || DATAFILE="$FILES"/ac2game.dta 26 | test -e "$DATAFILE" || { echo "error: none of datafiles game28.dta/ac2game.dta found" 2>&1; exit 1; } 27 | 28 | # fix timestamps of object files so they're newer than .s 29 | for i in "$OBJS"/*.o ; do touch "$i" ; done 30 | 31 | # fix timestamps of game files so they're newer than .o 32 | for i in "$FILES"/* ; do touch "$i" ; done 33 | 34 | MAKEFILE=$PWD/Makefile 35 | cat << EOF > "$MAKEFILE" 36 | .PHONY: all optimize 37 | 38 | FILES_DIR=$FILES 39 | FILES = \$(sort \$(wildcard \$(FILES_DIR)/*)) 40 | OBJS_DIR=$OBJS 41 | OBJS = \$(sort \$(wildcard \$(OBJS_DIR)/*.o)) 42 | SRCS = \$(OBJS:%.o=%.s) 43 | OPT_FILES = \$(SRCS:%.s=%.sopt) 44 | 45 | GAME_DATA=$DATAFILE 46 | GAME=game.ags 47 | 48 | OPT_FLAGS=-cmp -pushpop -sourceline -lnl -ll -cmp2 -axmar -mrswap 49 | 50 | -include config.mak 51 | 52 | all: \$(GAME) 53 | agspack \$(FILES_DIR) \$(GAME) 54 | 55 | optimize: \$(OPT_FILES) 56 | \$(foreach file, \$(OPT_FILES), mv \$(file) \$(file:%.sopt=%.s) ; ) 57 | 58 | \$(GAME): \$(FILES) 59 | 60 | \$(FILES_DIR)/%.crm: \$(OBJS_DIR)/%.o 61 | agsinject 0 \$< \$@ 62 | 63 | \$(OBJS_DIR)/%.sopt: \$(OBJS_DIR)/%.s 64 | agsoptimize \$(OPT_FLAGS) \$< \$@ 65 | 66 | \$(OBJS_DIR)/%.o: \$(OBJS_DIR)/%.s 67 | agssemble \$(AS_FLAGS) \$< \$@ 68 | 69 | GAME_DATA_FILES= \\ 70 | EOF 71 | (cd "$OBJS" 72 | for i in globalscript.o dialogscript.o gamescript*.o ; do 73 | test -e "$i" && printf '\t$(OBJS_DIR)/%s \\\n' "$i" >> "$MAKEFILE" || true 74 | done) 75 | 76 | cat << EOF >> "$MAKEFILE" 77 | 78 | \$(GAME_DATA): \$(GAME_DATA_FILES) 79 | agsinject -e -t \$(GAME_DATA) 0:\$(OBJS_DIR)/globalscript.o \\ 80 | EOF 81 | (cd "$OBJS" 82 | cnt=1 83 | i=dialogscript.o 84 | test -e "$i" && { 85 | printf '\t %d:$(OBJS_DIR)/%s \\\n' $cnt "$i" >> "$MAKEFILE" 86 | cnt=2 87 | } || true 88 | ocnt=0 89 | while test -e gamescript${ocnt}.o ; do 90 | i=gamescript${ocnt}.o 91 | printf '\t %d:$(OBJS_DIR)/%s \\\n' $cnt "$i" >> "$MAKEFILE" 92 | ocnt=$(($ocnt + 1)) 93 | cnt=$(($cnt + 1)) 94 | done) 95 | printf '\t\n' >> "$MAKEFILE" 96 | 97 | 98 | -------------------------------------------------------------------------------- /StringEscape.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | //FIXME out gets silently truncated if outsize is too small 6 | 7 | size_t escape(char* in, char* out, size_t outsize) { 8 | size_t l = 0; 9 | while(*in && l + 3 < outsize) { 10 | switch(*in) { 11 | case '\a': /* 0x07 */ 12 | case '\b': /* 0x08 */ 13 | case '\t': /* 0x09 */ 14 | case '\n': /* 0x0a */ 15 | case '\v': /* 0x0b */ 16 | case '\f': /* 0x0c */ 17 | case '\r': /* 0x0d */ 18 | case '\"': /* 0x22 */ 19 | case '\'': /* 0x27 */ 20 | case '\?': /* 0x3f */ 21 | case '\\': /* 0x5c */ 22 | *out++ = '\\'; 23 | l++; 24 | switch(*in) { 25 | case '\a': /* 0x07 */ 26 | *out = 'a'; break; 27 | case '\b': /* 0x08 */ 28 | *out = 'b'; break; 29 | case '\t': /* 0x09 */ 30 | *out = 't'; break; 31 | case '\n': /* 0x0a */ 32 | *out = 'n'; break; 33 | case '\v': /* 0x0b */ 34 | *out = 'v'; break; 35 | case '\f': /* 0x0c */ 36 | *out = 'f'; break; 37 | case '\r': /* 0x0d */ 38 | *out = 'r'; break; 39 | case '\"': /* 0x22 */ 40 | *out = '\"'; break; 41 | case '\'': /* 0x27 */ 42 | *out = '\''; break; 43 | case '\?': /* 0x3f */ 44 | *out = '\?'; break; 45 | case '\\': /* 0x5c */ 46 | *out = '\\'; break; 47 | } 48 | break; 49 | default: 50 | *out = *in; 51 | } 52 | in++; 53 | out++; 54 | l++; 55 | } 56 | *out = 0; 57 | return l; 58 | } 59 | 60 | size_t unescape(char* in, char *out, size_t outsize) { 61 | size_t l = 0; 62 | while(*in && l + 1 < outsize) { 63 | switch (*in) { 64 | case '\\': 65 | ++in; 66 | assert(*in); 67 | switch(*in) { 68 | case 'a': 69 | *out = '\a'; 70 | break; 71 | case 'b': 72 | *out = '\b'; 73 | break; 74 | case 't': 75 | *out='\t'; 76 | break; 77 | case 'n': 78 | *out='\n'; 79 | break; 80 | case 'v': 81 | *out='\v'; 82 | break; 83 | case 'f': 84 | *out = '\f'; 85 | break; 86 | case 'r': 87 | *out='\r'; 88 | break; 89 | case '"': 90 | *out='"'; 91 | break; 92 | case '\'': 93 | *out = '\''; 94 | break; 95 | case '?': 96 | *out = '\?'; 97 | break; 98 | case '\\': 99 | *out='\\'; 100 | break; 101 | // FIXME add handling of hex and octal 102 | default: 103 | abort(); 104 | } 105 | break; 106 | default: 107 | *out=*in; 108 | } 109 | in++; 110 | out++; 111 | l++; 112 | } 113 | *out = 0; 114 | return l; 115 | } 116 | -------------------------------------------------------------------------------- /tokenizer.h: -------------------------------------------------------------------------------- 1 | #ifndef TOKENIZER_H 2 | #define TOKENIZER_H 3 | 4 | #define MAX_TOK_LEN 4096 5 | #define MAX_UNGETC 8 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | struct tokenizer_getc_buf { 12 | int buf[MAX_UNGETC]; 13 | size_t cnt, buffered; 14 | }; 15 | 16 | enum markertype { 17 | MT_SINGLELINE_COMMENT_START = 0, 18 | MT_MULTILINE_COMMENT_START = 1, 19 | MT_MULTILINE_COMMENT_END = 2, 20 | MT_MAX = MT_MULTILINE_COMMENT_END 21 | }; 22 | 23 | #define MAX_CUSTOM_TOKENS 32 24 | 25 | enum tokentype { 26 | TT_IDENTIFIER = 1, 27 | TT_SQSTRING_LIT, 28 | TT_DQSTRING_LIT, 29 | TT_ELLIPSIS, 30 | TT_HEX_INT_LIT, 31 | TT_OCT_INT_LIT, 32 | TT_DEC_INT_LIT, 33 | TT_FLOAT_LIT, 34 | TT_SEP, 35 | /* errors and similar */ 36 | TT_UNKNOWN, 37 | TT_OVERFLOW, 38 | TT_WIDECHAR_LIT, 39 | TT_WIDESTRING_LIT, 40 | TT_EOF, 41 | TT_CUSTOM = 1000 /* start user defined tokentype values */ 42 | }; 43 | 44 | const char* tokentype_to_str(enum tokentype tt); 45 | 46 | struct token { 47 | enum tokentype type; 48 | uint32_t line; 49 | uint32_t column; 50 | int value; 51 | }; 52 | 53 | enum tokenizer_flags { 54 | TF_PARSE_STRINGS = 1 << 0, 55 | TF_PARSE_WIDE_STRINGS = 1 << 1, 56 | }; 57 | 58 | struct tokenizer { 59 | FILE *input; 60 | uint32_t line; 61 | uint32_t column; 62 | int flags; 63 | int custom_count; 64 | int peeking; 65 | const char *custom_tokens[MAX_CUSTOM_TOKENS]; 66 | char buf[MAX_TOK_LEN]; 67 | size_t bufsize; 68 | struct tokenizer_getc_buf getc_buf; 69 | const char* marker[MT_MAX+1]; 70 | const char* filename; 71 | struct token peek_token; 72 | }; 73 | 74 | void tokenizer_init(struct tokenizer *t, FILE* in, int flags); 75 | void tokenizer_set_filename(struct tokenizer *t, const char*); 76 | void tokenizer_set_flags(struct tokenizer *t, int flags); 77 | int tokenizer_get_flags(struct tokenizer *t); 78 | off_t tokenizer_ftello(struct tokenizer *t); 79 | void tokenizer_register_marker(struct tokenizer*, enum markertype, const char*); 80 | void tokenizer_register_custom_token(struct tokenizer*, int tokentype, const char*); 81 | int tokenizer_next(struct tokenizer *t, struct token* out); 82 | int tokenizer_peek_token(struct tokenizer *t, struct token* out); 83 | int tokenizer_peek(struct tokenizer *t); 84 | void tokenizer_skip_until(struct tokenizer *t, const char *marker); 85 | int tokenizer_skip_chars(struct tokenizer *t, const char *chars, int *count); 86 | int tokenizer_read_until(struct tokenizer *t, const char* marker, int stop_at_nl); 87 | int tokenizer_rewind(struct tokenizer *t); 88 | 89 | #ifdef __GNUC__ 90 | #pragma GCC diagnostic ignored "-Wunknown-pragmas" 91 | #endif 92 | #pragma RcB2 DEP "tokenizer.c" 93 | 94 | #endif 95 | 96 | -------------------------------------------------------------------------------- /File.c: -------------------------------------------------------------------------------- 1 | #include "File.h" 2 | 3 | int AF_open(AF *f, const char* fn) { 4 | f->b = &f->b_b; 5 | ByteArray_ctor(f->b); 6 | ByteArray_set_endian(f->b, BAE_LITTLE); 7 | return ByteArray_open_file(f->b, fn); 8 | } 9 | 10 | int AF_is_eof(AF *f) { 11 | return ByteArray_is_eof(f->b); 12 | } 13 | 14 | int AF_search(AF *f, unsigned char* bytes, size_t len) { 15 | return ByteArray_search(f->b, bytes, len); 16 | } 17 | 18 | void AF_close(AF* f) { 19 | ByteArray_close_file(f->b); 20 | } 21 | 22 | ssize_t AF_read(AF* f, void* buf, size_t len) { 23 | return ByteArray_readMultiByte(f->b, buf, len); 24 | } 25 | 26 | long long AF_read_longlong(AF* f) { 27 | return ByteArray_readUnsignedLongLong(f->b); 28 | } 29 | 30 | int AF_read_int(AF* f) { 31 | return ByteArray_readInt(f->b); 32 | } 33 | 34 | unsigned AF_read_uint(AF* f) { 35 | return ByteArray_readUnsignedInt(f->b); 36 | } 37 | 38 | short AF_read_short(AF* f) { 39 | return ByteArray_readShort(f->b); 40 | } 41 | 42 | unsigned short AF_read_ushort(AF* f) { 43 | return ByteArray_readUnsignedShort(f->b); 44 | } 45 | 46 | int AF_read_uchar(AF *f) { 47 | return ByteArray_readUnsignedByte(f->b); 48 | } 49 | 50 | off_t AF_get_pos(AF* f) { 51 | return ByteArray_get_position(f->b); 52 | } 53 | 54 | int AF_set_pos(AF* f, off_t x) { 55 | return ByteArray_set_position(f->b, x); 56 | } 57 | 58 | int AF_dump_chunk_stream(AF* a, size_t start, size_t len, FILE* out) { 59 | char buf[4096]; 60 | ByteArray_set_position(a->b, start); 61 | while(len) { 62 | size_t togo = len > sizeof(buf) ? sizeof(buf) : len; 63 | if(togo != (size_t) ByteArray_readMultiByte(a->b, buf, togo)) { 64 | return 0; 65 | } 66 | len -= togo; 67 | char *p = buf; 68 | while (togo) { 69 | size_t n = fwrite(p, 1, togo, out); 70 | if(!n) return 0; 71 | p += n; 72 | togo -= n; 73 | } 74 | } 75 | return 1; 76 | } 77 | 78 | int AF_dump_chunk(AF* a, size_t start, size_t len, char* fn) { 79 | FILE *out = fopen(fn, "wb"); 80 | if(!out) return 0; 81 | int ret = AF_dump_chunk_stream(a, start, len, out); 82 | fclose(out); 83 | return ret; 84 | } 85 | 86 | int AF_read_junk(AF* a, size_t l) { 87 | /*char buf[512]; 88 | while(l) { 89 | size_t togo = l > sizeof(buf) ? sizeof(buf) : l; 90 | if(togo != (size_t) ByteArray_readMultiByte(a->b, buf, togo)) return 0; 91 | l -= togo; 92 | } 93 | return 1;*/ 94 | if(!l) return 1; 95 | return ByteArray_set_position(a->b, ByteArray_get_position(a->b) + l); 96 | } 97 | 98 | int AF_read_string(AF* a, char* buf, size_t max) { 99 | size_t l = 0; 100 | while(l < max) { 101 | if(ByteArray_readMultiByte(a->b, buf + l, 1) != 1) 102 | return 0; 103 | if(!buf[l]) return 1; 104 | l++; 105 | } 106 | return 0; 107 | } 108 | -------------------------------------------------------------------------------- /agspack.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "Clib32.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "version.h" 8 | 9 | #define ADS ":::AGSpack " VERSION " by rofl0r:::" 10 | 11 | static int usage(char *argv0) { 12 | fprintf(stderr, ADS 13 | "\nusage:\n%s OPTIONS directory target-pack\n\n" 14 | "OPTIONS:\n" 15 | "-e: recreate original exe stub\n" 16 | , argv0); 17 | return 1; 18 | } 19 | 20 | int main(int argc, char** argv) { 21 | int c, exe_opt = 0; 22 | while((c = getopt(argc, argv, "e")) != -1) switch(c) { 23 | default: return usage(argv[0]); 24 | case 'e': exe_opt = 1; break; 25 | } 26 | if (!argv[optind] || !argv[optind+1]) 27 | return usage(argv[0]); 28 | 29 | char *dir = argv[optind]; 30 | char *pack = argv[optind+1]; 31 | char fnbuf[512]; 32 | char line[1024]; 33 | FILE* fp; 34 | snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dir, "agspack.info"); 35 | if(!(fp = fopen(fnbuf, "r"))) { 36 | fprintf(stderr, "couldnt open %s\n", fnbuf); 37 | return 1; 38 | } 39 | if(exe_opt) { 40 | snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dir, "agspack.exestub"); 41 | if(access(fnbuf, R_OK) == -1) { 42 | fprintf(stderr, "exestub requested, but couldnt read %s\n", fnbuf); 43 | return 1; 44 | } 45 | } 46 | size_t index = 0; 47 | struct AgsFile *ags = calloc(1, sizeof(*ags)); 48 | AgsFile_init(ags, pack); 49 | AgsFile_setSourceDir(ags, dir); 50 | 51 | if(!AgsFile_appendDataFile(ags, "AGSPACKv" VERSION)) { 52 | fprintf(stderr, "error: packname exceeds 20 chars\n"); 53 | return 1; 54 | } 55 | if(exe_opt) AgsFile_setExeStub(ags, "agspack.exestub"); 56 | size_t filecount = 0; 57 | while(fgets(line, sizeof(line), fp)) { 58 | size_t l = strlen(line); 59 | if(l) { 60 | line[l - 1] = 0; 61 | if(--l && line[l-1] == '\r') line[l - 1] = 0; 62 | } 63 | char *p = strchr(line, '='); 64 | if(!p) return 1; 65 | *p = 0; p++; 66 | if(0) ; 67 | else if(strcmp(line, "agsversion") == 0 || strcmp(line, "mflversion") == 0) 68 | AgsFile_setVersion(ags, atoi(p)); 69 | else if(strcmp(line, "filecount") == 0) { 70 | filecount = atoi(p); 71 | AgsFile_setNumFiles(ags, filecount); 72 | } 73 | else if(isdigit(*line)) { 74 | // FIXME: we just add the files sequentially now 75 | // though it's most likely correct... 76 | //if(!AgsFile_setFile(ags, index++, p)) 77 | if(filecount == 0) { 78 | fprintf(stderr, "error: filecount required before first file\n"); 79 | return 1; 80 | } 81 | ++index; 82 | if(!AgsFile_appendFile(ags, p)) { 83 | perror(p); 84 | return 1; 85 | } 86 | } 87 | } 88 | fclose(fp); 89 | for(index = 0; index < filecount; index++) { 90 | // TODO read from input file, but it seems to be all 0 for some games. 91 | AgsFile_setFileNumber(ags, index, 0); 92 | } 93 | int ret = AgsFile_write(ags); 94 | if(!ret) perror("write"); 95 | AgsFile_close(ags); 96 | free(ags); 97 | return !ret; 98 | } 99 | 100 | -------------------------------------------------------------------------------- /Clib32.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIB32_H 2 | #define CLIB32_H 3 | 4 | #include 5 | #include "ByteArray.h" 6 | 7 | #define MAX_FILES 10000 8 | #define MAXMULTIFILES 25 9 | 10 | struct MultiFileLib { 11 | char data_filenames[MAXMULTIFILES][20]; 12 | size_t num_data_files; 13 | char filenames[MAX_FILES][25]; 14 | unsigned offset[MAX_FILES]; 15 | unsigned length[MAX_FILES]; 16 | char file_datafile[MAX_FILES]; // number of datafile 17 | size_t num_files; 18 | }; 19 | 20 | struct MultiFileLibNew { 21 | char data_filenames[MAXMULTIFILES][50]; 22 | size_t num_data_files; 23 | char filenames[MAX_FILES][100]; 24 | unsigned long long offset[MAX_FILES]; 25 | unsigned long long length[MAX_FILES]; 26 | char file_datafile[MAX_FILES]; // number of datafile 27 | size_t num_files; 28 | }; 29 | 30 | struct strstore; 31 | struct MultiFileLibDyn { 32 | struct strstore *data_filenames; 33 | struct strstore *filenames; 34 | unsigned long long *offset; 35 | unsigned long long *length; 36 | char *file_datafile; // number of datafile 37 | }; 38 | 39 | struct AgsFile { 40 | struct ByteArray f[MAXMULTIFILES]; 41 | struct MultiFileLibDyn mflib; 42 | int libversion; 43 | char* fn; 44 | char *dir; 45 | ba_off_t pack_off; 46 | const char *exestub_fn; 47 | }; 48 | 49 | /* generic interface */ 50 | void AgsFile_init(struct AgsFile *buf, char* filename); 51 | void AgsFile_close(struct AgsFile *f); 52 | 53 | /* reader interface */ 54 | int AgsFile_open(struct AgsFile *buf); 55 | 56 | int AgsFile_getVersion(struct AgsFile *f); 57 | size_t AgsFile_getFileCount(struct AgsFile *f); 58 | size_t AgsFile_getOffset(struct AgsFile *f, size_t index); 59 | size_t AgsFile_getFileSize(struct AgsFile *f, size_t index); 60 | int AgsFile_getFileNumber(struct AgsFile *f, size_t index); 61 | size_t AgsFile_getDataFileCount(struct AgsFile *f); 62 | /* the availability of getFileName* APIs depends upon STRSTORE_LINEAR setting 63 | in CLib32.c */ 64 | char *AgsFile_getFileName(struct AgsFile *f, size_t index); 65 | char *AgsFile_getFileNameLinear(struct AgsFile *f, ba_off_t off); 66 | char *AgsFile_getDataFileName(struct AgsFile *f, size_t index); 67 | char *AgsFile_getDataFileNameLinear(struct AgsFile *f, ba_off_t off); 68 | 69 | int AgsFile_dump(struct AgsFile* f, size_t index, const char* outfn); 70 | int AgsFile_extract(struct AgsFile* f, int multifileno, ba_off_t start, size_t len, const char* outfn); 71 | 72 | /* writer interface */ 73 | // the directory containing the files passed via setFile 74 | void AgsFile_setSourceDir(struct AgsFile *f, char* sourcedir); 75 | void AgsFile_setVersion(struct AgsFile *f, int version); 76 | //int AgsFile_setFile(struct AgsFile *f, size_t index, char* fn); 77 | int AgsFile_setNumFiles(struct AgsFile *f, size_t num_files); 78 | int AgsFile_appendFile(struct AgsFile *f, char* fn); 79 | int AgsFile_appendDataFile(struct AgsFile *f, char* fn); 80 | void AgsFile_setFileNumber(struct AgsFile *f, size_t index, int number); 81 | int AgsFile_setDataFile(struct AgsFile *f, size_t index, char* fn); 82 | void AgsFile_setExeStub(struct AgsFile *f, const char *fn); 83 | int AgsFile_write(struct AgsFile *f); 84 | 85 | #pragma RcB2 DEP "Clib32.c" 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /agssemble.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #ifdef __POCC__ 3 | #define __STDC_WANT_LIB_EXT2__ 1 4 | #endif 5 | #include 6 | #include "Assembler.h" 7 | #include "DataFile.h" 8 | #include 9 | #include 10 | #include "version.h" 11 | #define ADS ":::AGSsemble " VERSION " by rofl0r:::" 12 | 13 | #ifndef DISABLE_CPP 14 | #include "preproc.h" 15 | #endif 16 | 17 | static int usage(char *argv0) { 18 | fprintf(stderr, ADS "\nusage:\n%s [-E] [-i file.i] [-I includedir] [-D preproc define] file.s [file.o]\n" 19 | "pass an ags assembly filename.\n" 20 | #ifndef DISABLE_CPP 21 | "-E: invoke built-in C preprocessor 'tinycpp' on the input file before assembling\n" 22 | "-I includedir - add include dir for CPP\n" 23 | "-D define - add define for CPP\n" 24 | "-i file save preprocessor output to file\n" 25 | #else 26 | "preprocessor disabled in this build, so option -E,-I,-D,-i are ignored\n" 27 | #endif 28 | "if optional second filename is ommited, will write into file.o\n", argv0); 29 | return 1; 30 | } 31 | 32 | #ifndef DISABLE_CPP 33 | static FILE *freopen_r(FILE *f, char **buf, size_t *size) { 34 | fflush(f); 35 | fclose(f); 36 | return fmemopen(*buf, *size, "r"); 37 | } 38 | #endif 39 | 40 | int main(int argc, char** argv) { 41 | #ifndef DISABLE_CPP 42 | struct cpp* cpp = cpp_new(); 43 | #endif 44 | char *tmp, *cppoutfn = 0; 45 | int flags = 0, c; 46 | while ((c = getopt(argc, argv, "Ei:I:D:")) != EOF) switch(c) { 47 | #ifndef DISABLE_CPP 48 | case 'E': flags |= 1; break; 49 | case 'i': cppoutfn = optarg; break; 50 | case 'I': cpp_add_includedir(cpp, optarg); break; 51 | case 'D': 52 | if((tmp = strchr(optarg, '='))) *tmp = ' '; 53 | cpp_add_define(cpp, optarg); 54 | break; 55 | #endif 56 | default: return usage(argv[0]); 57 | } 58 | if(!argv[optind]) return usage(argv[0]); 59 | char* file = argv[optind]; 60 | char out [256], *outn; 61 | if(!argv[optind+1]) { 62 | size_t l = strlen(file); 63 | char *p; 64 | snprintf(out, 256, "%s", file); 65 | p = strrchr(out, '.'); 66 | if(!p) p = out + l; 67 | *(p++) = '.'; 68 | *(p++) = 'o'; 69 | *p = 0; 70 | outn = out; 71 | } else outn = argv[optind+1]; 72 | if(!strcmp(outn, file)) { 73 | fprintf(stderr, "error: input and output file (%s) identical!\n", file); 74 | return 1; 75 | } 76 | 77 | FILE *in = fopen(file, "r"); 78 | if(!in) { 79 | fprintf(stderr, "error opening file %s\n", file); 80 | return 1; 81 | } 82 | 83 | #ifndef DISABLE_CPP 84 | if(flags & 1) { 85 | struct FILE_container { 86 | FILE *f; 87 | char *buf; 88 | size_t len; 89 | } output = {0}; 90 | if(!cppoutfn) output.f = open_memstream(&output.buf, &output.len); 91 | else output.f = fopen(cppoutfn, "w"); 92 | fprintf(stdout, "preprocessing %s ...", file); 93 | int ret = cpp_run(cpp, in, output.f, file); 94 | if(!ret) { 95 | fprintf(stdout, "FAIL\n"); 96 | return 1; 97 | } 98 | fprintf(stdout, "OK\n"); 99 | fclose(in); 100 | if(!cppoutfn) in = freopen_r(output.f, &output.buf, &output.len); 101 | else { 102 | fclose(output.f); 103 | in = fopen(cppoutfn, "r"); 104 | } 105 | } 106 | // cpp_free(cpp); // FIXME this crashes on windows, find out why 107 | #endif 108 | 109 | AS a_b, *a = &a_b; 110 | AS_open_stream(a, in); 111 | 112 | fprintf(stdout, "assembling %s -> %s ... ", file, outn); 113 | int ret = AS_assemble(a, outn); 114 | AS_close(a); 115 | 116 | if(!ret) fprintf(stdout, "FAIL\n"); 117 | else fprintf(stdout, "OK\n"); 118 | return !ret; 119 | } 120 | -------------------------------------------------------------------------------- /RoomFile.c: -------------------------------------------------------------------------------- 1 | #include "RoomFile.h" 2 | #include 3 | 4 | off_t ARF_find_code_start(AF* f, off_t start) { 5 | if(!AF_set_pos(f, start)) return -1LL; 6 | char buf[4]; 7 | unsigned match = 0; 8 | while(1) { 9 | if(AF_read(f, buf, 1) != 1) break;; 10 | switch(match) { 11 | case 0: 12 | if(*buf == 'S') match++; 13 | else match = 0; 14 | break; 15 | case 1: 16 | if(*buf == 'C') match++; 17 | else match = 0; 18 | break; 19 | case 2: 20 | if(*buf == 'O') match++; 21 | else match = 0; 22 | break; 23 | case 3: 24 | if(*buf == 'M') return AF_get_pos(f) - 4; 25 | else match = 0; 26 | break; 27 | default: 28 | assert(0); 29 | } 30 | } 31 | return -1LL; 32 | } 33 | 34 | static void roomfile_decrypt_text(char *s, int len) { 35 | unsigned i = 0; 36 | while (i < len) { 37 | *s += "Avis Durgan"[i % 11]; 38 | if (!*s) break; 39 | ++i; ++s; 40 | } 41 | } 42 | 43 | char *RoomFile_extract_source(AF *f, struct RoomFile *r, size_t *sizep) { 44 | *sizep = 0; 45 | off_t pos = r->blockpos[BLOCKTYPE_SCRIPT]; 46 | if(!pos || !AF_set_pos(f, pos)) return 0; 47 | int scriptlen = AF_read_int(f); 48 | assert(r->blocklen[BLOCKTYPE_SCRIPT] == scriptlen + 4); 49 | char* out = malloc(scriptlen+1); 50 | if(!out) return out; 51 | if((size_t) -1 == AF_read(f, out, scriptlen)) { 52 | free(out); 53 | return 0; 54 | } 55 | *sizep = scriptlen; 56 | roomfile_decrypt_text(out, scriptlen); 57 | out[scriptlen] = 0; 58 | return out; 59 | } 60 | 61 | 62 | int RoomFile_read(AF *f, struct RoomFile *r) { 63 | if(!AF_set_pos(f, 0)) return 0; 64 | r->version = AF_read_short(f); 65 | while(1) { 66 | unsigned char blocktype; 67 | if((size_t) -1 == AF_read(f, &blocktype, 1)) return 0; 68 | if(blocktype == BLOCKTYPE_EOF) break; 69 | if(blocktype < BLOCKTYPE_MIN || blocktype > BLOCKTYPE_MAX) return 0; 70 | long long blocklen; 71 | if(blocktype == BLOCKTYPE_EXT) { 72 | if(r->version < 32) { 73 | fprintf(stderr, "%s", "error: found blocktype_ext in incompatible room version\n"); 74 | return 0; 75 | } 76 | char buf[16]; 77 | AF_read(f, buf, 16); 78 | if(0); 79 | else if(!strcmp(buf, "Main")) 80 | blocktype = BLOCKTYPE_MAIN; 81 | else if(!strcmp(buf, "TextScript")) 82 | blocktype = BLOCKTYPE_SCRIPT; 83 | else if(!strcmp(buf, "CompScript")) 84 | blocktype = BLOCKTYPE_COMPSCRIPT; 85 | else if(!strcmp(buf, "CompScript2")) 86 | blocktype = BLOCKTYPE_COMPSCRIPT2; 87 | else if(!strcmp(buf, "CompScript3")) 88 | blocktype = BLOCKTYPE_COMPSCRIPT3; 89 | else if(!strcmp(buf, "ObjNames")) 90 | blocktype = BLOCKTYPE_OBJECTNAMES; 91 | else if(!strcmp(buf, "AnimBg")) 92 | blocktype = BLOCKTYPE_ANIMBKGRND; 93 | else if(!strcmp(buf, "Properties")) 94 | blocktype = BLOCKTYPE_PROPERTIES; 95 | else if(!strcmp(buf, "ObjScNames")) 96 | blocktype = BLOCKTYPE_OBJECTSCRIPTNAMES; 97 | } 98 | if(r->version < 32) blocklen = AF_read_int(f); 99 | else blocklen = AF_read_longlong(f); 100 | off_t curr_pos = AF_get_pos(f), next_block = curr_pos + blocklen; 101 | r->blockpos[blocktype] = curr_pos; 102 | r->blocklen[blocktype] = blocklen; 103 | switch(blocktype) { 104 | case BLOCKTYPE_COMPSCRIPT3: 105 | { 106 | char sig[4]; 107 | AF_read(f, sig, 4); 108 | assert(!memcmp(sig, "SCOM", 4)); 109 | } 110 | break; 111 | /* the older script types weren't supported by the released AGS sources ever */ 112 | default: 113 | break; 114 | } 115 | if(!AF_set_pos(f, next_block)) return 0; 116 | } 117 | return 1; 118 | } 119 | -------------------------------------------------------------------------------- /Bitmap.h: -------------------------------------------------------------------------------- 1 | #ifndef BITMAP_H 2 | #define BITMAP_H 3 | 4 | #define WORD unsigned short 5 | #define DWORD unsigned int 6 | #define LONG DWORD 7 | 8 | #define M_BITMAPFILEHEADER \ 9 | WORD bfType ; /* signature word "BM" or 0x4D42 */ \ 10 | DWORD bfSize ; /* entire size of file */ \ 11 | WORD bfReserved1 ; /* must be zero */ \ 12 | WORD bfReserved2 ; /* must be zero */ \ 13 | DWORD bfOffsetBits ; /* offset in file of DIB pixel bits */ \ 14 | 15 | /* note: original had biWidth/Height as WORD, thus size 12 */ 16 | #define M_BITMAPCOREHEADER \ 17 | DWORD biSize ; /* size of the structure = 12 */ \ 18 | LONG biWidth ; /* width of image in pixels */ \ 19 | LONG biHeight ; /* height of image in pixels */ \ 20 | WORD biPlanes ; /* = 1 */ \ 21 | WORD biBitCount ; /* bits per pixel (1, 4, 8, or 24) */ \ 22 | 23 | #define M_BITMAPINFOHEADER \ 24 | M_BITMAPCOREHEADER \ 25 | DWORD biCompression ; /* compression code */ \ 26 | DWORD biSizeImage ; /* number of bytes in image */ \ 27 | LONG biXPelsPerMeter ; /* horizontal resolution */ \ 28 | LONG biYPelsPerMeter ; /* vertical resolution */ \ 29 | DWORD biClrUsed ; /* number of colors used */ \ 30 | DWORD biClrImportant ; /* number of important colors */ \ 31 | 32 | #define M_BITMAPV2INFOHEADER \ 33 | M_BITMAPINFOHEADER \ 34 | DWORD biRedMask ; /* Red color mask */ \ 35 | DWORD biGreenMask ; /* Green color mask */ \ 36 | DWORD biBlueMask ; /* Blue color mask */ \ 37 | 38 | #define M_BITMAPV3INFOHEADER \ 39 | M_BITMAPV2INFOHEADER \ 40 | DWORD biAlphaMask ; /* Alpha mask */ \ 41 | 42 | #define M_BITMAPV4HEADER \ 43 | M_BITMAPV3INFOHEADER \ 44 | DWORD biCSType ; /* color space type */ \ 45 | LONG biRedX; /* X coordinate of red endpoint */ \ 46 | LONG biRedY; /* Y coordinate of red endpoint */ \ 47 | LONG biRedZ; /* Z coordinate of red endpoint */ \ 48 | LONG biGreenX; /* X coordinate of green endpoint */ \ 49 | LONG biGreenY; /* Y coordinate of green endpoint */ \ 50 | LONG biGreenZ; /* Z coordinate of green endpoint */ \ 51 | LONG biBlueX; /* X coordinate of blue endpoint */ \ 52 | LONG biBlueY; /* Y coordinate of blue endpoint */ \ 53 | LONG biBlueZ; /* Z coordinate of blue endpoint */ \ 54 | DWORD biGammaRed ; /* Red gamma value */ \ 55 | DWORD biGammaGreen ; /* Green gamma value */ \ 56 | DWORD biGammaBlue ; /* Blue gamma value */ \ 57 | 58 | #define M_BITMAPV5HEADER \ 59 | M_BITMAPV4HEADER \ 60 | DWORD biIntent ; /* rendering intent */ \ 61 | DWORD biProfileData ; /* profile data or filename */ \ 62 | DWORD biProfileSize ; /* size of embedded data or filename */\ 63 | DWORD biReserved ; \ 64 | 65 | #define BMP_STRUCT_DECL(X) \ 66 | struct X { \ 67 | M_ ## X \ 68 | } __attribute__((packed, aligned(2))) 69 | 70 | BMP_STRUCT_DECL(BITMAPFILEHEADER); 71 | BMP_STRUCT_DECL(BITMAPCOREHEADER); 72 | BMP_STRUCT_DECL(BITMAPINFOHEADER); 73 | BMP_STRUCT_DECL(BITMAPV2INFOHEADER); 74 | BMP_STRUCT_DECL(BITMAPV3INFOHEADER); 75 | BMP_STRUCT_DECL(BITMAPV4HEADER); 76 | BMP_STRUCT_DECL(BITMAPV5HEADER); 77 | 78 | #undef BMP_STRUCT_DECL 79 | 80 | #define BMP_STRUCT_DECL_X(X) \ 81 | struct X ## _X { \ 82 | M_BITMAPFILEHEADER \ 83 | M_ ## X \ 84 | } __attribute__((packed, aligned (2))) 85 | 86 | 87 | BMP_STRUCT_DECL_X(BITMAPCOREHEADER); 88 | BMP_STRUCT_DECL_X(BITMAPINFOHEADER); 89 | BMP_STRUCT_DECL_X(BITMAPV2INFOHEADER); 90 | BMP_STRUCT_DECL_X(BITMAPV3INFOHEADER); 91 | BMP_STRUCT_DECL_X(BITMAPV4HEADER); 92 | BMP_STRUCT_DECL_X(BITMAPV5HEADER); 93 | 94 | #undef BMP_STRUCT_DECL_X 95 | 96 | #endif 97 | -------------------------------------------------------------------------------- /agstract.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "Clib32.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include /* basename */ 9 | #include 10 | #include "version.h" 11 | #ifdef _WIN32 12 | #include 13 | #define MKDIR(D) mkdir(D) 14 | #else 15 | #define MKDIR(D) mkdir(D, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) 16 | #endif 17 | 18 | #define ADS ":::AGStract " VERSION " by rofl0r:::" 19 | 20 | static void usage(char *argv0) { 21 | fprintf(stderr, ADS "\nusage:\n%s agsgame.exe targetdir\n\n", argv0); 22 | exit(1); 23 | } 24 | 25 | static void dump_exe(struct AgsFile *ags, const char *dir) { 26 | if(ags->pack_off) { 27 | char fnbuf[512]; 28 | snprintf(fnbuf, sizeof(fnbuf), "%s/agspack.exestub", dir); 29 | AgsFile_extract(ags, 0, 0, ags->pack_off, fnbuf); 30 | } 31 | } 32 | 33 | static FILE* open_packfile(const char* fn) { 34 | return fopen(fn, "w"); 35 | } 36 | 37 | /* assert that the resolved absolute filename stays within root, 38 | without using tricks like /.. */ 39 | static int sec_check(char* want) { 40 | if(want[0] == '/') return 0; 41 | if(strstr(want, "../") || strstr(want, "/..")) 42 | return 0; 43 | return 1; 44 | } 45 | 46 | #define EFPRINTF(F, FMT, ...) \ 47 | do{if(fprintf(F, FMT, __VA_ARGS__) < 0) {perror("fprintf"); return 1;}}while(0) 48 | int main(int argc, char** argv) { 49 | if(argc < 3) usage(argv[0]); 50 | fprintf(stdout, ADS "\n"); 51 | struct AgsFile *ags = calloc(1, sizeof(*ags)); 52 | char *fn = argv[1]; 53 | char *dir = argv[2]; 54 | char fnbuf[512]; 55 | char db[512]; 56 | 57 | AgsFile_init(ags, fn); 58 | if(!AgsFile_open(ags)) { 59 | fprintf(stderr, "error opening %s\n", fn); 60 | return 1; 61 | } 62 | 63 | snprintf(fnbuf, sizeof(fnbuf), "%s/agspack.info", dir); 64 | FILE *outf = open_packfile(fnbuf); 65 | if(outf == 0 && errno == ENOENT) { 66 | MKDIR(dir); 67 | outf = open_packfile(fnbuf); 68 | } 69 | if(outf == 0) { 70 | perror("fopen"); 71 | fprintf(stderr, "did you forget to create %s?\n", dir); 72 | perror(fnbuf); 73 | return 1; 74 | } 75 | EFPRINTF(outf, "info=infofile created by %s\n" 76 | "info=this file is needed to reconstruct the packfile with AGSpack\n", ADS); 77 | 78 | if(strchr(fn, '/')) { 79 | strcpy(db, fn); 80 | *strrchr(db, '/') = 0; 81 | AgsFile_setSourceDir(ags, db); 82 | } 83 | dump_exe(ags, dir); 84 | int ec = 0; 85 | size_t i, l = AgsFile_getFileCount(ags), ld =AgsFile_getDataFileCount(ags); 86 | fprintf(stdout, "%s: mfl version %d, containing %zu files.\n", fn, AgsFile_getVersion(ags), l); 87 | EFPRINTF(outf, "agspackfile=%s\nmflversion=%d\nfilecount=%zu\n", fn, AgsFile_getVersion(ags), l); 88 | EFPRINTF(outf, "datafilecount=%zu\n", ld); 89 | EFPRINTF(outf, "df0=%s\n", basename(strdup(fn))); // leak that one string happily 90 | /* skip first data filename, as we replace it with the exe name the user has */ 91 | size_t ssoff = strlen(AgsFile_getDataFileNameLinear(ags, 0)) + 1; 92 | for(i = 1; i < ld; i++) { 93 | char *ss = AgsFile_getDataFileNameLinear(ags, ssoff); 94 | ssoff += strlen(ss+1); 95 | EFPRINTF(outf, "df%zu=%s\n", i, ss); 96 | } 97 | if(ld > 1) for(i = 0; i < l; i++) { 98 | char buf[16]; 99 | snprintf(buf, sizeof(buf), "%d", AgsFile_getFileNumber(ags, i)); 100 | EFPRINTF(outf, "fileno%zu=%s\n", i, buf); 101 | } 102 | ssoff = 0; 103 | for(i = 0; i < l; i++) { 104 | char *currfn = AgsFile_getFileNameLinear(ags, ssoff), *p; 105 | ssoff += strlen(currfn)+1; 106 | snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dir, currfn); 107 | if((p = strchr(currfn, '/'))) { 108 | if(!sec_check(currfn)) { 109 | fprintf(stderr, "error: file %s tries to write outside dest dir\n", currfn); 110 | return 1; 111 | } 112 | } 113 | fprintf(stdout, "%s -> %s\n", currfn, fnbuf); 114 | EFPRINTF(outf, "%zu=%s\n", i, currfn); 115 | if(!AgsFile_dump(ags, i, fnbuf)) ec++; 116 | } 117 | 118 | AgsFile_close(ags); 119 | free(ags); 120 | fclose(outf); 121 | ec = !!ec; 122 | return ec; 123 | } 124 | -------------------------------------------------------------------------------- /asmlex.l: -------------------------------------------------------------------------------- 1 | D [0-9] 2 | L [a-z] 3 | U [A-Z] 4 | A [A-Za-z] 5 | AN [A-Za-z0-9] 6 | AU [A-Za-z_] 7 | ANU [A-Za-z0-9_] 8 | 9 | %{ 10 | #define YY_SKIP_YYWRAP 11 | #define yylex asmlex 12 | 13 | #define yywrap() (1) 14 | void yyerror(char*); 15 | //#define YYSTYPE long long int 16 | //extern YYSTYPE yylval; 17 | #include "y.tab.h" 18 | #include 19 | 20 | #define BISON 21 | #include "scmd_tok.c" 22 | #include "regname_tok.c" 23 | 24 | #define USE_STRDUP 25 | #ifdef USE_STRDUP 26 | #define STRDUP(X) strdup(X) 27 | #else 28 | #define STRDUP(X) (X) 29 | #endif 30 | 31 | #define IRET(X) do{yylval.i = X; return X;}while(0) 32 | #define SRET(X) do{yylval.s = STRDUP(yytext); return X;}while(0) 33 | #define NRET(X) do{yylval.i = atoi(yytext); return X;}while(0) 34 | 35 | /* XXX */ 36 | YYSTYPE yylval; 37 | const char *yyfilename; 38 | int yyfatalerrors; 39 | 40 | %} 41 | 42 | %% 43 | \"([^\n"\\]|\\['"?\\abfnrtv]|\\[0-7]{1,3}|\\[Xx][0-9a-fA-F]+)*\" SRET(STRING); 44 | 45 | [\t ]+ ; 46 | 47 | [#;].*\n { /* ignore comments */ goto newline; } 48 | 49 | \n { newline:; ++yylineno; return 10; } 50 | 51 | "label"{ANU}+ SRET(LABEL); 52 | 53 | {AU}{ANU}*"$"{D}+ | 54 | {AU}{ANU}*"::"{ANU}+"$"{D}+ { SRET(FN_I); } 55 | 56 | {AU}{ANU}*("::"{ANU}+)?"^"{D}+ | 57 | {AU}{ANU}*"::"{ANU}+ { SRET(FN_E); } 58 | 59 | -?{D}+ { NRET(NUMBER);} 60 | 61 | {AU}{ANU}* SRET(ID); 62 | 63 | [=:@,\.\[\]] return yytext[0]; 64 | 65 | . { yyerror("unknown character/token"); } 66 | 67 | %% 68 | 69 | 70 | static int section = 0; 71 | static int ltok = 0; 72 | static int last_mnemonic = 0; 73 | 74 | #undef yylex 75 | int yylex() { 76 | 77 | int ret = asmlex(); 78 | if(ret == '\n') { 79 | ltok = 0; 80 | return ret; 81 | } else if (ltok==0 && ret == '.') goto set_section; 82 | switch(section) { 83 | case 0: if(ret != '.') yyerror("expected section start symbol '.'!"); 84 | set_section: 85 | ret = asmlex(); 86 | if(ret != ID) yyerror("expected section name!"); 87 | if(!strcmp(yytext, "data")) section = SECTION_DATA; 88 | else if(!strcmp(yytext, "text")) section = SECTION_TEXT; 89 | else if(!strcmp(yytext, "imports")) section = SECTION_IMPORTS; 90 | else if(!strcmp(yytext, "exports")) section = SECTION_EXPORTS; 91 | else if(!strcmp(yytext, "strings")) section = SECTION_STRINGS; 92 | else if(!strcmp(yytext, "sections")) section = SECTION_SECTIONS; 93 | else if(!strcmp(yytext, "fixups")) section = SECTION_FIXUPS; 94 | if(!section) yyerror("unknown section name!"); 95 | return section; 96 | case SECTION_DATA: 97 | /* FIXME variable names here can't be one of the 4 keywords */ 98 | if (ret == ID) { 99 | if(!strcmp(yytext, "export")) IRET(D_EXPORT); 100 | else if(!strcmp(yytext, "int")) IRET(D_INT); 101 | else if(!strcmp(yytext, "short")) IRET(D_SHORT); 102 | else if(!strcmp(yytext, "char")) IRET(D_CHAR); 103 | } 104 | break; 105 | case SECTION_TEXT: 106 | if(ret == ',') break; 107 | ++ltok; 108 | if (ret == ID) { 109 | if (ltok == 1) { 110 | int tmp; 111 | if((tmp = KW_SCMD_find_keyword(yytext, yyleng))) 112 | { 113 | last_mnemonic = tok2scmd(tmp); 114 | IRET(tmp); 115 | } 116 | } else { 117 | const struct opcode_info *i = &opcodes[last_mnemonic]; 118 | if(i->regcount >= ltok-1) { 119 | int tmp; 120 | if((tmp = RN_find_keyword(yytext, yyleng))) 121 | IRET(tmp); 122 | } 123 | } 124 | return ID; 125 | } else if (ret == LABEL) { 126 | /* user may haven chosen a variable name matching a label */ 127 | if(ltok == 1 || last_mnemonic == SCMD_JMP || 128 | last_mnemonic == SCMD_JZ || last_mnemonic == SCMD_JNZ) 129 | return ret; 130 | return ID; 131 | } 132 | } 133 | return ret; 134 | } 135 | 136 | void yyerror(char *s) { 137 | printf("%s:%d %s at `%s`\n", 138 | yyfilename==0?"stdin":yyfilename, 139 | yylineno, 140 | s, 141 | yytext[0] == 10 ? "" : yytext); 142 | if(yyfatalerrors) abort(); 143 | } 144 | 145 | void yylex_reset(FILE *f, const char *fn) { 146 | yyfilename = fn; 147 | yylineno = 1; 148 | #ifdef YY_FLUSH_BUFFER 149 | YY_FLUSH_BUFFER; 150 | #endif 151 | yyin = f; 152 | if(yytext) yytext[0] = 0; 153 | section = 0; 154 | ltok = 0; 155 | last_mnemonic = 0; 156 | } 157 | 158 | #ifdef LDEBUG 159 | int main() { 160 | while(1) { 161 | int n = yylex(); 162 | if(n == EOF) break; 163 | printf("%d\n", n); 164 | if(feof(yyin)) break; 165 | } 166 | } 167 | #endif 168 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # suppress built-in rules for % (file without extension, our PROGS on non-win) 2 | .SUFFIXES: 3 | 4 | prefix=/usr/local 5 | bindir=$(prefix)/bin 6 | 7 | PROGS_SRCS = \ 8 | agstract.c \ 9 | agspack.c \ 10 | agscriptxtract.c \ 11 | agssemble.c \ 12 | agsdisas.c \ 13 | agssim.c \ 14 | agsprite.c \ 15 | agsalphahack.c \ 16 | agsalphainfo.c \ 17 | agsinject.c 18 | 19 | LIB_SRCS = \ 20 | ags_cpu.c \ 21 | ByteArray.c \ 22 | Clib32.c \ 23 | DataFile.c \ 24 | Script.c \ 25 | File.c \ 26 | List.c \ 27 | MemGrow.c \ 28 | RoomFile.c \ 29 | StringEscape.c 30 | 31 | SPRITE_SRCS = \ 32 | defpal.c \ 33 | lzw.c \ 34 | miniz_tinfl.c \ 35 | SpriteFile.c 36 | 37 | CPP_SRCS = \ 38 | preproc.c \ 39 | tokenizer.c 40 | 41 | ASM_SRCS = \ 42 | hsearch.c \ 43 | Assembler.c 44 | 45 | CFLAGS_WARN = -Wall -Wextra -Wno-unknown-pragmas -Wno-sign-compare -Wno-switch -Wno-unused -Wno-pointer-sign -Wno-empty-body -Wno-type-limits 46 | 47 | GEN_FILES = scmd_tok.h scmd_tok.c scmd_tok.shilka regname_tok.h regname_tok.c regname_tok.shilka 48 | 49 | -include config.mak 50 | 51 | TOOLCHAIN := $(shell $(CC) -dumpmachine || echo 'unknown') 52 | 53 | # mingw doesn't support fmemopen, open_memstream etc used in preprocessor 54 | ifneq ($(findstring mingw,$(TOOLCHAIN)),mingw) 55 | ASM_SRCS += $(CPP_SRCS) 56 | endif 57 | 58 | ifeq ($(findstring mingw,$(TOOLCHAIN)),mingw) 59 | WIN=1 60 | CPPFLAGS += -D__USE_MINGW_ANSI_STDIO=1 -D_FILE_OFFSET_BITS=64 61 | ASM_CPPFLAGS = -DDISABLE_CPP 62 | endif 63 | ifeq ($(findstring cygwin,$(TOOLCHAIN)),cygwin) 64 | WIN=1 65 | endif 66 | 67 | # set this if you're on a windows shell - i.e. neither msys nor cygwin 68 | ifeq ($(WINBLOWS),1) 69 | WIN=1 70 | RM_F=del 71 | else 72 | RM_F=rm -f 73 | endif 74 | 75 | OBJ_EXT=.o 76 | 77 | ifdef WIN 78 | EXE_EXT=.exe 79 | OBJ_EXT=.obj 80 | endif 81 | 82 | ASM_OBJS = $(ASM_SRCS:.c=$(OBJ_EXT)) 83 | LIB_OBJS = $(LIB_SRCS:.c=$(OBJ_EXT)) 84 | PROGS_OBJS = $(PROGS_SRCS:.c=$(OBJ_EXT)) 85 | SPRITE_OBJS = $(SPRITE_SRCS:.c=$(OBJ_EXT)) 86 | 87 | CPROGS = $(PROGS_SRCS:.c=$(EXE_EXT)) 88 | PROGS = $(CPROGS) agsoptimize agsex 89 | 90 | ifeq ($(HOSTCC),) 91 | HOSTCC = $(CC) 92 | endif 93 | 94 | ifeq ($(SHILKA),) 95 | SHILKA = ./minishilka$(EXE_EXT) 96 | endif 97 | 98 | all: $(PROGS) 99 | 100 | .SECONDARY: $(PROGS_OBJS) 101 | 102 | Debug: 103 | $(MAKE) CFLAGS="-g3 -O0" all 104 | 105 | agssemble$(EXE_EXT): agssemble$(OBJ_EXT) $(LIB_OBJS) $(ASM_OBJS) 106 | agsprite$(EXE_EXT): agsprite$(OBJ_EXT) $(LIB_OBJS) $(SPRITE_OBJS) 107 | %$(EXE_EXT): %$(OBJ_EXT) $(LIB_OBJS) 108 | 109 | minishilka$(EXE_EXT): minishilka.c 110 | $(HOSTCC) -g3 -O0 $< -o $@ 111 | 112 | %$(OBJ_EXT): %.c 113 | $(CC) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_WARN) -o $@ -c $< 114 | 115 | %$(EXE_EXT): %$(OBJ_EXT) $(LIB_OBJS) 116 | $(CC) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_WARN) -o $@ $^ $(LDFLAGS) 117 | 118 | agssemble$(OBJ_EXT): CPPFLAGS += $(ASM_CPPFLAGS) 119 | 120 | kw_search.h: scmd_tok.h scmd_tok.c regname_tok.h regname_tok.c 121 | 122 | Assembler$(OBJ_EXT): kw_search.h 123 | 124 | scmd_tok.h: ags_cpu.h 125 | awk 'BEGIN{print("#ifndef BISON");} /#define SCMD_/{print $$1 " KW_" $$2 " (KW_TOK_SCMD_BASE + " $$3 ")";}END{print("#endif");}' < ags_cpu.h > $@ 126 | 127 | scmd_tok.shilka: ags_cpu.h 128 | awk 'BEGIN{printf "%%type short\n%%%%\n";}/[\t ]\[SCMD_/{w=substr($$3,3,length($$3)-4);s=length(w)>=8?"":"\t";print w s "\t{return KW_" substr($$1,2,length($$1)-2) ";}" ;}END{print "%other\t\t{return 0;}";}' < ags_cpu.h > $@ 129 | 130 | scmd_tok.c: $(SHILKA) 131 | scmd_tok.c: scmd_tok.shilka 132 | $(HOSTRUN) $(SHILKA) -inline -strip -pKW_SCMD_ -no-definitions $< 133 | 134 | regname_tok.h: ags_cpu.h 135 | awk '/[\t ]\[AR_/{r=substr($$1,2,length($$1)-2);printf("#define RN_%s\t(RN_TOK_BASE + %s)\n",r,r);}' < ags_cpu.h > $@ 136 | 137 | regname_tok.shilka: ags_cpu.h 138 | awk 'BEGIN{printf "%%type short\n%%%%\n";}/[\t ]\[AR_/{r=substr($$1,2,length($$1)-2);s=substr($$3,2,length($$3)-3);printf("%s\t{return RN_%s;}\n",s,r);}END{print("%other\t\t{return 0;}");}' < ags_cpu.h > $@ 139 | 140 | regname_tok.c: $(SHILKA) 141 | regname_tok.c: regname_tok.shilka 142 | $(HOSTRUN) $(SHILKA) -inline -strip -pRN_ -no-definitions $< 143 | 144 | lex.yy.c: scmd_tok.c regname_tok.c 145 | lex.yy.c: asmlex.l 146 | $(LEX) $< 147 | 148 | y.tab.h: y.tab.c 149 | y.tab.c: asmparse.y 150 | $(YACC) -d $< 151 | 152 | asmparse: y.tab.c lex.yy.c ags_cpu$(OBJ_EXT) 153 | $(CC) -o $@ $^ 154 | 155 | rcb: 156 | make -f Makefile.binary FNAME=agstract 157 | make -f Makefile.binary FNAME=agspack 158 | make -f Makefile.binary FNAME=agscriptxtract 159 | make -f Makefile.binary FNAME=agssemble 160 | make -f Makefile.binary FNAME=agsdisas 161 | make -f Makefile.binary FNAME=agsinject 162 | make -f Makefile.binary FNAME=agssim 163 | 164 | clean: 165 | $(RM_F) $(CPROGS) minishilka$(EXE_EXT) 166 | $(RM_F) $(GEN_FILES) 167 | $(RM_F) *.out *.o *.obj *.map *.rcb *.exe 168 | 169 | install: $(PROGS:%=$(DESTDIR)$(bindir)/%) 170 | 171 | $(DESTDIR)$(bindir)/%: % 172 | install -D -m 755 $< $@ 173 | 174 | .PHONY: all clean 175 | -------------------------------------------------------------------------------- /hsearch.c: -------------------------------------------------------------------------------- 1 | /* 2 | musl license, hsearch.c originally written by Szabolcs Nagy 3 | 4 | Copyright © 2005-2020 Rich Felker, et al. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | #include "hsearch.h" 29 | 30 | /* 31 | open addressing hash table with 2^n table size 32 | quadratic probing is used in case of hash collision 33 | tab indices and hash are size_t 34 | after resize fails with ENOMEM the state of tab is still usable 35 | */ 36 | 37 | typedef struct htab_entry { 38 | char *key; 39 | htab_value data; 40 | } htab_entry; 41 | 42 | struct elem { 43 | htab_entry item; 44 | size_t hash; 45 | }; 46 | 47 | struct htab { 48 | struct elem *elems; 49 | size_t mask; 50 | size_t used; 51 | size_t dead; 52 | }; 53 | 54 | #define MINSIZE 8 55 | #define MAXSIZE ((size_t)-1/2 + 1) 56 | 57 | static size_t keyhash(char *k) 58 | { 59 | unsigned char *p = (void *)k; 60 | size_t h = 0; 61 | 62 | while (*p) 63 | h = 31*h + *p++; 64 | return h; 65 | } 66 | 67 | static int resize(struct htab *htab, size_t nel) 68 | { 69 | size_t newsize; 70 | size_t i, j; 71 | struct elem *e, *newe; 72 | struct elem *oldtab = htab->elems; 73 | struct elem *oldend = htab->elems + htab->mask + 1; 74 | #ifdef HTAB_OOM_TEST 75 | if(oldtab) return 0; 76 | #endif 77 | 78 | if (nel > MAXSIZE) 79 | nel = MAXSIZE; 80 | for (newsize = MINSIZE; newsize < nel; newsize *= 2); 81 | htab->elems = calloc(newsize, sizeof *htab->elems); 82 | if (!htab->elems) { 83 | htab->elems = oldtab; 84 | return 0; 85 | } 86 | htab->mask = newsize - 1; 87 | if (!oldtab) 88 | return 1; 89 | for (e = oldtab; e < oldend; e++) 90 | if (e->item.key) { 91 | for (i=e->hash,j=1; ; i+=j++) { 92 | newe = htab->elems + (i & htab->mask); 93 | if (!newe->item.key) 94 | break; 95 | } 96 | *newe = *e; 97 | } 98 | free(oldtab); 99 | return 1; 100 | } 101 | 102 | static struct elem *lookup(struct htab *htab, char *key, size_t hash, size_t dead) 103 | { 104 | size_t i, j; 105 | struct elem *e; 106 | 107 | for (i=hash,j=1; ; i+=j++) { 108 | e = htab->elems + (i & htab->mask); 109 | if ((!e->item.key && (!e->hash || e->hash == dead)) || 110 | (e->hash==hash && strcmp(e->item.key, key)==0)) 111 | break; 112 | } 113 | return e; 114 | } 115 | 116 | struct htab *htab_create(size_t nel) 117 | { 118 | struct htab *r = calloc(1, sizeof *r); 119 | if(r && !resize(r, nel)) { 120 | free(r); 121 | r = 0; 122 | } 123 | return r; 124 | } 125 | 126 | void htab_destroy(struct htab *htab) 127 | { 128 | free(htab->elems); 129 | free(htab); 130 | } 131 | 132 | static struct elem *htab_find_elem(struct htab *htab, char* key) 133 | { 134 | size_t hash = keyhash(key); 135 | struct elem *e = lookup(htab, key, hash, 0); 136 | 137 | if (e->item.key) { 138 | return e; 139 | } 140 | return 0; 141 | } 142 | 143 | htab_value* htab_find(struct htab *htab, char* key) 144 | { 145 | struct elem *e = htab_find_elem(htab, key); 146 | if(!e) return 0; 147 | return &e->item.data; 148 | } 149 | 150 | htab_value* htab_find2(struct htab *htab, char* key, char **saved_key) 151 | { 152 | struct elem *e = htab_find_elem(htab, key); 153 | if(!e) return 0; 154 | *saved_key = e->item.key; 155 | return &e->item.data; 156 | } 157 | 158 | int htab_delete(struct htab *htab, char* key) 159 | { 160 | struct elem *e = htab_find_elem(htab, key); 161 | if(!e) return 0; 162 | e->item.key = 0; 163 | e->hash = 0xdeadc0de; 164 | --htab->used; 165 | ++htab->dead; 166 | return 1; 167 | } 168 | 169 | int htab_insert(struct htab *htab, char* key, htab_value value) 170 | { 171 | size_t hash = keyhash(key), oh; 172 | struct elem *e = lookup(htab, key, hash, 0xdeadc0de); 173 | if(e->item.key) { 174 | /* it's not allowed to overwrite existing data */ 175 | return 0; 176 | } 177 | 178 | oh = e->hash; /* save old hash in case it's tombstone marker */ 179 | e->item.key = key; 180 | e->item.data = value; 181 | e->hash = hash; 182 | if (++htab->used + htab->dead > htab->mask - htab->mask/4) { 183 | if (!resize(htab, 2*htab->used)) { 184 | htab->used--; 185 | e->item.key = 0; 186 | e->hash = oh; 187 | return 0; 188 | } 189 | htab->dead = 0; 190 | } else if (oh == 0xdeadc0de) { 191 | /* re-used tomb */ 192 | --htab->dead; 193 | } 194 | return 1; 195 | } 196 | 197 | size_t htab_next(struct htab *htab, size_t iterator, char** key, htab_value **v) 198 | { 199 | size_t i; 200 | for(i=iterator;imask+1;++i) { 201 | struct elem *e = htab->elems + i; 202 | if(e->item.key) { 203 | *key = e->item.key; 204 | *v = &e->item.data; 205 | return i+1; 206 | } 207 | } 208 | return 0; 209 | } 210 | -------------------------------------------------------------------------------- /ByteArray.h: -------------------------------------------------------------------------------- 1 | /* (C) 2011 - 2025 rofl0r. */ 2 | 3 | #ifndef BYTEARRAY_H 4 | #define BYTEARRAY_H 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "MemGrow.h" 16 | #ifdef _WIN32 17 | #define WIN32_LEAN_AND_MEAN 18 | #include /* for HANDLE */ 19 | #define FILEDESC HANDLE 20 | #else 21 | #define FILEDESC int 22 | #endif 23 | 24 | enum ByteArray_Endianess { 25 | BAE_BIG, 26 | BAE_LITTLE, 27 | }; 28 | 29 | enum ByteArray_Type { 30 | BAT_MEMSTREAM, 31 | BAT_FILESTREAM, 32 | }; 33 | 34 | enum ByteArray_Flags { 35 | BAF_CANGROW = 1, 36 | BAF_NONFATAL_READ_OOB = 2, 37 | }; 38 | 39 | typedef long long ba_off_t; 40 | 41 | struct ByteArray { 42 | int type; 43 | int flags; 44 | enum ByteArray_Endianess endian; 45 | enum ByteArray_Endianess sys_endian; 46 | ba_off_t pos; 47 | ba_off_t size; 48 | MG source_mem; 49 | FILEDESC source_fd; 50 | const char *filename; 51 | ssize_t (*readMultiByte)(struct ByteArray*, char*, size_t); 52 | unsigned long long (*readUnsignedLongLong)(struct ByteArray*); 53 | unsigned int (*readUnsignedInt)(struct ByteArray*); 54 | signed int (*readInt)(struct ByteArray*); 55 | unsigned short (*readUnsignedShort)(struct ByteArray*); 56 | signed short (*readShort)(struct ByteArray*); 57 | unsigned char (*readUnsignedByte)(struct ByteArray*); 58 | signed char (*readByte)(struct ByteArray*); 59 | ba_off_t (*readBytes) (struct ByteArray* self, struct ByteArray *dest, ba_off_t start, ba_off_t len); 60 | ba_off_t (*bytesAvailable)(struct ByteArray*); 61 | ba_off_t (*writeByte) (struct ByteArray* self, signed char what); 62 | ba_off_t (*writeUnsignedByte) (struct ByteArray* self, unsigned char what); 63 | ba_off_t (*writeShort) (struct ByteArray* self, signed short what); 64 | ba_off_t (*writeUnsignedShort) (struct ByteArray* self, unsigned short what); 65 | ba_off_t (*writeInt) (struct ByteArray* self, signed int what); 66 | ba_off_t (*writeUnsignedInt) (struct ByteArray* self, unsigned int what); 67 | ba_off_t (*writeMem) (struct ByteArray* self, unsigned char* what, size_t len); 68 | ba_off_t (*writeUTFBytes) (struct ByteArray* self, char* what); 69 | ba_off_t (*writeBytes) (struct ByteArray* self, struct ByteArray* what); 70 | ba_off_t (*writeFloat) (struct ByteArray* self, float what); 71 | int (*set_position_rel) (struct ByteArray* self, int rel); 72 | int (*set_position) (struct ByteArray* self, ba_off_t pos); 73 | ba_off_t (*get_position) (struct ByteArray* self); 74 | }; 75 | 76 | void ByteArray_defaults(struct ByteArray* self); 77 | void ByteArray_ctor(struct ByteArray* self); 78 | struct ByteArray* ByteArray_new(void); 79 | 80 | void ByteArray_set_endian(struct ByteArray* self, enum ByteArray_Endianess endian); 81 | void ByteArray_set_flags(struct ByteArray *self, int flags); 82 | enum ByteArray_Endianess ByteArray_get_endian(struct ByteArray* self); 83 | 84 | int ByteArray_open_file(struct ByteArray* self, const char* filename); 85 | void ByteArray_close_file(struct ByteArray *self); 86 | int ByteArray_open_mem(struct ByteArray* self, char* data, size_t size); 87 | void* ByteArray_get_mem(struct ByteArray* self, size_t offset, size_t byteswanted); 88 | void ByteArray_clear(struct ByteArray* self); 89 | void ByteArray_close(struct ByteArray* self); 90 | 91 | void ByteArray_set_length(struct ByteArray* self, ba_off_t len); 92 | ba_off_t ByteArray_get_length(struct ByteArray* self); 93 | 94 | ba_off_t ByteArray_get_position(struct ByteArray* self); 95 | int ByteArray_set_position(struct ByteArray* self, ba_off_t pos); 96 | int ByteArray_set_position_rel(struct ByteArray* self, int rel); 97 | ba_off_t ByteArray_bytesAvailable(struct ByteArray* self); 98 | int ByteArray_is_eof(struct ByteArray* self); 99 | ssize_t ByteArray_readMultiByte(struct ByteArray* self, char* buffer, size_t len); 100 | unsigned long long ByteArray_readUnsignedLongLong(struct ByteArray* self); 101 | unsigned int ByteArray_readUnsignedInt(struct ByteArray* self); 102 | int ByteArray_readInt(struct ByteArray* self); 103 | unsigned short ByteArray_readUnsignedShort(struct ByteArray* self); 104 | short ByteArray_readShort(struct ByteArray* self); 105 | unsigned char ByteArray_readUnsignedByte(struct ByteArray* self); 106 | signed char ByteArray_readByte(struct ByteArray* self); 107 | ba_off_t ByteArray_readBytes(struct ByteArray* self, struct ByteArray *dest, ba_off_t start, ba_off_t len); 108 | 109 | ba_off_t ByteArray_writeByte(struct ByteArray* self, signed char what); 110 | ba_off_t ByteArray_writeUnsignedByte(struct ByteArray* self, unsigned char what); 111 | ba_off_t ByteArray_writeShort(struct ByteArray* self, signed short what); 112 | ba_off_t ByteArray_writeUnsignedShort(struct ByteArray* self, unsigned short what); 113 | ba_off_t ByteArray_writeInt(struct ByteArray* self, signed int what); 114 | ba_off_t ByteArray_writeUnsignedInt(struct ByteArray* self, unsigned int what); 115 | ba_off_t ByteArray_writeFloat(struct ByteArray* self, float what); 116 | ba_off_t ByteArray_writeMem(struct ByteArray* self, unsigned char* what, size_t len); 117 | ba_off_t ByteArray_writeUTFBytes(struct ByteArray* self, char* what); 118 | ba_off_t ByteArray_writeBytes(struct ByteArray* self, struct ByteArray* what); 119 | 120 | unsigned char ByteArray_getUnsignedByte(struct ByteArray* self, ba_off_t index); 121 | void ByteArray_setUnsignedByte(struct ByteArray* self, ba_off_t index, unsigned char what); 122 | 123 | int ByteArray_search(struct ByteArray *self, unsigned char* bytes, size_t len); 124 | 125 | void ByteArray_dump_to_stream(struct ByteArray* self, FILE *out); 126 | void ByteArray_dump_to_file(struct ByteArray* self, char* filename); 127 | 128 | #ifdef __cplusplus 129 | } 130 | #endif 131 | 132 | #pragma RcB2 DEP "ByteArray.c" 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /strstore.h: -------------------------------------------------------------------------------- 1 | #include 2 | #ifndef STRSTORE_H 3 | #define STRSTORE_H 4 | 5 | /* 6 | strstore. 7 | 8 | an append-only store for strings, with a list-like interface 9 | and fast indexing. 10 | by default using 32 bit uint for field indices and counters to 11 | keep memory usage low and caches lines well used. 12 | if that's not a concern, you can define STRSTORE_INDEX_TYPE to 13 | e.g. size_t. 14 | you may even define it to uint16_t if you know you'll be handling 15 | less than 64K worth of combined string lengths. 16 | all strings are owned and managed by the strstore and copied 17 | upon strstore_append(). memory is allocated in chunks and 18 | strings are kept in its own region and indices into that region 19 | in another. 20 | this is useful if you want to avoid calling strdup() over and 21 | over using a regular list, and don't need to delete or reorder 22 | items. 23 | strings in the store may be modified, but only if the 24 | modification doesn't increase the length. 25 | 26 | for an even more compact representation, it is possible to 27 | define STRSTORE_LINEAR 1, in which case the 28 | STRSTORE_INDEX_TYPE is irrelevant, as the store doesn't even 29 | allocate the index array. 30 | this is only useful if the string items are only ever read 31 | in a linear fashion. 32 | the caller needs to use a running counter which adds up 33 | the length of the currently processed entry after its use. 34 | in this configuration, a modification is still possible, 35 | but it shall not modify the length of the strings at all. 36 | 37 | both the index array and the string region are allocated in 38 | steps of at least STRSTORE_MIN_ALLOC bytes, defaulting to 39 | 4096 (a page of memory on most systems). this also helps the 40 | system allocator to keep overhead low. 41 | this value needs to be a power of 2 and preferably exceed 42 | the size of the longest string you expect to add. 43 | the default should be good for most purposes. 44 | if on append the boundary of the index array needs to grow, 45 | the current size is doubled. 46 | if the string region needs to grow, the current average string 47 | length is used to approximately double the capacity. 48 | 49 | if you need to embed a strstore into a struct in a public header, 50 | don't include this header but simply forward-declare a pointer 51 | to struct strstore. 52 | 53 | */ 54 | 55 | #ifndef STRSTORE_INDEX_TYPE 56 | #define STRSTORE_INDEX_TYPE unsigned int 57 | #endif 58 | 59 | #ifndef STRSTORE_LINEAR 60 | #define STRSTORE_LINEAR 0 61 | #endif 62 | 63 | typedef struct strstore { 64 | size_t lcount; /* number of stored strings. */ 65 | size_t lcapa; /* currently allocated space for list entries, in slots */ 66 | size_t ssize; /* total size of all strings, including NULs */ 67 | size_t scapa; /* currently allocated space for strings, in bytes */ 68 | STRSTORE_INDEX_TYPE *ldata; /* contiguous list indices */ 69 | char *sdata; /* contiguous string data */ 70 | } strstore; 71 | 72 | #ifndef STRSTORE_MIN_ALLOC 73 | #define STRSTORE_MIN_ALLOC 4096 74 | #endif 75 | 76 | #define strstore_new() (strstore*) calloc(1, sizeof(strstore)) 77 | #define strstore_free(SS) do { \ 78 | free(SS->ldata); \ 79 | free(SS->sdata); \ 80 | free(SS); \ 81 | SS = (void*)0; \ 82 | } while(0) 83 | 84 | #define strstore_count(SS) SS->lcount 85 | 86 | #if ! STRSTORE_LINEAR 87 | /* a pointer returned here is only valid until the next call to _add */ 88 | #define strstore_get(SS, N) (SS->sdata + SS->ldata[N]) 89 | #endif 90 | 91 | /* using the linear API, you only fetch the pointer to the first string 92 | initially, then add strlen(elem)+1 to a running counter to know the 93 | position of the next string. calling strstore_append after having 94 | taken this pointer may invalidate it and cause UB. */ 95 | #define strstore_get_first(SS) (SS->sdata) 96 | 97 | 98 | #define ALIGN(X, A) ((X+(A-1)) & -(A)) 99 | 100 | static int strstore_grow_s(struct strstore* ss, size_t ncap) { 101 | if(!ncap) { 102 | size_t alen = ss->lcount ? (ss->ssize / ss->lcount) + 1 : 16; 103 | ncap = 16+(ss->lcount*2*alen); 104 | ncap = ALIGN(ncap, STRSTORE_MIN_ALLOC); 105 | } 106 | void *d = realloc(ss->sdata, ncap); 107 | if(!d) return 0; 108 | ss->scapa = ncap; 109 | ss->sdata = d; 110 | return 1; 111 | } 112 | 113 | static int strstore_grow_l(struct strstore* ss, size_t ncap) { 114 | if(STRSTORE_LINEAR) return 1; /* no-op in linear configuration */ 115 | if(!ncap) { 116 | ncap = ss->lcapa ? ss->lcapa*2*sizeof(*ss->ldata) : sizeof(*ss->ldata); 117 | ncap = ALIGN(ncap, STRSTORE_MIN_ALLOC); 118 | } 119 | void *d = realloc(ss->ldata, ncap); 120 | if(!d) return 0; 121 | ss->lcapa = ncap / sizeof(*ss->ldata); 122 | ss->ldata = d; 123 | return 1; 124 | } 125 | 126 | #undef ALIGN 127 | 128 | /* preallocate `ns` entries of `sl` size each. 129 | use this if you know from the start how many items you'll need. 130 | set sl to your estimate of the average string length. 131 | using this circumvents the STRSTORE_MIN_ALLOC alignment. */ 132 | static int strstore_prealloc(struct strstore *ss, size_t ns, size_t sl) { 133 | if(!strstore_grow_l(ss, ns*sizeof(*ss->ldata))) return 0; 134 | return strstore_grow_s(ss, ns*sl); 135 | } 136 | 137 | /* returns length of the appended string + 1, 138 | 0 on memory allocation failure. */ 139 | static unsigned strstore_append(struct strstore* ss, const char *s) { 140 | if(ss->lcount+1 >= ss->lcapa && !strstore_grow_l(ss, 0)) 141 | return 0; 142 | unsigned ret; 143 | size_t ssn = ss->ssize; 144 | while(1) { 145 | if(ssn+1 >= ss->scapa && !strstore_grow_s(ss, 0)) 146 | return 0; 147 | ss->sdata[ssn++] = *s; 148 | if(*s == 0) { 149 | if(!STRSTORE_LINEAR) 150 | ss->ldata[ss->lcount++] = ss->ssize; 151 | else 152 | ++ss->lcount; 153 | ret = ssn - ss->ssize; 154 | ss->ssize = ssn; 155 | return ret; 156 | } 157 | ++s; 158 | } 159 | } 160 | 161 | #endif 162 | -------------------------------------------------------------------------------- /regusage.h: -------------------------------------------------------------------------------- 1 | enum RegisterAccess { 2 | RA_NONE = 0, 3 | RA_READ = 1 << 0, 4 | RA_WRITE = 1 << 1, 5 | RA_READWRITE = 1 << 2, 6 | }; 7 | 8 | #pragma(pack(push, 1)) 9 | struct regaccess_info { 10 | /* enum RegisterAccess */ unsigned char ra_reg1; 11 | /* enum RegisterAccess */ unsigned char ra_reg2; 12 | /* enum RegisterAccess */ unsigned char ra_mar; 13 | /* enum RegisterAccess */ unsigned char ra_sp; 14 | }; 15 | #pragma(pack(pop)) 16 | 17 | static const struct regaccess_info regaccess_info[] = { 18 | [0] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, 19 | [SCMD_ADD] = {RA_READWRITE, RA_NONE, RA_NONE, RA_NONE}, 20 | [SCMD_SUB] = {RA_READWRITE, RA_NONE, RA_NONE, RA_NONE}, 21 | [SCMD_REGTOREG] = {RA_READ, RA_WRITE, RA_NONE, RA_NONE}, 22 | [SCMD_WRITELIT] = {RA_NONE, RA_NONE, RA_READ, RA_NONE}, 23 | [SCMD_RET] = {RA_NONE, RA_NONE, RA_NONE, RA_READWRITE}, 24 | [SCMD_LITTOREG] = {RA_WRITE, RA_NONE, RA_NONE, RA_NONE}, 25 | [SCMD_MEMREAD] = {RA_WRITE, RA_NONE, RA_READ, RA_NONE}, 26 | [SCMD_MEMWRITE] = {RA_READ, RA_NONE, RA_READ, RA_NONE}, 27 | [SCMD_MULREG] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 28 | [SCMD_DIVREG] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 29 | [SCMD_ADDREG] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 30 | [SCMD_SUBREG] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 31 | [SCMD_BITAND] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 32 | [SCMD_BITOR] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 33 | [SCMD_ISEQUAL] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 34 | [SCMD_NOTEQUAL] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 35 | [SCMD_GREATER] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 36 | [SCMD_LESSTHAN] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 37 | [SCMD_GTE] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 38 | [SCMD_LTE] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 39 | [SCMD_AND] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, /*logical*/ 40 | [SCMD_OR] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 41 | [SCMD_CALL] = {RA_READ, RA_NONE, RA_NONE, RA_READWRITE}, 42 | [SCMD_MEMREADB] = {RA_WRITE, RA_NONE, RA_READ, RA_NONE}, 43 | [SCMD_MEMREADW] = {RA_WRITE, RA_NONE, RA_READ, RA_NONE}, 44 | [SCMD_MEMWRITEB] = {RA_READ, RA_NONE, RA_READ, RA_NONE}, 45 | [SCMD_MEMWRITEW] = {RA_READ, RA_NONE, RA_READ, RA_NONE}, 46 | [SCMD_JZ] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, 47 | [SCMD_PUSHREG] = {RA_READ, RA_NONE, RA_NONE, RA_READWRITE}, 48 | [SCMD_POPREG] = {RA_WRITE, RA_NONE, RA_NONE, RA_READWRITE}, 49 | [SCMD_JMP] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, 50 | [SCMD_MUL] = {RA_READWRITE, RA_NONE, RA_NONE, RA_NONE}, 51 | [SCMD_CALLEXT] = {RA_READ, RA_NONE, RA_NONE, RA_NONE}, 52 | [SCMD_PUSHREAL] = {RA_READ, RA_NONE, RA_NONE, RA_NONE}, 53 | [SCMD_SUBREALSTACK] = {RA_READ, RA_NONE, RA_NONE, RA_NONE}, 54 | [SCMD_LINENUM] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, 55 | [SCMD_CALLAS] = {RA_READ, RA_NONE, RA_NONE, RA_NONE}, 56 | [SCMD_THISBASE] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, 57 | [SCMD_NUMFUNCARGS] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, 58 | [SCMD_MODREG] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 59 | [SCMD_XORREG] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 60 | [SCMD_NOTREG] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 61 | [SCMD_SHIFTLEFT] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 62 | [SCMD_SHIFTRIGHT] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 63 | [SCMD_CALLOBJ] = {RA_READ, RA_NONE, RA_NONE, RA_NONE}, 64 | [SCMD_CHECKBOUNDS] = {RA_READ, RA_NONE, RA_NONE, RA_NONE}, 65 | [SCMD_MEMWRITEPTR] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, //TODO 66 | [SCMD_MEMREADPTR] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, //TODO 67 | [SCMD_MEMZEROPTR] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, 68 | [SCMD_MEMINITPTR] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, //TODO 69 | [SCMD_LOADSPOFFS] = {RA_NONE, RA_NONE, RA_WRITE, RA_NONE}, 70 | [SCMD_CHECKNULL] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, 71 | [SCMD_FADD] = {RA_READWRITE, RA_NONE, RA_NONE, RA_NONE}, 72 | [SCMD_FSUB] = {RA_READWRITE, RA_NONE, RA_NONE, RA_NONE}, 73 | [SCMD_FMULREG] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 74 | [SCMD_FDIVREG] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 75 | [SCMD_FADDREG] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 76 | [SCMD_FSUBREG] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 77 | [SCMD_FGREATER] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 78 | [SCMD_FLESSTHAN] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 79 | [SCMD_FGTE] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 80 | [SCMD_FLTE] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 81 | [SCMD_ZEROMEMORY] = {RA_NONE, RA_NONE, RA_READ, RA_NONE}, 82 | [SCMD_CREATESTRING] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, //TODO 83 | [SCMD_STRINGSEQUAL] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 84 | [SCMD_STRINGSNOTEQ] = {RA_READWRITE, RA_READ, RA_NONE, RA_NONE}, 85 | [SCMD_CHECKNULLREG] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, //TODO 86 | [SCMD_LOOPCHECKOFF] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, 87 | [SCMD_MEMZEROPTRND] = {RA_NONE, RA_NONE, RA_READ, RA_NONE}, 88 | [SCMD_JNZ] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, 89 | [SCMD_DYNAMICBOUNDS] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, //TODO 90 | [SCMD_NEWARRAY] = {RA_NONE, RA_NONE, RA_NONE, RA_NONE}, //TODO 91 | }; 92 | 93 | enum RegisterUsage { 94 | RU_NONE = 0, 95 | RU_READ = 1 << 0, 96 | RU_WRITE = 1 << 1, 97 | RU_WRITE_AFTER_READ = 1 << 2, 98 | }; 99 | 100 | static enum RegisterUsage get_reg_usage(int regno, enum RegisterUsage old, enum RegisterAccess ra) { 101 | enum RegisterUsage ru = old; 102 | switch(ra) { 103 | case RA_READ: 104 | if(ru == RU_NONE || ru == RU_READ) ru = RU_READ; 105 | else if(ru == RU_WRITE); 106 | else if(ru == RU_WRITE_AFTER_READ); 107 | break; 108 | case RA_WRITE: 109 | if(ru == RU_NONE || ru == RU_WRITE) ru = RU_WRITE; 110 | else if(ru == RU_READ) ru = RU_WRITE_AFTER_READ; 111 | else if(ru == RU_WRITE_AFTER_READ); 112 | break; 113 | case RA_READWRITE: 114 | if(ru == RU_NONE || ru == RU_READ) ru = RU_WRITE_AFTER_READ; 115 | else if(ru == RU_WRITE); 116 | else if(ru == RU_WRITE_AFTER_READ); 117 | break; 118 | } 119 | return ru; 120 | } 121 | 122 | -------------------------------------------------------------------------------- /defpal.c: -------------------------------------------------------------------------------- 1 | #define COL(r, g, b) r, g, b 2 | 3 | unsigned char defpal[] = { 4 | COL( 0, 0, 0), COL( 0, 0, 168), COL( 0, 168, 0), 5 | COL( 0, 168, 168), COL(168, 0, 0), COL(168, 0, 168), 6 | COL(168, 84, 0), COL(168, 168, 168), COL( 84, 84, 84), 7 | COL( 84, 84, 252), COL( 84, 252, 84), COL( 84, 252, 252), 8 | COL(252, 84, 84), COL(252, 84, 252), COL(252, 252, 84), 9 | COL(252, 252, 252), COL( 0, 0, 0), COL( 20, 20, 20), 10 | COL( 32, 32, 32), COL( 44, 44, 44), COL( 56, 56, 56), 11 | COL( 68, 68, 68), COL( 80, 80, 80), COL( 96, 96, 96), 12 | COL(112, 112, 112), COL(128, 128, 128), COL(144, 144, 144), 13 | COL(160, 160, 160), COL(180, 180, 180), COL(200, 200, 200), 14 | COL(224, 224, 224), COL(252, 252, 252), COL( 0, 0, 252), 15 | COL( 68, 0, 252), COL(124, 0, 252), COL(184, 148, 0), 16 | COL(184, 128, 0), COL(252, 0, 188), COL(252, 0, 128), 17 | COL(252, 0, 84), COL(252, 0, 0), COL(252, 68, 0), 18 | COL(128, 120, 120), COL(136, 116, 112), COL(132, 8, 4), 19 | COL( 20, 4, 4), COL(196, 20, 4), COL(148, 136, 132), 20 | COL( 68, 12, 4), COL(148, 124, 120), COL(176, 168, 168), 21 | COL(164, 128, 124), COL( 88, 84, 84), COL(128, 116, 112), 22 | COL(148, 120, 112), COL(192, 160, 148), COL(152, 128, 116), 23 | COL(172, 152, 140), COL(104, 44, 12), COL(160, 144, 132), 24 | COL(156, 76, 24), COL(176, 128, 92), COL(200, 108, 32), 25 | COL(204, 192, 176), COL(176, 168, 148), COL(188, 180, 164), 26 | COL(136, 132, 120), COL(232, 228, 196), COL(212, 208, 200), 27 | COL(116, 112, 92), COL(232, 232, 232), COL(188, 188, 44), 28 | COL(216, 212, 16), COL(144, 144, 92), COL(168, 168, 68), 29 | COL(124, 128, 104), COL(196, 200, 192), COL( 0, 228, 204), 30 | COL( 40, 200, 188), COL( 80, 172, 168), COL( 4, 184, 176), 31 | COL( 16, 144, 156), COL(176, 188, 192), COL(188, 212, 216), 32 | COL(152, 180, 192), COL( 56, 152, 212), COL(112, 176, 216), 33 | COL( 0, 120, 200), COL( 96, 104, 112), COL(112, 120, 128), 34 | COL(180, 200, 216), COL(128, 164, 192), COL(140, 164, 180), 35 | COL(108, 148, 192), COL( 88, 140, 192), COL(116, 144, 172), 36 | COL( 68, 128, 192), COL(120, 132, 144), COL( 32, 76, 140), 37 | COL( 64, 76, 96), COL( 68, 76, 84), COL( 68, 96, 140), 38 | COL(112, 120, 140), COL( 76, 84, 100), COL( 52, 60, 76), 39 | COL( 80, 108, 152), COL( 96, 104, 120), COL(100, 120, 160), 40 | COL( 80, 92, 112), COL( 96, 108, 140), COL( 8, 32, 88), 41 | COL( 96, 108, 128), COL( 88, 100, 120), COL( 8, 32, 100), 42 | COL( 88, 100, 132), COL( 8, 24, 80), COL( 80, 88, 120), 43 | COL( 8, 24, 88), COL( 0, 16, 80), COL( 0, 16, 88), 44 | COL(112, 112, 128), COL( 56, 64, 104), COL( 72, 80, 128), 45 | COL( 40, 48, 96), COL( 36, 48, 116), COL( 24, 36, 100), 46 | COL( 24, 36, 120), COL( 4, 16, 72), COL( 48, 56, 104), 47 | COL( 48, 56, 116), COL( 44, 56, 136), COL( 24, 32, 88), 48 | COL( 8, 24, 100), COL( 64, 72, 136), COL( 56, 64, 124), 49 | COL( 16, 24, 80), COL( 16, 24, 88), COL( 8, 16, 80), 50 | COL(128, 132, 148), COL( 68, 72, 120), COL( 16, 24, 96), 51 | COL( 8, 16, 88), COL( 0, 8, 88), COL( 96, 96, 112), 52 | COL(104, 108, 140), COL( 84, 88, 132), COL( 36, 40, 96), 53 | COL( 24, 28, 80), COL( 56, 56, 96), COL( 44, 48, 108), 54 | COL( 36, 40, 88), COL( 24, 32, 164), COL( 32, 40, 216), 55 | COL( 24, 32, 216), COL( 20, 28, 200), COL( 24, 36, 228), 56 | COL( 16, 24, 216), COL( 12, 20, 192), COL( 8, 20, 232), 57 | COL( 96, 96, 140), COL( 72, 76, 112), COL( 8, 8, 72), 58 | COL( 44, 48, 232), COL( 32, 40, 228), COL( 16, 24, 228), 59 | COL(104, 104, 112), COL(120, 120, 128), COL(104, 104, 128), 60 | COL(112, 112, 140), COL( 96, 96, 120), COL( 88, 88, 112), 61 | COL( 96, 96, 128), COL( 88, 88, 120), COL( 24, 24, 36), 62 | COL( 68, 68, 104), COL( 80, 80, 124), COL( 56, 56, 108), 63 | COL( 48, 48, 96), COL( 96, 96, 228), COL( 24, 24, 88), 64 | COL( 16, 16, 80), COL( 16, 16, 88), COL(124, 120, 140), 65 | COL( 44, 44, 60), COL( 68, 64, 96), COL( 84, 80, 112), 66 | COL( 36, 28, 80), COL( 32, 24, 96), COL( 24, 16, 88), 67 | COL( 16, 12, 72), COL( 56, 48, 88), COL( 56, 48, 96), 68 | COL( 56, 48, 108), COL( 88, 80, 124), COL( 64, 56, 100), 69 | COL(104, 96, 136), COL( 68, 56, 120), COL( 76, 64, 104), 70 | COL( 80, 72, 96), COL(104, 96, 128), COL( 96, 88, 120), 71 | COL(100, 88, 132), COL( 52, 40, 88), COL( 84, 72, 112), 72 | COL(104, 96, 120), COL(120, 112, 140), COL( 96, 88, 112), 73 | COL(144, 140, 148), COL( 68, 52, 88), COL( 88, 72, 104), 74 | COL(120, 112, 128), COL(112, 104, 120), COL(116, 104, 128), 75 | COL(104, 88, 120), COL( 96, 80, 112), COL(104, 96, 112), 76 | COL(136, 128, 140), COL(100, 68, 120), COL( 92, 80, 100), 77 | COL(112, 96, 120), COL( 84, 64, 96), COL(140, 108, 156), 78 | COL(104, 88, 112), COL(120, 84, 132), COL(160, 120, 168), 79 | COL(116, 88, 120), COL(132, 88, 136), COL(128, 112, 128), 80 | COL(120, 104, 120), COL(124, 72, 120), COL(112, 108, 112), 81 | COL(120, 96, 116), COL(108, 84, 100), COL(148, 104, 136), 82 | COL(140, 80, 120), COL(156, 152, 156), COL(112, 96, 108), 83 | COL(180, 120, 156), COL(176, 88, 140), COL(152, 56, 112), 84 | COL(116, 116, 116), COL(128, 112, 120), COL(212, 84, 136), 85 | COL(144, 120, 132), COL(188, 28, 88), COL(136, 124, 128), 86 | COL(136, 112, 120), COL(124, 96, 104), COL(124, 36, 52), 87 | COL(132, 104, 108), COL(120, 108, 108), COL(228, 224, 224), 88 | COL(180, 180, 180), COL(200, 200, 200), COL(160, 160, 160), 89 | COL(120, 120, 120) 90 | }; 91 | 92 | #define STATIC_ASSERT(COND) static char static_assert_ ## __LINE__ [COND ? 1 : -1] 93 | STATIC_ASSERT(sizeof(defpal) == 256*3); 94 | -------------------------------------------------------------------------------- /bmap.h: -------------------------------------------------------------------------------- 1 | #ifndef BMAP_H 2 | #define BMAP_H 3 | 4 | /* this is a map behaving like a hashmap, but it uses binary search 5 | on a sorted list behind the curtains. this allows to find the 6 | required entry very quickly, while avoiding a lot of the complexity 7 | of a hashmap. 8 | 9 | for e.g. 10000 array elements, a binary search will only require 12 10 | comparisons, whereas a hashmap needs to compute a hash, then find 11 | a corresponding bucket and iterate over the bucket items, of which 12 | there could be multiple as well. so it shouldn't be much faster, 13 | while having a lot more overhead in code and RAM. 14 | 15 | since we use our per-value tglist behind the scenes, which looks 16 | basically like a flat array of the stored items, there's almost 17 | zero memory overhead with our method here. 18 | 19 | the slowest part is insertion of the item into the list, which uses 20 | memmove on a single block of memory. this is no problem when the 21 | total number of entries is relatively small. 22 | 23 | when comparing to khash, which is known as one of the fastest hashmap 24 | implementations due to usage of macros to generate inlined code, 25 | we reach about 80-75% of its speed with around 1000 items, but only 26 | 40% with 3000, 33% with 10000 etc. the more items, the slower it 27 | becomes in comparison. 28 | 29 | so for the vast majority of tasks, this implementation provides 30 | speed comparable to the fastest hashmap implementation, while adding 31 | only a few hundred byte to binary size. a size-optimized benchmark 32 | program with bmap is 5.5KB, an equivalent one with kash 9.6KB. 33 | 34 | an additional advantage is that the map can be iterated like a 35 | regular array, which is already sorted by key. 36 | 37 | */ 38 | 39 | #include "tglist.h" 40 | #include 41 | #include 42 | #include 43 | #include /* ssize_t */ 44 | 45 | #define bmap_cat(a, b) bmap_cat_impl(a, b) 46 | #define bmap_cat_impl(a, b) a ## b 47 | 48 | #ifdef BMAP_USE_TGILIST 49 | #include "tgilist.h" 50 | #define VAL_LIST_TYPE tgilist 51 | #define VAL_LIST_ARGCALL(FN, A, B, C) FN(A, B, C) 52 | #else 53 | #define VAL_LIST_TYPE tglist 54 | #define VAL_LIST_ARGCALL(FN, A, B, C) FN(A, B) 55 | #endif 56 | 57 | 58 | typedef int (*bmap_compare_func)(const void *, const void *); 59 | 60 | #define bmap_impl(NAME, KEYTYPE, VALTYPE) \ 61 | struct NAME { \ 62 | tglist_impl(, KEYTYPE) keys; \ 63 | VAL_LIST_ARGCALL(bmap_cat(VAL_LIST_TYPE, _impl), ,VALTYPE, unsigned) values; \ 64 | bmap_compare_func compare; \ 65 | union { \ 66 | KEYTYPE* kt; \ 67 | VALTYPE* vt; \ 68 | ssize_t ss; \ 69 | } tmp; \ 70 | } 71 | 72 | #define bmap(KEYTYPE, VALTYPE) bmap_impl(, KEYTYPE, VALTYPE) 73 | #define bmap_decl(ID, KEYTYPE, VALTYPE) bmap_impl(bmap_ ## ID, KEYTYPE, VALTYPE) 74 | #define bmap_proto bmap_impl(, void*, void*) 75 | 76 | /* initialization */ 77 | /* bmap_compare_func is a typical compare function used for qsort, etc such as strcmp 78 | */ 79 | #define bmap_init(X, COMPAREFUNC) do{\ 80 | memset(X, 0, sizeof(*(X))); \ 81 | (X)->compare = COMPAREFUNC; } while(0) 82 | 83 | static inline void* bmap_new(bmap_compare_func fn) { 84 | bmap_proto *nyu = malloc(sizeof(bmap_proto)); 85 | if(nyu) bmap_init(nyu, fn); 86 | return nyu; 87 | } 88 | 89 | /* destruction */ 90 | /* freeflags: 91 | 0: free only internal mem 92 | 1: 0+free all keys, 93 | 2: 0+free all values, 94 | 3: 0+free both 95 | */ 96 | #define bmap_fini(X, FREEFLAGS) do { \ 97 | if(FREEFLAGS & 1) {tglist_free_values(&(X)->keys);} \ 98 | if(FREEFLAGS & 2) {bmap_cat(VAL_LIST_TYPE, _free_values)(&(X)->values);} \ 99 | tglist_free_items(&(X)->keys); \ 100 | bmap_cat(VAL_LIST_TYPE, _free_items)(&(X)->values); \ 101 | } while(0) 102 | 103 | /* set value when key index is known. returns int 0 on failure, 1 on succ.*/ 104 | #define bmap_setvalue(B, VAL, POS) bmap_cat(VAL_LIST_TYPE, _set)(&(B)->values, VAL, POS) 105 | 106 | #define bmap_getsize(B) tglist_getsize(&(B)->keys) 107 | #define bmap_getkey(B, X) tglist_get(&(B)->keys, X) 108 | #define bmap_getval(B, X) bmap_cat(VAL_LIST_TYPE, _get)(&(B)->values, X) 109 | #define bmap_getkeysize(B) (tglist_itemsize(&(B)->keys)) 110 | #define bmap_getvalsize(B) (bmap_cat(VAL_LIST_TYPE, _itemsize)(&(B)->values)) 111 | 112 | #define bmap_find(X, KEY) \ 113 | ( (X)->tmp.kt = (void*)&(KEY), bmap_find_impl(X, (X)->tmp.kt, bmap_getkeysize(X)) ) 114 | 115 | #define bmap_contains(B, KEY) (bmap_find(B, KEY) != (ssize_t)-1) 116 | 117 | /* unlike bmap_getkey/val with index, this returns a pointer-to-item, or NULL */ 118 | #define bmap_get(X, KEY) \ 119 | ( (((X)->tmp.kt = (void*)&(KEY)), 1) && \ 120 | ( (X)->tmp.ss = bmap_find_impl(X, (X)->tmp.kt, bmap_getkeysize(X)) ) == (ssize_t) -1 ? \ 121 | 0 : &bmap_getval(X, (X)->tmp.ss) ) 122 | 123 | /* same as bmap_insert, but inserts blindly without checking for existing items. 124 | this is faster and can be used when it's impossible that duplicate 125 | items are added */ 126 | #define bmap_insert_nocheck(X, KEY, VAL) ( \ 127 | ( \ 128 | ( (X)->tmp.ss = tglist_insert_sorted(&(X)->keys, KEY, (X)->compare) ) \ 129 | == (ssize_t) -1) ? (ssize_t) -1 : ( \ 130 | bmap_cat(VAL_LIST_TYPE, _insert)(&(X)->values, VAL, (X)->tmp.ss) ? (X)->tmp.ss : \ 131 | ( tglist_delete(&(X)->keys, (X)->tmp.ss), (ssize_t) -1 ) \ 132 | ) \ 133 | ) 134 | /* insert item into mapping, overwriting existing items with the same key */ 135 | /* return index of new item, or -1. overwrites existing items. */ 136 | // FIXME evaluates KEY twice 137 | #define bmap_insert(X, KEY, VAL) ( \ 138 | ( (X)->tmp.ss = bmap_find(X, KEY) ) \ 139 | == (ssize_t) -1 ? bmap_insert_nocheck(X, KEY, VAL) : \ 140 | bmap_cat(VAL_LIST_TYPE, _set)(&(X)->values, VAL, (X)->tmp.ss), (X)->tmp.ss \ 141 | ) 142 | 143 | #define bmap_delete(X, POS) ( \ 144 | (X)->tmp.ss = POS, \ 145 | tglist_delete(&(X)->keys, POS), \ 146 | bmap_cat(VAL_LIST_TYPE, _delete)(&(X)->values, POS) \ 147 | ) 148 | 149 | static ssize_t bmap_find_impl(void* bm, const void* key, size_t keysize) { 150 | bmap_proto *b = bm; 151 | void *r = bsearch(key, b->keys.items, bmap_getsize(b), keysize, b->compare); 152 | if(!r) return -1; 153 | return ((uintptr_t) r - (uintptr_t) b->keys.items)/keysize; 154 | } 155 | 156 | #endif 157 | -------------------------------------------------------------------------------- /hbmap.h: -------------------------------------------------------------------------------- 1 | #ifndef HBMAP_H 2 | #define HBMAP_H 3 | 4 | /* this is a hashmap using a fixed number of buckets, 5 | which in turn are of type bmap. this combines the advantages of both 6 | approaches. 7 | limitations: max no of buckets and items per bucket is 2^32-1 each. 8 | speed is almost identical to khash with small number of items per 9 | bucket. with 100.000 items it's about 15% slower. 10 | 11 | unlike bmap, _find(), insert(), etc return an iterator instead of indices. 12 | the iterator needs to be used for e.g. _getkey(), etc. 13 | */ 14 | 15 | #include "bmap.h" 16 | #include 17 | #include 18 | #include 19 | #include /* ssize_t */ 20 | 21 | #ifndef ARRAY_SIZE 22 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 23 | #endif 24 | 25 | typedef uint64_t hbmap_iter; 26 | 27 | #define hbmap_impl(NAME, KEYTYPE, VALTYPE, NUMBUCKETS) \ 28 | struct NAME { \ 29 | unsigned (*hash_func)(const KEYTYPE); \ 30 | union { \ 31 | hbmap_iter it; \ 32 | } tmp; \ 33 | bmap_impl(, KEYTYPE, VALTYPE) buckets[NUMBUCKETS]; \ 34 | } 35 | 36 | #define hbmap(KEYTYPE, VALTYPE, NUMBUCKETS) \ 37 | hbmap_impl(, KEYTYPE, VALTYPE, NUMBUCKETS) 38 | 39 | #define hbmap_decl(ID, KEYTYPE, VALTYPE, NUMBUCKETS) \ 40 | hbmap_impl(hbmap_ ## ID, KEYTYPE, VALTYPE, NUMBUCKETS) 41 | 42 | #define hbmap_proto(NUMBUCKETS) \ 43 | hbmap_impl(, void*, void*, NUMBUCKETS) 44 | 45 | #define hbmap_getbucketcount(X) ARRAY_SIZE((X)->buckets) 46 | 47 | #define hbmap_struct_size_impl(NUMBUCKETS) ( \ 48 | offsetof(hbmap_proto(1), buckets) + \ 49 | NUMBUCKETS * sizeof(bmap_proto) \ 50 | ) 51 | 52 | #define hbmap_init_impl(X, COMPAREFUNC, HASHFUNC, NUMBUCKETS) do{\ 53 | memset(X, 0, hbmap_struct_size_impl(NUMBUCKETS)); \ 54 | ((hbmap_proto(1)*)(void*)(X))->hash_func = (void*)HASHFUNC; \ 55 | bmap_proto *p = (void*)(&((hbmap_proto(1)*)(void*)(X))->buckets[0]); \ 56 | size_t i; for(i=0; ibuckets[i], FREEFLAGS); } \ 83 | } while(0) 84 | 85 | /* internal stuff needed for iterator impl */ 86 | 87 | #define hbmap_iter_bucket(I) ( (I) >> 32) 88 | #define hbmap_iter_index(I) ( (I) & 0xffffffff ) 89 | #define hbmap_iter_makebucket(I) ( (I) << 32) 90 | 91 | #define hbmap_iter_bucket_valid(X, ITER, NUMBUCKETS) ( \ 92 | hbmap_iter_bucket(ITER) < NUMBUCKETS ) 93 | #define hbmap_iter_index_valid(X, ITER) ( \ 94 | hbmap_iter_index(ITER) < bmap_getsize(& \ 95 | (((bmap_proto *)( \ 96 | (void*)(&((hbmap_proto(1)*)(void*)(X))->buckets[0]) \ 97 | ))[hbmap_iter_bucket(ITER)]) \ 98 | )) 99 | 100 | #define hbmap_iter_valid(X, ITER) (\ 101 | hbmap_iter_bucket_valid(X, ITER, hbmap_getbucketcount(X)) && \ 102 | hbmap_iter_index_valid(X, ITER)) 103 | 104 | #define hbmap_next_step(X, ITER) ( \ 105 | hbmap_iter_index_valid(X, (ITER)+1) ? (ITER)+1 : \ 106 | hbmap_iter_makebucket(hbmap_iter_bucket(ITER)+1) \ 107 | ) 108 | 109 | static hbmap_iter hbmap_next_valid_impl(void *h, hbmap_iter iter, size_t nbucks) { 110 | do iter = hbmap_next_step(h, iter); 111 | while(hbmap_iter_bucket_valid(h, iter, nbucks) && !hbmap_iter_index_valid(h, iter)); 112 | return iter; 113 | } 114 | 115 | /* public API continues */ 116 | 117 | /* note that if you use foreach to delete items, the iterator isn't aware of that 118 | and will skip over the next item. you need to use something like: 119 | hbmap_foreach(map, i) { while(hbmap_iter_index_valid(map, i)) hbmap_delete(map, i); } 120 | */ 121 | #define hbmap_foreach(X, ITER_VAR) \ 122 | for(ITER_VAR = hbmap_iter_valid(X, (hbmap_iter)0) ? 0 \ 123 | : hbmap_next_valid_impl(X, 0, hbmap_getbucketcount(X)); \ 124 | hbmap_iter_valid(X, ITER_VAR); \ 125 | ITER_VAR = hbmap_next_valid_impl(X, ITER_VAR, hbmap_getbucketcount(X))) 126 | 127 | #define hbmap_getkey(X, ITER) \ 128 | bmap_getkey(&(X)->buckets[hbmap_iter_bucket(ITER)], hbmap_iter_index(ITER)) 129 | 130 | #define hbmap_getval(X, ITER) \ 131 | bmap_getval(&(X)->buckets[hbmap_iter_bucket(ITER)], hbmap_iter_index(ITER)) 132 | 133 | #define hbmap_setvalue(X, VAL, ITER) \ 134 | bmap_setvalue(&(X)->buckets[hbmap_iter_bucket(ITER)], VAL, hbmap_iter_index(ITER)) 135 | 136 | #define hbmap_getkeysize(X) (bmap_getkeysize(&(X)->buckets[0])) 137 | #define hbmap_getvalsize(X) (bmap_getvalsize(&(X)->buckets[0])) 138 | 139 | #define hbmap_buckindex_impl(X, KEY) \ 140 | ( (hbmap_iter) (X)->hash_func(KEY) % hbmap_getbucketcount(X) ) 141 | 142 | #define hbmap_find(X, KEY) ( \ 143 | ( (X)->tmp.it = hbmap_iter_makebucket(hbmap_buckindex_impl(X, KEY) ) ), \ 144 | ((X)->tmp.it |= (int64_t) bmap_find(&(X)->buckets[ hbmap_iter_bucket((X)->tmp.it) ], KEY)), \ 145 | (X)->tmp.it) 146 | 147 | #define hbmap_contains(X, KEY) (hbmap_find(X, KEY) != (hbmap_iter)-1) 148 | 149 | /* unlike hbmap_getkey/val with index, this returns a pointer-to-item, or NULL */ 150 | #define hbmap_get(X, KEY) ( \ 151 | ( hbmap_find(X, KEY) == (hbmap_iter) -1 ) ? 0 : &hbmap_getval(X, (X)->tmp.it) \ 152 | ) 153 | 154 | /* same as hbmap_insert, but inserts blindly without checking for existing items. 155 | this is faster and can be used when it's impossible that duplicate 156 | items are added */ 157 | #define hbmap_insert_nocheck(X, KEY, VAL) ( \ 158 | ( (X)->tmp.it = hbmap_iter_makebucket(hbmap_buckindex_impl(X, KEY) ) ), \ 159 | ((X)->tmp.it |= (int64_t) bmap_insert_nocheck(&(X)->buckets[hbmap_iter_bucket((X)->tmp.it)], KEY, VAL)), \ 160 | (X)->tmp.it) 161 | 162 | /* insert item into mapping, overwriting existing items with the same key */ 163 | /* return index of new item, or -1. overwrites existing items. */ 164 | #define hbmap_insert(X, KEY, VAL) ( \ 165 | ( hbmap_find(X, KEY) == (hbmap_iter) -1 ) ? hbmap_insert_nocheck(X, KEY, VAL) : \ 166 | ( hbmap_setvalue(X, VAL, (X)->tmp.it), (X)->tmp.it ) \ 167 | ) 168 | 169 | #define hbmap_delete(X, ITER) ( \ 170 | bmap_delete(&(X)->buckets[hbmap_iter_bucket(ITER)], hbmap_iter_index(ITER)), 1) 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /agsinject.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "DataFile.h" 3 | #include "RoomFile.h" 4 | #include "ByteArray.h" 5 | #ifdef __POCC__ 6 | #define WINFILE_EXPORT static 7 | #include "winfile.h" 8 | #endif 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "version.h" 16 | #define ADS ":::AGSinject " VERSION " by rofl0r:::" 17 | 18 | #ifdef __POCC__ 19 | #include 20 | static char *tempnam(const char *dir, const char *pfx) { 21 | (void) dir; 22 | char buf[L_tmpnam + 64]; 23 | buf[0] = 0; 24 | tmpnam(buf); 25 | int i; char *p = buf + strlen(buf); 26 | for(i = 0; pfx[i] && i < 8; ++i, ++p) 27 | *p = pfx[i]; 28 | *p = 0; 29 | sprintf(p, "%016llx", (unsigned long long) _rdtsc()); 30 | return strdup(buf); 31 | } 32 | #define RENAME(OLD, NEW) win_rename(OLD, NEW) 33 | #else 34 | #define RENAME(OLD, NEW) rename(OLD, NEW) 35 | #endif 36 | 37 | int usage(char *argv0) { 38 | fprintf(stderr, 39 | ADS "\n" 40 | "usage (simple):\n" 41 | "---------------\n" 42 | "%s index input.o inject_to.crm\n" 43 | "index is the number of script to replace, i.e. 0 for first script\n" 44 | "only relevant if the output file is a gamefile which contains multiple scripts\n" 45 | "for example gamescript is 0, dialogscript is 1 (if existing), etc\n" 46 | "a room file (.crm) only has one script so you must pass 0.\n\n" 47 | 48 | "usage (extended):\n" 49 | "-----------------\n" 50 | "%s -e [OPTIONS] target index1:input1.o [index2:input2.o...indexN:inputN.o]\n" 51 | "in extended mode, indicated by -e switch, target denotes destination file\n" 52 | "(e.g. game28.dta, *.crm...), and file(s) to inject are passed as\n" 53 | "index:filename tuples.\n" 54 | "this allows to inject several compiled scripts at once.\n" 55 | "OPTIONS:\n" 56 | "-t : only inject obj files whose timestamps are newer than the one of target.\n" 57 | "example: %s -e game28.dta 0:globalscript.o 1:dialogscript.o\n" 58 | , argv0, argv0, argv0); 59 | return 1; 60 | } 61 | 62 | /* inj = filename of file to inject in */ 63 | static int inject(const char *o, const char *inj, unsigned which) { 64 | //ARF_find_code_start 65 | AF f_b, *f = &f_b; 66 | unsigned long long index, found; 67 | int isroom = !strcmp(".crm", inj + strlen(inj) - 4); 68 | if(isroom && which != 0) return -2; 69 | if(!AF_open(f, inj)) return -1; 70 | long long start; 71 | for(index = found = 0; 1 ; found++, index = start + 4) { 72 | int room_length_bytes = 4; 73 | if(!isroom && (start = ARF_find_code_start(f, index)) == -1LL) { 74 | fprintf(stderr, "error, only %llu scripts found\n", (long long)found); 75 | return -3; 76 | } else if(isroom) { 77 | /* use roomfile specific script lookup, as it's faster */ 78 | struct RoomFile rinfo = {0}; 79 | if(!RoomFile_read(f, &rinfo)) return -3; 80 | start = rinfo.blockpos[BLOCKTYPE_COMPSCRIPT3]; 81 | if(rinfo.version >= 32) room_length_bytes = 8; 82 | } 83 | if(found != which) continue; 84 | char *tmp = tempnam(".", "agsinject.tmp"); 85 | FILE *out = fopen(tmp, "wb"); 86 | if(!out) return -1; 87 | 88 | /* 1) dump header */ 89 | AF_dump_chunk_stream(f, 0, isroom ? start -room_length_bytes : start, out); 90 | AF_set_pos(f, start); 91 | 92 | /* open replacement object file */ 93 | struct ByteArray b; 94 | ByteArray_ctor(&b); 95 | ByteArray_open_file(&b, o); 96 | 97 | if(isroom) { 98 | /* 2a) if room, write length */ 99 | /* room files, unlike game files, have a length field of size 4 before 100 | * the compiled script starts. */ 101 | unsigned l = ByteArray_get_length(&b); 102 | struct ByteArray c; 103 | ByteArray_ctor(&c); 104 | ByteArray_open_mem(&c, 0, 0); 105 | ByteArray_set_flags(&c, BAF_CANGROW); 106 | ByteArray_set_endian(&c, BAE_LITTLE); 107 | ByteArray_writeInt(&c, l); 108 | if(room_length_bytes == 8) 109 | /* we should actually write one long long 110 | instead of 2 ints, but we assume that no 111 | room script will be bigger than 2 GB. */ 112 | ByteArray_writeInt(&c, 0); 113 | ByteArray_dump_to_stream(&c, out); 114 | ByteArray_close(&c); 115 | } 116 | /* 2b) dump object file */ 117 | ByteArray_dump_to_stream(&b, out); 118 | ByteArray_close_file(&b); 119 | 120 | ASI s; 121 | if(!ASI_read_script(f, &s)) { 122 | fprintf(stderr, "trouble finding script in %s\n", inj); 123 | return -3; 124 | } 125 | /* 3) dump rest of file */ 126 | AF_dump_chunk_stream(f, start + s.len, ByteArray_get_length(f->b) - (start + s.len), out); 127 | AF_close(f); 128 | fclose(out); 129 | 130 | int rnret = RENAME(tmp, inj); 131 | if(rnret == -1 && errno == EEXIST) { 132 | /* windows is special, as usual */ 133 | fprintf(stderr, "rename failed from %s to %s\n", tmp, inj); 134 | } 135 | return rnret; 136 | } 137 | return -5; 138 | } 139 | 140 | static int check_objname(const char* o) { 141 | const char* p; 142 | if(!(p = strrchr(o, '.')) || strcmp(p, ".o")) { 143 | fprintf(stderr, "error: object file has no .o extension\n"); 144 | return 0; 145 | } 146 | return 1; 147 | } 148 | 149 | static int injectpr(const char *obj, const char *out, unsigned which) { 150 | printf("injecting %s into %s as %d'th script ...", obj, out, which); 151 | int ret = inject(obj, out, which); 152 | if(ret == 0) printf("OK\n"); 153 | else { 154 | printf("FAIL\n"); 155 | if(ret == -2) { 156 | fprintf(stderr, "invalid index %d for roomfile, only 0 possible\n", which); 157 | ret = 0; 158 | } else if (ret == -1) perror("error"); 159 | return 0; 160 | } 161 | return 1; 162 | } 163 | 164 | static int getstamp(const char* fn, time_t *stamp) { 165 | struct stat st; 166 | if(stat(fn, &st) == -1) { 167 | perror("stat"); 168 | return 0; 169 | } 170 | *stamp = st.st_mtime; 171 | return 1; 172 | } 173 | 174 | static int ts_is_newer(const time_t *t1, const time_t *t2) 175 | { 176 | return *t2 > *t1; 177 | } 178 | 179 | int main(int argc, char**argv) { 180 | char *out, *obj; 181 | int which; 182 | if(argc == 4 && isdigit(*argv[1])) { 183 | obj = argv[2]; 184 | out = argv[3]; 185 | which = atoi(argv[1]); 186 | if(!check_objname(obj)) return 1; 187 | if(!injectpr(obj, out, which)) return 1; 188 | return 0; 189 | } 190 | int c, extended = 0, usestamps = 0; 191 | while ((c = getopt(argc, argv, "et")) != EOF) switch(c) { 192 | case 'e': extended = 1; break; 193 | case 't': usestamps = 1; break; 194 | default: return usage(argv[0]); 195 | } 196 | if(!extended || !argv[optind] || !argv[optind+1]) 197 | return usage(argv[0]); 198 | 199 | out = argv[optind]; 200 | time_t stamp = {0}; 201 | 202 | if(usestamps && !getstamp(out, &stamp)) return 1; 203 | 204 | while(argv[++optind]) { 205 | obj = argv[optind]; 206 | char *p = strchr(obj, ':'); 207 | if(!isdigit(*obj) || !p) return usage(argv[0]); 208 | *p = 0; 209 | which = atoi(obj); 210 | obj = ++p; 211 | if(!check_objname(obj)) return 1; 212 | if(usestamps) { 213 | time_t ostamp; 214 | if(!getstamp(obj, &ostamp)) return 1; 215 | if(!ts_is_newer(&stamp, &ostamp)) continue; 216 | } 217 | if(!injectpr(obj, out, which)) return 1; 218 | } 219 | return 0; 220 | } 221 | -------------------------------------------------------------------------------- /miniz.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* Decompression flags used by tinfl_decompress(). */ 5 | /* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */ 6 | /* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */ 7 | /* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */ 8 | /* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */ 9 | enum 10 | { 11 | TINFL_FLAG_PARSE_ZLIB_HEADER = 1, 12 | TINFL_FLAG_HAS_MORE_INPUT = 2, 13 | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, 14 | TINFL_FLAG_COMPUTE_ADLER32 = 8 15 | }; 16 | 17 | void *tinfl_decompress_mem_to_heap(const void *, size_t, size_t *, int); 18 | 19 | #ifdef MINIZ_PRIVATE 20 | 21 | #include 22 | #include 23 | 24 | typedef unsigned char mz_uint8; 25 | typedef signed short mz_int16; 26 | typedef unsigned short mz_uint16; 27 | typedef unsigned int mz_uint32; 28 | typedef unsigned int mz_uint; 29 | typedef int64_t mz_int64; 30 | typedef uint64_t mz_uint64; 31 | typedef int mz_bool; 32 | 33 | typedef mz_uint32 tinfl_bit_buf_t; 34 | #define TINFL_BITBUF_SIZE (32) 35 | 36 | enum 37 | { 38 | TINFL_MAX_HUFF_TABLES = 3, 39 | TINFL_MAX_HUFF_SYMBOLS_0 = 288, 40 | TINFL_MAX_HUFF_SYMBOLS_1 = 32, 41 | TINFL_MAX_HUFF_SYMBOLS_2 = 19, 42 | TINFL_FAST_LOOKUP_BITS = 10, 43 | TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS 44 | }; 45 | 46 | struct tinfl_decompressor_tag; 47 | typedef struct tinfl_decompressor_tag tinfl_decompressor; 48 | struct tinfl_decompressor_tag 49 | { 50 | mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; 51 | tinfl_bit_buf_t m_bit_buf; 52 | size_t m_dist_from_out_buf_start; 53 | mz_int16 m_look_up[TINFL_MAX_HUFF_TABLES][TINFL_FAST_LOOKUP_SIZE]; 54 | mz_int16 m_tree_0[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; 55 | mz_int16 m_tree_1[TINFL_MAX_HUFF_SYMBOLS_1 * 2]; 56 | mz_int16 m_tree_2[TINFL_MAX_HUFF_SYMBOLS_2 * 2]; 57 | mz_uint8 m_code_size_0[TINFL_MAX_HUFF_SYMBOLS_0]; 58 | mz_uint8 m_code_size_1[TINFL_MAX_HUFF_SYMBOLS_1]; 59 | mz_uint8 m_code_size_2[TINFL_MAX_HUFF_SYMBOLS_2]; 60 | mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; 61 | }; 62 | 63 | #define MZ_MALLOC(x) malloc(x) 64 | #define MZ_FREE(x) free(x) 65 | #define MZ_REALLOC(p, x) realloc(p, x) 66 | 67 | #define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) 68 | #define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) 69 | #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) 70 | #define MZ_CLEAR_ARR(obj) memset((obj), 0, sizeof(obj)) 71 | #define MZ_CLEAR_PTR(obj) memset((obj), 0, sizeof(*obj)) 72 | 73 | /* Return status. */ 74 | typedef enum { 75 | /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */ 76 | /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */ 77 | /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */ 78 | TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4, 79 | 80 | /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */ 81 | TINFL_STATUS_BAD_PARAM = -3, 82 | 83 | /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */ 84 | TINFL_STATUS_ADLER32_MISMATCH = -2, 85 | 86 | /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */ 87 | TINFL_STATUS_FAILED = -1, 88 | 89 | /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */ 90 | 91 | /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */ 92 | /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */ 93 | TINFL_STATUS_DONE = 0, 94 | 95 | /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */ 96 | /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */ 97 | /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */ 98 | TINFL_STATUS_NEEDS_MORE_INPUT = 1, 99 | 100 | /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */ 101 | /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */ 102 | /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */ 103 | /* so I may need to add some code to address this. */ 104 | TINFL_STATUS_HAS_MORE_OUTPUT = 2 105 | } tinfl_status; 106 | 107 | #define MZ_MACRO_END while (0) 108 | #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) 109 | #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) 110 | 111 | #define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) 112 | 113 | #if 0 114 | #include 115 | #define MZ_ASSERT(X) assert(X) 116 | #else 117 | #define MZ_ASSERT(X) 118 | #endif 119 | 120 | /* Initializes the decompressor to its initial state. */ 121 | #define tinfl_init(r) \ 122 | do \ 123 | { \ 124 | (r)->m_state = 0; \ 125 | } \ 126 | MZ_MACRO_END 127 | 128 | typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); 129 | 130 | /* Max size of LZ dictionary. */ 131 | #define TINFL_LZ_DICT_SIZE 32768 132 | 133 | #endif 134 | -------------------------------------------------------------------------------- /tglist.h: -------------------------------------------------------------------------------- 1 | #ifndef TGLIST_H 2 | #define TGLIST_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #ifdef __GNUC__ 13 | #pragma GCC diagnostic ignored "-Wunused-value" 14 | #endif 15 | 16 | /* 17 | * type generic list (dynamic array). 18 | * 19 | * unlike sblist, doesn't do any bounds check. 20 | * so you can e.g. only delete positions that are valid, 21 | * without causing memory corruption. 22 | * 23 | * this implementation is header-only, so it is easy 24 | * to embed elsewhere, however there's some overhead 25 | * if it's used in different TUs. 26 | * however the code is pretty slim, the static funcs 27 | * compile to about 400 byte total on x86_64. 28 | * 29 | * right now, this is in the testing stage. 30 | * functions/macros ending in _impl are not supposed 31 | * to be used by the user. 32 | * 33 | * the advantage of using a typed container is 34 | * A) the declaration of the type already documents the use 35 | * (like e.g. List in java) 36 | * B) no casts required when accessing the elements. 37 | * C) added type safety 38 | * 39 | * use like tglist(somename_or_id, int) *l = tglist_new(); 40 | * the name or id can be anything that produces a valid token 41 | * when concatenated to "tglist_". it simply serves to give 42 | * the struct declaration a unique name. 43 | * 44 | * the code was designed such that the type/id is only required 45 | * for the declaration of the struct, not for every function call. 46 | * therefore unfortunately the static funcs have to resort to 47 | * use void* at times. 48 | */ 49 | 50 | #define tglist_impl(NAME, TYPE) \ 51 | struct NAME { \ 52 | size_t count; \ 53 | size_t capa; \ 54 | TYPE* items; \ 55 | union { \ 56 | TYPE* vt; \ 57 | size_t s; \ 58 | } tmp; \ 59 | } 60 | 61 | #define tglist(TYPE) tglist_impl(, TYPE) 62 | /* use tglist_decl if you need a named struct, e.g. to put in a header */ 63 | #define tglist_decl(ID, TYPE) tglist_impl(tglist_ ## ID, TYPE) 64 | #define tglist_proto tglist_impl(, void*) 65 | 66 | #define tglist_getsize(X) ((X)->count) 67 | #define tglist_get_count(X) ((X)->count) 68 | #define tglist_empty(X) ((X)->count == 0) 69 | 70 | /* --- for dynamic style --- */ 71 | // allocate and initialize a new tglist 72 | #define tglist_new() calloc(1, sizeof(tglist_proto)) 73 | 74 | // free dynamically allocated list and its internal buffers 75 | #define tglist_free(X) do {free((X)->items); free(X);} while(0) 76 | 77 | /* --- for static style --- */ 78 | // initialize existing list in user-allocated storage (e.g. stack-allocated) 79 | #define tglist_init(X) memset(X, 0, sizeof(*(X))) 80 | 81 | // free internal buffers of the list 82 | #define tglist_free_items(X) free((X)->items) 83 | 84 | /* in case your list contains pointers to heap-allocated mem, 85 | not values, this will iterate over all list entries and 86 | free them */ 87 | /* the casts here serve to suppress warnings when the macro 88 | is expanded on a non-pointer list, (but using it would be 89 | bogus anyway) */ 90 | #define tglist_free_values(X) \ 91 | if(tglist_itemsize(X) == sizeof(void*)) while((X)->count > 0) \ 92 | {free(*(void**)(((char*)(X)->items)+ ( (--((X)->count)) *tglist_itemsize(X) ) ) \ 93 | );}else{} 94 | 95 | // accessors 96 | #define tglist_get(L, POS) ((L)->items[POS]) 97 | 98 | #define tglist_set(X, ITEM, POS) \ 99 | ((X)->items[POS] = ITEM, 1) 100 | 101 | #define tglist_itemsize(X) sizeof( (X)->items[0] ) 102 | 103 | #define tglist_foreach(X, ITER) for(ITER=0;ITERcount) : 0 \ 109 | ) 110 | 111 | #define tglist_add(X, ITEM) ( \ 112 | tglist_prepare_addition(X) ? \ 113 | tglist_set(X, ITEM, (X)->count-1) : \ 114 | 0 ) 115 | 116 | #define tglist_ptr_from_index_impl(L, POS, ITEMSZ) \ 117 | ( (char*) ((L)->items) + (POS * ITEMSZ) ) 118 | 119 | /* void */ 120 | #define tglist_delete(X, POS) \ 121 | tglist_memmove_impl(X, POS, +1, tglist_itemsize(X)) && \ 122 | ( --((X)->count) , 1 ) 123 | 124 | /* int : 0=err, 1=success. */ 125 | #define tglist_insert(X, ITEM, POS) ( \ 126 | (((X)->tmp.s = (POS)), 1) && \ 127 | tglist_grow_if_needed( X, tglist_itemsize(X) ) ? \ 128 | tglist_memmove_impl(X, ((X)->tmp.s)+1, -1, tglist_itemsize(X)) && \ 129 | ++((X)->count) && \ 130 | tglist_set(X, ITEM, (X)->tmp.s) \ 131 | : 0 ) 132 | 133 | /* internal */ 134 | #define tglist_insert_memcpy(X, ITEMPTR, POS, ITEMSIZE) ( \ 135 | tglist_grow_if_needed( X, ITEMSIZE ) ? \ 136 | tglist_memmove_impl(X, (POS)+1, -1, ITEMSIZE) && \ 137 | ++((X)->count) && \ 138 | memcpy(tglist_ptr_from_index_impl(X, POS, ITEMSIZE), ITEMPTR, ITEMSIZE) \ 139 | : 0 ) 140 | 141 | /* the compare func for all sort-related stuff is qsort-style. 142 | note that if the list contains pointers, the compare func will get 143 | pointers to pointers. so to use e.g. strcmp, you need a wrapper to 144 | deref the const char** pointers before passing them to strcmp. */ 145 | 146 | #define tglist_sort(X, COMPAREFUNC) \ 147 | qsort((X)->items, (X)->count, tglist_itemsize(X), COMPAREFUNC) 148 | 149 | /* insert element into presorted list, returns listindex of new entry or -1 150 | */ 151 | #define tglist_insert_sorted(X, ITEM, COMPAREFUNC) (\ 152 | ((X)->tmp.vt = (void*)&(ITEM)), \ 153 | tglist_insert_sorted_impl(X, (X)->tmp.vt, tglist_itemsize(X), COMPAREFUNC)) 154 | 155 | #ifndef MAX 156 | #define MAX(x, y) ((x) > (y) ? (x) : (y)) 157 | #endif 158 | 159 | static int tglist_grow_if_needed(void* lst, size_t itemsize) { 160 | tglist_proto *l = lst; 161 | void* temp; 162 | if(l->count == l->capa) { 163 | size_t newsz = l->capa == 0 ? 4 : l->capa*2; 164 | temp = realloc(l->items, newsz * itemsize); 165 | if(!temp) return 0; 166 | l->capa = newsz; 167 | l->items = temp; 168 | } 169 | return 1; 170 | } 171 | 172 | static int tglist_memmove_impl(void *lst, size_t pos1, int pos2diff, size_t itemsz) { 173 | tglist_proto *l = lst; 174 | char* dst = tglist_ptr_from_index_impl(l, pos1, itemsz); 175 | const char* src = dst + (itemsz*pos2diff); 176 | return !!memmove(dst, src, (tglist_getsize(l) - (pos1 + pos2diff))*itemsz); 177 | } 178 | 179 | static size_t tglist_sorted_insert_pos_impl(void* lst, void* o, size_t itemsz, int (*compar)(const void *, const void *)) { 180 | tglist_proto *l = lst; 181 | size_t hi, lo; 182 | lo = tglist_getsize(l); 183 | if(!lo) return 0; 184 | lo--; 185 | hi = 0; 186 | while(1) { 187 | size_t c = hi + ((lo - hi) / 2); 188 | void *p = tglist_ptr_from_index_impl(l, c, itemsz); 189 | int r = compar(o, p); 190 | if(hi == lo) { 191 | if(r > 0) lo++; 192 | return lo; 193 | } 194 | if(r < 0) lo = c ? c-1 : 0; 195 | else if(r > 0) hi = c+1; 196 | else hi = lo = c; 197 | if(hi > lo) hi = lo; 198 | } 199 | } 200 | 201 | static size_t tglist_insert_sorted_impl(void *lst, void *ptr_to_item, size_t itemsz, int (*compar)(const void *, const void *)) { 202 | size_t idx = tglist_sorted_insert_pos_impl(lst, ptr_to_item, itemsz, compar); 203 | if(idx == (size_t) -1) return idx; 204 | if(tglist_insert_memcpy((tglist_proto*) lst, ptr_to_item, idx, itemsz)) return idx; 205 | return (size_t) -1; 206 | } 207 | 208 | #ifdef __cplusplus 209 | } 210 | #endif 211 | 212 | #endif 213 | -------------------------------------------------------------------------------- /ags_cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef AGSCPU_H 2 | #define AGSCPU_H 3 | 4 | // virtual CPU commands 5 | #define SCMD_ADD 1 // reg1 += arg2 6 | #define SCMD_SUB 2 // reg1 -= arg2 7 | #define SCMD_REGTOREG 3 // reg2 = reg1 8 | #define SCMD_WRITELIT 4 // m[MAR] = arg2 (copy arg1 bytes) 9 | #define SCMD_RET 5 // return from subroutine 10 | #define SCMD_LITTOREG 6 // set reg1 to literal value arg2 11 | #define SCMD_MEMREAD 7 // reg1 = m[MAR] 12 | #define SCMD_MEMWRITE 8 // m[MAR] = reg1 13 | #define SCMD_MULREG 9 // reg1 *= reg2 14 | #define SCMD_DIVREG 10 // reg1 /= reg2 15 | #define SCMD_ADDREG 11 // reg1 += reg2 16 | #define SCMD_SUBREG 12 // reg1 -= reg2 17 | #define SCMD_BITAND 13 // bitwise reg1 & reg2 18 | #define SCMD_BITOR 14 // bitwise reg1 | reg2 19 | #define SCMD_ISEQUAL 15 // reg1 == reg2 reg1=1 if true, =0 if not 20 | #define SCMD_NOTEQUAL 16 // reg1 != reg2 21 | #define SCMD_GREATER 17 // reg1 > reg2 22 | #define SCMD_LESSTHAN 18 // reg1 < reg2 23 | #define SCMD_GTE 19 // reg1 >= reg2 24 | #define SCMD_LTE 20 // reg1 <= reg2 25 | #define SCMD_AND 21 // (reg1!=0) && (reg2!=0) -> reg1 26 | #define SCMD_OR 22 // (reg1!=0) || (reg2!=0) -> reg1 27 | #define SCMD_CALL 23 // jump to subroutine at reg1 28 | #define SCMD_MEMREADB 24 // reg1 = m[MAR] (1 byte) 29 | #define SCMD_MEMREADW 25 // reg1 = m[MAR] (2 bytes) 30 | #define SCMD_MEMWRITEB 26 // m[MAR] = reg1 (1 byte) 31 | #define SCMD_MEMWRITEW 27 // m[MAR] = reg1 (2 bytes) 32 | #define SCMD_JZ 28 // jump if ax==0 to arg1 33 | #define SCMD_PUSHREG 29 // m[sp]=reg1; sp++ 34 | #define SCMD_POPREG 30 // sp--; reg1=m[sp] 35 | #define SCMD_JMP 31 // jump to arg1 36 | #define SCMD_MUL 32 // reg1 *= arg2 37 | #define SCMD_CALLEXT 33 // call external (imported) function reg1 38 | #define SCMD_PUSHREAL 34 // push reg1 onto real stack 39 | #define SCMD_SUBREALSTACK 35 40 | #define SCMD_LINENUM 36 // debug info - source code line number 41 | #define SCMD_CALLAS 37 // call external script function. this instruction is never emitted to bytecodes in files. it's only used via "live-patching" to change CALLEXT insns if the script to call is in a different instance. 42 | #define SCMD_THISBASE 38 // current relative address 43 | #define SCMD_NUMFUNCARGS 39 // number of arguments for ext func call 44 | #define SCMD_MODREG 40 // reg1 %= reg2 45 | #define SCMD_XORREG 41 // reg1 ^= reg2 46 | #define SCMD_NOTREG 42 // reg1 = !reg1 47 | #define SCMD_SHIFTLEFT 43 // reg1 = reg1 << reg2 48 | #define SCMD_SHIFTRIGHT 44 // reg1 = reg1 >> reg2 49 | #define SCMD_CALLOBJ 45 // op = reg1 (set "this" argument for next function call) 50 | #define SCMD_CHECKBOUNDS 46 // check reg1 is between 0 and arg2 51 | #define SCMD_MEMWRITEPTR 47 // m[MAR] = reg1 (adjust ptr addr) 52 | #define SCMD_MEMREADPTR 48 // reg1 = m[MAR] (adjust ptr addr) 53 | #define SCMD_MEMZEROPTR 49 // m[MAR] = 0 (blank ptr) 54 | #define SCMD_MEMINITPTR 50 // m[MAR] = reg1 (like memwrite4, but doesn't remove reference/free the old pointer) 55 | #define SCMD_LOADSPOFFS 51 // MAR = SP - arg1 (optimization for local var access) 56 | #define SCMD_CHECKNULL 52 // error if MAR==0 57 | #define SCMD_FADD 53 // reg1 += arg2 (float,int) 58 | #define SCMD_FSUB 54 // reg1 -= arg2 (float,int) 59 | #define SCMD_FMULREG 55 // reg1 *= reg2 (float) 60 | #define SCMD_FDIVREG 56 // reg1 /= reg2 (float) 61 | #define SCMD_FADDREG 57 // reg1 += reg2 (float) 62 | #define SCMD_FSUBREG 58 // reg1 -= reg2 (float) 63 | #define SCMD_FGREATER 59 // reg1 > reg2 (float) 64 | #define SCMD_FLESSTHAN 60 // reg1 < reg2 (float) 65 | #define SCMD_FGTE 61 // reg1 >= reg2 (float) 66 | #define SCMD_FLTE 62 // reg1 <= reg2 (float) 67 | #define SCMD_ZEROMEMORY 63 // m[MAR]..m[MAR+(arg1-1)] = 0 68 | #define SCMD_CREATESTRING 64 // reg1 = new String(reg1) 69 | #define SCMD_STRINGSEQUAL 65 // (char*)reg1 == (char*)reg2 reg1=1 if true, =0 if not 70 | #define SCMD_STRINGSNOTEQ 66 // (char*)reg1 != (char*)reg2 71 | #define SCMD_CHECKNULLREG 67 // error if reg1 == NULL 72 | #define SCMD_LOOPCHECKOFF 68 // no loop checking for this function 73 | #define SCMD_MEMZEROPTRND 69 // m[MAR] = 0 (blank ptr, no dispose if = ax) 74 | #define SCMD_JNZ 70 // jump to arg1 if ax!=0 75 | #define SCMD_DYNAMICBOUNDS 71 // check reg1 is between 0 and m[MAR-4] 76 | #define SCMD_NEWARRAY 72 // reg1 = new array of reg1 elements, each of size arg2 (arg3=managed type?) 77 | #define SCMD_NEWUSEROBJECT 73 // reg1 = new user object of arg1 size 78 | 79 | #define SCMD_MAX 74 80 | 81 | struct opcode_info { 82 | const char* mnemonic; 83 | const unsigned char argcount; 84 | const unsigned char regcount; 85 | }; 86 | 87 | /* these are called e.g. SREG_OP in upstream */ 88 | enum ags_reg { 89 | AR_NULL = 0, 90 | AR_SP, /* stack ptr */ 91 | AR_MAR, /* memory address register, i.e. holding pointer for mem* funcs */ 92 | AR_AX, /* 4 GPRs */ 93 | AR_BX, 94 | AR_CX, 95 | AR_OP, /* object pointer for member func calls, i.e. "this". 96 | ags engine only sets it via SCMD_CALLOBJ, otherwise, it is only ever pushed/popped */ 97 | AR_DX, 98 | AR_MAX 99 | }; 100 | 101 | extern const struct opcode_info opcodes[SCMD_MAX]; 102 | extern const char *regnames[AR_MAX]; 103 | 104 | #ifdef AGS_CPU_IMPL 105 | 106 | const struct opcode_info opcodes[SCMD_MAX] = { 107 | [0] = {"NULL", 0, 0}, 108 | [SCMD_ADD] = {"addi", 2, 1}, 109 | [SCMD_SUB] = {"subi", 2, 1}, 110 | [SCMD_REGTOREG] = {"mr", 2, 2}, 111 | [SCMD_WRITELIT] = {"memcpy", 2, 0}, 112 | [SCMD_RET] = {"ret", 0, 0}, 113 | [SCMD_LITTOREG] = {"li", 2, 1}, 114 | [SCMD_MEMREAD] = {"memread4", 1, 1}, 115 | [SCMD_MEMWRITE] = {"memwrite4", 1, 1}, 116 | [SCMD_MULREG] = {"mul", 2, 2}, 117 | [SCMD_DIVREG] = {"div", 2, 2}, 118 | [SCMD_ADDREG] = {"add", 2, 2}, 119 | [SCMD_SUBREG] = {"sub", 2, 2}, 120 | [SCMD_BITAND] = {"and", 2, 2}, 121 | [SCMD_BITOR] = {"or", 2, 2}, 122 | [SCMD_ISEQUAL] = {"cmpeq", 2, 2}, 123 | [SCMD_NOTEQUAL] = {"cmpne", 2, 2}, 124 | [SCMD_GREATER] = {"gt", 2, 2}, 125 | [SCMD_LESSTHAN] = {"lt", 2, 2}, 126 | [SCMD_GTE] = {"gte", 2, 2}, 127 | [SCMD_LTE] = {"lte", 2, 2}, 128 | [SCMD_AND] = {"land", 2, 2}, /*logical*/ 129 | [SCMD_OR] = {"lor", 2, 2}, 130 | [SCMD_CALL] = {"call", 1, 1}, 131 | [SCMD_MEMREADB] = {"memread1", 1, 1}, 132 | [SCMD_MEMREADW] = {"memread2", 1, 1}, 133 | [SCMD_MEMWRITEB] = {"memwrite1", 1, 1}, 134 | [SCMD_MEMWRITEW] = {"memwrite2", 1, 1}, 135 | [SCMD_JZ] = {"jzi", 1, 0}, 136 | [SCMD_PUSHREG] = {"push", 1, 1}, 137 | [SCMD_POPREG] = {"pop", 1, 1}, 138 | [SCMD_JMP] = {"jmpi", 1, 0}, 139 | [SCMD_MUL] = {"muli", 2, 1}, 140 | [SCMD_CALLEXT] = {"farcall", 1, 1}, 141 | [SCMD_PUSHREAL] = {"farpush", 1, 1}, 142 | [SCMD_SUBREALSTACK] = {"farsubsp", 1, 0}, 143 | [SCMD_LINENUM] = {"sourceline", 1, 0}, 144 | [SCMD_CALLAS] = {"callscr", 1, 1}, 145 | [SCMD_THISBASE] = {"thisaddr", 1, 0}, 146 | [SCMD_NUMFUNCARGS] = {"setfuncargs", 1, 0}, 147 | [SCMD_MODREG] = {"mod", 2, 2}, 148 | [SCMD_XORREG] = {"xor", 2, 2}, 149 | [SCMD_NOTREG] = {"not", 1, 1}, 150 | [SCMD_SHIFTLEFT] = {"shl", 2, 2}, 151 | [SCMD_SHIFTRIGHT] = {"shr", 2, 2}, 152 | [SCMD_CALLOBJ] = {"callobj", 1, 1}, 153 | [SCMD_CHECKBOUNDS] = {"assertlte", 2, 1}, 154 | [SCMD_MEMWRITEPTR] = {"ptrset", 1, 1}, 155 | [SCMD_MEMREADPTR] = {"ptrget", 1, 1}, 156 | [SCMD_MEMZEROPTR] = {"ptrzero", 0, 0}, 157 | [SCMD_MEMINITPTR] = {"ptrinit", 1, 1}, 158 | [SCMD_LOADSPOFFS] = {"ptrstack", 1, 0}, 159 | [SCMD_CHECKNULL] = {"ptrassert", 0, 0}, 160 | [SCMD_FADD] = {"faddi", 2, 1}, 161 | [SCMD_FSUB] = {"fsubi", 2, 1}, 162 | [SCMD_FMULREG] = {"fmul", 2, 2}, 163 | [SCMD_FDIVREG] = {"fdiv", 2, 2}, 164 | [SCMD_FADDREG] = {"fadd", 2, 2}, 165 | [SCMD_FSUBREG] = {"fsub", 2, 2}, 166 | [SCMD_FGREATER] = {"fgt", 2, 2}, 167 | [SCMD_FLESSTHAN] = {"flt", 2, 2}, 168 | [SCMD_FGTE] = {"fgte", 2, 2}, 169 | [SCMD_FLTE] = {"flte", 2, 2}, 170 | [SCMD_ZEROMEMORY] = {"zeromem", 1, 0}, 171 | [SCMD_CREATESTRING] = {"newstr", 1, 1}, 172 | [SCMD_STRINGSEQUAL] = {"streq", 2, 2}, 173 | [SCMD_STRINGSNOTEQ] = {"strne", 2, 2}, 174 | [SCMD_CHECKNULLREG] = {"assert", 1, 1}, 175 | [SCMD_LOOPCHECKOFF] = {"loopcheckoff", 0, 0}, 176 | [SCMD_MEMZEROPTRND] = {"ptrzerond", 0, 0}, 177 | [SCMD_JNZ] = {"jnzi", 1, 0}, 178 | [SCMD_DYNAMICBOUNDS] = {"dynamicbounds", 1, 1}, 179 | [SCMD_NEWARRAY] = {"newarr", 3, 1}, 180 | [SCMD_NEWUSEROBJECT] = {"newuserobject", 2, 1}, 181 | }; 182 | 183 | const char *regnames[AR_MAX] = { 184 | [AR_NULL] = "null", 185 | [AR_SP] = "sp", 186 | [AR_MAR] = "mar", 187 | [AR_AX] = "ax", 188 | [AR_BX] = "bx", 189 | [AR_CX] = "cx", 190 | [AR_OP] = "op", 191 | [AR_DX] = "dx", 192 | }; 193 | #endif 194 | 195 | #endif 196 | -------------------------------------------------------------------------------- /endianness.h: -------------------------------------------------------------------------------- 1 | #ifndef ENDIANNESS_H 2 | #define ENDIANNESS_H 3 | 4 | /* Public domain implementation for endianness detection and byte ordering on 5 | several platforms. In case the concept of public domain does not exist 6 | under your jurisdiction, you can consider it to be dual licensed 7 | under the MIT, Apache and WTFPL licenses. 8 | 9 | Grab it and drop it into your project, include it and use 10 | the following macros to determine endianness: 11 | 12 | ENDIANNESS_LE, ENDIANNESS_BE 13 | 14 | e.g. #if ENDIANNESS_LE ... 15 | 16 | or, even nicer without littering your code with #ifdefs: 17 | 18 | if (ENDIANNESS_BE) { big_endian_code(); } else { little_endian_code(); } 19 | 20 | ... since the compiler can optimize away unused branches, this makes your 21 | code easier to read while not loosing any of the advantage of using 22 | conditional compilation, plus you get a free compile-time check of the 23 | unused code path (rarely used conditonally compiled code paths often get 24 | defunct over time if nobody checks them all the time). 25 | 26 | To debug this header yourself, you can define ENDIANNESS_DEBUG to see 27 | warnings from where we take the defs for the specific target. 28 | 29 | If you need only the conversion functions from big to little endian 30 | and vice versa, you may want to #define ENDIANNESS_PORTABLE_CONVERSION 31 | prior to including this header. That way, when the endiannes can't be 32 | determined at compile time, the code will fallback to a slower, 33 | but portable version of those functions. 34 | However, if using it, it's not guaranteed that ENDIANNESS_LE/BE 35 | will be defined. 36 | Most people however need only the conversion functions in their code, 37 | so if you stick to them you can safely turn the portable conversion on. 38 | */ 39 | 40 | /* This should catch all modern GCCs and Clang */ 41 | #if (defined __BYTE_ORDER__) && (defined __ORDER_LITTLE_ENDIAN__) 42 | # ifdef ENDIANNESS_DEBUG 43 | # warning "Taking endiannes from built-in __BYTE_ORDER__" 44 | # endif 45 | # if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 46 | # define ENDIANNESS_LE 1 47 | # define ENDIANNESS_BE 0 48 | # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 49 | # define ENDIANNESS_LE 0 50 | # define ENDIANNESS_BE 1 51 | # endif 52 | /* Try to derive from arch/compiler-specific macros */ 53 | #elif defined(_X86_) || defined(__x86_64__) || defined(__i386__) || \ 54 | defined(__i486__) || defined(__i586__) || defined(__i686__) || \ 55 | defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) || \ 56 | defined(__ARMEL__) || \ 57 | (defined(__LITTLE_ENDIAN__) && __LITTLE_ENDIAN__ == 1) || \ 58 | (defined(_LITTLE_ENDIAN) && _LITTLE_ENDIAN == 1) || \ 59 | defined(_M_IX86) || defined(_M_AMD64) /* MSVC */ 60 | # ifdef ENDIANNESS_DEBUG 61 | # warning "Detected Little Endian target CPU" 62 | # endif 63 | # define ENDIANNESS_LE 1 64 | # define ENDIANNESS_BE 0 65 | #elif defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) || \ 66 | defined(__MICROBLAZEEB__) || defined(__ARMEB__) || \ 67 | (defined(__BIG_ENDIAN__) && __BIG_ENDIAN__ == 1) || \ 68 | (defined(_BIG_ENDIAN) && _BIG_ENDIAN == 1) 69 | # ifdef ENDIANNESS_DEBUG 70 | # warning "Detected Big Endian target CPU" 71 | # endif 72 | # define ENDIANNESS_LE 0 73 | # define ENDIANNESS_BE 1 74 | /* Try to get it from a header */ 75 | #else 76 | # if defined(__linux) 77 | # ifdef ENDIANNESS_DEBUG 78 | # warning "Taking endiannes from endian.h" 79 | # endif 80 | # include 81 | # else 82 | # ifdef ENDIANNESS_DEBUG 83 | # warning "Taking endiannes from machine/endian.h" 84 | # endif 85 | # include 86 | # endif 87 | #endif 88 | 89 | #ifndef ENDIANNESS_LE 90 | # undef ENDIANNESS_BE 91 | # if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) 92 | # if __BYTE_ORDER == __LITTLE_ENDIAN 93 | # define ENDIANNESS_LE 1 94 | # define ENDIANNESS_BE 0 95 | # elif __BYTE_ORDER == __BIG_ENDIAN 96 | # define ENDIANNESS_LE 0 97 | # define ENDIANNESS_BE 1 98 | # endif 99 | # elif defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) 100 | # if BYTE_ORDER == LITTLE_ENDIAN 101 | # define ENDIANNESS_LE 1 102 | # define ENDIANNESS_BE 0 103 | # elif BYTE_ORDER == BIG_ENDIAN 104 | # define ENDIANNESS_LE 0 105 | # define ENDIANNESS_BE 1 106 | # endif 107 | # endif 108 | #endif 109 | 110 | /* In case the user passed one of -DENDIANNESS_LE or BE in CPPFLAS, 111 | set the second one too */ 112 | #if defined(ENDIANNESS_LE) && !(defined(ENDIANNESS_BE)) 113 | # if ENDIANNESS_LE == 0 114 | # define ENDIANNESS_BE 1 115 | # else 116 | # define ENDIANNESS_BE 0 117 | # endif 118 | #elif defined(ENDIANNESS_BE) && !(defined(ENDIANNESS_LE)) 119 | # if ENDIANNESS_BE == 0 120 | # define ENDIANNESS_LE 1 121 | # else 122 | # define ENDIANNESS_LE 0 123 | # endif 124 | #endif 125 | 126 | #if !(defined(ENDIANNESS_LE)) && !(defined(ENDIANNESS_PORTABLE_CONVERSION)) 127 | # error "Sorry, we couldn't detect endiannes for your system! Please set -DENDIANNESS_LE=1 or 0 using your CPPFLAGS/CFLAGS and open an issue for your system on https://github.com/rofl0r/endianness.h - Thanks!" 128 | #endif 129 | 130 | #ifdef ENDIANNESS_DEBUG 131 | # if ENDIANNESS_LE == 1 132 | # warning "Detected Little Endian target CPU" 133 | # endif 134 | # if ENDIANNESS_BE == 1 135 | # warning "Detected BIG Endian target CPU" 136 | # endif 137 | #endif 138 | 139 | #include 140 | #include 141 | 142 | static __inline uint16_t end_bswap16(uint16_t __x) 143 | { 144 | return (__x<<8) | (__x>>8); 145 | } 146 | 147 | static __inline uint32_t end_bswap32(uint32_t __x) 148 | { 149 | return (__x>>24) | (__x>>8&0xff00) | (__x<<8&0xff0000) | (__x<<24); 150 | } 151 | 152 | static __inline uint64_t end_bswap64(uint64_t __x) 153 | { 154 | return ((end_bswap32(__x)+0ULL)<<32) | (end_bswap32(__x>>32)); 155 | } 156 | 157 | static __inline uint16_t end_net2host16(uint16_t net_number) 158 | { 159 | uint16_t result = 0; 160 | int i; 161 | for (i = 0; i < (int)sizeof(result); i++) { 162 | result <<= CHAR_BIT; 163 | result += (((unsigned char *)&net_number)[i] & UCHAR_MAX); 164 | } 165 | return result; 166 | } 167 | 168 | static __inline uint16_t end_host2net16(uint16_t native_number) 169 | { 170 | uint16_t result = 0; 171 | int i; 172 | for (i = (int)sizeof(result) - 1; i >= 0; i--) { 173 | ((unsigned char *)&result)[i] = native_number & UCHAR_MAX; 174 | native_number >>= CHAR_BIT; 175 | } 176 | return result; 177 | } 178 | 179 | static __inline uint32_t end_net2host32(uint32_t net_number) 180 | { 181 | uint32_t result = 0; 182 | int i; 183 | for (i = 0; i < (int)sizeof(result); i++) { 184 | result <<= CHAR_BIT; 185 | result += (((unsigned char *)&net_number)[i] & UCHAR_MAX); 186 | } 187 | return result; 188 | } 189 | 190 | static __inline uint32_t end_host2net32(uint32_t native_number) 191 | { 192 | uint32_t result = 0; 193 | int i; 194 | for (i = (int)sizeof(result) - 1; i >= 0; i--) { 195 | ((unsigned char *)&result)[i] = native_number & UCHAR_MAX; 196 | native_number >>= CHAR_BIT; 197 | } 198 | return result; 199 | } 200 | 201 | static __inline uint64_t end_net2host64(uint64_t net_number) 202 | { 203 | uint64_t result = 0; 204 | int i; 205 | for (i = 0; i < (int)sizeof(result); i++) { 206 | result <<= CHAR_BIT; 207 | result += (((unsigned char *)&net_number)[i] & UCHAR_MAX); 208 | } 209 | return result; 210 | } 211 | 212 | static __inline uint64_t end_host2net64(uint64_t native_number) 213 | { 214 | uint64_t result = 0; 215 | int i; 216 | for (i = (int)sizeof(result) - 1; i >= 0; i--) { 217 | ((unsigned char *)&result)[i] = native_number & UCHAR_MAX; 218 | native_number >>= CHAR_BIT; 219 | } 220 | return result; 221 | } 222 | 223 | #if ENDIANNESS_LE+0 == 1 224 | # define end_htobe16(x) end_bswap16(x) 225 | # define end_be16toh(x) end_bswap16(x) 226 | # define end_htobe32(x) end_bswap32(x) 227 | # define end_be32toh(x) end_bswap32(x) 228 | # define end_htobe64(x) end_bswap64(x) 229 | # define end_be64toh(x) end_bswap64(x) 230 | # define end_htole16(x) (uint16_t)(x) 231 | # define end_le16toh(x) (uint16_t)(x) 232 | # define end_htole32(x) (uint32_t)(x) 233 | # define end_le32toh(x) (uint32_t)(x) 234 | # define end_htole64(x) (uint64_t)(x) 235 | # define end_le64toh(x) (uint64_t)(x) 236 | #elif ENDIANNESS_BE+0 == 1 237 | # define end_htobe16(x) (uint16_t)(x) 238 | # define end_be16toh(x) (uint16_t)(x) 239 | # define end_htobe32(x) (uint32_t)(x) 240 | # define end_be32toh(x) (uint32_t)(x) 241 | # define end_htobe64(x) (uint64_t)(x) 242 | # define end_be64toh(x) (uint64_t)(x) 243 | # define end_htole16(x) end_bswap16(x) 244 | # define end_le16toh(x) end_bswap16(x) 245 | # define end_htole32(x) end_bswap32(x) 246 | # define end_le32toh(x) end_bswap32(x) 247 | # define end_htole64(x) end_bswap64(x) 248 | # define end_le64toh(x) end_bswap64(x) 249 | #else 250 | /* Resort to slower, but neutral code */ 251 | # define end_htobe16(x) end_host2net16(x) 252 | # define end_be16toh(x) end_net2host16(x) 253 | # define end_htobe32(x) end_host2net32(x) 254 | # define end_be32toh(x) end_net2host32(x) 255 | # define end_htobe64(x) end_host2net64(x) 256 | # define end_be64toh(x) end_net2host64(x) 257 | # define end_htole16(x) end_bswap_16(end_host2net16(x)) 258 | # define end_le16toh(x) end_bswap_16(end_host2net16(x)) 259 | # define end_htole32(x) end_bswap_32(end_host2net32(x)) 260 | # define end_le32toh(x) end_bswap_32(end_host2net32(x)) 261 | # define end_htole64(x) end_bswap_64(end_host2net64(x)) 262 | # define end_le64toh(x) end_bswap_64(end_host2net64(x)) 263 | #endif 264 | 265 | #define end_ntoh16(x) end_be16toh(x) 266 | #define end_hton16(x) end_htobe16(x) 267 | #define end_ntoh32(x) end_be32toh(x) 268 | #define end_hton32(x) end_htobe32(x) 269 | #define end_ntoh64(x) end_be64toh(x) 270 | #define end_hton64(x) end_htobe64(x) 271 | 272 | #endif 273 | -------------------------------------------------------------------------------- /agscriptxtract.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include "DataFile.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "version.h" 8 | 9 | #ifdef _WIN32 10 | #include 11 | #define MKDIR(D) mkdir(D) 12 | #else 13 | #define MKDIR(D) mkdir(D, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) 14 | #endif 15 | 16 | #define ADS ":::AGStract " VERSION " by rofl0r:::" 17 | 18 | static int usage(char *argv0) { 19 | fprintf(stderr, ADS "\nusage:\n%s [-oblf] dir [outdir]\n" 20 | "extract all scripts from game files in dir\n" 21 | "pass a directory with extracted game files.\n" 22 | "options:\n" 23 | "-o : dump offset comments in disassembly\n" 24 | "-b : dump hexadecimal bytecode comments in disassembly\n" 25 | "-f : dump informative original fixups section\n" 26 | "-l : remove linenumber debug assembly directives [produces smaller files]\n" 27 | "-v : enable verbose warning output\n" 28 | , argv0); 29 | return 1; 30 | } 31 | 32 | static float gamefileversion2engine(int gamever) { 33 | static const float oldversions[] = { 34 | [5] = 2.00, [6] = 2.01, [7] = 2.03, [9] = 2.07, 35 | [11] = 2.20, [12] = 2.30, [18] = 2.50, [19] = 2.51, 36 | [20] = 2.53, [21] = 2.54, [22] = 2.55, [24] = 2.56, 37 | [25] = 2.60, [26] = 2.61, [27] = 2.62, [31] = 2.70, 38 | [32] = 2.72, [35] = 3.00, [36] = 3.01, [37] = 3.10, 39 | [39] = 3.11, [40] = 3.12, [41] = 3.20, [42] = 3.21, 40 | [43] = 3.30, [44] = 3.31, [45] = 3.40, [46] = 3.401, 41 | [47] = 3.402, [48] = 3.41, [49] = 3.411, [50] = 3.50, 42 | }; 43 | if(gamever <= 50) return oldversions[gamever]; 44 | return gamever / 1000000 + ((double)gamever / (double)100000.f) - gamever / 100000; 45 | } 46 | 47 | static void disas(const char*inp, char *o, int flags) { 48 | //ARF_find_code_start 49 | AF f_b, *f = &f_b; 50 | ASI sc; 51 | if(AF_open(f, o)) { 52 | char s[256]; 53 | size_t l = strlen(o); 54 | memcpy(s, o, l + 1); 55 | s[l-1] = 's'; 56 | ASI *i = ASI_read_script(f, &sc) ? &sc : 0; 57 | fprintf(stdout, "disassembling [%s] %s -> %s", inp, o, s); 58 | if(!i || !ASI_disassemble(f, i, s, flags)) fprintf(stdout, " FAIL"); 59 | fprintf(stdout, "\n"); 60 | AF_close(f); 61 | } 62 | } 63 | 64 | static char *filename(const char *dir, const char *fn, char *buf, size_t bsize) { 65 | snprintf(buf, bsize, "%s/%s", dir, fn); 66 | return buf; 67 | } 68 | 69 | #include "RoomFile.h" 70 | #include 71 | 72 | static int is_roomfile(const char *fn) { 73 | const char *p = strrchr(fn, '.'); 74 | if(!p) return 0; 75 | ++p; 76 | #define LOWER(X) tolower((unsigned) (X)) 77 | return LOWER(p[0]) == 'c' && LOWER(p[1]) == 'r' && LOWER(p[2]) == 'm' && p[3] == 0; 78 | #undef LOWER 79 | } 80 | 81 | static int dumprooms(const char* dir, const char* out, int flags) { 82 | DIR* d = opendir(dir); 83 | if(!d) return 1; 84 | int errors = 0; 85 | struct dirent* di = 0; 86 | 87 | while((di = readdir(d))) { 88 | size_t l = strlen(di->d_name); 89 | if(l > 4 && is_roomfile(di->d_name)) { 90 | char fnbuf[512]; 91 | snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dir, di->d_name); 92 | AF f; ssize_t off; ASI s; 93 | if(!AF_open(&f, fnbuf)) goto extract_error; 94 | struct RoomFile rinfo = {0}; 95 | if(!RoomFile_read(&f, &rinfo)) goto extract_error; 96 | if((off = ARF_find_code_start(&f, 0)) == -1) goto extract_error; 97 | assert(off == rinfo.blockpos[BLOCKTYPE_COMPSCRIPT3]); 98 | AF_set_pos(&f, off); 99 | if(!ASI_read_script(&f, &s)) { 100 | fprintf(stderr, "trouble finding script in %s\n", di->d_name); 101 | continue; 102 | } 103 | char buf[256]; 104 | assert(l < sizeof(buf)); 105 | memcpy(buf, di->d_name, l - 4); 106 | buf[l-4] = '.'; 107 | buf[l-3] = 'o'; 108 | buf[l-2] = 0; 109 | char outbuf[256]; 110 | AF_dump_chunk(&f, s.start, s.len, filename(out, buf, outbuf, sizeof outbuf)); 111 | disas(di->d_name, outbuf, flags); 112 | size_t sourcelen; 113 | char *source = RoomFile_extract_source(&f, &rinfo, &sourcelen); 114 | if(source) { 115 | buf[l-3] = 'a'; 116 | buf[l-2] = 's'; 117 | buf[l-1] = 'c'; 118 | buf[l] = 0; 119 | FILE *f = fopen(filename(out, buf, outbuf, sizeof outbuf), "wb"); 120 | if(f) { 121 | fprintf(stdout, "extracting room source %s -> %s\n", di->d_name, outbuf); 122 | fwrite(source, 1, sourcelen, f); 123 | fclose(f); 124 | } 125 | free(source); 126 | } 127 | continue; 128 | extract_error: 129 | fprintf(stderr, "warning: extraction of file %s failed\n", di->d_name); 130 | ++errors; 131 | } 132 | } 133 | closedir(d); 134 | return errors; 135 | } 136 | 137 | void dump_script(AF* f, ASI* s, char* fn, int flags) { 138 | if(!s->len) return; 139 | AF_dump_chunk(f, s->start, s->len, fn); 140 | disas("game28.dta", fn, flags); 141 | } 142 | 143 | static char *pfx_capitalize(char *in, char prefix, char *out) { 144 | char *p = out, *n = in; 145 | *(p++) = prefix; 146 | *(p++) = toupper(*(n++)); 147 | while(*n) *(p++) = tolower(*(n++)); 148 | *p = 0; 149 | return out; 150 | } 151 | 152 | void dump_header(ADF *a, char *fn) { 153 | unsigned i; 154 | fprintf(stdout, "regenerating script header %s\n", fn); 155 | FILE *f = fopen(fn, "w"); 156 | fprintf(f, "#if SCRIPT_API < 300000 && SCRIPT_API > 262000\n"); 157 | if(ADF_get_cursorcount(a)) fprintf(f, "enum CursorMode {\n"); 158 | for(i=0; i 0) fprintf(f, ",\n"); 160 | char buf[16] = {0}, *p = ADF_get_cursorname(a, i), *q = buf; 161 | while(*p) { 162 | if(!isspace(*p)) *(q++) = *p; 163 | ++p; 164 | } 165 | fprintf(f, " eMode%s = %u", buf, i); 166 | } 167 | if(i) fprintf(f, "};\n"); 168 | fprintf(f, "import Character character[%u];\n", ADF_get_charactercount(a)); 169 | for(i=0; iinventorynames) for(i=1; iold_dialogscripts) return; 200 | size_t i, n =a->game.dialogcount; 201 | for(i=0; iold_dialogscripts[i]); 211 | fclose(f); 212 | } 213 | } 214 | 215 | int main(int argc, char**argv) { 216 | int flags = 0, c; 217 | while ((c = getopt(argc, argv, "oblfv")) != EOF) switch(c) { 218 | case 'o': flags |= DISAS_DEBUG_OFFSETS; break; 219 | case 'b': flags |= DISAS_DEBUG_BYTECODE; break; 220 | case 'l': flags |= DISAS_SKIP_LINENO; break; 221 | case 'f': flags |= DISAS_DEBUG_FIXUPS; break; 222 | case 'v': flags |= DISAS_VERBOSE; break; 223 | default: return usage(argv[0]); 224 | } 225 | if(!argv[optind]) return usage(argv[0]); 226 | char *dir = argv[optind]; 227 | char *out = argv[optind+1]; 228 | if(!out) out = "."; 229 | else MKDIR(out); 230 | 231 | int errors = 0; 232 | ADF a_b, *a = &a_b; 233 | char fnbuf[512]; 234 | enum ADF_open_error aoe; 235 | if(!ADF_find_datafile(dir, fnbuf, sizeof(fnbuf))) { 236 | fprintf(stderr, "failed to find datafile\n"); 237 | return 1; 238 | } 239 | aoe = ADF_open(a, fnbuf); 240 | if(aoe != AOE_success && aoe <= AOE_script) { 241 | fprintf(stderr, "failed to open/process data file: %s\n", AOE2str(aoe)); 242 | return 1; 243 | } else if (aoe != AOE_success) { 244 | fprintf(stderr, "warning: failed to process some non-essential parts (%s) of gamefile, probably from a newer game format\n", AOE2str(aoe)); 245 | } 246 | fprintf(stdout, "info: ags engine version code %d (%.3f)\n", 247 | a->version, gamefileversion2engine(a->version)); 248 | ASI* s; 249 | s = ADF_get_global_script(a); 250 | char buf[256]; 251 | dump_script(a->f, s, filename(out, "globalscript.o", buf, sizeof buf), flags); 252 | s = ADF_get_dialog_script(a); 253 | dump_script(a->f, s, filename(out, "dialogscript.o", buf, sizeof buf), flags); 254 | size_t i, l = ADF_get_scriptcount(a); 255 | for(i = 0; i < l; i++) { 256 | char fnbuf[32]; 257 | s = ADF_get_script(a, i); 258 | snprintf(fnbuf, sizeof(fnbuf), "gamescript%zu.o", i); 259 | dump_script(a->f, s, filename(out, fnbuf, buf, sizeof buf), flags); 260 | } 261 | 262 | if(aoe == AOE_success) { 263 | dump_header(a, filename(out, "builtinscriptheader.ash", buf, sizeof buf)); 264 | dump_old_dialogscripts(a, out); 265 | } else { 266 | fprintf(stderr, "skipping scriptheader and dialogscripts due to non-fatal errors\n"); 267 | } 268 | ADF_close(a); 269 | errors += dumprooms(dir, out, flags); 270 | if(errors) fprintf(stderr, "agscriptxtract: got %d errors\n", errors); 271 | 272 | return !!errors; 273 | } 274 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | agsutils by rofl0r 2 | ================== 3 | 4 | tools for (un)packing, disassembling, modifying and recompiling ags games. 5 | 6 | agsex: 7 | runs agstract and agscriptxtract, then creates a Makefile with rules 8 | to quickly and automatically repack a pack with changed asm files. 9 | this basically does everything for you automatically. 10 | can also use `make optimize` to run agsoptimize on all asm files. 11 | 12 | agstract: 13 | extracts the files a game "pack" (.exe) consists of. 14 | creates a file agspack.info which contains metadata about the 15 | files included in the pack. 16 | example: 17 | 18 | agstract 252test.exe FILES 19 | :::AGStract 0.9.1 by rofl0r::: 20 | 252test.exe: version 10, containing 6 files. 21 | ac2game.dta -> FILES/ac2game.dta 22 | acsprset.spr -> FILES/acsprset.spr 23 | agsfnt0.wfn -> FILES/agsfnt0.wfn 24 | agsfnt1.wfn -> FILES/agsfnt1.wfn 25 | agsfnt2.wfn -> FILES/agsfnt2.wfn 26 | room1.crm -> FILES/room1.crm 27 | 28 | agstract can also extract speech.vox and audio.vox files. 29 | make sure to use a different output directory, otherwise your 30 | agspack.info will be overwritten. 31 | 32 | in some games, speech.vox is insanely big because the speech files are 33 | saved in studio quality. 34 | 35 | i achieved good results by converting them to 16Khz via ffmpeg: 36 | 37 | for i in SPEECH/*.ogg ; do 38 | ffmpeg -i "$i" -c:a libvorbis -ar 16384 tmp.oga && mv tmp.oga "$i" 39 | done 40 | 41 | then repacking it with agspack, which results in about 6x space improvement. 42 | 43 | for audio.vox, you might want to use -b:a 80k instead of -ar 16384. 44 | this shrinks input files by about 400% with almost CD-like quality. 45 | 46 | agspack: 47 | takes the files created by agstract and makes a new bundle out of it. 48 | example: 49 | 50 | agspack FILES/ 252mytest.ags 51 | agspack -e FILES/ 252mytest.exe 52 | 53 | without arguments, agspack produces a game pack missing the original 54 | windows exe stub, but is compatible with the opensource AGS engine, 55 | as well as with scummvm ags port. 56 | that way, a game is about 500 KB smaller after extract and repack than it 57 | originally was. 58 | 59 | using the -e option, the original exestub is prepended to the game pack, 60 | and the offset to the start of the pack data encoded into the end signature, 61 | which reproduces the fully working windows executable with the new content. 62 | 63 | note that agspack always produces a version 20/30 packfile, compatible only 64 | with ags 3.0 or newer, so if your exe stub is from an earlier game, it 65 | will fail to recognize the attached game data. in such a case you can 66 | use the exestub from a newer version, e.g. 67 | https://github.com/ags-archives/engines/blob/master/ags302sp1/acwin.exe 68 | save it as agspack.exestub in your extract directory. 69 | 70 | agscriptxtract: 71 | detects and unpacks all binary scripts embedded in room and game files. 72 | the compiled scripts are stored with a .o extension, the disassembled files with .s. 73 | example: 74 | 75 | mkdir OBJ ; cd OBJ 76 | agscriptxtract ../FILES 77 | disassembling globalscript.o -> globalscript.s 78 | disassembling room1.o -> room1.s 79 | 80 | agsdisas: 81 | disassembles a single .o file to .s. useful to compare a re-assembled file with the original. 82 | example: 83 | 84 | agsdisas room1.o room1.s 85 | disassembling room1.o -> room1.s 86 | 87 | agssemble: 88 | creates a compiled object file from a .s assembly file. 89 | example: 90 | 91 | agssemble room1.s 92 | creates a new room1.o (you will notice filesize/date has changed) 93 | 94 | the compiled object file will miss unused strings and thus be smaller than the original. 95 | also imports and exports cover only really used ones. 96 | 97 | agsinject: 98 | once you have recompiled an .o file with agssemble, you can inject it into the original 99 | container file (either a .crm room file or a game file like "ac2game.dta") 100 | example: 101 | 102 | agsinject 0 OBJ/room1.o FILES/room1.crm 103 | 104 | injects room1.o at the first found script (0) in room1.crm. 105 | rooms have only 1 script so you must always use 0. 106 | for ac2game.dta kinda gamefiles, the index is i.e. 0 for the globalscript, 107 | 1 for the dialogscript (if one exists), otherwise 1 is the first gamescript, etc. 108 | 109 | after you injected your .o file, the next thing you want to do is agspack it all up again. 110 | then you can test your changes in the ags engine. 111 | 112 | agsprite: 113 | a tool to extract sprites from acsprset.spr files, and to create a new one 114 | from a directory full of extracted sprites. has several options to create 115 | smaller spritecache files than original, for example by converting all 116 | true-color images to high-color, which is almost impossible to differentiate 117 | visually. 118 | unfortunately ags saves a "uses alpha channel" flag for every sprite in a 119 | different file. 120 | if it was set, a picture converted from 32 to 16bit will become 121 | invisible, unless it is fixed with `agsalphahack` tool. 122 | after repacking a .spr file, a new sprindex.dat must be created and the old 123 | one replaced with it (if one existed). agsprite has an option for that too. 124 | optionally, the old sprindex.dat can simply be removed by commenting out the 125 | line with sprindex.dat in agspack.info and reducing the filecount by 1. 126 | at present, agsprite only creates and accepts TGA files. 127 | if you want to edit a sprite and your tool can't handle TGA format (unlikely), 128 | you can still convert the file into a format of your choice with e.g. 129 | imagemagick's `convert` tool, and then convert it back to TGA before creating 130 | a new sprite pack. 131 | run agsprite --help for usage information, and/or check the git log of 132 | agsprite.c to read a lot of detailed commit messages. 133 | 134 | agsalphahack: 135 | a tool to remove alphachannel flags from gamefiles. can delete either all 136 | or a single specified sprite number's alpha channel flag. this tool is a 137 | supplement to agsprite. 138 | 139 | agsalphainfo: 140 | a tool to print alphachannel information for each sprite. 141 | 142 | agssim: 143 | a simple simulator for ags assembly instructions. 144 | run agssim --help for more info. 145 | 146 | agsoptimize: 147 | a python script which is run on some .s asm files, detecting common inefficient 148 | patterns emitted by the AGS compiler, and replacing them with more efficient 149 | versions. using all preset optimization patterns, this improves speed of the 150 | CPU-heavy (because of a lots of scripts) game "Operation Forklift" by ~15-20%, 151 | which is almost identical to the number of asm statements it removes. 152 | another advantage is that the script files become smaller. 153 | 154 | ascc: 155 | the original AGS script compiler with some tweaks to support more C features. 156 | it's pretty handy to generate assembly code to inject into scripts. 157 | since ascc is based on the original AGS sources and is written in C++, it's 158 | available in a different repository: https://github.com/rofl0r/ascc . 159 | 160 | 161 | compilation: 162 | ------------ 163 | 164 | after acquiration of a C compiler toolchain (optimally GCC) and GNU make 165 | (e.g. on ubuntu: `apt install build-essential`), 166 | 167 | simply run `make`. 168 | 169 | if you need any special CFLAGS, LDFLAGS etc put them into config.mak 170 | or append them to the make command, i.e. `make CFLAGS="-O2 -g"` 171 | 172 | compilation on windos: 173 | ---------------------- 174 | 175 | preferably, use cygwin. with cygwin, every works just fine without *any* 176 | changes. you only need to ship cygwin1.dll together with the binaries. 177 | 178 | mingw is not supported. 179 | even though it' s a half-baked PoS, which lacks a dozen or more of 180 | the POSIX APIs we require. i went to great effort to make it compile 181 | anyway - however with some functionality disabled, namely the pre- 182 | processor of agssemble. 183 | 184 | agsutils can now also be compiled using [PellesC](http://www.smorgasbordet.com/pellesc/) 185 | (use [pellescc wrapper](https://github.com/rofl0r/pellescc) to build from Makefile). 186 | unlike mingw, it supports the whole functionality of agsutils. 187 | since 32-bit pellesC (at least in version 8) uses a signed 32-bit off_t, 188 | i wrote some code to use win32 api directly in critical pieces of code 189 | that may need to access files > 2GB. 190 | 191 | alternatively you can use WSL/WSL2 and just use native linux compiler and the 192 | binaries inside the WSL environment. 193 | 194 | that's really a lot of options to compile from source. 195 | however, for you lazy windos consumer-minded types, we now even offer 196 | pre-built windos binaries: 197 | 198 | pre-built binaries: 199 | ------------------- 200 | 201 | you can find stable versions attached to releases, or get "nightlies" 202 | from latest commits - you need to be logged in to github for the latter 203 | though. 204 | you can find them by either clicking on the green checkmark on the latest 205 | commit title, or click on the "Actions" tab in github. 206 | there are 2 actions run on every push, click on either 207 | "compile_cygwin" for win x64 binaries or "build" for mingw i686 builds 208 | or statically linked x86_64 linux binaries (for use in WSL/Linux/FreeBSD). 209 | scroll down to "Artifacts" to find the file attachments. 210 | 211 | third-party library requirements: 212 | --------------------------------- 213 | none. \o/ 214 | 215 | supported AGS versions: 216 | ----------------------- 217 | like the opensource AGS engine, version 2.0 - 2.4 aren't properly supported 218 | due to lack of available format information. currently it's possible to 219 | extract the packfiles, sprites, and scripts (for 2.2+), though. 220 | full support for those might be completed at some point in the future. 221 | all versions >= 2.5.0 and < 4.0.0 are 100% supported, and if one of the tools 222 | bails out on them it's considered a bug. 223 | 224 | License: 225 | -------- 226 | there is a tiny part of code left from the original AGS engine, about 150 LOC 227 | in CLib32.c. 228 | it is covered under the artistic license, see file header for details. 229 | all other code is (C) 2012-2025 rofl0r and licensed under the LGPL 2.1+ with 230 | the "NO C++" exception. 231 | the "NO C++" exception means that you are not allowed to convert the code into 232 | C++, but you can link it to C++ code. 233 | 234 | -------------------------------------------------------------------------------- /minishilka.c: -------------------------------------------------------------------------------- 1 | /* 2 | * minishilka, (C) 2021 rofl0r 3 | * 4 | * licensed under the LGPL 2.1+ 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define INLINE_MEMCMP \ 14 | "#ifndef SHILKA_INLINE_MEMCMP_DEFINED\n" \ 15 | "#define SHILKA_INLINE_MEMCMP_DEFINED\n" \ 16 | "static inline size_t shilka_inline_memcmp(const void* restrict pa, const void* restrict pb, size_t n) {\n" \ 17 | "\tconst unsigned char *l=pa, *r=pb;\n" \ 18 | "\tfor (; n && *l == *r; n--, l++, r++);\n" \ 19 | "\treturn n ? *l-*r : 0;" \ 20 | "}\n" \ 21 | "#endif\n" 22 | 23 | #define EXPORT_EXPORT 0 24 | #define EXPORT_STATIC 1 25 | #define EXPORT_INLINE 2 26 | static void print_header(FILE* out, char *pfx, char *return_type, int expflags, int inlinememcmp) { 27 | static const char *vistab[] = { 28 | [EXPORT_EXPORT] = "", 29 | [EXPORT_STATIC] = "static ", 30 | [EXPORT_INLINE] = "inline ", 31 | [EXPORT_STATIC|EXPORT_INLINE] = "static inline ", 32 | }; 33 | fprintf(out, 34 | "#include \n" 35 | "%s" 36 | "%svoid %sreset (void) {}\n" 37 | "%svoid %soutput_statistics (void) {}\n" 38 | "%s%s %sfind_keyword (const char *keyword, unsigned length) {\n", 39 | inlinememcmp ? INLINE_MEMCMP : "", 40 | vistab[expflags], pfx, 41 | vistab[expflags], pfx, 42 | vistab[expflags], return_type, pfx); 43 | } 44 | 45 | static int usage() { 46 | fprintf(stderr, "%s", 47 | "minishilka [OPTIONS] descriptionfile.shilka\n\n" 48 | "minishilka (c) rofl0r is a small replacement for shilka which generates code\n" 49 | "for fast keyword search. minishilka only supports a single usecase\n" 50 | "of shilka, namely the one i use.\n" 51 | "it's meant to be used if compiling the real shilka program would be too much\n" 52 | "of a hassle. minishilka produces somewhat slower, but still quite fast code.\n" 53 | "output is written to XXX.c, where XXX is the descriptionfile sans extension.\n" 54 | "\n" 55 | "a supported input file starts with a declaration section, followed by a line\n" 56 | "containing '%%' signifying the start of the keywords section, consisting of\n" 57 | "one or more lines containing a plain keyword followed by whitespace\n" 58 | "and an action statement written in C surrounded by curly braces, e.g.\n\n" 59 | "while {return KEYWORD_TOKEN_WHILE;}\n\n" 60 | "the last line in the file may be %other {action-if-no-keyword-matches;}\n" 61 | "if the %other statement is not provided, 0 is returned if none of the\n" 62 | "keywords match.\n" 63 | "the declarations section may contain the statement `%type T`, where T is\n" 64 | "the type to be returned by the KR_find_keyword function.\n" 65 | "\nOPTIONS\n" 66 | "-pprefix - use prefix instead of KR_ for the generated function names.\n" 67 | "-case - use strcasecmp for keyword comparison instead of memcmp.\n" 68 | "-no-definitions - this shilka mode is default for mini-shilka and ignored.\n" 69 | "-inline - emit inline functions plus an inlined memcpy if avg token len<=16.\n" 70 | "\nNOTES\n" 71 | "minishilka tries to detect whether the action code for the keywords meets\n" 72 | "certain simplicity criteria, for example if all statements look like\n" 73 | "{return SOME_VALUE;}, the return value is being put into a table, which improves\n" 74 | "both codesize and performance. you can suppress that by making a single statement\n" 75 | "with 2 semicolons, which results in your actions being literally copied into a\n" 76 | "less efficient huge switch statement. codesize can be further improved by selecting\n" 77 | "the smallest C type that can hold all return values, for example an unsigned short\n" 78 | "if all of the returned values are < 65536, using the mentioned %type declaration.\n" 79 | ); 80 | return 1; 81 | } 82 | 83 | struct item { 84 | char *kw; 85 | char *action; 86 | size_t length; 87 | struct item* next; 88 | }; 89 | 90 | static int listcmp(const void *pa, const void *pb) { 91 | const struct item *a = pa, *b = pb; 92 | return (int)a->length - (int)b->length; 93 | } 94 | 95 | /* to piss off newchurch zealots :-) */ 96 | #define master main 97 | 98 | int master(int argc, char** argv) { 99 | int i = 0, j, k, o_case = 0, expflags = EXPORT_STATIC; 100 | char* pfx = "KR_"; 101 | while(++i < argc) { 102 | if(argv[i][0] != '-') break; 103 | switch(argv[i][1]) { 104 | case 'p': pfx=argv[i]+2; break; 105 | case 'i': 106 | if(!strcmp(argv[i]+1, "inline")) expflags |= EXPORT_INLINE; 107 | else return usage(); 108 | break; 109 | case 's': 110 | if(!strcmp(argv[i]+1, "strip")) ; /* o_strip = 1; */ 111 | else return usage(); 112 | break; 113 | case 'c': 114 | if(!strcmp(argv[i]+1, "case")) o_case = 1; 115 | else return usage(); 116 | break; 117 | case 'n': 118 | if(!strcmp(argv[i]+1, "no-definitions")) ; /* default */ 119 | else return usage(); 120 | break; 121 | default: 122 | return usage(); 123 | } 124 | } 125 | if(argc == i) return usage(); 126 | 127 | char buf[1024], *other = 0, *p, *q; 128 | FILE *in = fopen(argv[i], "r"), *out; 129 | if(!in) { 130 | perror("fopen"); 131 | return 1; 132 | } 133 | snprintf(buf, sizeof buf, "%s", argv[i]); 134 | if((p = strrchr(buf, '.'))) { 135 | *(++p) = 'c'; 136 | *(++p) = 0; 137 | } else strcat(buf, ".c"); 138 | out = fopen(buf, "w"); 139 | if(!out) { 140 | perror("fopen"); 141 | return 1; 142 | } 143 | 144 | int seen = 0, use_inline_memcmp = 0, simple_value = 1, simple_return_type = 1; 145 | size_t maxlen = 0, listcount = 0, strlensum = 0; 146 | char *return_type = "int"; 147 | struct item *list = 0, *last = 0; 148 | while(fgets(buf, sizeof buf, in)) { 149 | p = strrchr(buf, '\n'); 150 | if(p) *p = 0; 151 | p = buf + strlen(buf)-1; 152 | while(p > buf && isspace(*p)) *(p--) = 0; 153 | 154 | if(buf[0] == 0) continue; 155 | 156 | if(!seen && !memcmp(buf, "%type", 5)) { 157 | p = buf+5; 158 | while(isspace(*p)) ++p; 159 | return_type = strdup(p); 160 | if(strchr(p, '*') || !memcmp(p, "struct", 6)) 161 | simple_return_type = 0; 162 | else if(!memcmp(p, "unsigned", 8) || !memcmp(p, "signed", 6) || 163 | !memcmp(p, "enum", 4) || !memcmp(p, "char", 4) || 164 | !memcmp(p, "uint", 4) || !memcmp(p, "short", 5) || 165 | !memcmp(p, "int", 3) || !memcmp(p, "long", 4)) 166 | simple_return_type = 1; 167 | else 168 | simple_return_type = 0; 169 | continue; 170 | } 171 | if(!seen && strcmp(buf, "%%")) { 172 | fprintf(stderr, "error: mini-shilka file must begin with %%\n"); 173 | return 1; 174 | } 175 | if(!seen) { 176 | seen = 1; 177 | continue; 178 | } 179 | p = buf; 180 | while(!isspace(*p))++p; 181 | *p = 0; 182 | 183 | q = p+1; 184 | while(isspace(*q))++q; 185 | if(!strcmp(buf, "%other")) { 186 | other = q; 187 | continue; 188 | } 189 | if(*q != '{') { 190 | fprintf(stderr, "error: shilka C statements need to be surrounded by {}!\n"); 191 | return 1; 192 | } else if (simple_return_type && simple_value != 0) { 193 | p = q+1; 194 | while(isspace(*p)) ++p; 195 | if(!memcmp(p, "return", 6)) { 196 | p += 6; 197 | while(isspace(*p)) ++p; 198 | int commas = 0; 199 | while(*p && !(*p == '}' && p[1] == 0)) { 200 | if(*p == ';') ++commas; 201 | else if(!strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-+ \t", *p)) { 202 | simple_value = 0; 203 | break; 204 | } 205 | ++p; 206 | } 207 | if(commas > 1) simple_value = 0; 208 | if(!commas) { 209 | fprintf(stderr, "error: C action statement lacks ';'\n"); 210 | return 1; 211 | } 212 | } else simple_value = 0; 213 | } 214 | struct item *it = calloc(1, sizeof(*it)); 215 | it->kw = strdup(buf); 216 | it->action = strdup(q); 217 | it->length = strlen(buf); 218 | it->next = 0; 219 | if(!list) { 220 | list=it; 221 | last=it; 222 | } else { 223 | last->next = it; 224 | last = it; 225 | } 226 | if(it->length > maxlen) maxlen = it->length; 227 | strlensum += it->length + o_case; 228 | ++listcount; 229 | } 230 | 231 | struct item *it, *flat_list = calloc(listcount, sizeof *list); 232 | for(i = 0, it = list; it; ++i, it = it->next) 233 | flat_list[i] = *it; 234 | 235 | qsort(flat_list, listcount, sizeof *list, listcmp); 236 | 237 | use_inline_memcmp = o_case ? 0 : (expflags & EXPORT_INLINE) && strlensum/listcount <= 16; 238 | print_header(out, pfx, return_type, expflags, use_inline_memcmp); 239 | 240 | /* index into string table */ 241 | char *stridx_type = strlensum > 255 ? "unsigned short" : "unsigned char"; 242 | fprintf(out, "\tstatic const %s sitab[%zu] = {\n\t\t", stridx_type, maxlen+1); 243 | for(i = j = k = 0; i <= maxlen; ++i) { 244 | if(j >= listcount || flat_list[j].length > i) fprintf(out, "0, "); 245 | else { 246 | fprintf(out, "%d, ", k); 247 | while(flat_list[j].length == i) 248 | k += flat_list[j++].length + o_case; 249 | } 250 | } 251 | fprintf(out, "\n\t};\n"); 252 | 253 | /* count of items of length */ 254 | fprintf(out, "\tstatic const unsigned char sctab[%zu] = {\n\t\t", maxlen+1); 255 | for(i = j = 0; i <= maxlen; ++i) { 256 | if(j >= listcount || flat_list[j].length > i) fprintf(out, "0, "); 257 | else { 258 | k = j; 259 | while(flat_list[++j].length == i); 260 | fprintf(out, "%d, ", j-k); 261 | } 262 | } 263 | fprintf(out, "\n\t};\n"); 264 | 265 | /* array index for return value assignment */ 266 | fprintf(out, "\tstatic const unsigned char itab[%zu] = {\n\t\t", maxlen+1); 267 | for(i = j = 0; i <= maxlen; ++i) { 268 | if(flat_list[j].length > i) fprintf(out, "0, "); 269 | else { 270 | fprintf(out, "%d, ", j); 271 | while(flat_list[++j].length == i); 272 | } 273 | } 274 | fprintf(out, "\n\t};\n"); 275 | 276 | fprintf(out, "\tstatic const char keywords[] = {\n"); 277 | for(i = 0; i < listcount; ++i) { 278 | it = &flat_list[i]; 279 | fprintf(out, "\t\t\"%s%s\"\n", it->kw, o_case ? "\\000" : ""); 280 | } 281 | fprintf(out, "\t};\n"); 282 | 283 | if(simple_value) { 284 | fprintf(out, "\tstatic const %s retval[] = {\n", return_type); 285 | for(i = 0; i < listcount; ++i) { 286 | char *p = flat_list[i].action, *q; 287 | while(*p != 'r') ++p; 288 | p += 6; 289 | while(isspace(*p)) ++p; 290 | q = p+1; 291 | while(*q != ';') ++q; 292 | fprintf(out, "\t\t%.*s,\n", (int)(q-p), p); 293 | } 294 | fprintf(out, "\t};\n"); 295 | } 296 | 297 | fprintf(out, 298 | "\tif(length>%d) goto fail;\n" 299 | "\tint i, count = sctab[length];\n" 300 | "\tconst char *p = keywords + sitab[length];\n" 301 | "\tfor(i = 0; iaction); 316 | } 317 | fprintf(out, "\tdefault: goto fail;\n\t}\n\tfail:; %s\n}\n", other ? other : "return 0;"); 318 | } 319 | 320 | fclose(in); 321 | fclose(out); 322 | return 0; 323 | } 324 | -------------------------------------------------------------------------------- /winfile.h: -------------------------------------------------------------------------------- 1 | /* 2 | replacement for unix/C file handling functions on win32 to always have 3 | 64 bit off_t regardless of what the libc implementation uses. 4 | also supplying replacements for mmap() and friends. 5 | note that mmap() on 32bit windows will never be able to map files > 2-3 GB, 6 | due to lack of address space or physical memory. 7 | */ 8 | 9 | #define WIN32_LEAN_AND_MEAN 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifndef WINFILE_EXPORT 20 | #define WINFILE_EXPORT 21 | #endif 22 | 23 | #define MAP_FAILED ((void *) -1) 24 | #define MAP_PRIVATE 0x02 25 | #define MAP_FIXED 0x10 26 | #define PROT_NONE 0 27 | #define PROT_READ 1 28 | #define PROT_WRITE 2 29 | #define PROT_EXEC 4 30 | 31 | typedef int64_t WIN_OFF64_T; 32 | 33 | #define set_errno(e) errno = e 34 | #ifdef WINFILE_DEBUG 35 | #define translate_errno() translate_errno_dbg(__FILE__, __LINE__) 36 | #else 37 | #define translate_errno() translate_errno_dbg(0, 0) 38 | #endif 39 | 40 | static void translate_errno_dbg(char *file, int line) { 41 | DWORD lastError = GetLastError(); 42 | #ifdef WINFILE_DEBUG 43 | fprintf(stderr, "translate_errno called from %s:%d\n", file, line); 44 | #else 45 | (void) file; (void) line; 46 | #endif 47 | 48 | switch (lastError) { 49 | // Common errors 50 | case ERROR_FILE_NOT_FOUND: 51 | case ERROR_PATH_NOT_FOUND: 52 | set_errno(ENOENT); // No such file or directory 53 | break; 54 | case ERROR_ACCESS_DENIED: 55 | set_errno(EACCES); // Permission denied 56 | break; 57 | case ERROR_ALREADY_EXISTS: 58 | case ERROR_FILE_EXISTS: 59 | set_errno(EEXIST); // File exists 60 | break; 61 | case ERROR_INVALID_HANDLE: 62 | set_errno(EBADF); // Bad file descriptor 63 | break; 64 | case ERROR_NOT_ENOUGH_MEMORY: 65 | case ERROR_OUTOFMEMORY: 66 | set_errno(ENOMEM); // Out of memory 67 | break; 68 | case ERROR_INVALID_PARAMETER: 69 | set_errno(EINVAL); // Invalid argument 70 | break; 71 | case ERROR_DISK_FULL: 72 | set_errno(ENOSPC); // No space left on device 73 | break; 74 | case ERROR_HANDLE_EOF: 75 | set_errno(0); // EOF reached (not strictly an error in POSIX) 76 | break; 77 | 78 | // Errors specific to CreateFileMappingA and MapViewOfFile(Ex) 79 | case ERROR_FILE_INVALID: 80 | set_errno(EBADF); // Bad file descriptor 81 | break; 82 | case ERROR_COMMITMENT_LIMIT: 83 | set_errno(ENOMEM); // Out of memory 84 | break; 85 | case ERROR_INVALID_ADDRESS: 86 | set_errno(EFAULT); // Bad address 87 | break; 88 | #ifdef EOPNOTSUPP 89 | case ERROR_NOT_SUPPORTED: 90 | set_errno(EOPNOTSUPP); // Operation not supported 91 | break; 92 | #endif 93 | default: 94 | set_errno(EIO); // Input/output error for unhandled cases 95 | break; 96 | } 97 | } 98 | 99 | WINFILE_EXPORT 100 | void *win_mmap(void *addr, WIN_OFF64_T len, int prot, int flags, HANDLE hFile, WIN_OFF64_T off) 101 | { 102 | DWORD flProtect; 103 | DWORD dwDesiredAccess; 104 | HANDLE map; 105 | void *mapview; 106 | DWORD fsizeL, fsizeH; 107 | uint64_t fsize, alignment_offset, aligned_offset, mapping_size; 108 | struct _SYSTEM_INFO si; 109 | 110 | if (!len) { 111 | l_einval: 112 | set_errno(EINVAL); 113 | return MAP_FAILED; 114 | } 115 | 116 | GetSystemInfo(&si); 117 | /* don't allow offsets not aligned to PAGE_SIZE as per POSIX. */ 118 | if(off & (si.dwPageSize - 1)) goto l_einval; 119 | // Get misalignment of offset in respect to Allocation Granularity. 120 | alignment_offset = off & (si.dwAllocationGranularity - 1); 121 | 122 | /* 123 | Adjust the offset to be aligned to the granularity boundary (typ. 64K). 124 | The adjusted offset represents the nearest lower multiple of the 125 | granularity that is less than or equal to the original offset. 126 | Required by MapViewOfFileEx: https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffileex 127 | */ 128 | aligned_offset = off - alignment_offset; 129 | 130 | /* 131 | Calculate the size of the mapping region. 132 | The mapping size must include the misaligned portion of the file 133 | (alignment_offset) to ensure that the desired range can be accessed 134 | after the mapping. 135 | The total mapping size is the sum of the requested length (`len`) and the misaligned portion. 136 | */ 137 | mapping_size = alignment_offset + len; 138 | 139 | fsizeL = GetFileSize(hFile, &fsizeH); 140 | if (fsizeL == -1) { 141 | translate_errno(); 142 | return MAP_FAILED; 143 | } 144 | fsize = fsizeL | ((int64_t)fsizeH << 32); 145 | 146 | /* 147 | Ensure that the mapping size does not exceed the file size. 148 | If the requested range (alignment_offset + len) extends beyond the 149 | end of the file,the mapping size is truncated to the remaining 150 | file size. 151 | */ 152 | if ((aligned_offset + mapping_size) > fsize) { 153 | set_errno(ENXIO); 154 | return MAP_FAILED; 155 | } 156 | 157 | /* 158 | Ensure the mapping size is lower than 4GB on 32bit windows, 159 | which is necessary because the dwNumberOfBytesToMap parameter is 160 | SIZE_T (and more memory can't be addressed anyhow). 161 | */ 162 | if(sizeof(SIZE_T) < 8 && mapping_size > 0xFFFFFFFFULL) 163 | goto l_einval; 164 | 165 | switch (prot) { 166 | case PROT_WRITE | PROT_EXEC: 167 | case PROT_READ | PROT_WRITE | PROT_EXEC: 168 | flProtect = PAGE_EXECUTE_READWRITE; 169 | dwDesiredAccess = SECTION_ALL_ACCESS; 170 | break; 171 | case PROT_READ | PROT_WRITE: 172 | flProtect = PAGE_READWRITE; 173 | dwDesiredAccess = SECTION_ALL_ACCESS; 174 | break; 175 | case PROT_WRITE: 176 | flProtect = PAGE_READWRITE; 177 | dwDesiredAccess = FILE_MAP_WRITE; 178 | break; 179 | case PROT_READ: 180 | flProtect = PAGE_READONLY; 181 | dwDesiredAccess = FILE_MAP_READ; 182 | break; 183 | case PROT_EXEC: 184 | flProtect = PAGE_EXECUTE; 185 | dwDesiredAccess = FILE_MAP_READ; 186 | break; 187 | case PROT_NONE: 188 | flProtect = PAGE_NOACCESS; 189 | dwDesiredAccess = FILE_MAP_READ; 190 | break; 191 | default: 192 | goto l_einval; 193 | } 194 | 195 | if ( flags & MAP_PRIVATE ) { 196 | flProtect = PAGE_WRITECOPY; 197 | dwDesiredAccess = FILE_MAP_COPY; 198 | } 199 | 200 | if (!(flags & MAP_FIXED)) 201 | addr = 0; 202 | 203 | /* 204 | Create a file mapping object to enable memory mapping of the file. 205 | A file mapping object represents a portion of a file in memory. 206 | The object is created with the desired protection level and the 207 | maximum mapping size. 208 | */ 209 | map = CreateFileMappingA( 210 | hFile, // HANDLE hFile: Handle to the file to be mapped. 211 | NULL, // LPSECURITY_ATTRIBUTES lpAttributes: Default security attributes. 212 | flProtect, // DWORD flProtect: Protection level (e.g., PAGE_READWRITE). 213 | mapping_size >> 32, // DWORD dwMaximumSizeHigh: High 32 bits of maximum mapping size. 214 | mapping_size & 0xFFFFFFFF, // DWORD dwMaximumSizeLow: Low 32 bits of maximum mapping size. 215 | NULL // LPCSTR lpName: No named mapping object. 216 | ); 217 | if (!map) { 218 | translate_errno(); 219 | return MAP_FAILED; 220 | } 221 | // Map a view of the file into the process's address space. 222 | mapview = MapViewOfFileEx( 223 | map, // HANDLE hFileMappingObject: Handle to the file mapping object. 224 | dwDesiredAccess, // DWORD dwDesiredAccess: Desired access level (e.g., FILE_MAP_READ). 225 | aligned_offset >> 32, // DWORD dwFileOffsetHigh: High 32 bits of the file offset. 226 | aligned_offset & 0xFFFFFFFF, // DWORD dwFileOffsetLow: Low 32 bits of the file offset. 227 | mapping_size, // SIZE_T dwNumberOfBytesToMap: Number of bytes to map (max 4 GB). 228 | addr // LPVOID lpBaseAddress: Desired starting address (NULL for automatic). 229 | ); 230 | 231 | if(!mapview) { 232 | translate_errno(); 233 | mapview = MAP_FAILED; 234 | } else { 235 | /* 236 | if a user passes a legal page-aligned offset, which is 237 | however not aligned to allocation granularity, he expects 238 | the returned pointer to point to that offset regardless. */ 239 | mapview = (char*)mapview + alignment_offset; 240 | } 241 | CloseHandle(map); 242 | return mapview; 243 | } 244 | 245 | WINFILE_EXPORT 246 | int win_msync(void * addr, size_t length, int flags) 247 | { 248 | (void) flags; 249 | if (FlushViewOfFile(addr, length)) 250 | return 0; 251 | translate_errno(); 252 | return -1; 253 | } 254 | 255 | /* 256 | The only supported use of this implementation is to unmap an entire 257 | mapping as obtained by win_mmap(). unmapping only parts of it isn't. 258 | */ 259 | WINFILE_EXPORT 260 | int win_munmap(void *addr, size_t len) 261 | { 262 | (void) len; 263 | struct _SYSTEM_INFO si; 264 | GetSystemInfo(&si); 265 | /* 266 | If the addr passed isnt aligned to Alloc. granularity, we must 267 | subtract the misalignment we added earlier to get the start 268 | of the mapping. 269 | */ 270 | addr = (char*)addr - ((SIZE_T)addr & (si.dwAllocationGranularity - 1)); 271 | 272 | if (UnmapViewOfFile(addr)) 273 | return 0; 274 | translate_errno(); 275 | return -1; 276 | } 277 | 278 | /* return INVALID_HANDLE_VALUE or a valid HANDLE on success. */ 279 | WINFILE_EXPORT 280 | HANDLE win_open(const char *pathname, int flags) { 281 | DWORD access, shareMode; 282 | DWORD creation = OPEN_EXISTING; 283 | DWORD attributes = FILE_ATTRIBUTE_NORMAL; 284 | 285 | // ignore O_BINARY as it doesn't affect raw winapi calls 286 | if (flags & O_WRONLY) { 287 | access = GENERIC_WRITE; 288 | shareMode = FILE_SHARE_WRITE; // Only allow shared write access 289 | } else if (flags & O_RDWR) { 290 | access = GENERIC_READ | GENERIC_WRITE; 291 | shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; // Allow shared read/write access 292 | } else { 293 | // Fallback for O_RDONLY (since O_RDONLY is 0) 294 | access = GENERIC_READ; 295 | shareMode = FILE_SHARE_READ; // Only allow shared read access 296 | } 297 | 298 | // Handle O_CREAT, O_TRUNC, and O_EXCL logic 299 | if (flags & O_CREAT) { 300 | if (flags & O_EXCL) { 301 | creation = CREATE_NEW; // Fails if the file exists 302 | } else if (flags & O_TRUNC) { 303 | creation = CREATE_ALWAYS; // Create file if it doesn't exist, truncate if it does 304 | } else { 305 | creation = OPEN_ALWAYS; // Create file if it doesn't exist 306 | } 307 | } else if (flags & O_TRUNC) { 308 | creation = TRUNCATE_EXISTING; // Truncate file if it exists (fails if it doesn't) 309 | } 310 | 311 | // Handle O_APPEND 312 | if (flags & O_APPEND) { 313 | access |= FILE_APPEND_DATA; // Append data to the end of the file 314 | } 315 | 316 | HANDLE hFile = CreateFileA( 317 | pathname, 318 | access, 319 | shareMode, 320 | NULL, // Default security attributes 321 | creation, // OPEN_EXISTING by default 322 | FILE_ATTRIBUTE_NORMAL, // Normal file 323 | NULL // No template file 324 | ); 325 | 326 | if (hFile == INVALID_HANDLE_VALUE) { 327 | translate_errno(); 328 | } 329 | return hFile; 330 | } 331 | 332 | WINFILE_EXPORT 333 | ssize_t win_read(HANDLE hFile, void *buffer, size_t count) { 334 | DWORD bytesRead = 0; 335 | if (!ReadFile(hFile, buffer, (DWORD)count, &bytesRead, NULL)) { 336 | translate_errno(); 337 | return -1; 338 | } 339 | return (ssize_t)bytesRead; 340 | } 341 | 342 | WINFILE_EXPORT 343 | ssize_t win_write(HANDLE hFile, const void *buffer, size_t count) { 344 | DWORD bytesWritten = 0; 345 | if (!WriteFile(hFile, buffer, (DWORD)count, &bytesWritten, NULL)) { 346 | translate_errno(); 347 | return -1; 348 | } 349 | return (ssize_t)bytesWritten; 350 | } 351 | 352 | WINFILE_EXPORT 353 | WIN_OFF64_T win_lseek(HANDLE hFile, WIN_OFF64_T offset, int whence) { 354 | LARGE_INTEGER liDistanceToMove; 355 | LARGE_INTEGER liNewFilePointer; 356 | DWORD moveMethod; 357 | 358 | liDistanceToMove.QuadPart = offset; 359 | 360 | switch (whence) { 361 | case SEEK_SET: moveMethod = FILE_BEGIN; break; 362 | case SEEK_CUR: moveMethod = FILE_CURRENT; break; 363 | case SEEK_END: moveMethod = FILE_END; break; 364 | default: 365 | errno = EINVAL; 366 | return -1LL; 367 | } 368 | 369 | if (!SetFilePointerEx(hFile, liDistanceToMove, &liNewFilePointer, moveMethod)) { 370 | translate_errno(); 371 | return -1LL; 372 | } 373 | return liNewFilePointer.QuadPart; 374 | } 375 | 376 | WINFILE_EXPORT 377 | void win_close(HANDLE hFile) { 378 | if (!CloseHandle(hFile)) { 379 | translate_errno(); 380 | } 381 | } 382 | 383 | WINFILE_EXPORT 384 | int win_rename(const char *old, const char *new) { 385 | BOOL res = MoveFileExA(old, new, MOVEFILE_REPLACE_EXISTING|MOVEFILE_COPY_ALLOWED); 386 | if(res) return 0; 387 | translate_errno(); 388 | return -1; 389 | } 390 | 391 | WINFILE_EXPORT 392 | WIN_OFF64_T win_filesize(const char *fn) { 393 | HANDLE fd = win_open(fn, O_RDONLY); 394 | if(fd == INVALID_HANDLE_VALUE) 395 | return -1LL; 396 | WIN_OFF64_T res = win_lseek(fd, 0, SEEK_END); 397 | win_close(fd); 398 | return res; 399 | } 400 | 401 | #ifdef WINFILE_TEST 402 | 403 | int main() { 404 | const char *filename = "example.txt"; 405 | 406 | // Open the file for reading and writing (O_RDWR) in binary mode (O_BINARY) 407 | HANDLE hFile = win_open(filename, O_RDWR | O_BINARY); 408 | if (hFile == INVALID_HANDLE_VALUE) { 409 | perror("Error opening file"); 410 | return 1; 411 | } 412 | 413 | // Write data to the file 414 | const char *data = "Hello, Win32!"; 415 | if (win_write(hFile, data, strlen(data)) == -1) { 416 | perror("Error writing to file"); 417 | win_close(hFile); 418 | return 1; 419 | } 420 | 421 | // Seek to the end of the file 422 | WIN_OFF64_T offset = win_lseek(hFile, 0, SEEK_END); 423 | if (offset == -1LL) { 424 | perror("Error seeking in file"); 425 | win_close(hFile); 426 | return 1; 427 | } 428 | printf("File size: %lld bytes\n", offset); 429 | 430 | // Seek to the beginning of the file 431 | if (win_lseek(hFile, 0, SEEK_SET) == -1LL) { 432 | perror("Error seeking in file"); 433 | win_close(hFile); 434 | return 1; 435 | } 436 | 437 | // Read data from the file 438 | char buffer[128] = {0}; 439 | if (win_read(hFile, buffer, sizeof(buffer) - 1) == -1) { 440 | perror("Error reading from file"); 441 | win_close(hFile); 442 | return 1; 443 | } 444 | 445 | printf("Read from file: %s\n", buffer); 446 | 447 | // Close the file 448 | win_close(hFile); 449 | 450 | return 0; 451 | } 452 | 453 | #endif 454 | -------------------------------------------------------------------------------- /Targa.h: -------------------------------------------------------------------------------- 1 | #ifndef TARGA_H 2 | #define TARGA_H 3 | 4 | /* define generic and easily accessible image type, used 5 | for targa conversion */ 6 | 7 | typedef struct ImageData { 8 | short width; 9 | short height; 10 | short bytesperpixel; 11 | unsigned data_size; 12 | unsigned char* data; 13 | } ImageData; 14 | 15 | #ifndef TARGA_IMPL 16 | #define TARGA_EXPORT extern 17 | TARGA_EXPORT int 18 | Targa_writefile(const char *name, ImageData* d, unsigned char *palette); 19 | TARGA_EXPORT int 20 | Targa_readfile(const char *name, ImageData *idata, int skip_palette); 21 | 22 | #else 23 | 24 | struct TargaHeader { 25 | char idlength; 26 | char colourmaptype; 27 | char datatypecode; 28 | short colourmaporigin; 29 | short colourmaplength; 30 | char colourmapdepth; 31 | short x_origin; 32 | short y_origin; 33 | short width; 34 | short height; 35 | char bitsperpixel; 36 | char imagedescriptor; 37 | }; 38 | 39 | #define THO(f) THO_ ## f 40 | enum TargaHeader_offsets { 41 | THO(idlength) = 0, 42 | THO(colourmaptype) = 1, 43 | THO(datatypecode) = 2, 44 | THO(colourmaporigin) = 3, 45 | THO(colourmaplength) = 5, 46 | THO(colourmapdepth) = 7, 47 | THO(x_origin) = 8, 48 | THO(y_origin) = 10, 49 | THO(width) = 12, 50 | THO(height) = 14, 51 | THO(bitsperpixel) = 16, 52 | THO(imagedescriptor) = 17, 53 | }; 54 | 55 | static inline uint8_t read_header_field1(unsigned char *hdr, unsigned offset) { 56 | return hdr[offset]; 57 | } 58 | 59 | static inline uint16_t read_header_field2(unsigned char *hdr, unsigned offset) { 60 | uint16_t tmp; 61 | memcpy(&tmp, hdr+offset, 2); 62 | return end_le16toh(tmp); 63 | } 64 | 65 | static void TargaHeader_from_buf(struct TargaHeader *hdr, unsigned char* hdr_buf) { 66 | hdr->idlength = read_header_field1(hdr_buf, THO(idlength)); 67 | hdr->colourmaptype = read_header_field1(hdr_buf, THO(colourmaptype)); 68 | hdr->datatypecode = read_header_field1(hdr_buf, THO(datatypecode)); 69 | hdr->colourmaporigin = read_header_field2(hdr_buf, THO(colourmaporigin)); 70 | hdr->colourmaplength = read_header_field2(hdr_buf, THO(colourmaplength)); 71 | hdr->colourmapdepth = read_header_field1(hdr_buf, THO(colourmapdepth)); 72 | hdr->x_origin = read_header_field2(hdr_buf, THO(x_origin)); 73 | hdr->y_origin = read_header_field2(hdr_buf, THO(y_origin)); 74 | hdr->width = read_header_field2(hdr_buf, THO(width)); 75 | hdr->height = read_header_field2(hdr_buf, THO(height)); 76 | hdr->bitsperpixel = read_header_field1(hdr_buf, THO(bitsperpixel)); 77 | hdr->imagedescriptor = read_header_field1(hdr_buf, THO(imagedescriptor)); 78 | } 79 | 80 | static inline void write_header_field1(unsigned char* buf, unsigned off, uint8_t v) { 81 | buf[off] = v; 82 | } 83 | static inline void write_header_field2(unsigned char* buf, unsigned off, uint16_t v) { 84 | uint16_t tmp = end_htole16(v); 85 | memcpy(buf+off, &tmp, 2); 86 | } 87 | 88 | static void TargaHeader_to_buf(struct TargaHeader *hdr, unsigned char* hdr_buf) { 89 | write_header_field1(hdr_buf, THO(idlength), hdr->idlength); 90 | write_header_field1(hdr_buf, THO(colourmaptype), hdr->colourmaptype); 91 | write_header_field1(hdr_buf, THO(datatypecode), hdr->datatypecode); 92 | write_header_field2(hdr_buf, THO(colourmaporigin), hdr->colourmaporigin); 93 | write_header_field2(hdr_buf, THO(colourmaplength), hdr->colourmaplength); 94 | write_header_field1(hdr_buf, THO(colourmapdepth), hdr->colourmapdepth); 95 | write_header_field2(hdr_buf, THO(x_origin), hdr->x_origin); 96 | write_header_field2(hdr_buf, THO(y_origin), hdr->y_origin); 97 | write_header_field2(hdr_buf, THO(width), hdr->width); 98 | write_header_field2(hdr_buf, THO(height), hdr->height); 99 | write_header_field1(hdr_buf, THO(bitsperpixel), hdr->bitsperpixel); 100 | write_header_field1(hdr_buf, THO(imagedescriptor), hdr->imagedescriptor); 101 | } 102 | 103 | enum TargaImageType { 104 | TIT_COLOR_MAPPED = 1, 105 | TIT_TRUE_COLOR = 2, 106 | TIT_BLACK_WHITE = 3, 107 | TIT_RLE_COLOR_MAPPED = 9, 108 | TIT_RLE_TRUE_COLOR = 10, 109 | TIT_RLE_BLACK_WHITE = 11, 110 | }; 111 | 112 | struct TargaFooter { 113 | unsigned extensionareaoffset; 114 | unsigned developerdirectoryoffset; 115 | char signature[16]; 116 | char dot; 117 | char null; 118 | }; 119 | 120 | 121 | #define STATIC_ASSERT(COND) static char static_assert_ ## __LINE__ [COND ? 1 : -1] 122 | //STATIC_ASSERT(sizeof(struct TargaHeader) == 18); 123 | 124 | #ifndef TARGA_EXPORT 125 | #define TARGA_EXPORT static 126 | #endif 127 | 128 | #define TARGA_FOOTER_SIGNATURE "TRUEVISION-XFILE" 129 | 130 | 131 | /* helper funcs */ 132 | 133 | #include 134 | #include 135 | #include "endianness.h" 136 | 137 | #define rle_read_col(OUT, IDX) \ 138 | for(OUT=0, i=0; i1 && mode < 2) rle_read_col(col[1], count+1); 159 | } else { 160 | if(count) goto write_series; 161 | else break; 162 | } 163 | switch(mode) { 164 | case 0: 165 | if(togo>1) { 166 | if(col[0] == col[1]) { 167 | start_rep: 168 | mode = 2; 169 | repcol = col[0]; 170 | } else { 171 | start_series: 172 | mode = 1; 173 | } 174 | count = 1; 175 | } else { 176 | goto start_series; 177 | } 178 | break; 179 | case 1: 180 | if(togo>1) { 181 | if(col[0] == col[1]) { 182 | jump_flag = 1; 183 | goto write_series; 184 | } else { 185 | advance: 186 | if(++count == 128) { 187 | write_series: 188 | *(p++) = ((mode - 1) << 7) | (count - 1); 189 | if(mode == 1) for(i=0;i> i; 194 | q += count * bpp; 195 | } 196 | if(!togo) goto done; 197 | if(jump_flag == 1) goto start_rep; 198 | if(jump_flag == 2) goto start_series; 199 | mode = 0; 200 | count = 0; 201 | } 202 | } 203 | } else goto advance; 204 | break; 205 | case 2: 206 | if(col[0] == repcol) goto advance; 207 | else { 208 | jump_flag = 2; 209 | goto write_series; 210 | } 211 | } 212 | togo--; 213 | } 214 | done: 215 | *result = out; 216 | return p-out; 217 | } 218 | #undef rle_read_col 219 | 220 | /* caller needs to provide result buffer of w*h*bpp size. */ 221 | static void rle_decode( 222 | unsigned char *data, unsigned data_size, 223 | unsigned bpp, unsigned char* result, unsigned result_size) { 224 | unsigned char 225 | *p = result, *p_e = p + result_size, 226 | *q = data, *q_e = q + data_size; 227 | while(q+1+bpp <= q_e) { 228 | unsigned count = (*q & 127) + 1; 229 | unsigned rep = *q & 128; 230 | unsigned color = 0, i, j; 231 | ++q; 232 | if(rep) { 233 | for(i = 0; i < bpp; ++i) 234 | color = (color << 8) | *(q++); 235 | for(i = 0; i < count && p+bpp <= p_e; ++i) 236 | for(j=0; j> ((bpp-j-1)*8)) & 0xff; 238 | } else { 239 | for(i = 0; i < count && p+bpp <= p_e && q+bpp <= q_e; ++i) 240 | for(j=0; j> 5); 249 | *g = ((hi & 7) << 5) | ((lo & 224) >> 3) | ((hi & 7) >> 1); 250 | *b = ((lo & 31) << 3) | ((lo & 28) >> 2); 251 | } 252 | 253 | static int ImageData_convert_16_to_24(ImageData *d) { 254 | size_t outsz = d->width*d->height*3UL; 255 | unsigned char *out = malloc(outsz), 256 | *p = out, *pe = out + outsz, 257 | *q = d->data; 258 | if(!out) return 0; 259 | while(p < pe) { 260 | unsigned r,g,b,lo,hi; 261 | lo = *(q++); 262 | hi = *(q++); 263 | rgb565_to_888(lo, hi, &r, &g, &b); 264 | *(p++) = b; 265 | *(p++) = g; 266 | *(p++) = r; 267 | } 268 | free(d->data); 269 | d->data = out; 270 | d->data_size = outsz; 271 | d->bytesperpixel = 3; 272 | return 1; 273 | } 274 | 275 | static int lookup_palette(unsigned color, unsigned *palette, int ncols) 276 | { 277 | int i; 278 | for(i=0; idata, *q = p + d->data_size; 287 | *data = malloc(d->width * d->height); 288 | unsigned char *o = *data, *e = o + (d->width * d->height); 289 | unsigned a = 0xff; 290 | while(p < q && o < e) { 291 | unsigned col, r, g, b; 292 | switch(d->bytesperpixel) { 293 | case 2: { 294 | unsigned lo = *(p++); 295 | unsigned hi = *(p++); 296 | rgb565_to_888(lo, hi, &r, &g, &b); 297 | break; } 298 | case 3: 299 | b = *(p++); 300 | g = *(p++); 301 | r = *(p++); 302 | break; 303 | case 4: 304 | b = *(p++); 305 | g = *(p++); 306 | r = *(p++); 307 | a = *(p++); 308 | break; 309 | default: b=g=r=0; break; 310 | } 311 | if(a != 0xff) goto nope; 312 | col = a << 24 | r << 16 | g << 8 | b; 313 | int n = lookup_palette(col, palette, ret); 314 | if(n < 0) { 315 | if(ret == 256) { 316 | nope: 317 | free(*data); 318 | *data = 0; 319 | return -1; 320 | } 321 | n = ret; 322 | palette[ret++] = col; 323 | } 324 | *(o++) = n; 325 | } 326 | return ret; 327 | } 328 | 329 | static void convert_bottom_left_tga(ImageData *d) { 330 | size_t y, w = d->width*d->bytesperpixel; 331 | unsigned char *swp = malloc(w); 332 | if(!swp) return; 333 | for(y = 0; y < d->height/2; ++y) { 334 | size_t to = w*y, bo = (d->height-1-y)*w; 335 | memcpy(swp, d->data + to, w); 336 | memcpy(d->data + to, d->data + bo, w); 337 | memcpy(d->data + bo, swp, w); 338 | } 339 | free(swp); 340 | } 341 | 342 | 343 | 344 | /* exports */ 345 | TARGA_EXPORT int 346 | Targa_readfile(const char *name, ImageData *idata, int skip_palette) { 347 | struct TargaHeader hdr; unsigned char hdr_buf[18]; 348 | unsigned char ftr_buf[18+8]; 349 | FILE *f = fopen(name, "rb"); 350 | if(!f) return 0; 351 | fread(&hdr_buf, 1, sizeof(hdr_buf), f); 352 | fseek(f, 0, SEEK_END); 353 | off_t fs = ftello(f); 354 | if(fs > sizeof ftr_buf) { 355 | fseek(f, 0-sizeof ftr_buf, SEEK_END); 356 | fread(ftr_buf, 1, sizeof ftr_buf, f); 357 | if(!memcmp(ftr_buf+8, TARGA_FOOTER_SIGNATURE, 16)) 358 | fs -= sizeof ftr_buf; 359 | } 360 | TargaHeader_from_buf(&hdr, hdr_buf); 361 | fseek(f, sizeof(hdr_buf) + hdr.idlength, SEEK_SET); 362 | fs -= ftello(f); 363 | unsigned char *data = malloc(fs), *palette = 0; 364 | unsigned palsz = 0; 365 | fread(data, 1, fs, f); 366 | if(hdr.colourmaptype) { 367 | palette = data; 368 | palsz = hdr.colourmaplength * hdr.colourmapdepth/8; 369 | if(fs <= palsz) return 0; 370 | data += palsz; 371 | fs -= palsz; 372 | } 373 | unsigned char *workdata = 0; 374 | unsigned tmp; 375 | switch(hdr.datatypecode) { 376 | case TIT_RLE_COLOR_MAPPED: 377 | case TIT_RLE_TRUE_COLOR: 378 | tmp = hdr.width*hdr.height*hdr.bitsperpixel/8; 379 | workdata = malloc(tmp); 380 | rle_decode(data, fs, hdr.bitsperpixel/8, workdata, tmp); 381 | break; 382 | case TIT_COLOR_MAPPED: 383 | case TIT_TRUE_COLOR: 384 | workdata = data; 385 | break; 386 | default: 387 | return 0; 388 | } 389 | idata->width = hdr.width; 390 | idata->height = hdr.height; 391 | if(skip_palette) 392 | idata->bytesperpixel = hdr.bitsperpixel/8; 393 | else 394 | idata->bytesperpixel = hdr.colourmapdepth? hdr.colourmapdepth/8 : hdr.bitsperpixel/8; 395 | 396 | tmp = idata->width*idata->height*idata->bytesperpixel; 397 | idata->data_size = tmp; 398 | idata->data = malloc(tmp); 399 | if(palette && !skip_palette) { 400 | unsigned i, j, bpp = hdr.colourmapdepth/8; 401 | unsigned char *p = idata->data, *q = workdata; 402 | for(i=0; i < idata->width*idata->height; ++i) { 403 | unsigned idx = *(q++); 404 | if(idx >= hdr.colourmaplength) return 0; 405 | for(j=0; j < bpp; ++j) 406 | *(p++) = palette[idx*bpp+j]; 407 | } 408 | } else { 409 | memcpy(idata->data, workdata, tmp); 410 | } 411 | if(workdata != data) free(workdata); 412 | if(palette) free(palette); 413 | else free(data); 414 | if(hdr.y_origin == 0) convert_bottom_left_tga(idata); 415 | fclose(f); 416 | return 1; 417 | } 418 | 419 | /* palette needs to be either 24bit rgb data of 256*3 bytes, or NULL. 420 | it's only used if imagedata.bytesperpixel is 1. in this case, if 421 | NULL, a random palette is used. */ 422 | TARGA_EXPORT int 423 | Targa_writefile(const char *name, ImageData* d, unsigned char *palette) 424 | { 425 | unsigned pal[256]; 426 | unsigned char *paldata = 0, *data = d->data; 427 | unsigned bpp = d->bytesperpixel; 428 | unsigned data_size = d->data_size; 429 | int palcount = 256; 430 | int i; 431 | 432 | FILE *f = fopen(name, "wb"); 433 | if(!f) { 434 | fprintf(stderr, "error opening %s\n", name); 435 | return 0; 436 | } 437 | 438 | if(bpp == 1) { 439 | if(!palette) for(i=0; i<256; ++i) pal[i] = rand(); 440 | else for(i=0; i<256; ++i) { 441 | unsigned r = *(palette++); 442 | unsigned g = *(palette++); 443 | unsigned b = *(palette++); 444 | pal[i] = r << 16 | g << 8 | b ; 445 | } 446 | } else if( /* bpp != 2 && */ 447 | (palcount = ImageData_create_palette_pic(d, pal, &paldata)) > 0) { 448 | /* can be saved as 8 bit palettized image */ 449 | bpp = 1; 450 | data = paldata; 451 | data_size = d->width*d->height; 452 | } else if(bpp == 2 && ImageData_convert_16_to_24(d)) { 453 | bpp = 3; 454 | data = d->data; 455 | data_size = d->data_size; 456 | } 457 | unsigned char *rle_data = 0; 458 | unsigned rle_data_size = rle_encode(data, data_size, bpp, &rle_data); 459 | int use_rle = 0; 460 | if(rle_data && rle_data_size < data_size) { 461 | data_size = rle_data_size; 462 | data = rle_data; 463 | use_rle = 1; 464 | } 465 | struct TargaHeader hdr = { 466 | .idlength = 0, 467 | .colourmaptype = bpp == 1 ? 1 : 0, 468 | .datatypecode = bpp == 1 ? 469 | (use_rle ? TIT_RLE_COLOR_MAPPED : TIT_COLOR_MAPPED) : 470 | (use_rle ? TIT_RLE_TRUE_COLOR : TIT_TRUE_COLOR), 471 | .colourmaporigin = 0, 472 | .colourmaplength = bpp == 1 ? palcount : 0, 473 | .colourmapdepth = bpp == 1 ? 24 : 0, 474 | .x_origin = 0, 475 | .y_origin = d->height, /* image starts at the top */ 476 | .width = d->width, 477 | .height = d->height, 478 | .bitsperpixel = bpp*8, 479 | .imagedescriptor = 0x20, /* image starts at the top */ 480 | }; 481 | unsigned char hdr_buf[18]; 482 | TargaHeader_to_buf(&hdr, hdr_buf); 483 | fwrite(&hdr_buf, 1, sizeof hdr_buf, f); 484 | unsigned tmp; 485 | if(bpp == 1) for(i=0; i