#include #include #include "common.h" uint16_t Q; static int8_t next(FILE * input) { char c; while((c = getc(input)) != EOF) { Q++; if(c == ' ' || c == '\n' || c == '\t') return c; } return 0; } static int32_t getnum(FILE * input) { uint8_t sign = next(input), c; int32_t n = 0, q; if(sign != '\t' && sign != ' ') return 0; q = sign == '\t' ? -1 : 1; while((c = next(input)) != '\n') { n <<= 1; if(c == '\t') n++; if(!c) break; } return n * q; } static vector(char) getlab(FILE * input) { vector(char) label = NULL; uint8_t c; while((c = next(input)) != '\n') vector_push_back(label, c == '\t' ? 'T' : 'S'); vector_push_back(label, 0); return label; } static struct instruction_t parse_heap(FILE * input, void (*fatal)(char * s)) { switch(next(input)) { case ' ': return (struct instruction_t) { STO, 0 }; case '\t': return (struct instruction_t) { RCL, 0 }; default: fatal("|e, IMP heap"); return (struct instruction_t) { ERR, 0 }; } } static struct instruction_t parse_arith(FILE * input, void (*fatal)(char * s)) { switch(next(input)) { case ' ': switch(next(input)) { case ' ': return (struct instruction_t) { ADD, 0 }; case '\t': return (struct instruction_t) { SUB, 0 }; case '\n': return (struct instruction_t) { MUL, 0 }; default: fatal("e, IMP arith"); return (struct instruction_t) { ERR, 0 }; } case '\t': switch(next(input)) { case ' ': return (struct instruction_t) { DIV, 0 }; case '\t': return (struct instruction_t) { MOD, 0 }; default: fatal("|e, IMP arith"); return (struct instruction_t) { ERR, 0 }; } default: fatal("|e, IMP arith"); return (struct instruction_t) { ERR, 0 }; } } static struct instruction_t parse_io(FILE * input, void (*fatal)(char * s)) { switch(next(input)) { case ' ': switch(next(input)) { case ' ': return (struct instruction_t) { PUTC, 0 }; case '\t': return (struct instruction_t) { PUTN, 0 }; default: fatal("|e, IMP io"); return (struct instruction_t) { ERR, 0 }; } case '\t': switch(next(input)) { case ' ': return (struct instruction_t) { GETC, 0 }; case '\t': return (struct instruction_t) { GETN, 0 }; default: fatal("|e, IMP io"); return (struct instruction_t) { ERR, 0 }; } default: fatal("|e, IMP io"); return (struct instruction_t) { ERR, 0 }; } } static struct instruction_t parse_stack(FILE * input, void (*fatal)(char * s)) { switch(next(input)) { case ' ': return (struct instruction_t) { PSH, getnum(input) }; case '\n': switch(next(input)) { case ' ': return (struct instruction_t) { DUP, 0 }; case '\t': return (struct instruction_t) { XCHG, 0 }; case '\n': return (struct instruction_t) { DROP, 0 }; default: fatal("e, IMP stk"); return (struct instruction_t) { ERR, 0 }; } case '\t': switch(next(input)) { case ' ': return (struct instruction_t) { COPY, .data = getnum(input) }; case '\n': return (struct instruction_t) { SLIDE, .data = getnum(input) }; default: fatal("|e, IMP stk"); return (struct instruction_t) { ERR, 0 }; } default: fatal("e, IMP stk"); return (struct instruction_t) { ERR, 0 }; } } static struct instruction_t parse_flow(FILE * input, void (*fatal)(char * s)) { switch(next(input)) { case ' ': switch(next(input)) { case ' ':return (struct instruction_t) { LBL, .label = getlab(input) }; case '\t': return (struct instruction_t) { CALL, .label = getlab(input) }; case '\n': return (struct instruction_t) { JMP, .label = getlab(input) }; default: fatal("e, IMP flow"); return (struct instruction_t) { ERR, 0 }; } case '\t': switch(next(input)) { case ' ': return (struct instruction_t) { BZ, .label = getlab(input) }; case '\t': return (struct instruction_t) { BLTZ, .label = getlab(input) }; case '\n': return (struct instruction_t) { RET, 0 }; default: fatal("e, IMP flow"); return (struct instruction_t) { ERR, 0 }; } case '\n': if(next(input) == '\n') return (struct instruction_t) { STOP, 0 }; else { fatal("bad end, IMP flow"); return (struct instruction_t) { ERR, 0 }; } default: fatal("e, IMP flow"); return (struct instruction_t) { ERR, 0 }; } } struct _label_t { int32_t id; char * name; struct instruction_t parent; }; static struct _label_t * getlabel(vector(struct _label_t) vec, char * label_text) { vector_foreach(struct _label_t, it, vec) if(!strcmp(label_text, it->name)) return it; return NULL; } static vector(struct label_t) fixup_labels(vector(struct instruction_t) program, void(*warn)(char * s)) { vector(struct _label_t) labels = NULL; vector(struct label_t) labs = NULL; int32_t labid = 1; vector_foreach(struct instruction_t, it, program) if(it->type == LBL) { struct _label_t * l; if((l = getlabel(labels, it->label)) != NULL) { warn("duplicated label."); vector_free(it->label); it->data = l->id; continue; } struct _label_t lab = {.id = labid, .parent = *it, .name = it->label}; struct label_t public_label = {.id = labid, .parent = it}; it->data = labid++; vector_push_back(labels, lab); vector_push_back(labs, public_label); } vector_foreach(struct instruction_t, it, program) switch(it->type) { case CALL: case JMP: case BZ: case BLTZ: { struct _label_t * l; if((l = getlabel(labels, it->label)) != NULL) { vector_free(it->label); it->data = l->id; } else { vector_free(it->label); warn("dead label."); it->data = 0; } } } vector_foreach(struct _label_t, it, labels) vector_free(it->name); vector_free(labels); return labs; } struct parse_result_t parse(FILE * input, void (*fatal)(char * s), void (*warn)(char * s)) { vector(struct instruction_t) q = NULL; struct instruction_t cur; while(!feof(input)) { switch(next(input)) { case '\t': switch(next(input)) { case '\t': vector_push_back(q, cur = parse_heap(input, fatal)); break; case ' ': vector_push_back(q, cur = parse_arith(input, fatal)); break; case '\n': vector_push_back(q, cur = parse_io(input, fatal)); break; default: fatal("? , E, IMP N/A"); return (struct parse_result_t) { NULL, NULL }; } break; case ' ': vector_push_back(q, cur = parse_stack(input, fatal)); break; case '\n': vector_push_back(q, cur = parse_flow(input, fatal)); break; } if(cur.type == ERR) return (struct parse_result_t) { NULL, NULL }; } return (struct parse_result_t) { q, fixup_labels(q, warn) }; } #include #include #include #include #include #include #include "vector.h" #include "common.h" static void append_code(char ** buf, char * format, ...) { va_list args, args2; va_start(args, format); va_copy(args2, args); uint32_t buflen = *buf ? strlen(*buf) : 0; uint32_t length = 1 + buflen + vsnprintf(NULL, 0, format, args); va_end(args); if(*buf) *buf = realloc(*buf, length); else *buf = malloc(length); assert(*buf); vsnprintf(*buf + buflen, length - buflen, format, args2); va_end(args2); } #define emit(x) append_code(&code, x) #define emitf(x,...) append_code(&code, x, __VA_ARGS__) char * compile(struct parse_result_t program) { unsigned callid = vector_size(program.labels); char * code = NULL; emit( "#include \"vector.h\"\n" "#ifndef DURING_JIT\n" "\t#include \n" "\t#include \n" "\t#include \n" "#else\n" "\tint putchar(int);\n" "\tint getchar(void);\n" "\tint printf(char * fmt, ...);\n" "\tint scanf(char * fmt, ...);\n" "\ttypedef int int32_t;\n" "\t#define NULL ((void *) 0)\n" "#endif\n" "\n" "static vector(int32_t) stack;\n" "static vector(int32_t) heap;\n" "static vector(int32_t) callstack;\n" "static int32_t lhs, rhs, tmp, ip;\n" "\n" "#define AT(x, y) vector_end(x)[-y]\n" "#define BILOAD \\\n" "\trhs = AT(stack, 1); \\\n" "\tlhs = AT(stack, 2); \\\n" "\tvector_pop_back(stack); \\\n" "\tvector_pop_back(stack)\n" "\n" "int main(void) {\n" "\tbranch: switch(ip) {\n" "case 0:\n" ); vector_foreach(struct instruction_t, ins, program.program) { switch(ins->type) { case GETC: emit( "\trhs = AT(stack, 1);\n" "\tvector_pop_back(stack);\n" "\twhile(vector_size(heap) <= (unsigned) rhs)\n" "\t\tvector_push_back(heap, 0);\n" "\theap[rhs] = getchar();\n" "\tif(heap[rhs] < 0) heap[rhs] = -1;\n" ); break; case PUTC: emit( "\tputchar(AT(stack, 1));\n" "\tvector_pop_back(stack);\n" ); break; case GETN: emit( "\trhs = AT(stack, 1);\n" "\tvector_pop_back(stack);\n" "\twhile(vector_size(heap) <= (unsigned) rhs)\n" "\t\tvector_push_back(heap, 0);\n" "\tscanf(\"%%d\", &tmp);\n" "\theap[rhs] = tmp;\n" ); break; case PUTN: emit( "\tprintf(\"%%d\", AT(stack, 1));\n" "\tvector_pop_back(stack);\n" ); break; case PSH: emitf("\tvector_push_back(stack, %d);\n", ins->data); break; case DUP: emit("\tvector_push_back(stack, AT(stack, 1));\n"); break; case XCHG: emit( "\ttmp = AT(stack, 2);\n" "\tAT(stack, 2) = AT(stack, 1);\n" "\tAT(stack, 1) = tmp;\n" ); break; case DROP: emit("\tvector_pop_back(stack);\n"); break; case ADD: emit("\tBILOAD; vector_push_back(stack, lhs + rhs);\n"); break; case SUB: emit("\tBILOAD; vector_push_back(stack, lhs - rhs);\n"); break; case DIV: emit("\tBILOAD; vector_push_back(stack, lhs / rhs);\n"); break; case MUL: emit("\tBILOAD; vector_push_back(stack, lhs * rhs);\n"); break; case MOD: emit("\tBILOAD; vector_push_back(stack, lhs %% rhs);\n"); break; case STOP: emit("\tgoto end;\n"); break; case STO: emit( "\tBILOAD;\n" "\twhile(vector_size(heap) <= (unsigned) lhs)\n" "\t\tvector_push_back(heap, 0);\n" "\theap[lhs] = rhs;\n" ); break; case RCL: emit( "\tlhs = AT(stack, 1);\n" "\tvector_pop_back(stack);\n" "\tif(vector_size(heap) <= (unsigned) lhs)\n" "\t\tvector_push_back(stack, 0);\n" "\telse\n" "\t\tvector_push_back(stack, heap[lhs]);\n" ); break; case LBL: emitf("case %d:;\n", ins->data); break; case JMP: emitf( "\tip = %d;\n" "\tgoto branch;\n" , ins->data); break; case BZ: emitf( "\tlhs = AT(stack, 1);\n" "\tvector_pop_back(stack);\n" "\tif(lhs == 0) {\n" "\t\tip = %d;\n" "\t\tgoto branch;\n" "\t}\n" , ins->data); break; case BLTZ: emitf( "\tlhs = AT(stack, 1);\n" "\tvector_pop_back(stack);\n" "\tif(lhs < 0) {\n" "\t\tip = %d;\n" "\t\tgoto branch;\n" "\t}\n" , ins->data); break; case CALL: emitf( "\tvector_push_back(callstack, %d);\n" "\tip = %d;\n" "\tgoto branch;\n" "case %d:\n" , callid + 1, ins->data, callid + 1); callid++; break; case RET: emit( "\tip = AT(callstack, 1);\n" "\tvector_pop_back(callstack);\n" "\tgoto branch;\n" ); break; case COPY: emitf( "\tvector_push_back(stack, stack[vector_size(stack) - 1 - %d]);\n" , ins->data); break; case SLIDE: emitf( "\tfor(rhs = 0; rhs < %d; rhs++)\n" "\t\tvector_erase(stack, vector_size(stack) - 1);\n" , ins->data); break; } } emit( "\t}\n" "\tend:\n" "\tvector_free(stack);\n" "\tvector_free(heap);\n" "\tvector_free(callstack);\n" "}\n" ); return code; } #ifndef _MAP_H_ #define _MAP_H_ #define hashmap_str_lit(str) (str), sizeof(str) - 1 #define hashmap_static_arr(arr) (arr), sizeof(arr) #include #include #include #define HASHMAP_HASH_INIT 2166136261u #ifdef DIRAC_64 static uint32_t hash_data(const unsigned char* data, size_t size) { size_t nblocks = size / 8; uint64_t hash = HASHMAP_HASH_INIT, last; size_t i; for (i = 0; i < nblocks; ++i) { hash ^= (uint64_t)data[0] << 0 | (uint64_t)data[1] << 8 | (uint64_t)data[2] << 16 | (uint64_t)data[3] << 24 | (uint64_t)data[4] << 32 | (uint64_t)data[5] << 40 | (uint64_t)data[6] << 48 | (uint64_t)data[7] << 56; hash *= 0xbf58476d1ce4e5b9; data += 8; } last = size & 0xff; switch (size % 8) { case 7: last |= (uint64_t)data[6] << 56; /* fallthrough */ case 6: last |= (uint64_t)data[5] << 48; /* fallthrough */ case 5: last |= (uint64_t)data[4] << 40; /* fallthrough */ case 4: last |= (uint64_t)data[3] << 32; /* fallthrough */ case 3: last |= (uint64_t)data[2] << 24; /* fallthrough */ case 2: last |= (uint64_t)data[1] << 16; /* fallthrough */ case 1: last |= (uint64_t)data[0] << 8; hash ^= last; hash *= 0xd6e8feb86659fd93; } /* compress to a 32-bit result. also serves as a finalizer. */ return hash ^ hash >> 32; } #else #ifdef DIRAC_32 static uint32_t hash_data(const unsigned char* data, size_t size) { int i, j; unsigned int byte, crc, mask; i = 0; crc = 0xFFFFFFFF; while (i < size) { byte = data[i]; crc = crc ^ byte; for (j = 7; j >= 0; j--) { mask = -(crc & 1); crc = (crc >> 1) ^ (0xEDB88320 & mask); } i = i + 1; } return ~crc; } #else static uint16_t hash_data(const unsigned char* data, size_t size) { unsigned char x; unsigned short crc = 0xFFFF; while (size--){ x = crc >> 8 ^ *data++; x ^= x>>4; crc = (crc << 8) ^ ((unsigned short)(x << 12)) ^ ((unsigned short)(x <<5)) ^ ((unsigned short)x); } return crc; } #endif #endif /* hashmaps can associate keys with pointer values or integral types. */ typedef struct hashmap hashmap; /* a callback type used for iterating over a map/freeing entries: * `void (void* key, size_t size, uintptr_t value, void* usr)` * `usr` is a user pointer which can be passed through `hashmap_iterate`. */ typedef void (*hashmap_callback)(void *key, size_t ksize, uintptr_t value, void *usr); static hashmap* hashmap_create(void); /* only frees the hashmap object and buckets. * does not call free on each element's `key` or `value`. * to free data associated with an element, call `hashmap_iterate`. */ static void hashmap_free(hashmap* map); /* does not make a copy of `key`. * you must copy it yourself if you want to guarantee its lifetime, * or if you intend to call `hashmap_key_free`. */ static void hashmap_set(hashmap* map, void* key, size_t ksize, uintptr_t value); /* adds an entry if it doesn't exist, using the value of `*out_in`. * if it does exist, it sets value in `*out_in`, meaning the value * of the entry will be in `*out_in` regardless of whether or not * it existed in the first place. * returns true if the entry already existed, returns false otherwise. */ static int hashmap_get_set(hashmap* map, void* key, size_t ksize, uintptr_t* out_in); /* similar to `hashmap_set()`, but when overwriting an entry, * you'll be able properly free the old entry's data via a callback. * unlike `hashmap_set()`, this function will overwrite the original key pointer, * which means you can free the old key in the callback if applicable. */ static void hashmap_set_free(hashmap* map, void* key, size_t ksize, uintptr_t value, hashmap_callback c, void* usr); static int hashmap_get(hashmap* map, void* key, size_t ksize, uintptr_t* out_val); static int hashmap_size(hashmap* map); /* iterate over the map, calling `c` on every element. * goes through elements in the order they were added. * the element's key, key size, value, and `usr` will be passed to `c`. */ static void hashmap_iterate(hashmap* map, hashmap_callback c, void* usr); #define HASHMAP_DEFAULT_CAPACITY 5 #define HASHMAP_MAX_LOAD 0.75f #define HASHMAP_RESIZE_FACTOR 2 struct bucket { /* `next` must be the first struct element. * changing the order will break multiple functions */ struct bucket* next; /* key, key size, key hash, and associated value */ void* key; size_t ksize; uint32_t hash; uintptr_t value; }; struct hashmap { struct bucket* buckets; int capacity; int count; /* a linked list of all valid entries, in order */ struct bucket* first; /* lets us know where to add the next element */ struct bucket* last; }; static hashmap* hashmap_create(void) { hashmap* m = malloc(sizeof(hashmap)); m->capacity = HASHMAP_DEFAULT_CAPACITY; m->count = 0; m->buckets = calloc(HASHMAP_DEFAULT_CAPACITY, sizeof(struct bucket)); m->first = NULL; /* this prevents branching in hashmap_set. * m->first will be treated as the "next" pointer in an imaginary bucket. * when the first item is added, m->first will be set to the correct address. */ m->last = (struct bucket*)&m->first; return m; } static void hashmap_free(hashmap* m) { free(m->buckets); free(m); } /* puts an old bucket into a resized hashmap */ static struct bucket* resize_entry(hashmap* m, struct bucket* old_entry) { uint32_t index = old_entry->hash % m->capacity; for (;;) { struct bucket* entry = &m->buckets[index]; if (entry->key == NULL) { *entry = *old_entry; return entry; } index = (index + 1) % m->capacity; } } static void hashmap_resize(hashmap* m) { struct bucket* old_buckets = m->buckets; m->capacity *= HASHMAP_RESIZE_FACTOR; m->buckets = calloc(m->capacity, sizeof(struct bucket)); m->last = (struct bucket*)&m->first; do { m->last->next = resize_entry(m, m->last->next); m->last = m->last->next; } while (m->last->next != NULL); free(old_buckets); } static struct bucket* find_entry(hashmap* m, void* key, size_t ksize, uint32_t hash) { uint32_t index = hash % m->capacity; for (;;) { struct bucket* entry = &m->buckets[index]; /* kind of a thicc condition; */ /* I didn't want this to span multiple if statements or functions. */ if (entry->key == NULL || /* compare sizes, then hashes, then key data as a last resort. */ (entry->ksize == ksize && entry->hash == hash && memcmp(entry->key, key, ksize) == 0)) { /* return the entry if a match or an empty bucket is found */ return entry; } index = (index + 1) % m->capacity; } } static void hashmap_set(hashmap* m, void* key, size_t ksize, uintptr_t val) { uint32_t hash; struct bucket * entry; if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) hashmap_resize(m); hash = hash_data(key, ksize); entry = find_entry(m, key, ksize, hash); if (entry->key == NULL) { m->last->next = entry; m->last = entry; entry->next = NULL; ++m->count; entry->key = key; entry->ksize = ksize; entry->hash = hash; } entry->value = val; } static int hashmap_get_set(hashmap* m, void* key, size_t ksize, uintptr_t* out_in) { uint32_t hash; struct bucket * entry; if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) hashmap_resize(m); hash = hash_data(key, ksize); entry = find_entry(m, key, ksize, hash); if (entry->key == NULL) { m->last->next = entry; m->last = entry; entry->next = NULL; ++m->count; entry->value = *out_in; entry->key = key; entry->ksize = ksize; entry->hash = hash; return 0; } *out_in = entry->value; return 1; } static void hashmap_set_free(hashmap* m, void* key, size_t ksize, uintptr_t val, hashmap_callback c, void* usr) { uint32_t hash; struct bucket * entry; if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) hashmap_resize(m); hash = hash_data(key, ksize); entry = find_entry(m, key, ksize, hash); if (entry->key == NULL) { m->last->next = entry; m->last = entry; entry->next = NULL; ++m->count; entry->key = key; entry->ksize = ksize; entry->hash = hash; entry->value = val; return; } /* allow the callback to free entry data. * use old key and value so the callback can free them. * the old key and value will be overwritten after this call. */ c(entry->key, ksize, entry->value, usr); /* overwrite the old key pointer in case the callback frees it. */ entry->key = key; entry->value = val; } static int hashmap_get(hashmap* m, void* key, size_t ksize, uintptr_t* out_val) { uint32_t hash = hash_data(key, ksize); struct bucket* entry = find_entry(m, key, ksize, hash); /* if there is no match, output val will just be NULL */ *out_val = entry->value; return entry->key != NULL; } static int hashmap_size(hashmap* m) { return m->count; } static void hashmap_iterate(hashmap* m, hashmap_callback c, void* user_ptr) { /* loop through the linked list of valid entries * this way we can skip over empty buckets */ struct bucket* current = m->first; int co = 0; while (current != NULL) { c(current->key, current->ksize, current->value, user_ptr); current = current->next; if (co > 1000) { break; } co++; } } #endif /* Copyright (C) 2006-2016,2018 Con Kolivas Copyright (C) 2011, 2022 Peter Hyman Copyright (C) 1998-2003 Andrew Tridgell Copyright (C) 2022 Kamila Szewczyk This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "../include/config.h" #include "../include/runzip.h" #include "../include/rzip.h" #include "../include/stream.h" #include "../include/util.h" #define MAGIC_LEN (20) // new v 0.9 magic header #define MAGIC_V8_LEN (18) // new v 0.8 magic header #define OLD_MAGIC_LEN (24) // Just to read older versions #define MAGIC_HEADER (6) // to validate file initially static void release_hashes(rzip_control * control); static i64 fdout_seekto(rzip_control * control, i64 pos) { if (TMP_OUTBUF) { pos -= control->out_relofs; control->out_ofs = pos; if (unlikely(pos > control->out_len || pos < 0)) { print_err("Trying to seek to %'" PRId64 " outside tmp outbuf in fdout_seekto\n", pos); return -1; } return 0; } return lseek(control->fd_out, pos, SEEK_SET); } i64 get_ram(rzip_control * control) { #ifdef __APPLE__ #include int mib[2]; size_t len; i64 ramsize; mib[0] = CTL_HW; mib[1] = HW_MEMSIZE; sysctl(mib, 2, &ramsize, &len, NULL, 0); #elif defined(__OpenBSD__) #include struct rlimit rl; i64 ramsize = (i64)sysconf(_SC_PHYS_PAGES) * PAGE_SIZE; /* Raise limits all the way to the max */ if (getrlimit(RLIMIT_DATA, &rl) == -1) fatal(("Failed to get limits in get_ram\n"), -1); rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_DATA, &rl) == -1) fatal(("Failed to set limits in get_ram\n"), -1); /* Declare detected RAM to be either the max RAM available from physical memory or the max RAM allowed by RLIMIT_DATA, whatever is smaller, to prevent the heuristics from selecting compression windows which cause mrzip to go into deep swap */ if (rl.rlim_max < ramsize) return rl.rlim_max; return ramsize; #else /* __APPLE__ or __Open_BSD__ */ i64 ramsize; FILE * meminfo; char aux[256]; ramsize = (i64)sysconf(_SC_PHYS_PAGES) * PAGE_SIZE; if (ramsize <= 0) { /* Workaround for uclibc which doesn't properly support sysconf */ if (!(meminfo = fopen("/proc/meminfo", "r"))) fatal("Failed to open /proc/meminfo\n"); while (!feof(meminfo) && !fscanf(meminfo, "MemTotal: %" PRId64 " kB", &ramsize)) { if (unlikely(fgets(aux, sizeof(aux), meminfo) == NULL)) { fclose(meminfo); fatal("Failed to fgets in get_ram\n"); } } if (fclose(meminfo) == -1) fatal("Failed to close /proc/meminfo\n"); ramsize *= 1024; } #endif if (ramsize <= 0) fatal("No memory or can't determine ram? Can't continue.\n"); return ramsize; } i64 nloops(i64 seconds, uchar * b1, uchar * b2) { i64 nloops; int nbits; nloops = ARBITRARY_AT_EPOCH * pow(MOORE_TIMES_PER_SECOND, seconds); if (nloops < ARBITRARY) nloops = ARBITRARY; for (nbits = 0; nloops > 255; nbits++) nloops = nloops >> 1; *b1 = nbits; *b2 = nloops; return nloops << nbits; } bool write_magic(rzip_control * control) { unsigned char magic[MAGIC_LEN] = { 'M', 'R', 'Z', 'I', MRZIP_MAJOR, MRZIP_MINOR }; /* In encrypted files, the size is left unknown * and instead the salt is stored here to preserve space. */ // FIXME. I think we can do better. 8 bytes is no reason to save space if (ENCRYPT) { memcpy(&magic[6], &control->salt, 8); magic[15] = control->enc_code; /* write whatever encryption code */ } else if (control->eof) { i64 esize = htole64(control->st_size); // we know file size even when piped memcpy(&magic[6], &esize, 8); } /* This is a flag that the archive contains an hash sum at the end * which can be used as an integrity check instead of crc check. * crc is still stored for compatibility with 0.5 versions. */ if (HAS_HASH) magic[14] = control->hash_code; /* write whatever hash */ magic[16] = 0; /* save LZMA dictionary size */ if (ZPAQ_COMPRESS) { /* Save zpaq compression level and block size as one byte */ /* High order bits = 128 + (16 * Compression Level 3-5) * Low order bits = Block Size 1-11 * 128 necessary to distinguish in decoding LZMA which is 1-40 * 1CCC BBBB in binary */ magic[17] = 0b10000000 + (control->zpaq_level << 4) + control->zpaq_bs; /* Decoding would be * magic byte & 127 (clear high bit) * zpaq_bs = magic byte & 0X0F * zpaq_level = magic_byte >> 4 */ } else if (BZIP3_COMPRESS) { /* Save block size. ZPAQ compression level is from 3 to 5, so this is sound. bzip3 blocksize is from 1 to 8 (or 0 to 7). */ magic[17] = 0b11110000 + bzip3_prop_from_block_size(control->bzip3_block_size); } /* save compression levels * high order bits, rzip compression level * low order bits mrzip compression level */ magic[18] = (control->rzip_compression_level << 4) + control->compression_level; /* store comment length */ magic[19] = (char)control->comment_length; if (unlikely(fdout_seekto(control, 0))) fatal("Failed to seek to BOF to write Magic Header\n"); if (unlikely(put_fdout(control, magic, MAGIC_LEN) != MAGIC_LEN)) fatal("Failed to write magic header\n"); /* now write comment if any */ if (magic[19]) { if (unlikely(put_fdout(control, control->comment, control->comment_length) != control->comment_length)) fatal("Failed to write comment after magic header\n"); } control->magic_written = 1; return true; } static inline i64 enc_loops(uchar b1, uchar b2) { return (i64)b2 << (i64)b1; } // check for comments // Called only if comment length > 0 static void get_comment(rzip_control * control, int fd_in, unsigned char * magic) { if (unlikely(!(control->comment = malloc(magic[19] + 1)))) fatal("Failed to allocate memory for comment\n"); /* read comment */ if (unlikely(read(fd_in, control->comment, magic[19]) != magic[19])) fatal("Failed to read comment\n"); control->comment_length = magic[19]; control->comment[control->comment_length] = '\0'; return; } // retriev lzma properties static void get_hash_from_magic(rzip_control * control, unsigned char * magic) { /* Whether this archive contains hash data at the end or not */ if (*magic > 0 && *magic <= MAXHASH) { control->flags |= FLAG_HASHED; control->hash_code = *magic; /* set hash code */ control->hash_label = &hashes[control->hash_code].label[0]; control->hash_gcode = &hashes[control->hash_code].gcode; control->hash_len = &hashes[control->hash_code].length; } else print_verbose("Unknown hash, falling back to CRC\n"); return; } // get encrypted salt static void get_encryption(rzip_control * control, unsigned char * magic, unsigned char * salt) { if (*magic > 0 && *magic <= MAXENC) { control->flags |= FLAG_ENCRYPT; control->enc_code = *magic; /* In encrypted files, the size field is used to store the salt * instead and the size is unknown, just like a STDOUT chunked * file */ memcpy(&control->salt, salt, 8); control->st_size = 0; control->encloops = enc_loops(control->salt[0], control->salt[1]); } else if (ENCRYPT) { print_err("Asked to decrypt a non-encrypted archive. Bypassing decryption. May fail!\n"); control->flags &= ~FLAG_ENCRYPT; control->enc_code = 0; } control->enc_label = &encryptions[control->enc_code].label[0]; control->enc_gcode = &encryptions[control->enc_code].gcode; control->enc_keylen = &encryptions[control->enc_code].keylen; control->enc_ivlen = &encryptions[control->enc_code].ivlen; return; } // expected size static void get_expected_size(rzip_control * control, unsigned char * magic) { i64 expected_size; memcpy(&expected_size, &magic[6], 8); control->st_size = le64toh(expected_size); return; } // new mrzip v8 magic header format. static void get_magic_v8(rzip_control * control, unsigned char * magic) { int i; if (!magic[15]) // not encrypted get_expected_size(control, magic); get_encryption(control, &magic[15], &magic[6]); if ((magic[17] & 0b10000000)) // bzip3 or zpaq block sizes/levels stored { if ((magic[17] & 0b11110000) == 0b11110000) { // bzip3 block size control->bzip3_bs = magic[17] & 0b00001111; // bzip3 block size code 0 to 8 control->bzip3_block_size = BZIP3_BLOCK_SIZE_FROM_PROP(control->bzip3_bs); // Real Block Size } else // zpaq block and compression level stored { control->zpaq_bs = magic[17] & 0b00001111; // low order bits are block size magic[17] &= 0b01110000; // strip high bit control->zpaq_level = magic[17] >> 4; // divide by 16 } } get_hash_from_magic(control, &magic[14]); return; } // new mrzip v9 magic header format. static void get_magic_v9(rzip_control * control, int fd_in, unsigned char * magic) { /* get compression levels * rzip level is high order bits * mrzip level is low order bits */ control->compression_level = magic[18] & 0b00001111; control->rzip_compression_level = magic[18] >> 4; if (magic[19]) /* get comment if there is one */ get_comment(control, fd_in, magic); return; } static bool get_magic(rzip_control * control, int fd_in, unsigned char * magic) { memcpy(&control->major_version, &magic[4], 1); memcpy(&control->minor_version, &magic[5], 1); /* zero out compression levels so info does not show for earlier versions */ control->rzip_compression_level = control->compression_level = 0; /* remove checks for mrzip < 0.6 */ if (control->major_version == 0) { switch (control->minor_version) { case 9: /* version 0.9 adds two bytes */ get_magic_v8(control, magic); get_magic_v9(control, fd_in, magic); break; default: print_err("mrzip version %d.%d archive is not supported. Aborting\n", control->major_version, control->minor_version); return false; } } return true; } static bool read_magic(rzip_control * control, int fd_in, i64 * expected_size) { unsigned char magic[OLD_MAGIC_LEN]; // Make at least big enough for old magic int bytes_to_read; // simplify reading of magic memset(magic, 0, sizeof(magic)); /* Initially read only file type and version */ if (unlikely(read(fd_in, magic, MAGIC_HEADER) != MAGIC_HEADER)) fatal("Failed to read initial magic header\n"); if (unlikely(strncmp(magic, "MRZI", 4))) fatal("Not an mrzip file\n"); if (magic[4] == 0) { if (magic[5] < 8) /* old magic */ bytes_to_read = OLD_MAGIC_LEN; else if (magic[5] == 8) /* 0.8 file */ bytes_to_read = MAGIC_V8_LEN; else /* ASSUME current version */ bytes_to_read = MAGIC_LEN; if (unlikely(read(fd_in, &magic[6], bytes_to_read - MAGIC_HEADER) != bytes_to_read - MAGIC_HEADER)) fatal("Failed to read magic header\n"); } if (unlikely(!get_magic(control, fd_in, magic))) return false; *expected_size = control->st_size; return true; } /* show mrzip version * helps preserve output format when validating */ static void show_version(rzip_control * control) { print_verbose("Detected mrzip version %'d.%'d file.\n", control->major_version, control->minor_version); } /* preserve ownership and permissions where possible */ static bool preserve_perms(rzip_control * control, int fd_in, int fd_out) { struct stat st; if (unlikely(fstat(fd_in, &st))) fatal("Failed to fstat input file\n"); if (unlikely(fchmod(fd_out, (st.st_mode & 0666)))) print_verbose("Warning, unable to set permissions on %s\n", control->outfile); /* chown fail is not fatal_return(( */ if (unlikely(fchown(fd_out, st.st_uid, st.st_gid))) print_verbose("Warning, unable to set owner on %s\n", control->outfile); return true; } static bool preserve_times(rzip_control * control, int fd_in) { struct utimbuf times; struct stat st; if (unlikely(fstat(fd_in, &st))) fatal("Failed to fstat input file\n"); times.actime = 0; times.modtime = st.st_mtime; if (unlikely(utime(control->outfile, ×))) print_verbose("Warning, unable to set time on %s\n", control->outfile); return true; } /* Open a temporary outputfile to emulate stdout */ int open_tmpoutfile(rzip_control * control) { int fd_out; if (STDOUT && !TEST_ONLY) print_verbose("Outputting to stdout.\n"); control->outfile = realloc(NULL, strlen(control->tmpdir) + 16); if (unlikely(!control->outfile)) fatal("Failed to allocate outfile name\n"); strcpy(control->outfile, control->tmpdir); strcat(control->outfile, "mrzipout.XXXXXX"); fd_out = mkstemp(control->outfile); if (fd_out == -1) fatal("Failed to create out tmpfile: %s\n", control->outfile); register_outfile(control, control->outfile, TEST_ONLY || STDOUT || !KEEP_BROKEN); return fd_out; } static bool fwrite_stdout(rzip_control * control, void * buf, i64 len) { uchar * offset_buf = buf; ssize_t ret, nmemb; i64 total; total = 0; while (len > 0) { nmemb = len; ret = fwrite(offset_buf, 1, nmemb, control->outFILE); if (unlikely(ret == -1)) fatal("Failed to fwrite %'" PRId64 " bytes in fwrite_stdout\n", nmemb); len -= ret; offset_buf += ret; total += ret; } fflush(control->outFILE); return true; } bool write_fdout(rzip_control * control, void * buf, i64 len) { uchar * offset_buf = buf; ssize_t ret, nmemb; while (len > 0) { nmemb = len; ret = write(control->fd_out, offset_buf, (size_t)nmemb); /* error if ret == -1 only. Otherwise, buffer not wholly written */ if (unlikely(ret == -1)) /* error, not underflow */ fatal("Failed to write %'" PRId64 " bytes to fd_out in write_fdout\n", nmemb); len -= ret; offset_buf += ret; } return true; } bool flush_tmpoutbuf(rzip_control * control) { if (!TEST_ONLY) { print_maxverbose("Dumping buffer to physical file.\n"); if (STDOUT) { if (unlikely(!fwrite_stdout(control, control->tmp_outbuf, control->out_len))) return false; } else { if (unlikely(!write_fdout(control, control->tmp_outbuf, control->out_len))) return false; } } control->out_relofs += control->out_len; control->out_ofs = control->out_len = 0; return true; } /* Dump temporary outputfile to perform stdout */ bool dump_tmpoutfile(rzip_control * control, int fd_out) { FILE * tmpoutfp; int tmpchar; if (unlikely(fd_out == -1)) fatal("Failed: No temporary outfile created, unable to do in ram\n"); /* flush anything not yet in the temporary file */ fsync(fd_out); tmpoutfp = fdopen(fd_out, "r"); if (unlikely(tmpoutfp == NULL)) fatal("Failed to fdopen out tmpfile\n"); rewind(tmpoutfp); if (!TEST_ONLY) { print_verbose("Dumping temporary file to control->outFILE.\n"); while ((tmpchar = fgetc(tmpoutfp)) != EOF) putchar(tmpchar); fflush(control->outFILE); rewind(tmpoutfp); } if (unlikely(ftruncate(fd_out, 0))) fatal("Failed to ftruncate fd_out in dump_tmpoutfile\n"); return true; } /* Used if we're unable to read STDIN into the temporary buffer, shunts data * to temporary file */ bool write_fdin(rzip_control * control) { uchar * offset_buf = control->tmp_inbuf; i64 len = control->in_len; ssize_t ret; while (len > 0) { ret = len; ret = write(control->fd_in, offset_buf, (size_t)ret); if (unlikely(ret == -1)) fatal("Failed to write to fd_in in write_fdin\n"); len -= ret; offset_buf += ret; } return true; } /* Open a temporary inputfile to perform stdin decompression */ int open_tmpinfile(rzip_control * control) { int fd_in = -1; /* Use temporary directory if there is one. /tmp is default */ control->infile = malloc(strlen(control->tmpdir) + 15); if (unlikely(!control->infile)) fatal("Failed to allocate infile name\n"); strcpy(control->infile, control->tmpdir); strcat(control->infile, "mrzipin.XXXXXX"); fd_in = mkstemp(control->infile); if (fd_in == -1) fatal("Failed to create in tmpfile: %s\n", control->infile); register_infile(control, control->infile, (DECOMPRESS || TEST_ONLY) && STDIN); /* Unlink temporary file immediately to minimise chance of files left * lying around */ if (unlikely(unlink(control->infile))) { close(fd_in); fatal("Failed to unlink tmpfile: %s\n", control->infile); } return fd_in; } static bool read_tmpinmagic(rzip_control * control, int fd_in) { /* just in case < 0.8 file */ char magic[OLD_MAGIC_LEN]; int bytes_to_read, i, tmpchar; memset(magic, 0, sizeof(magic)); /* Initially read only file type and version */ for (i = 0; i < MAGIC_HEADER; i++) { tmpchar = getchar(); if (unlikely(tmpchar == EOF)) fatal("Reached end of file on STDIN prematurely on magic read\n"); magic[i] = (char)tmpchar; } if (unlikely(strncmp(magic, "MRZI", 4))) fatal("Not an mrzip stream\n"); if (magic[4] == 0) { if (magic[5] < 8) /* old magic */ bytes_to_read = OLD_MAGIC_LEN; else if (magic[5] == 8) /* 0.8 file */ bytes_to_read = MAGIC_V8_LEN; else /* ASSUME current version */ bytes_to_read = MAGIC_LEN; for (; i < bytes_to_read; i++) { tmpchar = getchar(); if (unlikely(tmpchar == EOF)) fatal("Reached end of file on STDIN prematurely on magic read\n"); magic[i] = (char)tmpchar; } } return get_magic(control, fd_in, magic); } /* Read data from stdin into temporary inputfile */ bool read_tmpinfile(rzip_control * control, int fd_in) { FILE * tmpinfp; int tmpchar; if (fd_in == -1) return false; if (control->flags & FLAG_SHOW_PROGRESS) fprintf(control->msgout, "Copying from stdin.\n"); tmpinfp = fdopen(fd_in, "w+"); if (unlikely(tmpinfp == NULL)) fatal("Failed to fdopen in tmpfile\n"); while ((tmpchar = getchar()) != EOF) fputc(tmpchar, tmpinfp); fflush(tmpinfp); rewind(tmpinfp); return true; } /* To perform STDOUT, we allocate a proportion of ram that is then used as * a pseudo-temporary file */ static bool open_tmpoutbuf(rzip_control * control) { i64 maxlen = control->maxram; void * buf; while (42) { round_to_page(&maxlen); buf = malloc(maxlen); if (buf) { print_maxverbose("Malloced %'" PRId64 " for tmp_outbuf\n", maxlen); break; } maxlen = maxlen / 3 * 2; if (maxlen < 100000000) fatal("Unable to even malloc 100MB for tmp_outbuf\n"); } control->flags |= FLAG_TMP_OUTBUF; /* Allocate slightly more so we can cope when the buffer overflows and * fall back to a real temporary file */ control->out_maxlen = maxlen + control->page_size; control->tmp_outbuf = buf; if (!DECOMPRESS && !TEST_ONLY) control->out_ofs = control->out_len = MAGIC_LEN + control->comment_length; return true; } /* We've decided to use a temporary output file instead of trying to store * all the output buffer in ram so we can free up the ram and increase the * maximum sizes of ram we can allocate */ void close_tmpoutbuf(rzip_control * control) { control->flags &= ~FLAG_TMP_OUTBUF; dealloc(control->tmp_outbuf); control->usable_ram = control->maxram += control->ramsize / 18; } static bool open_tmpinbuf(rzip_control * control) { control->flags |= FLAG_TMP_INBUF; control->in_maxlen = control->maxram; control->tmp_inbuf = malloc(control->maxram + control->page_size); if (unlikely(!control->tmp_inbuf)) fatal("Failed to malloc tmp_inbuf in open_tmpinbuf\n"); return true; } void clear_tmpinbuf(rzip_control * control) { control->in_len = control->in_ofs = 0; } bool clear_tmpinfile(rzip_control * control) { if (unlikely(lseek(control->fd_in, 0, SEEK_SET))) fatal("Failed to lseek on fd_in in clear_tmpinfile\n"); if (unlikely(ftruncate(control->fd_in, 0))) fatal("Failed to truncate fd_in in clear_tmpinfile\n"); return true; } /* As per temporary output file but for input file */ void close_tmpinbuf(rzip_control * control) { control->flags &= ~FLAG_TMP_INBUF; dealloc(control->tmp_inbuf); control->usable_ram = control->maxram += control->ramsize / 18; } static int get_pass(rzip_control * control, char * s) { int len; memset(s, 0, PASS_LEN - SALT_LEN); if (control->passphrase) strncpy(s, control->passphrase, PASS_LEN - SALT_LEN - 1); else if (unlikely(fgets(s, PASS_LEN - SALT_LEN, stdin) == NULL)) fatal("Failed to retrieve passphrase\n"); len = strlen(s); if (len > 0 && ('\r' == s[len - 1] || '\n' == s[len - 1])) s[len - 1] = '\0'; if (len > 1 && ('\r' == s[len - 2] || '\n' == s[len - 2])) s[len - 2] = '\0'; len = strlen(s); if (unlikely(0 == len)) fatal("Empty passphrase\n"); return len; } static bool get_hash(rzip_control * control, int make_hash) { char *passphrase, *testphrase; struct termios termios_p; int prompt = control->passphrase == NULL; passphrase = calloc(PASS_LEN, 1); testphrase = calloc(PASS_LEN, 1); control->salt_pass = calloc(PASS_LEN, 1); control->hash = calloc(HASH_LEN, 1); if (unlikely(!passphrase || !testphrase || !control->salt_pass || !control->hash)) { dealloc(testphrase); dealloc(passphrase); dealloc(control->salt_pass); dealloc(control->hash); fatal("Failed to calloc encrypt buffers in get_hash\n"); } mlock(passphrase, PASS_LEN); mlock(testphrase, PASS_LEN); mlock(control->salt_pass, PASS_LEN); mlock(control->hash, HASH_LEN); /* mrzip library callback code removed */ /* Disable stdin echo to screen */ tcgetattr(fileno(stdin), &termios_p); termios_p.c_lflag &= ~ECHO; tcsetattr(fileno(stdin), 0, &termios_p); retry_pass: if (prompt) print_output("Enter passphrase: "); control->salt_pass_len = get_pass(control, passphrase) + SALT_LEN; if (prompt) print_output("\n"); if (make_hash) { if (prompt) print_output("Re-enter passphrase: "); get_pass(control, testphrase); if (prompt) print_output("\n"); if (strcmp(passphrase, testphrase)) { print_output("Passwords do not match. Try again.\n"); goto retry_pass; } } termios_p.c_lflag |= ECHO; tcsetattr(fileno(stdin), 0, &termios_p); memset(testphrase, 0, PASS_LEN); memcpy(control->salt_pass, control->salt, SALT_LEN); memcpy(control->salt_pass + SALT_LEN, passphrase, PASS_LEN - SALT_LEN); lrz_stretch(control); memset(passphrase, 0, PASS_LEN); munlock(passphrase, PASS_LEN); munlock(testphrase, PASS_LEN); dealloc(testphrase); dealloc(passphrase); return true; } static void release_hashes(rzip_control * control) { memset(control->salt_pass, 0, PASS_LEN); memset(control->hash, 0, HASH_LEN); munlock(control->salt_pass, PASS_LEN); munlock(control->hash, HASH_LEN); dealloc(control->salt_pass); dealloc(control->hash); } bool get_header_info(rzip_control * control, int fd_in, uchar * ctype, i64 * c_len, i64 * u_len, i64 * last_head, int chunk_bytes) { uchar enc_head[25 + SALT_LEN]; if (ENCRYPT) { // read in salt // first 8 bytes, instead of chunk bytes and size if (unlikely(read(fd_in, enc_head, SALT_LEN) != SALT_LEN)) fatal("Failed to read encrypted header in get_header_info\n"); } if (unlikely(read(fd_in, ctype, 1) != 1)) fatal("Failed to read in get_header_info\n"); *c_len = *u_len = *last_head = 0; /* remove checks for mrzip < 0.6 */ if (control->major_version == 0) { // header the same after v 0.4 except for chunk bytes int read_len; read_len = chunk_bytes; if (unlikely(read(fd_in, c_len, read_len) != read_len)) fatal("Failed to read in get_header_info\n"); if (unlikely(read(fd_in, u_len, read_len) != read_len)) fatal("Failed to read in get_header_info\n"); if (unlikely(read(fd_in, last_head, read_len) != read_len)) fatal("Failed to read_i64 in get_header_info\n"); *c_len = le64toh(*c_len); *u_len = le64toh(*u_len); *last_head = le64toh(*last_head); if (ENCRYPT) { // decrypt header suppressing printing max verbose message if (unlikely(!decrypt_header(control, enc_head, ctype, c_len, u_len, last_head, LRZ_VALIDATE))) fatal("Failed to decrypt header in get_header_info\n"); } } // control->major_version return true; } static double percentage(i64 num, i64 den) { double d_num, d_den; if (den < 100) { d_num = num * 100; d_den = den; if (!d_den) d_den = 1; } else { d_num = num; d_den = den / 100; } return d_num / d_den; } // If Decompressing or Testing, omit printing, just read file and see if valid // using construct if (INFO) // Encrypted files cannot be checked now bool get_fileinfo(rzip_control * control) { i64 u_len, c_len, second_last, last_head, utotal = 0, ctotal = 0, ofs, stream_head[2]; i64 expected_size, infile_size, chunk_size = 0, chunk_total = 0; int header_length = 0, stream = 0, chunk = 0; char *tmp, *infilecopy = NULL; char chunk_byte = 0; long double cratio, bpb; uchar ctype = 0; uchar save_ctype = 255; struct stat st; int fd_in; int lzma_ret; // Take out all STDIN checks struct stat fdin_stat; if (unlikely(stat(control->infile, &fdin_stat))) fatal("File %s not found...\n", control->infile); else if (unlikely(!S_ISREG(fdin_stat.st_mode))) fatal("File %s us not a regular file. mrzip cannot continue...\n", control->infile); else infilecopy = strdupa(control->infile); fd_in = open(infilecopy, O_RDONLY); if (unlikely(fd_in == -1)) fatal("Failed to open %s\n", infilecopy); /* Get file size */ if (unlikely(fstat(fd_in, &st))) fatal("bad magic file descriptor!?\n"); infile_size = st.st_size; /* Get decompressed size */ if (unlikely(!read_magic(control, fd_in, &expected_size))) goto error; if (INFO) show_version(control); // show version if not validating if (ENCRYPT) { /* can only show info for current mrzip files */ if (control->major_version == 0) { if (!control->salt_pass_len) // Only get password if needed if (unlikely(!get_hash(control, 0))) return false; } } /* remove checks for mrzip < 0.6 */ if (control->major_version == 0) { if (unlikely(read(fd_in, &chunk_byte, 1) != 1)) fatal("Failed to read chunk_byte in get_fileinfo\n"); if (unlikely(chunk_byte < 1 || chunk_byte > 8)) fatal("Invalid chunk bytes %'d\n", chunk_byte); if (unlikely(read(fd_in, &control->eof, 1) != 1)) fatal("Failed to read eof in get_fileinfo\n"); if (!ENCRYPT) { if (unlikely(read(fd_in, &chunk_size, chunk_byte) != chunk_byte)) fatal("Failed to read chunk_size in get_fileinfo\n"); chunk_size = le64toh(chunk_size); if (unlikely(chunk_size < 0)) fatal("Invalid chunk size %'" PRId64 "\n", chunk_size); /* set header offsets for earlier versions */ switch (control->minor_version) { case 9: ofs = 22 + control->comment_length; /* comment? Add length */ break; } ofs += chunk_byte; /* header length is the same for non-encrypted files */ header_length = 1 + (chunk_byte * 3); } else { /* ENCRYPTED */ chunk_byte = 8; // chunk byte size is always 8 for encrypted files chunk_size = 0; // chunk size is unknown with encrypted files header_length = 33; // 25 + 8 // salt is first 8 bytes if (control->major_version == 0) { switch (control->minor_version) { case 9: ofs = 22 + control->comment_length; break; default: fatal("Cannot decrypt earlier versions of mrzip\n"); break; } } } } next_chunk: stream = 0; stream_head[0] = 0; stream_head[1] = stream_head[0] + header_length; if (!ENCRYPT) { chunk_total += chunk_size; if (unlikely(chunk_byte && (chunk_byte > 8 || chunk_size <= 0))) fatal("Invalid chunk data\n"); } if (INFO) { print_verbose("Rzip chunk: %'d\n", ++chunk); print_verbose("Chunk byte width: %'d\n", chunk_byte); print_verbose("Chunk size: "); if (!ENCRYPT) print_verbose("%'" PRId64 "\n", chunk_size); else print_verbose("N/A %s Encrypted File\n", control->enc_label); } while (stream < NUM_STREAMS) { int block = 1; second_last = 0; if (unlikely(lseek(fd_in, stream_head[stream] + ofs, SEEK_SET) == -1)) fatal("Failed to seek to header data in get_fileinfo\n"); if (unlikely(!get_header_info(control, fd_in, &ctype, &c_len, &u_len, &last_head, chunk_byte))) return false; if (ENCRYPT && ctype != CTYPE_NONE) fatal("Invalid stream ctype (%02x) for encrypted file. Bad Password?\n", ctype); if (INFO) { print_verbose("Stream: %'d\n", stream); print_maxverbose("Offset: %'" PRId64 "\n", stream_head[stream] + ofs); print_verbose("%s\t%s\t%s\t%16s / %14s", "Block", "Comp", "Percent", "Comp Size", "UComp Size"); print_maxverbose("%18s : %14s", "Offset", "Head"); print_verbose("\n"); } do { i64 head_off; if (unlikely(last_head && last_head <= second_last)) fatal("Invalid earlier last_head position, corrupt archive.\n"); second_last = last_head; if (!ENCRYPT) { if (unlikely(last_head + ofs > infile_size)) fatal("Offset greater than archive size, likely corrupted/truncated archive.\n"); } else { if (unlikely(last_head + ofs + header_length > infile_size)) fatal("Offset greater than archive size, likely corrupted/truncated archive.\n"); } if (unlikely((head_off = lseek(fd_in, last_head + ofs, SEEK_SET)) == -1)) fatal("Failed to seek to header data in get_fileinfo\n"); if (unlikely(!get_header_info(control, fd_in, &ctype, &c_len, &u_len, &last_head, chunk_byte))) return false; if (unlikely(last_head < 0 || c_len < 0 || u_len < 0)) fatal("Entry negative, likely corrupted archive.\n"); if (INFO) print_verbose("%'d\t", block); if (ctype == CTYPE_NONE) { if (INFO) print_verbose("none"); } else if (ctype == CTYPE_LZ4) { if (INFO) print_verbose("lz4"); } else if (ctype == CTYPE_LZMA) { if (INFO) print_verbose("lzma"); } else if (ctype == CTYPE_ZSTD) { if (INFO) print_verbose("zstd"); } else if (ctype == CTYPE_ZPAQ) { if (INFO) print_verbose("zpaq"); } else if (ctype == CTYPE_BZIP3) { if (INFO) print_verbose("bzip3"); } else fatal("Unknown Compression Type: %'d\n", ctype); if (save_ctype == 255) save_ctype = ctype; /* need this for lzma when some chunks could have no compression * and info will show rzip + none on info display if last chunk * is not compressed. Adjust for all types in case it's used in * the future */ utotal += u_len; ctotal += c_len; if (INFO) { print_verbose("\t%5.1f%%\t%'16" PRId64 " / %'14" PRId64 "", percentage(c_len, u_len), c_len, u_len); print_maxverbose("%'18" PRId64 " : %'14" PRId64 "", head_off, last_head); print_verbose("\n"); } block++; } while (last_head); ++stream; } if (unlikely((ofs = lseek(fd_in, c_len, SEEK_CUR)) == -1)) fatal("Failed to lseek c_len in get_fileinfo\n"); if (ofs >= infile_size - *control->hash_len) goto done; else if (ENCRYPT) if (ofs + header_length + *control->hash_len > infile_size) goto done; /* Chunk byte entry */ /* remove checks for mrzip < 0.6 */ if (control->major_version == 0) { if (!ENCRYPT) { if (unlikely(read(fd_in, &chunk_byte, 1) != 1)) fatal("Failed to read chunk_byte in get_fileinfo\n"); if (unlikely(chunk_byte < 1 || chunk_byte > 8)) fatal("Invalid chunk bytes %'d\n", chunk_byte); ofs++; if (unlikely(read(fd_in, &control->eof, 1) != 1)) fatal("Failed to read eof in get_fileinfo\n"); if (unlikely(read(fd_in, &chunk_size, chunk_byte) != chunk_byte)) fatal("Failed to read chunk_size in get_fileinfo\n"); chunk_size = le64toh(chunk_size); if (unlikely(chunk_size < 0)) fatal("Invalid chunk size %'" PRId64 "\n", chunk_size); ofs += 1 + chunk_byte; header_length = 1 + (chunk_byte * 3); } else { // ENCRYPTED // no change to chunk_byte ofs += 10; // no change to header_length } } goto next_chunk; done: /* compression ratio and bits per byte ratio */ cratio = (long double)expected_size / (long double)infile_size; bpb = ((long double)infile_size / (long double)expected_size) * 8; if (unlikely(ofs > infile_size)) fatal("Offset greater than archive size, likely corrupted/truncated archive.\n"); if (INFO) { print_output("\nSummary\n=======\n"); print_output("File: %s\nmrzip version: %'d.%'d ", infilecopy, control->major_version, control->minor_version, ENCRYPT ? "Encrypted " : ""); if (ENCRYPT) print_output("%s Encrypted ", control->enc_label); print_output("file\n"); if (control->comment_length) /* print comment */ print_output("Archive Comment: %s\n", control->comment); print_output("Compression Method: "); if (save_ctype == CTYPE_NONE) print_output("rzip alone\n"); else if (save_ctype == CTYPE_LZ4) print_output("rzip + lz4\n"); else if (save_ctype == CTYPE_LZMA) { print_output("rzip + lzma"); } else if (save_ctype == CTYPE_ZSTD) print_output("rzip + zstd\n"); else if (save_ctype == CTYPE_ZPAQ) { print_output("rzip + zpaq "); if (control->zpaq_level) // update magic with zpaq coding. print_output("-- Compression Level = %d, Block Size = %d, %'dMB\n", control->zpaq_level, control->zpaq_bs, (1 << control->zpaq_bs)); else // early 0.8 or <0.8 file without zpaq coding in magic header print_output("\n"); } else if (save_ctype == BZIP3_COMPRESS) { print_output("rzip + bzip3 -- Block Size: %d - %'" PRIu32 "\n", control->bzip3_bs, control->bzip3_block_size); } else print_output("Dunno wtf\n"); /* only print stored compression level for versions that have it! */ if (control->compression_level) print_output("Rzip Compression Level: %d, Lrzip-next Compressinn Level: %d\n", control->rzip_compression_level, control->compression_level); if (!expected_size) print_output("Due to using %s, expected decompression size not available\n", ENCRYPT ? "Encryption" : "Compression to STDOUT"); print_verbose( " Stats Percent Compressed / Uncompressed\n " "-------------------------------------------------------\n"); /* If we can't show expected size, tailor output for it */ if (expected_size) { print_verbose(" Rzip: %5.1f%%\t%'16" PRId64 " / %'14" PRId64 "\n", percentage(utotal, expected_size), utotal, expected_size); print_verbose(" Back end: %5.1f%%\t%'16" PRId64 " / %'14" PRId64 "\n", percentage(ctotal, utotal), ctotal, utotal); print_verbose(" Overall: %5.1f%%\t%'16" PRId64 " / %'14" PRId64 "\n", percentage(ctotal, expected_size), ctotal, expected_size); } else { print_verbose(" Rzip: Unavailable\n"); print_verbose(" Back end: %5.1f%%\t%'16" PRId64 " / %'14" PRId64 "\n", percentage(ctotal, utotal), ctotal, utotal); print_verbose(" Overall: Unavailable\n"); } if (expected_size) { print_output("\n Decompressed file size: %'14" PRIu64 "\n", expected_size); print_output(" Compressed file size: %'14" PRIu64 "\n", infile_size); print_output(" Compression ratio: %14.3Lfx, bpb: %.3Lf\n", cratio, bpb); } else { print_output(" Decompressed file size: Unavailable\n"); print_output(" Compressed file size: %'14" PRIu64 "\n", infile_size); print_output(" Compression ratio: Unavailable\n"); } } /* end if (INFO) */ if (HAS_HASH) { uchar * hash_stored; int i; if (INFO) { hash_stored = calloc(*control->hash_len, 1); if (unlikely(lseek(fd_in, -*control->hash_len, SEEK_END) == -1)) fatal("Failed to seek to %s data in get_fileinfo.\n", control->hash_label); if (unlikely(read(fd_in, hash_stored, *control->hash_len) != *control->hash_len)) fatal("Failed to read %s data in get_fileinfo.\n", control->hash_label); if (ENCRYPT) if (unlikely(!lrz_decrypt(control, hash_stored, *control->hash_len, control->salt_pass, LRZ_VALIDATE))) fatal("Failure decrypting %s in get_fileinfo.\n", control->hash_label); print_output("\n %s Checksum: ", control->hash_label); for (i = 0; i < *control->hash_len; i++) print_output("%02x", hash_stored[i]); print_output("\n"); dealloc(hash_stored); } } else { if (INFO) print_output("\n CRC32 used for integrity testing\n"); } out: if (unlikely(close(fd_in))) fatal("Failed to close fd_in in get_fileinfo\n"); return true; error: dealloc(control->outfile); return false; } /* compress one file from the command line */ bool compress_file(rzip_control * control) { const char *tmp, *tmpinfile; /* we're just using this as a proxy for control->infile. * Spares a compiler warning */ int fd_in = -1, fd_out = -1, len = MAGIC_LEN + control->comment_length; char * header; header = calloc(len, 1); control->flags |= FLAG_HASHED; /* allocate result block for selected hash */ control->hash_resblock = calloc(*control->hash_len, 1); if (ENCRYPT) { /* AES 128 now default */ if (unlikely(!get_hash(control, 1))) return false; } if (!STDIN) { fd_in = open(control->infile, O_RDONLY); if (unlikely(fd_in == -1)) fatal("Failed to open %s\n", control->infile); } else fd_in = fileno(control->inFILE); if (!STDOUT) { if (control->outname) { control->outfile = strdup(control->outname); } else { /* default output name from control->infile * test if outdir specified. If so, strip path from filename of * control->infile */ if (control->outdir && (tmp = strrchr(control->infile, '/'))) tmpinfile = tmp + 1; else tmpinfile = control->infile; control->outfile = malloc((control->outdir == NULL ? 0 : strlen(control->outdir)) + strlen(tmpinfile) + strlen(control->suffix) + 1); if (unlikely(!control->outfile)) fatal("Failed to allocate outfile name\n"); if (control->outdir) { /* prepend control->outdir */ strcpy(control->outfile, control->outdir); strcat(control->outfile, tmpinfile); } else strcpy(control->outfile, tmpinfile); strcat(control->outfile, control->suffix); // print_progress("Output filename is: %s\n", control->outfile); // Not needed since printed at end of decompression } if (!strcmp(control->infile, control->outfile)) fatal("Input and Output files are the same. %s. Exiting\n", control->infile); fd_out = open(control->outfile, O_RDWR | O_CREAT | O_EXCL, 0666); if (FORCE_REPLACE && (-1 == fd_out) && (EEXIST == errno)) { if (unlikely(unlink(control->outfile))) fatal("Failed to unlink an existing file: %s\n", control->outfile); fd_out = open(control->outfile, O_RDWR | O_CREAT | O_EXCL, 0666); } if (unlikely(fd_out == -1)) { /* We must ensure we don't delete a file that already * exists just because we tried to create a new one */ control->flags |= FLAG_KEEP_BROKEN; fatal("Failed to create %s\n", control->outfile); } control->fd_out = fd_out; if (!STDIN) { if (unlikely(!preserve_perms(control, fd_in, fd_out))) goto error; } } else { if (unlikely(!open_tmpoutbuf(control))) goto error; } /* Write zeroes to header at beginning of file */ if (unlikely(!STDOUT && write(fd_out, header, len) != len)) fatal("Cannot write file header\n"); rzip_fd(control, fd_in, fd_out); /* need to write magic after compression for expected size */ if (!STDOUT) { if (unlikely(!write_magic(control))) goto error; } if (ENCRYPT) release_hashes(control); if (unlikely(!STDIN && !STDOUT && !preserve_times(control, fd_in))) { fatal("Failed to preserve times on output file\n"); goto error; } if (unlikely(close(fd_in))) { fatal("Failed to close fd_in\n"); fd_in = -1; goto error; } if (unlikely(!STDOUT && close(fd_out))) fatal("Failed to close fd_out\n"); if (TMP_OUTBUF) close_tmpoutbuf(control); if (!KEEP_FILES && !STDIN) { if (unlikely(unlink(control->infile))) fatal("Failed to unlink %s\n", control->infile); } dealloc(control->outfile); dealloc(control->hash_resblock); dealloc(header); return true; error: dealloc(header); if (!STDIN && (fd_in > 0)) close(fd_in); if ((!STDOUT) && (fd_out > 0)) close(fd_out); return false; } /* decompress one file from the command line */ bool decompress_file(rzip_control * control) { char *tmp, *tmpoutfile, *infilecopy = NULL; int fd_in, fd_out = -1, fd_hist = -1; i64 expected_size = 0, free_space; struct statvfs fbuf; if (!STDIN) { struct stat fdin_stat; infilecopy = strdupa(control->infile); if (unlikely(stat(infilecopy, &fdin_stat))) fatal("File %s not found...\n", control->infile); else if (unlikely(!S_ISREG(fdin_stat.st_mode))) fatal("mrzip only works on regular FILES\n"); /* regardless, infilecopy has the input filename */ } if (!STDOUT && !TEST_ONLY) { /* if output name already set, use it */ if (control->outname) control->outfile = strdup(control->outname); else { /* default output name from infilecopy * test if outdir specified. If so, strip path from filename of * infilecopy, then remove suffix. */ if (control->outdir && (tmp = strrchr(infilecopy, '/'))) tmpoutfile = strdupa(tmp + 1); else tmpoutfile = strdupa(infilecopy); /* remove suffix to make outfile name */ if ((tmp = strrchr(tmpoutfile, '.')) && !strcmp(tmp, control->suffix)) *tmp = '\0'; control->outfile = malloc((control->outdir == NULL ? 0 : strlen(control->outdir)) + strlen(tmpoutfile) + 1); if (unlikely(!control->outfile)) fatal("Failed to allocate outfile name\n"); if (control->outdir) { /* prepend control->outdir */ strcpy(control->outfile, control->outdir); strcat(control->outfile, tmpoutfile); } else strcpy(control->outfile, tmpoutfile); } if (!STDOUT) print_progress("Output filename is: %s\n", control->outfile); if (unlikely(!strcmp(control->outfile, infilecopy))) { control->flags |= FLAG_TEST_ONLY; // stop and no more decompres or deleting files. fatal("Output and Input files are the same...Cannot continue\n"); } } if (STDIN) { fd_in = open_tmpinfile(control); read_tmpinmagic(control, fd_in); if (ENCRYPT) fatal("Cannot decompress encrypted file from STDIN\n"); expected_size = control->st_size; if (unlikely(!open_tmpinbuf(control))) return false; } else { fd_in = open(infilecopy, O_RDONLY); if (unlikely(fd_in == -1)) { fatal("Failed to open %s\n", infilecopy); } } control->fd_in = fd_in; if (!(TEST_ONLY || STDOUT)) { fd_out = open(control->outfile, O_WRONLY | O_CREAT | O_EXCL, 0666); if (FORCE_REPLACE && (-1 == fd_out) && (EEXIST == errno)) { if (unlikely(unlink(control->outfile))) fatal("Failed to unlink an existing file: %s\n", control->outfile); fd_out = open(control->outfile, O_WRONLY | O_CREAT | O_EXCL, 0666); } if (unlikely(fd_out == -1)) { /* We must ensure we don't delete a file that already * exists just because we tried to create a new one */ control->flags |= FLAG_KEEP_BROKEN; fatal("Failed to create %s\n", control->outfile); } fd_hist = open(control->outfile, O_RDONLY); if (unlikely(fd_hist == -1)) fatal("Failed to open history file %s\n", control->outfile); /* Can't copy permissions from STDIN */ if (!STDIN) if (unlikely(!preserve_perms(control, fd_in, fd_out))) return false; } else { fd_out = open_tmpoutfile(control); if (fd_out == -1) { fd_hist = -1; } else { fd_hist = open(control->outfile, O_RDONLY); if (unlikely(fd_hist == -1)) fatal("Failed to open history file %s\n", control->outfile); /* Unlink temporary file as soon as possible */ if (unlikely(unlink(control->outfile))) fatal("Failed to unlink tmpfile: %s\n", control->outfile); } } // check for STDOUT removed. In memory compression speedup. No memory leak. if (unlikely(!open_tmpoutbuf(control))) return false; if (!STDIN) { if (unlikely(!read_magic(control, fd_in, &expected_size))) return false; if (unlikely(expected_size < 0)) fatal("Invalid expected size %'" PRId64 "\n", expected_size); } if (!STDOUT) { /* Check if there's enough free space on the device chosen to fit the * decompressed or test file. */ if (unlikely(fstatvfs(fd_out, &fbuf))) fatal("Failed to fstatvfs in decompress_file\n"); free_space = (i64)fbuf.f_bsize * (i64)fbuf.f_bavail; if (free_space < expected_size) { if (FORCE_REPLACE && !TEST_ONLY) print_err( "Warning, inadequate free space detected, but attempting to decompress file due to -f option being " "used.\n"); else fatal("Inadequate free space to %s. Space needed: %'" PRId64 ". Space available: %'" PRId64 ".\nTry %s and select a larger volume.\n", TEST_ONLY ? "test file" : "decompress file. Use -f to override", expected_size, free_space, TEST_ONLY ? "setting `TMP=dirname`" : "using `-O dirname` or `-o [dirname/]filename` options"); } } control->fd_out = fd_out; control->fd_hist = fd_hist; show_version(control); if (NO_HASH) print_verbose("Not performing hash check\n"); if (HAS_HASH) print_verbose("%s ", control->hash_label); else print_verbose("CRC32 "); print_verbose("being used for integrity testing.\n"); control->hash_resblock = calloc(*control->hash_len, 1); if (ENCRYPT && !control->salt_pass_len) { // Only get password if needed if (unlikely(!get_hash(control, 0))) return false; print_maxverbose("Encryption hash loops %'" PRId64 "\n", control->encloops); if (!INFO) print_verbose("%s Encryption Used\n", control->enc_label); } // vailidate file on decompression or test if (STDIN) print_err("Unable to validate a file from STDIN. To validate, check file directly.\n"); else { print_progress("Validating file for consistency..."); if (unlikely((get_fileinfo(control)) == false)) fatal("File validation failed. Corrupt mrzip archive. Cannot continue\n"); print_progress("[OK]"); if (!VERBOSE) print_progress("\n"); // output LF to prevent overwriing decompression output } show_version(control); // show version here to preserve output formatting print_progress("Decompressing..."); if (unlikely(runzip_fd(control, fd_in, fd_out, fd_hist, expected_size) < 0)) { clear_rulist(control); return false; } /* We can now safely delete sinfo and pthread data of all threads * created. */ clear_rulist(control); if (STDOUT && !TMP_OUTBUF) { if (unlikely(!dump_tmpoutfile(control, fd_out))) return false; } /* if we get here, no fatal_return(( errors during decompression */ print_progress("\r"); if (!(STDOUT || TEST_ONLY)) print_progress("Output filename is: %s: ", control->outfile); if (!expected_size) expected_size = control->st_size; if (!ENCRYPT) print_progress("[OK] - %'" PRId64 " bytes \n", expected_size); else print_progress("[OK] \n"); if (TMP_OUTBUF) close_tmpoutbuf(control); if (fd_out > 0) if (unlikely(close(fd_hist) || close(fd_out))) fatal("Failed to close files\n"); if (unlikely(!STDIN && !STDOUT && !TEST_ONLY && !preserve_times(control, fd_in))) return false; if (!STDIN) close(fd_in); if (!KEEP_FILES && !STDIN) if (unlikely(unlink(control->infile))) fatal("Failed to unlink %s\n", infilecopy); if (ENCRYPT) release_hashes(control); dealloc(control->outfile); dealloc(control->hash_resblock); return true; } bool initialise_control(rzip_control * control) { time_t now_t, tdiff; char localeptr[] = "/tmp", *eptr; /* for environment. OR Default to /tmp if none set */ size_t len; memset(control, 0, sizeof(rzip_control)); control->locale = ""; /* empty string for default locale */ control->msgout = stderr; control->msgerr = stderr; register_outputfile(control, control->msgout); control->flags = FLAG_SHOW_PROGRESS | FLAG_KEEP_FILES | FLAG_THRESHOLD; control->filter_flag = 0; /* filter flag. Default to none */ control->compression_level = 7; /* compression level default */ control->rzip_compression_level = 0; /* rzip compression level default will equal compression level unless explicitly set */ control->ramsize = get_ram(control); /* if something goes wrong, exit from get_ram */ control->threshold = 100; /* default for no threshold limiting */ /* for testing single CPU */ control->threads = PROCESSORS; /* get CPUs for LZMA */ control->page_size = PAGE_SIZE; control->nice_val = 19; /* The first 5 bytes of the salt is the time in seconds. * The next 2 bytes encode how many times to hash the password. * The last 9 bytes are random data, making 16 bytes of salt */ if (unlikely((now_t = time(NULL)) == ((time_t)-1))) fatal("Failed to call time in main\n"); if (unlikely(now_t < T_ZERO)) { print_output("Warning your time reads before the year 2011, check your system clock\n"); now_t = T_ZERO; } /* Workaround for CPUs no longer keeping up with Moore's law! * This way we keep the magic header format unchanged. */ tdiff = (now_t - T_ZERO) / 4; now_t = T_ZERO + tdiff; control->secs = now_t; control->encloops = nloops(control->secs, control->salt, control->salt + 1); gcry_create_nonce(control->salt + 2, 6); /* Get Temp Dir. Try variations on canonical unix environment variable */ eptr = getenv("TMPDIR"); if (!eptr) eptr = getenv("TMP"); if (!eptr) eptr = getenv("TEMPDIR"); if (!eptr) eptr = getenv("TEMP"); if (!eptr) eptr = localeptr; len = strlen(eptr); control->tmpdir = malloc(len + 2); if (control->tmpdir == NULL) fatal("Failed to allocate for tmpdir\n"); strcpy(control->tmpdir, eptr); if (control->tmpdir[len - 1] != '/') { control->tmpdir[len] = '/'; /* need a trailing slash */ control->tmpdir[len + 1] = '\0'; } /* just in case, set pointers for hash and encryptions */ return true; } /* Copyright (C) 2006-2016 Con Kolivas Copyright (C) 2011 Peter Hyman Copyright (C) 1998 Andrew Tridgell Copyright (C) 2022 Kamila Szewczyk This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MRZIP_UTIL_H #define MRZIP_UTIL_H #include #include #include #include #include #include #include "./mrzip_private.h" void register_infile(rzip_control * control, const char * name, char delete); void register_outfile(rzip_control * control, const char * name, char delete); void unlink_files(rzip_control * control); void register_outputfile(rzip_control * control, FILE * f); noreturn void fatal_exit(rzip_control * control); void setup_overhead(rzip_control * control); void setup_ram(rzip_control * control); void round_to_page(i64 * size); size_t round_up_page(rzip_control * control, size_t len); bool read_config(rzip_control * control); void lrz_stretch(rzip_control * control); bool lrz_crypt(const rzip_control * control, uchar * buf, i64 len, const uchar * salt, int encrypt); /* decrypt_header will take a final variable for either decrypt or validate. * Valdidate will suppress printing message during validation or info */ bool decrypt_header(rzip_control * control, uchar * head, uchar * c_type, i64 * c_len, i64 * u_len, i64 * last_head, int decompress_type); static inline noreturn void fatal(const rzip_control * control, unsigned int line, const char * file, const char * func, const char * format, ...) { va_list ap; /* mrzip library callback code removed */ va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fatal_exit((rzip_control *)control); } #ifdef fatal #undef fatal #endif #define fatal(...) fatal(control, __LINE__, __FILE__, __func__, __VA_ARGS__) static inline bool lrz_encrypt(const rzip_control * control, uchar * buf, i64 len, const uchar * salt) { return lrz_crypt(control, buf, len, salt, LRZ_ENCRYPT); } static inline bool lrz_decrypt(const rzip_control * control, uchar * buf, i64 len, const uchar * salt, int dec_or_validate) { return lrz_crypt(control, buf, len, salt, dec_or_validate); } /* ck specific wrappers for true unnamed semaphore usage on platforms * that support them and for apple which does not. We use a single byte across * a pipe to emulate semaphore behaviour there. */ #ifdef __APPLE__ static inline void cksem_init(const rzip_control * control, cksem_t * cksem) { int flags, fd, i; if (pipe(cksem->pipefd) == -1) fatal("Failed pipe errno=%d", errno); /* Make the pipes FD_CLOEXEC to allow them to close should we call * execv on restart. */ for (i = 0; i < 2; i++) { fd = cksem->pipefd[i]; flags = fcntl(fd, F_GETFD, 0); flags |= FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags) == -1) fatal("Failed to fcntl errno=%d", errno); } } static inline void cksem_post(const rzip_control * control, cksem_t * cksem) { const char buf = 1; int ret; ret = write(cksem->pipefd[1], &buf, 1); if (unlikely(ret == 0)) fatal("Failed to write in cksem_post errno=%d", errno); } static inline void cksem_wait(const rzip_control * control, cksem_t * cksem) { char buf; int ret; ret = read(cksem->pipefd[0], &buf, 1); if (unlikely(ret == 0)) fatal("Failed to read in cksem_post errno=%d", errno); } #else static inline void cksem_init(const rzip_control * control, cksem_t * cksem) { int ret; if ((ret = sem_init(cksem, 0, 0))) fatal("Failed to sem_init ret=%d errno=%d", ret, errno); } static inline void cksem_post(const rzip_control * control, cksem_t * cksem) { if (unlikely(sem_post(cksem))) fatal("Failed to sem_post errno=%d cksem=0x%p", errno, cksem); } static inline void cksem_wait(const rzip_control * control, cksem_t * cksem) { if (unlikely(sem_wait(cksem))) fatal("Failed to sem_wait errno=%d cksem=0x%p", errno, cksem); } #endif #endif #if defined(RAUDIO_STANDALONE) #include "raudio.h" #else #include "raylib.h" // Declares module functions // Check if config flags have been externally provided on compilation line #if !defined(EXTERNAL_CONFIG_FLAGS) #include "config.h" // Defines module configuration flags #endif #include "utils.h" // Required for: fopen() Android mapping #endif #if defined(SUPPORT_MODULE_RAUDIO) #if defined(_WIN32) // To avoid conflicting windows.h symbols with raylib, some flags are defined // WARNING: Those flags avoid inclusion of some Win32 headers that could be required // by user at some point and won't be included... //------------------------------------------------------------------------------------- // If defined, the following flags inhibit definition of the indicated items. #define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ #define NOVIRTUALKEYCODES // VK_* #define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* #define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* #define NOSYSMETRICS // SM_* #define NOMENUS // MF_* #define NOICONS // IDI_* #define NOKEYSTATES // MK_* #define NOSYSCOMMANDS // SC_* #define NORASTEROPS // Binary and Tertiary raster ops #define NOSHOWWINDOW // SW_* #define OEMRESOURCE // OEM Resource values #define NOATOM // Atom Manager routines #define NOCLIPBOARD // Clipboard routines #define NOCOLOR // Screen colors #define NOCTLMGR // Control and Dialog routines #define NODRAWTEXT // DrawText() and DT_* #define NOGDI // All GDI defines and routines #define NOKERNEL // All KERNEL defines and routines #define NOUSER // All USER defines and routines //#define NONLS // All NLS defines and routines #define NOMB // MB_* and MessageBox() #define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines #define NOMETAFILE // typedef METAFILEPICT #define NOMINMAX // Macros min(a,b) and max(a,b) #define NOMSG // typedef MSG and associated routines #define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* #define NOSCROLL // SB_* and scrolling routines #define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. #define NOSOUND // Sound driver routines #define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines #define NOWH // SetWindowsHook and WH_* #define NOWINOFFSETS // GWL_*, GCL_*, associated routines #define NOCOMM // COMM driver routines #define NOKANJI // Kanji support stuff. #define NOHELP // Help engine interface. #define NOPROFILER // Profiler interface. #define NODEFERWINDOWPOS // DeferWindowPos routines #define NOMCX // Modem Configuration Extensions // Type required before windows.h inclusion typedef struct tagMSG *LPMSG; #include // Windows functionality (miniaudio) // Type required by some unused function... typedef struct tagBITMAPINFOHEADER { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER, *PBITMAPINFOHEADER; #include // Component Object Model (COM) header #include // Windows Multimedia, defines some WAVE structs #include // Windows Multimedia, used by Windows GDI, defines DIBINDEX macro // Some required types defined for MSVC/TinyC compiler #if defined(_MSC_VER) || defined(__TINYC__) #include "propidl.h" #endif #endif #define MA_MALLOC RL_MALLOC #define MA_FREE RL_FREE #define MA_NO_JACK #define MA_NO_WAV #define MA_NO_FLAC #define MA_NO_MP3 // Threading model: Default: [0] COINIT_MULTITHREADED: COM calls objects on any thread (free threading) #define MA_COINIT_VALUE 2 // [2] COINIT_APARTMENTTHREADED: Each object has its own thread (apartment model) #define MINIAUDIO_IMPLEMENTATION //#define MA_DEBUG_OUTPUT #include "external/miniaudio.h" // Audio device initialization and management #undef PlaySound // Win32 API: windows.h > mmsystem.h defines PlaySound macro #include // Required for: malloc(), free() #include // Required for: FILE, fopen(), fclose(), fread() #include // Required for: strcmp() [Used in IsFileExtension(), LoadWaveFromMemory(), LoadMusicStreamFromMemory()] #if defined(RAUDIO_STANDALONE) #ifndef TRACELOG #define TRACELOG(level, ...) printf(__VA_ARGS__) #endif // Allow custom memory allocators #ifndef RL_MALLOC #define RL_MALLOC(sz) malloc(sz) #endif #ifndef RL_CALLOC #define RL_CALLOC(n,sz) calloc(n,sz) #endif #ifndef RL_REALLOC #define RL_REALLOC(ptr,sz) realloc(ptr,sz) #endif #ifndef RL_FREE #define RL_FREE(ptr) free(ptr) #endif #endif #if defined(SUPPORT_FILEFORMAT_WAV) #define DRWAV_MALLOC RL_MALLOC #define DRWAV_REALLOC RL_REALLOC #define DRWAV_FREE RL_FREE #define DR_WAV_IMPLEMENTATION #include "external/dr_wav.h" // WAV loading functions #endif #if defined(SUPPORT_FILEFORMAT_OGG) // TODO: Remap stb_vorbis malloc()/free() calls to RL_MALLOC/RL_FREE #include "external/stb_vorbis.c" // OGG loading functions #endif #if defined(SUPPORT_FILEFORMAT_MP3) #define DRMP3_MALLOC RL_MALLOC #define DRMP3_REALLOC RL_REALLOC #define DRMP3_FREE RL_FREE #define DR_MP3_IMPLEMENTATION #include "external/dr_mp3.h" // MP3 loading functions #endif #if defined(SUPPORT_FILEFORMAT_QOA) #define QOA_MALLOC RL_MALLOC #define QOA_FREE RL_FREE #if defined(_MSC_VER) // Disable some MSVC warning #pragma warning(push) #pragma warning(disable : 4018) #pragma warning(disable : 4267) #pragma warning(disable : 4244) #endif #define QOA_IMPLEMENTATION #include "external/qoa.h" // QOA loading and saving functions #include "external/qoaplay.c" // QOA stream playing helper functions #if defined(_MSC_VER) #pragma warning(pop) // Disable MSVC warning suppression #endif #endif #if defined(SUPPORT_FILEFORMAT_FLAC) #define DRFLAC_MALLOC RL_MALLOC #define DRFLAC_REALLOC RL_REALLOC #define DRFLAC_FREE RL_FREE #define DR_FLAC_IMPLEMENTATION #define DR_FLAC_NO_WIN32_IO #include "external/dr_flac.h" // FLAC loading functions #endif #if defined(SUPPORT_FILEFORMAT_XM) #define JARXM_MALLOC RL_MALLOC #define JARXM_FREE RL_FREE #if defined(_MSC_VER) // Disable some MSVC warning #pragma warning(push) #pragma warning(disable : 4244) #endif #define JAR_XM_IMPLEMENTATION #include "external/jar_xm.h" // XM loading functions #if defined(_MSC_VER) #pragma warning(pop) // Disable MSVC warning suppression #endif #endif #if defined(SUPPORT_FILEFORMAT_MOD) #define JARMOD_MALLOC RL_MALLOC #define JARMOD_FREE RL_FREE #define JAR_MOD_IMPLEMENTATION #include "external/jar_mod.h" // MOD loading functions #endif //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- #ifndef AUDIO_DEVICE_FORMAT #define AUDIO_DEVICE_FORMAT ma_format_f32 // Device output format (float-32bit) #endif #ifndef AUDIO_DEVICE_CHANNELS #define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo #endif #ifndef AUDIO_DEVICE_SAMPLE_RATE #define AUDIO_DEVICE_SAMPLE_RATE 0 // Device output sample rate #endif #ifndef MAX_AUDIO_BUFFER_POOL_CHANNELS #define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Audio pool channels #endif //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- #if defined(RAUDIO_STANDALONE) // Trace log level // NOTE: Organized by priority level typedef enum { LOG_ALL = 0, // Display all logs LOG_TRACE, // Trace logging, intended for internal use only LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds LOG_INFO, // Info logging, used for program execution info LOG_WARNING, // Warning logging, used on recoverable failures LOG_ERROR, // Error logging, used on unrecoverable failures LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) LOG_NONE // Disable logging } TraceLogLevel; #endif // Music context type // NOTE: Depends on data structure provided by the library // in charge of reading the different file types typedef enum { MUSIC_AUDIO_NONE = 0, // No audio context loaded MUSIC_AUDIO_WAV, // WAV audio context MUSIC_AUDIO_OGG, // OGG audio context MUSIC_AUDIO_FLAC, // FLAC audio context MUSIC_AUDIO_MP3, // MP3 audio context MUSIC_AUDIO_QOA, // QOA audio context MUSIC_MODULE_XM, // XM module audio context MUSIC_MODULE_MOD // MOD module audio context } MusicContextType; // NOTE: Different logic is used when feeding data to the playback device // depending on whether data is streamed (Music vs Sound) typedef enum { AUDIO_BUFFER_USAGE_STATIC = 0, AUDIO_BUFFER_USAGE_STREAM } AudioBufferUsage; // Audio buffer struct struct rAudioBuffer { ma_data_converter converter; // Audio data converter AudioCallback callback; // Audio buffer callback for buffer filling on audio threads rAudioProcessor *processor; // Audio processor float volume; // Audio buffer volume float pitch; // Audio buffer pitch float pan; // Audio buffer pan (0.0f to 1.0f) bool playing; // Audio buffer state: AUDIO_PLAYING bool paused; // Audio buffer state: AUDIO_PAUSED bool looping; // Audio buffer looping, default to true for AudioStreams int usage; // Audio buffer usage mode: STATIC or STREAM bool isSubBufferProcessed[2]; // SubBuffer processed (virtual double buffer) unsigned int sizeInFrames; // Total buffer size in frames unsigned int frameCursorPos; // Frame cursor position unsigned int framesProcessed; // Total frames processed in this buffer (required for play timing) unsigned char *data; // Data buffer, on music stream keeps filling rAudioBuffer *next; // Next audio buffer on the list rAudioBuffer *prev; // Previous audio buffer on the list }; // Audio processor struct // NOTE: Useful to apply effects to an AudioBuffer struct rAudioProcessor { AudioCallback process; // Processor callback function rAudioProcessor *next; // Next audio processor on the list rAudioProcessor *prev; // Previous audio processor on the list }; #define AudioBuffer rAudioBuffer // HACK: To avoid CoreAudio (macOS) symbol collision // Audio data context typedef struct AudioData { struct { ma_context context; // miniaudio context data ma_device device; // miniaudio device ma_mutex lock; // miniaudio mutex lock bool isReady; // Check if audio device is ready size_t pcmBufferSize; // Pre-allocated buffer size void *pcmBuffer; // Pre-allocated buffer to read audio data from file/memory } System; struct { AudioBuffer *first; // Pointer to first AudioBuffer in the list AudioBuffer *last; // Pointer to last AudioBuffer in the list int defaultSize; // Default audio buffer size for audio streams } Buffer; rAudioProcessor *mixedProcessor; } AudioData; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- static AudioData AUDIO = { // Global AUDIO context // NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number // After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds and a // standard double-buffering system, a 4096 samples buffer has been chosen, it should be enough // In case of music-stalls, just increase this number .Buffer.defaultSize = 0, .mixedProcessor = NULL }; //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage); static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount); static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer); #if defined(RAUDIO_STANDALONE) static bool IsFileExtension(const char *fileName, const char *ext); // Check file extension static const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes the dot: .png) static unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) static bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write) static bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated #endif //---------------------------------------------------------------------------------- // AudioBuffer management functions declaration // NOTE: Those functions are not exposed by raylib... for the moment //---------------------------------------------------------------------------------- AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage); void UnloadAudioBuffer(AudioBuffer *buffer); bool IsAudioBufferPlaying(AudioBuffer *buffer); void PlayAudioBuffer(AudioBuffer *buffer); void StopAudioBuffer(AudioBuffer *buffer); void PauseAudioBuffer(AudioBuffer *buffer); void ResumeAudioBuffer(AudioBuffer *buffer); void SetAudioBufferVolume(AudioBuffer *buffer, float volume); void SetAudioBufferPitch(AudioBuffer *buffer, float pitch); void SetAudioBufferPan(AudioBuffer *buffer, float pan); void TrackAudioBuffer(AudioBuffer *buffer); void UntrackAudioBuffer(AudioBuffer *buffer); //---------------------------------------------------------------------------------- // Module Functions Definition - Audio Device initialization and Closing //---------------------------------------------------------------------------------- // Initialize audio device void InitAudioDevice(void) { // Init audio context ma_context_config ctxConfig = ma_context_config_init(); ma_log_callback_init(OnLog, NULL); ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.System.context); if (result != MA_SUCCESS) { TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize context"); return; } // Init audio device // NOTE: Using the default device. Format is floating point because it simplifies mixing. ma_device_config config = ma_device_config_init(ma_device_type_playback); config.playback.pDeviceID = NULL; // NULL for the default playback AUDIO.System.device. config.playback.format = AUDIO_DEVICE_FORMAT; config.playback.channels = AUDIO_DEVICE_CHANNELS; config.capture.pDeviceID = NULL; // NULL for the default capture AUDIO.System.device. config.capture.format = ma_format_s16; config.capture.channels = 1; config.sampleRate = AUDIO_DEVICE_SAMPLE_RATE; config.dataCallback = OnSendAudioDataToDevice; config.pUserData = NULL; result = ma_device_init(&AUDIO.System.context, &config, &AUDIO.System.device); if (result != MA_SUCCESS) { TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize playback device"); ma_context_uninit(&AUDIO.System.context); return; } // Mixing happens on a separate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may // want to look at something a bit smarter later on to keep everything real-time, if that's necessary. if (ma_mutex_init(&AUDIO.System.lock) != MA_SUCCESS) { TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing"); ma_device_uninit(&AUDIO.System.device); ma_context_uninit(&AUDIO.System.context); return; } // Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running // while there's at least one sound being played. result = ma_device_start(&AUDIO.System.device); if (result != MA_SUCCESS) { TRACELOG(LOG_WARNING, "AUDIO: Failed to start playback device"); ma_device_uninit(&AUDIO.System.device); ma_context_uninit(&AUDIO.System.context); return; } TRACELOG(LOG_INFO, "AUDIO: Device initialized successfully"); TRACELOG(LOG_INFO, " > Backend: miniaudio / %s", ma_get_backend_name(AUDIO.System.context.backend)); TRACELOG(LOG_INFO, " > Format: %s -> %s", ma_get_format_name(AUDIO.System.device.playback.format), ma_get_format_name(AUDIO.System.device.playback.internalFormat)); TRACELOG(LOG_INFO, " > Channels: %d -> %d", AUDIO.System.device.playback.channels, AUDIO.System.device.playback.internalChannels); TRACELOG(LOG_INFO, " > Sample rate: %d -> %d", AUDIO.System.device.sampleRate, AUDIO.System.device.playback.internalSampleRate); TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.System.device.playback.internalPeriodSizeInFrames*AUDIO.System.device.playback.internalPeriods); AUDIO.System.isReady = true; } // Close the audio device for all contexts void CloseAudioDevice(void) { if (AUDIO.System.isReady) { ma_mutex_uninit(&AUDIO.System.lock); ma_device_uninit(&AUDIO.System.device); ma_context_uninit(&AUDIO.System.context); AUDIO.System.isReady = false; RL_FREE(AUDIO.System.pcmBuffer); AUDIO.System.pcmBuffer = NULL; AUDIO.System.pcmBufferSize = 0; TRACELOG(LOG_INFO, "AUDIO: Device closed successfully"); } else TRACELOG(LOG_WARNING, "AUDIO: Device could not be closed, not currently initialized"); } // Check if device has been initialized successfully bool IsAudioDeviceReady(void) { return AUDIO.System.isReady; } // Set master volume (listener) void SetMasterVolume(float volume) { ma_device_set_master_volume(&AUDIO.System.device, volume); } // Get master volume (listener) float GetMasterVolume(void) { float volume = 0.0f; ma_device_get_master_volume(&AUDIO.System.device, &volume); return volume; } //---------------------------------------------------------------------------------- // Module Functions Definition - Audio Buffer management //---------------------------------------------------------------------------------- // Initialize a new audio buffer (filled with silence) AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage) { AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(1, sizeof(AudioBuffer)); if (audioBuffer == NULL) { TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for buffer"); return NULL; } if (sizeInFrames > 0) audioBuffer->data = RL_CALLOC(sizeInFrames*channels*ma_get_bytes_per_sample(format), 1); // Audio data runs through a format converter ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO.System.device.sampleRate); converterConfig.allowDynamicSampleRate = true; ma_result result = ma_data_converter_init(&converterConfig, NULL, &audioBuffer->converter); if (result != MA_SUCCESS) { TRACELOG(LOG_WARNING, "AUDIO: Failed to create data conversion pipeline"); RL_FREE(audioBuffer); return NULL; } // Init audio buffer values audioBuffer->volume = 1.0f; audioBuffer->pitch = 1.0f; audioBuffer->pan = 0.5f; audioBuffer->callback = NULL; audioBuffer->processor = NULL; audioBuffer->playing = false; audioBuffer->paused = false; audioBuffer->looping = false; audioBuffer->usage = usage; audioBuffer->frameCursorPos = 0; audioBuffer->sizeInFrames = sizeInFrames; // Buffers should be marked as processed by default so that a call to // UpdateAudioStream() immediately after initialization works correctly audioBuffer->isSubBufferProcessed[0] = true; audioBuffer->isSubBufferProcessed[1] = true; // Track audio buffer to linked list next position TrackAudioBuffer(audioBuffer); return audioBuffer; } // Delete an audio buffer void UnloadAudioBuffer(AudioBuffer *buffer) { if (buffer != NULL) { ma_data_converter_uninit(&buffer->converter, NULL); UntrackAudioBuffer(buffer); RL_FREE(buffer->data); RL_FREE(buffer); } } // Check if an audio buffer is playing bool IsAudioBufferPlaying(AudioBuffer *buffer) { bool result = false; if (buffer != NULL) result = (buffer->playing && !buffer->paused); return result; } // Play an audio buffer // NOTE: Buffer is restarted to the start. // Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position should be maintained. void PlayAudioBuffer(AudioBuffer *buffer) { if (buffer != NULL) { buffer->playing = true; buffer->paused = false; buffer->frameCursorPos = 0; } } // Stop an audio buffer void StopAudioBuffer(AudioBuffer *buffer) { if (buffer != NULL) { if (IsAudioBufferPlaying(buffer)) { buffer->playing = false; buffer->paused = false; buffer->frameCursorPos = 0; buffer->framesProcessed = 0; buffer->isSubBufferProcessed[0] = true; buffer->isSubBufferProcessed[1] = true; } } } // Pause an audio buffer void PauseAudioBuffer(AudioBuffer *buffer) { if (buffer != NULL) buffer->paused = true; } // Resume an audio buffer void ResumeAudioBuffer(AudioBuffer *buffer) { if (buffer != NULL) buffer->paused = false; } // Set volume for an audio buffer void SetAudioBufferVolume(AudioBuffer *buffer, float volume) { if (buffer != NULL) buffer->volume = volume; } // Set pitch for an audio buffer void SetAudioBufferPitch(AudioBuffer *buffer, float pitch) { if ((buffer != NULL) && (pitch > 0.0f)) { // Pitching is just an adjustment of the sample rate. // Note that this changes the duration of the sound: // - higher pitches will make the sound faster // - lower pitches make it slower ma_uint32 outputSampleRate = (ma_uint32)((float)buffer->converter.sampleRateOut/pitch); ma_data_converter_set_rate(&buffer->converter, buffer->converter.sampleRateIn, outputSampleRate); buffer->pitch = pitch; } } // Set pan for an audio buffer void SetAudioBufferPan(AudioBuffer *buffer, float pan) { if (pan < 0.0f) pan = 0.0f; else if (pan > 1.0f) pan = 1.0f; if (buffer != NULL) buffer->pan = pan; } // Track audio buffer to linked list next position void TrackAudioBuffer(AudioBuffer *buffer) { ma_mutex_lock(&AUDIO.System.lock); { if (AUDIO.Buffer.first == NULL) AUDIO.Buffer.first = buffer; else { AUDIO.Buffer.last->next = buffer; buffer->prev = AUDIO.Buffer.last; } AUDIO.Buffer.last = buffer; } ma_mutex_unlock(&AUDIO.System.lock); } // Untrack audio buffer from linked list void UntrackAudioBuffer(AudioBuffer *buffer) { ma_mutex_lock(&AUDIO.System.lock); { if (buffer->prev == NULL) AUDIO.Buffer.first = buffer->next; else buffer->prev->next = buffer->next; if (buffer->next == NULL) AUDIO.Buffer.last = buffer->prev; else buffer->next->prev = buffer->prev; buffer->prev = NULL; buffer->next = NULL; } ma_mutex_unlock(&AUDIO.System.lock); } //---------------------------------------------------------------------------------- // Module Functions Definition - Sounds loading and playing (.WAV) //---------------------------------------------------------------------------------- // Load wave data from file Wave LoadWave(const char *fileName) { Wave wave = { 0 }; // Loading file to memory int dataSize = 0; unsigned char *fileData = LoadFileData(fileName, &dataSize); // Loading wave from memory data if (fileData != NULL) wave = LoadWaveFromMemory(GetFileExtension(fileName), fileData, dataSize); RL_FREE(fileData); return wave; } // Load wave from memory buffer, fileType refers to extension: i.e. ".wav" // WARNING: File extension must be provided in lower-case Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) { Wave wave = { 0 }; if (false) { } #if defined(SUPPORT_FILEFORMAT_WAV) else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0)) { drwav wav = { 0 }; bool success = drwav_init_memory(&wav, fileData, dataSize, NULL); if (success) { wave.frameCount = (unsigned int)wav.totalPCMFrameCount; wave.sampleRate = wav.sampleRate; wave.sampleSize = 16; wave.channels = wav.channels; wave.data = (short *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(short)); // NOTE: We are forcing conversion to 16bit sample size on reading drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, wave.data); } else TRACELOG(LOG_WARNING, "WAVE: Failed to load WAV data"); drwav_uninit(&wav); } #endif #if defined(SUPPORT_FILEFORMAT_OGG) else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0)) { stb_vorbis *oggData = stb_vorbis_open_memory((unsigned char *)fileData, dataSize, NULL, NULL); if (oggData != NULL) { stb_vorbis_info info = stb_vorbis_get_info(oggData); wave.sampleRate = info.sample_rate; wave.sampleSize = 16; // By default, ogg data is 16 bit per sample (short) wave.channels = info.channels; wave.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData); // NOTE: It returns frames! wave.data = (short *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(short)); // NOTE: Get the number of samples to process (be careful! we ask for number of shorts, not bytes!) stb_vorbis_get_samples_short_interleaved(oggData, info.channels, (short *)wave.data, wave.frameCount*wave.channels); stb_vorbis_close(oggData); } else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data"); } #endif #if defined(SUPPORT_FILEFORMAT_MP3) else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0)) { drmp3_config config = { 0 }; unsigned long long int totalFrameCount = 0; // NOTE: We are forcing conversion to 32bit float sample size on reading wave.data = drmp3_open_memory_and_read_pcm_frames_f32(fileData, dataSize, &config, &totalFrameCount, NULL); wave.sampleSize = 32; if (wave.data != NULL) { wave.channels = config.channels; wave.sampleRate = config.sampleRate; wave.frameCount = (int)totalFrameCount; } else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data"); } #endif #if defined(SUPPORT_FILEFORMAT_QOA) else if ((strcmp(fileType, ".qoa") == 0) || (strcmp(fileType, ".QOA") == 0)) { qoa_desc qoa = { 0 }; // NOTE: Returned sample data is always 16 bit? wave.data = qoa_decode(fileData, dataSize, &qoa); wave.sampleSize = 16; if (wave.data != NULL) { wave.channels = qoa.channels; wave.sampleRate = qoa.samplerate; wave.frameCount = qoa.samples; } else TRACELOG(LOG_WARNING, "WAVE: Failed to load QOA data"); } #endif #if defined(SUPPORT_FILEFORMAT_FLAC) else if ((strcmp(fileType, ".flac") == 0) || (strcmp(fileType, ".FLAC") == 0)) { unsigned long long int totalFrameCount = 0; // NOTE: We are forcing conversion to 16bit sample size on reading wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, dataSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL); wave.sampleSize = 16; if (wave.data != NULL) wave.frameCount = (unsigned int)totalFrameCount; else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data"); } #endif else TRACELOG(LOG_WARNING, "WAVE: Data format not supported"); TRACELOG(LOG_INFO, "WAVE: Data loaded successfully (%i Hz, %i bit, %i channels)", wave.sampleRate, wave.sampleSize, wave.channels); return wave; } // Checks if wave data is ready bool IsWaveReady(Wave wave) { return ((wave.data != NULL) && // Validate wave data available (wave.frameCount > 0) && // Validate frame count (wave.sampleRate > 0) && // Validate sample rate is supported (wave.sampleSize > 0) && // Validate sample size is supported (wave.channels > 0)); // Validate number of channels supported } // Load sound from file // NOTE: The entire file is loaded to memory to be played (no-streaming) Sound LoadSound(const char *fileName) { Wave wave = LoadWave(fileName); Sound sound = LoadSoundFromWave(wave); UnloadWave(wave); // Sound is loaded, we can unload wave return sound; } // Load sound from wave data // NOTE: Wave data must be unallocated manually Sound LoadSoundFromWave(Wave wave) { Sound sound = { 0 }; if (wave.data != NULL) { // When using miniaudio we need to do our own mixing. // To simplify this we need convert the format of each sound to be consistent with // the format used to open the playback AUDIO.System.device. We can do this two ways: // // 1) Convert the whole sound in one go at load time (here). // 2) Convert the audio data in chunks at mixing time. // // First option has been selected, format conversion is done on the loading stage. // The downside is that it uses more memory if the original sound is u8 or s16. ma_format formatIn = ((wave.sampleSize == 8)? ma_format_u8 : ((wave.sampleSize == 16)? ma_format_s16 : ma_format_f32)); ma_uint32 frameCountIn = wave.frameCount; ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, NULL, frameCountIn, formatIn, wave.channels, wave.sampleRate); if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed to get frame count for format conversion"); AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, frameCount, AUDIO_BUFFER_USAGE_STATIC); if (audioBuffer == NULL) { TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer"); return sound; // early return to avoid dereferencing the audioBuffer null pointer } frameCount = (ma_uint32)ma_convert_frames(audioBuffer->data, frameCount, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, wave.data, frameCountIn, formatIn, wave.channels, wave.sampleRate); if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed format conversion"); sound.frameCount = frameCount; sound.stream.sampleRate = AUDIO.System.device.sampleRate; sound.stream.sampleSize = 32; sound.stream.channels = AUDIO_DEVICE_CHANNELS; sound.stream.buffer = audioBuffer; } return sound; } // Clone sound from existing sound data, clone does not own wave data // NOTE: Wave data must be unallocated manually and will be shared across all clones Sound LoadSoundAlias(Sound source) { Sound sound = { 0 }; if (source.stream.buffer->data != NULL) { AudioBuffer* audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, 0, AUDIO_BUFFER_USAGE_STATIC); if (audioBuffer == NULL) { TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer"); return sound; // early return to avoid dereferencing the audioBuffer null pointer } audioBuffer->sizeInFrames = source.stream.buffer->sizeInFrames; audioBuffer->volume = source.stream.buffer->volume; audioBuffer->data = source.stream.buffer->data; sound.frameCount = source.frameCount; sound.stream.sampleRate = AUDIO.System.device.sampleRate; sound.stream.sampleSize = 32; sound.stream.channels = AUDIO_DEVICE_CHANNELS; sound.stream.buffer = audioBuffer; } return sound; } // Checks if a sound is ready bool IsSoundReady(Sound sound) { return ((sound.frameCount > 0) && // Validate frame count (sound.stream.buffer != NULL) && // Validate stream buffer (sound.stream.sampleRate > 0) && // Validate sample rate is supported (sound.stream.sampleSize > 0) && // Validate sample size is supported (sound.stream.channels > 0)); // Validate number of channels supported } // Unload wave data void UnloadWave(Wave wave) { RL_FREE(wave.data); //TRACELOG(LOG_INFO, "WAVE: Unloaded wave data from RAM"); } // Unload sound void UnloadSound(Sound sound) { UnloadAudioBuffer(sound.stream.buffer); //TRACELOG(LOG_INFO, "SOUND: Unloaded sound data from RAM"); } void UnloadSoundAlias(Sound alias) { // untrack and unload just the sound buffer, not the sample data, it is shared with the source for the alias if (alias.stream.buffer != NULL) { ma_data_converter_uninit(&alias.stream.buffer->converter, NULL); UntrackAudioBuffer(alias.stream.buffer); RL_FREE(alias.stream.buffer); } } // Update sound buffer with new data void UpdateSound(Sound sound, const void *data, int frameCount) { if (sound.stream.buffer != NULL) { StopAudioBuffer(sound.stream.buffer); // TODO: May want to lock/unlock this since this data buffer is read at mixing time memcpy(sound.stream.buffer->data, data, frameCount*ma_get_bytes_per_frame(sound.stream.buffer->converter.formatIn, sound.stream.buffer->converter.channelsIn)); } } // Export wave data to file bool ExportWave(Wave wave, const char *fileName) { bool success = false; if (false) { } #if defined(SUPPORT_FILEFORMAT_WAV) else if (IsFileExtension(fileName, ".wav")) { drwav wav = { 0 }; drwav_data_format format = { 0 }; format.container = drwav_container_riff; if (wave.sampleSize == 32) format.format = DR_WAVE_FORMAT_IEEE_FLOAT; else format.format = DR_WAVE_FORMAT_PCM; format.channels = wave.channels; format.sampleRate = wave.sampleRate; format.bitsPerSample = wave.sampleSize; void *fileData = NULL; size_t fileDataSize = 0; success = drwav_init_memory_write(&wav, &fileData, &fileDataSize, &format, NULL); if (success) success = (int)drwav_write_pcm_frames(&wav, wave.frameCount, wave.data); drwav_result result = drwav_uninit(&wav); if (result == DRWAV_SUCCESS) success = SaveFileData(fileName, (unsigned char *)fileData, (unsigned int)fileDataSize); drwav_free(fileData, NULL); } #endif #if defined(SUPPORT_FILEFORMAT_QOA) else if (IsFileExtension(fileName, ".qoa")) { if (wave.sampleSize == 16) { qoa_desc qoa = { 0 }; qoa.channels = wave.channels; qoa.samplerate = wave.sampleRate; qoa.samples = wave.frameCount; int bytesWritten = qoa_write(fileName, wave.data, &qoa); if (bytesWritten > 0) success = true; } else TRACELOG(LOG_WARNING, "AUDIO: Wave data must be 16 bit per sample for QOA format export"); } #endif else if (IsFileExtension(fileName, ".raw")) { // Export raw sample data (without header) // NOTE: It's up to the user to track wave parameters success = SaveFileData(fileName, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8); } if (success) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave data exported successfully", fileName); else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave data", fileName); return success; } // Export wave sample data to code (.h) bool ExportWaveAsCode(Wave wave, const char *fileName) { bool success = false; #ifndef TEXT_BYTES_PER_LINE #define TEXT_BYTES_PER_LINE 20 #endif int waveDataSize = wave.frameCount*wave.channels*wave.sampleSize/8; // NOTE: Text data buffer size is estimated considering wave data size in bytes // and requiring 6 char bytes for every byte: "0x00, " char *txtData = (char *)RL_CALLOC(waveDataSize*6 + 2000, sizeof(char)); int byteCount = 0; byteCount += sprintf(txtData + byteCount, "\n//////////////////////////////////////////////////////////////////////////////////\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "// WaveAsCode exporter v1.1 - Wave data exported as an array of bytes //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2023 Ramon Santamaria (@raysan5) //\n"); byteCount += sprintf(txtData + byteCount, "// //\n"); byteCount += sprintf(txtData + byteCount, "//////////////////////////////////////////////////////////////////////////////////\n\n"); // Get file name from path and convert variable name to uppercase char varFileName[256] = { 0 }; strcpy(varFileName, GetFileNameWithoutExt(fileName)); for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } //Add wave information byteCount += sprintf(txtData + byteCount, "// Wave data information\n"); byteCount += sprintf(txtData + byteCount, "#define %s_FRAME_COUNT %u\n", varFileName, wave.frameCount); byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_RATE %u\n", varFileName, wave.sampleRate); byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_SIZE %u\n", varFileName, wave.sampleSize); byteCount += sprintf(txtData + byteCount, "#define %s_CHANNELS %u\n\n", varFileName, wave.channels); // Write wave data as an array of values // Wave data is exported as byte array for 8/16bit and float array for 32bit float data // NOTE: Frame data exported is channel-interlaced: frame01[sampleChannel1, sampleChannel2, ...], frame02[], frame03[] if (wave.sampleSize == 32) { byteCount += sprintf(txtData + byteCount, "static float %s_DATA[%i] = {\n", varFileName, waveDataSize/4); for (int i = 1; i < waveDataSize/4; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.4ff,\n " : "%.4ff, "), ((float *)wave.data)[i - 1]); byteCount += sprintf(txtData + byteCount, "%.4ff };\n", ((float *)wave.data)[waveDataSize/4 - 1]); } else { byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%i] = { ", varFileName, waveDataSize); for (int i = 1; i < waveDataSize; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n " : "0x%x, "), ((unsigned char *)wave.data)[i - 1]); byteCount += sprintf(txtData + byteCount, "0x%x };\n", ((unsigned char *)wave.data)[waveDataSize - 1]); } // NOTE: Text data length exported is determined by '\0' (NULL) character success = SaveFileText(fileName, txtData); RL_FREE(txtData); if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave as code exported successfully", fileName); else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave as code", fileName); return success; } // Play a sound void PlaySound(Sound sound) { PlayAudioBuffer(sound.stream.buffer); } // Pause a sound void PauseSound(Sound sound) { PauseAudioBuffer(sound.stream.buffer); } // Resume a paused sound void ResumeSound(Sound sound) { ResumeAudioBuffer(sound.stream.buffer); } // Stop reproducing a sound void StopSound(Sound sound) { StopAudioBuffer(sound.stream.buffer); } // Check if a sound is playing bool IsSoundPlaying(Sound sound) { return IsAudioBufferPlaying(sound.stream.buffer); } // Set volume for a sound void SetSoundVolume(Sound sound, float volume) { SetAudioBufferVolume(sound.stream.buffer, volume); } // Set pitch for a sound void SetSoundPitch(Sound sound, float pitch) { SetAudioBufferPitch(sound.stream.buffer, pitch); } // Set pan for a sound void SetSoundPan(Sound sound, float pan) { SetAudioBufferPan(sound.stream.buffer, pan); } // Convert wave data to desired format void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) { ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32)); ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32)); ma_uint32 frameCountIn = wave->frameCount; ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate); if (frameCount == 0) { TRACELOG(LOG_WARNING, "WAVE: Failed to get frame count for format conversion"); return; } void *data = RL_MALLOC(frameCount*channels*(sampleSize/8)); frameCount = (ma_uint32)ma_convert_frames(data, frameCount, formatOut, channels, sampleRate, wave->data, frameCountIn, formatIn, wave->channels, wave->sampleRate); if (frameCount == 0) { TRACELOG(LOG_WARNING, "WAVE: Failed format conversion"); return; } wave->frameCount = frameCount; wave->sampleSize = sampleSize; wave->sampleRate = sampleRate; wave->channels = channels; RL_FREE(wave->data); wave->data = data; } // Copy a wave to a new wave Wave WaveCopy(Wave wave) { Wave newWave = { 0 }; newWave.data = RL_MALLOC(wave.frameCount*wave.channels*wave.sampleSize/8); if (newWave.data != NULL) { // NOTE: Size must be provided in bytes memcpy(newWave.data, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8); newWave.frameCount = wave.frameCount; newWave.sampleRate = wave.sampleRate; newWave.sampleSize = wave.sampleSize; newWave.channels = wave.channels; } return newWave; } // Crop a wave to defined samples range // NOTE: Security check in case of out-of-range void WaveCrop(Wave *wave, int initSample, int finalSample) { if ((initSample >= 0) && (initSample < finalSample) && ((unsigned int)finalSample < (wave->frameCount*wave->channels))) { int sampleCount = finalSample - initSample; void *data = RL_MALLOC(sampleCount*wave->sampleSize/8); memcpy(data, (unsigned char *)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->sampleSize/8); RL_FREE(wave->data); wave->data = data; } else TRACELOG(LOG_WARNING, "WAVE: Crop range out of bounds"); } // Load samples data from wave as a floats array // NOTE 1: Returned sample values are normalized to range [-1..1] // NOTE 2: Sample data allocated should be freed with UnloadWaveSamples() float *LoadWaveSamples(Wave wave) { float *samples = (float *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(float)); // NOTE: sampleCount is the total number of interlaced samples (including channels) for (unsigned int i = 0; i < wave.frameCount*wave.channels; i++) { if (wave.sampleSize == 8) samples[i] = (float)(((unsigned char *)wave.data)[i] - 127)/256.0f; else if (wave.sampleSize == 16) samples[i] = (float)(((short *)wave.data)[i])/32767.0f; else if (wave.sampleSize == 32) samples[i] = ((float *)wave.data)[i]; } return samples; } // Unload samples data loaded with LoadWaveSamples() void UnloadWaveSamples(float *samples) { RL_FREE(samples); } //---------------------------------------------------------------------------------- // Module Functions Definition - Music loading and stream playing //---------------------------------------------------------------------------------- // Load music stream from file Music LoadMusicStream(const char *fileName) { Music music = { 0 }; bool musicLoaded = false; if (false) { } #if defined(SUPPORT_FILEFORMAT_WAV) else if (IsFileExtension(fileName, ".wav")) { drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); bool success = drwav_init_file(ctxWav, fileName, NULL); music.ctxType = MUSIC_AUDIO_WAV; music.ctxData = ctxWav; if (success) { int sampleSize = ctxWav->bitsPerSample; if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount; music.looping = true; // Looping enabled by default musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_OGG) else if (IsFileExtension(fileName, ".ogg")) { // Open ogg audio stream music.ctxType = MUSIC_AUDIO_OGG; music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); if (music.ctxData != NULL) { stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info // OGG bit rate defaults to 16 bit, it's enough for compressed format music.stream = LoadAudioStream(info.sample_rate, 16, info.channels); // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData); music.looping = true; // Looping enabled by default musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (IsFileExtension(fileName, ".mp3")) { drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); int result = drmp3_init_file(ctxMp3, fileName, NULL); music.ctxType = MUSIC_AUDIO_MP3; music.ctxData = ctxMp3; if (result > 0) { music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3); music.looping = true; // Looping enabled by default musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_QOA) else if (IsFileExtension(fileName, ".qoa")) { qoaplay_desc *ctxQoa = qoaplay_open(fileName); music.ctxType = MUSIC_AUDIO_QOA; music.ctxData = ctxQoa; if (ctxQoa->file != NULL) { // NOTE: We are loading samples are 32bit float normalized data, so, // we configure the output audio stream to also use float 32bit music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels); music.frameCount = ctxQoa->info.samples; music.looping = true; // Looping enabled by default musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_FLAC) else if (IsFileExtension(fileName, ".flac")) { music.ctxType = MUSIC_AUDIO_FLAC; music.ctxData = drflac_open_file(fileName, NULL); if (music.ctxData != NULL) { drflac *ctxFlac = (drflac *)music.ctxData; music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; music.looping = true; // Looping enabled by default musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_XM) else if (IsFileExtension(fileName, ".xm")) { jar_xm_context_t *ctxXm = NULL; int result = jar_xm_create_context_from_file(&ctxXm, AUDIO.System.device.sampleRate, fileName); music.ctxType = MUSIC_MODULE_XM; music.ctxData = ctxXm; if (result == 0) // XM AUDIO.System.context created successfully { jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops unsigned int bits = 32; if (AUDIO_DEVICE_FORMAT == ma_format_s16) bits = 16; else if (AUDIO_DEVICE_FORMAT == ma_format_u8) bits = 8; // NOTE: Only stereo is supported for XM music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, AUDIO_DEVICE_CHANNELS); music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); // NOTE: Always 2 channels (stereo) music.looping = true; // Looping enabled by default jar_xm_reset(ctxXm); // make sure we start at the beginning of the song musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_MOD) else if (IsFileExtension(fileName, ".mod")) { jar_mod_context_t *ctxMod = RL_CALLOC(1, sizeof(jar_mod_context_t)); jar_mod_init(ctxMod); int result = jar_mod_load_file(ctxMod, fileName); music.ctxType = MUSIC_MODULE_MOD; music.ctxData = ctxMod; if (result > 0) { // NOTE: Only stereo is supported for MOD music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, AUDIO_DEVICE_CHANNELS); music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); // NOTE: Always 2 channels (stereo) music.looping = true; // Looping enabled by default musicLoaded = true; } } #endif else TRACELOG(LOG_WARNING, "STREAM: [%s] File format not supported", fileName); if (!musicLoaded) { if (false) { } #if defined(SUPPORT_FILEFORMAT_WAV) else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_OGG) else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } #endif #if defined(SUPPORT_FILEFORMAT_QOA) else if (music.ctxType == MUSIC_AUDIO_QOA) qoaplay_close((qoaplay_desc *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_FLAC) else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); #endif #if defined(SUPPORT_FILEFORMAT_XM) else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_MOD) else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } #endif music.ctxData = NULL; TRACELOG(LOG_WARNING, "FILEIO: [%s] Music file could not be opened", fileName); } else { // Show some music stream info TRACELOG(LOG_INFO, "FILEIO: [%s] Music file loaded successfully", fileName); TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); TRACELOG(LOG_INFO, " > Total frames: %i", music.frameCount); } return music; } // Load music stream from memory buffer, fileType refers to extension: i.e. ".wav" // WARNING: File extension must be provided in lower-case Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize) { Music music = { 0 }; bool musicLoaded = false; if (false) { } #if defined(SUPPORT_FILEFORMAT_WAV) else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0)) { drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); bool success = drwav_init_memory(ctxWav, (const void *)data, dataSize, NULL); music.ctxType = MUSIC_AUDIO_WAV; music.ctxData = ctxWav; if (success) { int sampleSize = ctxWav->bitsPerSample; if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount; music.looping = true; // Looping enabled by default musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_OGG) else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0)) { // Open ogg audio stream music.ctxType = MUSIC_AUDIO_OGG; //music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); music.ctxData = stb_vorbis_open_memory((const unsigned char *)data, dataSize, NULL, NULL); if (music.ctxData != NULL) { stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info // OGG bit rate defaults to 16 bit, it's enough for compressed format music.stream = LoadAudioStream(info.sample_rate, 16, info.channels); // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData); music.looping = true; // Looping enabled by default musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_MP3) else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0)) { drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); int success = drmp3_init_memory(ctxMp3, (const void*)data, dataSize, NULL); music.ctxType = MUSIC_AUDIO_MP3; music.ctxData = ctxMp3; if (success) { music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3); music.looping = true; // Looping enabled by default musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_QOA) else if ((strcmp(fileType, ".qoa") == 0) || (strcmp(fileType, ".QOA") == 0)) { qoaplay_desc *ctxQoa = qoaplay_open_memory(data, dataSize); music.ctxType = MUSIC_AUDIO_QOA; music.ctxData = ctxQoa; if ((ctxQoa->file_data != NULL) && (ctxQoa->file_data_size != 0)) { // NOTE: We are loading samples are 32bit float normalized data, so, // we configure the output audio stream to also use float 32bit music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels); music.frameCount = ctxQoa->info.samples; music.looping = true; // Looping enabled by default musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_FLAC) else if ((strcmp(fileType, ".flac") == 0) || (strcmp(fileType, ".FLAC") == 0)) { music.ctxType = MUSIC_AUDIO_FLAC; music.ctxData = drflac_open_memory((const void*)data, dataSize, NULL); if (music.ctxData != NULL) { drflac *ctxFlac = (drflac *)music.ctxData; music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; music.looping = true; // Looping enabled by default musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_XM) else if ((strcmp(fileType, ".xm") == 0) || (strcmp(fileType, ".XM") == 0)) { jar_xm_context_t *ctxXm = NULL; int result = jar_xm_create_context_safe(&ctxXm, (const char *)data, dataSize, AUDIO.System.device.sampleRate); if (result == 0) // XM AUDIO.System.context created successfully { music.ctxType = MUSIC_MODULE_XM; jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops unsigned int bits = 32; if (AUDIO_DEVICE_FORMAT == ma_format_s16) bits = 16; else if (AUDIO_DEVICE_FORMAT == ma_format_u8) bits = 8; // NOTE: Only stereo is supported for XM music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, 2); music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); // NOTE: Always 2 channels (stereo) music.looping = true; // Looping enabled by default jar_xm_reset(ctxXm); // make sure we start at the beginning of the song music.ctxData = ctxXm; musicLoaded = true; } } #endif #if defined(SUPPORT_FILEFORMAT_MOD) else if ((strcmp(fileType, ".mod") == 0) || (strcmp(fileType, ".MOD") == 0)) { jar_mod_context_t *ctxMod = (jar_mod_context_t *)RL_MALLOC(sizeof(jar_mod_context_t)); int result = 0; jar_mod_init(ctxMod); // Copy data to allocated memory for default UnloadMusicStream unsigned char *newData = (unsigned char *)RL_MALLOC(dataSize); int it = dataSize/sizeof(unsigned char); for (int i = 0; i < it; i++) newData[i] = data[i]; // Memory loaded version for jar_mod_load_file() if (dataSize && (dataSize < 32*1024*1024)) { ctxMod->modfilesize = dataSize; ctxMod->modfile = newData; if (jar_mod_load(ctxMod, (void *)ctxMod->modfile, dataSize)) result = dataSize; } if (result > 0) { music.ctxType = MUSIC_MODULE_MOD; // NOTE: Only stereo is supported for MOD music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, 2); music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); // NOTE: Always 2 channels (stereo) music.looping = true; // Looping enabled by default musicLoaded = true; music.ctxData = ctxMod; musicLoaded = true; } } #endif else TRACELOG(LOG_WARNING, "STREAM: Data format not supported"); if (!musicLoaded) { if (false) { } #if defined(SUPPORT_FILEFORMAT_WAV) else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_OGG) else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } #endif #if defined(SUPPORT_FILEFORMAT_QOA) else if (music.ctxType == MUSIC_AUDIO_QOA) qoaplay_close((qoaplay_desc *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_FLAC) else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); #endif #if defined(SUPPORT_FILEFORMAT_XM) else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_MOD) else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } #endif music.ctxData = NULL; TRACELOG(LOG_WARNING, "FILEIO: Music data could not be loaded"); } else { // Show some music stream info TRACELOG(LOG_INFO, "FILEIO: Music data loaded successfully"); TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); TRACELOG(LOG_INFO, " > Total frames: %i", music.frameCount); } return music; } // Checks if a music stream is ready bool IsMusicReady(Music music) { return ((music.ctxData != NULL) && // Validate context loaded (music.frameCount > 0) && // Validate audio frame count (music.stream.sampleRate > 0) && // Validate sample rate is supported (music.stream.sampleSize > 0) && // Validate sample size is supported (music.stream.channels > 0)); // Validate number of channels supported } // Unload music stream void UnloadMusicStream(Music music) { UnloadAudioStream(music.stream); if (music.ctxData != NULL) { if (false) { } #if defined(SUPPORT_FILEFORMAT_WAV) else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_OGG) else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } #endif #if defined(SUPPORT_FILEFORMAT_QOA) else if (music.ctxType == MUSIC_AUDIO_QOA) qoaplay_close((qoaplay_desc *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_FLAC) else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); #endif #if defined(SUPPORT_FILEFORMAT_XM) else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_MOD) else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } #endif } } // Start music playing (open stream) void PlayMusicStream(Music music) { if (music.stream.buffer != NULL) { // For music streams, we need to make sure we maintain the frame cursor position // This is a hack for this section of code in UpdateMusicStream() // NOTE: In case window is minimized, music stream is stopped, just make sure to // play again on window restore: if (IsMusicStreamPlaying(music)) PlayMusicStream(music); ma_uint32 frameCursorPos = music.stream.buffer->frameCursorPos; PlayAudioStream(music.stream); // WARNING: This resets the cursor position. music.stream.buffer->frameCursorPos = frameCursorPos; } } // Pause music playing void PauseMusicStream(Music music) { PauseAudioStream(music.stream); } // Resume music playing void ResumeMusicStream(Music music) { ResumeAudioStream(music.stream); } // Stop music playing (close stream) void StopMusicStream(Music music) { StopAudioStream(music.stream); switch (music.ctxType) { #if defined(SUPPORT_FILEFORMAT_WAV) case MUSIC_AUDIO_WAV: drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_OGG) case MUSIC_AUDIO_OGG: stb_vorbis_seek_start((stb_vorbis *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_MP3) case MUSIC_AUDIO_MP3: drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_QOA) case MUSIC_AUDIO_QOA: qoaplay_rewind((qoaplay_desc *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_FLAC) case MUSIC_AUDIO_FLAC: drflac__seek_to_first_frame((drflac *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_XM) case MUSIC_MODULE_XM: jar_xm_reset((jar_xm_context_t *)music.ctxData); break; #endif #if defined(SUPPORT_FILEFORMAT_MOD) case MUSIC_MODULE_MOD: jar_mod_seek_start((jar_mod_context_t *)music.ctxData); break; #endif default: break; } } // Seek music to a certain position (in seconds) void SeekMusicStream(Music music, float position) { // Seeking is not supported in module formats if ((music.ctxType == MUSIC_MODULE_XM) || (music.ctxType == MUSIC_MODULE_MOD)) return; unsigned int positionInFrames = (unsigned int)(position*music.stream.sampleRate); switch (music.ctxType) { #if defined(SUPPORT_FILEFORMAT_WAV) case MUSIC_AUDIO_WAV: drwav_seek_to_pcm_frame((drwav *)music.ctxData, positionInFrames); break; #endif #if defined(SUPPORT_FILEFORMAT_OGG) case MUSIC_AUDIO_OGG: stb_vorbis_seek_frame((stb_vorbis *)music.ctxData, positionInFrames); break; #endif #if defined(SUPPORT_FILEFORMAT_MP3) case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, positionInFrames); break; #endif #if defined(SUPPORT_FILEFORMAT_QOA) case MUSIC_AUDIO_QOA: { int qoaFrame = positionInFrames/QOA_FRAME_LEN; qoaplay_seek_frame((qoaplay_desc *)music.ctxData, qoaFrame); // Seeks to QOA frame, not PCM frame // We need to compute QOA frame number and update positionInFrames positionInFrames = ((qoaplay_desc *)music.ctxData)->sample_position; } break; #endif #if defined(SUPPORT_FILEFORMAT_FLAC) case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, positionInFrames); break; #endif default: break; } music.stream.buffer->framesProcessed = positionInFrames; } // Update (re-fill) music buffers if data already processed void UpdateMusicStream(Music music) { if (music.stream.buffer == NULL) return; unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2; // On first call of this function we lazily pre-allocated a temp buffer to read audio files/memory data in int frameSize = music.stream.channels*music.stream.sampleSize/8; unsigned int pcmSize = subBufferSizeInFrames*frameSize; if (AUDIO.System.pcmBufferSize < pcmSize) { RL_FREE(AUDIO.System.pcmBuffer); AUDIO.System.pcmBuffer = RL_CALLOC(1, pcmSize); AUDIO.System.pcmBufferSize = pcmSize; } // Check both sub-buffers to check if they require refilling for (int i = 0; i < 2; i++) { if ((music.stream.buffer != NULL) && !music.stream.buffer->isSubBufferProcessed[i]) continue; // No refilling required, move to next sub-buffer unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; // Frames left to be processed unsigned int framesToStream = 0; // Total frames to be streamed if ((framesLeft >= subBufferSizeInFrames) || music.looping) framesToStream = subBufferSizeInFrames; else framesToStream = framesLeft; int frameCountStillNeeded = framesToStream; int frameCountReadTotal = 0; switch (music.ctxType) { #if defined(SUPPORT_FILEFORMAT_WAV) case MUSIC_AUDIO_WAV: { if (music.stream.sampleSize == 16) { while (true) { int frameCountRead = (int)drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); frameCountReadTotal += frameCountRead; frameCountStillNeeded -= frameCountRead; if (frameCountStillNeeded == 0) break; else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); } } else if (music.stream.sampleSize == 32) { while (true) { int frameCountRead = (int)drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); frameCountReadTotal += frameCountRead; frameCountStillNeeded -= frameCountRead; if (frameCountStillNeeded == 0) break; else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); } } } break; #endif #if defined(SUPPORT_FILEFORMAT_OGG) case MUSIC_AUDIO_OGG: { while (true) { int frameCountRead = stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded*music.stream.channels); frameCountReadTotal += frameCountRead; frameCountStillNeeded -= frameCountRead; if (frameCountStillNeeded == 0) break; else stb_vorbis_seek_start((stb_vorbis *)music.ctxData); } } break; #endif #if defined(SUPPORT_FILEFORMAT_MP3) case MUSIC_AUDIO_MP3: { while (true) { int frameCountRead = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); frameCountReadTotal += frameCountRead; frameCountStillNeeded -= frameCountRead; if (frameCountStillNeeded == 0) break; else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); } } break; #endif #if defined(SUPPORT_FILEFORMAT_QOA) case MUSIC_AUDIO_QOA: { unsigned int frameCountRead = qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream); frameCountReadTotal += frameCountRead; /* while (true) { int frameCountRead = (int)qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded); frameCountReadTotal += frameCountRead; frameCountStillNeeded -= frameCountRead; if (frameCountStillNeeded == 0) break; else qoaplay_rewind((qoaplay_desc *)music.ctxData); } */ } break; #endif #if defined(SUPPORT_FILEFORMAT_FLAC) case MUSIC_AUDIO_FLAC: { while (true) { int frameCountRead = (int)drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); frameCountReadTotal += frameCountRead; frameCountStillNeeded -= frameCountRead; if (frameCountStillNeeded == 0) break; else drflac__seek_to_first_frame((drflac *)music.ctxData); } } break; #endif #if defined(SUPPORT_FILEFORMAT_XM) case MUSIC_MODULE_XM: { // NOTE: Internally we consider 2 channels generation, so sampleCount/2 if (AUDIO_DEVICE_FORMAT == ma_format_f32) jar_xm_generate_samples((jar_xm_context_t *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream); else if (AUDIO_DEVICE_FORMAT == ma_format_s16) jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream); else if (AUDIO_DEVICE_FORMAT == ma_format_u8) jar_xm_generate_samples_8bit((jar_xm_context_t *)music.ctxData, (char *)AUDIO.System.pcmBuffer, framesToStream); //jar_xm_reset((jar_xm_context_t *)music.ctxData); } break; #endif #if defined(SUPPORT_FILEFORMAT_MOD) case MUSIC_MODULE_MOD: { // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream, 0); //jar_mod_seek_start((jar_mod_context_t *)music.ctxData); } break; #endif default: break; } UpdateAudioStream(music.stream, AUDIO.System.pcmBuffer, framesToStream); music.stream.buffer->framesProcessed = music.stream.buffer->framesProcessed%music.frameCount; if (framesLeft <= subBufferSizeInFrames) { if (!music.looping) { // Streaming is ending, we filled latest frames from input StopMusicStream(music); return; } } } // NOTE: In case window is minimized, music stream is stopped, // just make sure to play again on window restore if (IsMusicStreamPlaying(music)) PlayMusicStream(music); } // Check if any music is playing bool IsMusicStreamPlaying(Music music) { return IsAudioStreamPlaying(music.stream); } // Set volume for music void SetMusicVolume(Music music, float volume) { SetAudioStreamVolume(music.stream, volume); } // Set pitch for music void SetMusicPitch(Music music, float pitch) { SetAudioBufferPitch(music.stream.buffer, pitch); } // Set pan for a music void SetMusicPan(Music music, float pan) { SetAudioBufferPan(music.stream.buffer, pan); } // Get music time length (in seconds) float GetMusicTimeLength(Music music) { float totalSeconds = 0.0f; totalSeconds = (float)music.frameCount/music.stream.sampleRate; return totalSeconds; } // Get current music time played (in seconds) float GetMusicTimePlayed(Music music) { float secondsPlayed = 0.0f; if (music.stream.buffer != NULL) { #if defined(SUPPORT_FILEFORMAT_XM) if (music.ctxType == MUSIC_MODULE_XM) { uint64_t framesPlayed = 0; jar_xm_get_position(music.ctxData, NULL, NULL, NULL, &framesPlayed); secondsPlayed = (float)framesPlayed/music.stream.sampleRate; } else #endif { //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; int framesProcessed = (int)music.stream.buffer->framesProcessed; int subBufferSize = (int)music.stream.buffer->sizeInFrames/2; int framesInFirstBuffer = music.stream.buffer->isSubBufferProcessed[0]? 0 : subBufferSize; int framesInSecondBuffer = music.stream.buffer->isSubBufferProcessed[1]? 0 : subBufferSize; int framesSentToMix = music.stream.buffer->frameCursorPos%subBufferSize; int framesPlayed = (framesProcessed - framesInFirstBuffer - framesInSecondBuffer + framesSentToMix)%(int)music.frameCount; if (framesPlayed < 0) framesPlayed += music.frameCount; secondsPlayed = (float)framesPlayed/music.stream.sampleRate; } } return secondsPlayed; } // Load audio stream (to stream audio pcm data) AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) { AudioStream stream = { 0 }; stream.sampleRate = sampleRate; stream.sampleSize = sampleSize; stream.channels = channels; ma_format formatIn = ((stream.sampleSize == 8)? ma_format_u8 : ((stream.sampleSize == 16)? ma_format_s16 : ma_format_f32)); // The size of a streaming buffer must be at least double the size of a period unsigned int periodSize = AUDIO.System.device.playback.internalPeriodSizeInFrames; // If the buffer is not set, compute one that would give us a buffer good enough for a decent frame rate unsigned int subBufferSize = (AUDIO.Buffer.defaultSize == 0)? AUDIO.System.device.sampleRate/30 : AUDIO.Buffer.defaultSize; if (subBufferSize < periodSize) subBufferSize = periodSize; // Create a double audio buffer of defined size stream.buffer = LoadAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM); if (stream.buffer != NULL) { stream.buffer->looping = true; // Always loop for streaming buffers TRACELOG(LOG_INFO, "STREAM: Initialized successfully (%i Hz, %i bit, %s)", stream.sampleRate, stream.sampleSize, (stream.channels == 1)? "Mono" : "Stereo"); } else TRACELOG(LOG_WARNING, "STREAM: Failed to load audio buffer, stream could not be created"); return stream; } // Checks if an audio stream is ready bool IsAudioStreamReady(AudioStream stream) { return ((stream.buffer != NULL) && // Validate stream buffer (stream.sampleRate > 0) && // Validate sample rate is supported (stream.sampleSize > 0) && // Validate sample size is supported (stream.channels > 0)); // Validate number of channels supported } // Unload audio stream and free memory void UnloadAudioStream(AudioStream stream) { UnloadAudioBuffer(stream.buffer); TRACELOG(LOG_INFO, "STREAM: Unloaded audio stream data from RAM"); } // Update audio stream buffers with data // NOTE 1: Only updates one buffer of the stream source: dequeue -> update -> queue // NOTE 2: To dequeue a buffer it needs to be processed: IsAudioStreamProcessed() void UpdateAudioStream(AudioStream stream, const void *data, int frameCount) { if (stream.buffer != NULL) { if (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1]) { ma_uint32 subBufferToUpdate = 0; if (stream.buffer->isSubBufferProcessed[0] && stream.buffer->isSubBufferProcessed[1]) { // Both buffers are available for updating. // Update the first one and make sure the cursor is moved back to the front. subBufferToUpdate = 0; stream.buffer->frameCursorPos = 0; } else { // Just update whichever sub-buffer is processed. subBufferToUpdate = (stream.buffer->isSubBufferProcessed[0])? 0 : 1; } ma_uint32 subBufferSizeInFrames = stream.buffer->sizeInFrames/2; unsigned char *subBuffer = stream.buffer->data + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); // Total frames processed in buffer is always the complete size, filled with 0 if required stream.buffer->framesProcessed += subBufferSizeInFrames; // Does this API expect a whole buffer to be updated in one go? // Assuming so, but if not will need to change this logic. if (subBufferSizeInFrames >= (ma_uint32)frameCount) { ma_uint32 framesToWrite = (ma_uint32)frameCount; ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); memcpy(subBuffer, data, bytesToWrite); // Any leftover frames should be filled with zeros. ma_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; if (leftoverFrameCount > 0) memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8)); stream.buffer->isSubBufferProcessed[subBufferToUpdate] = false; } else TRACELOG(LOG_WARNING, "STREAM: Attempting to write too many frames to buffer"); } else TRACELOG(LOG_WARNING, "STREAM: Buffer not available for updating"); } } // Check if any audio stream buffers requires refill bool IsAudioStreamProcessed(AudioStream stream) { if (stream.buffer == NULL) return false; return (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1]); } // Play audio stream void PlayAudioStream(AudioStream stream) { PlayAudioBuffer(stream.buffer); } // Play audio stream void PauseAudioStream(AudioStream stream) { PauseAudioBuffer(stream.buffer); } // Resume audio stream playing void ResumeAudioStream(AudioStream stream) { ResumeAudioBuffer(stream.buffer); } // Check if audio stream is playing. bool IsAudioStreamPlaying(AudioStream stream) { return IsAudioBufferPlaying(stream.buffer); } // Stop audio stream void StopAudioStream(AudioStream stream) { StopAudioBuffer(stream.buffer); } // Set volume for audio stream (1.0 is max level) void SetAudioStreamVolume(AudioStream stream, float volume) { SetAudioBufferVolume(stream.buffer, volume); } // Set pitch for audio stream (1.0 is base level) void SetAudioStreamPitch(AudioStream stream, float pitch) { SetAudioBufferPitch(stream.buffer, pitch); } // Set pan for audio stream void SetAudioStreamPan(AudioStream stream, float pan) { SetAudioBufferPan(stream.buffer, pan); } // Default size for new audio streams void SetAudioStreamBufferSizeDefault(int size) { AUDIO.Buffer.defaultSize = size; } // Audio thread callback to request new data void SetAudioStreamCallback(AudioStream stream, AudioCallback callback) { if (stream.buffer != NULL) stream.buffer->callback = callback; } // Add processor to audio stream. Contrary to buffers, the order of processors is important. // The new processor must be added at the end. As there aren't supposed to be a lot of processors attached to // a given stream, we iterate through the list to find the end. That way we don't need a pointer to the last element. void AttachAudioStreamProcessor(AudioStream stream, AudioCallback process) { ma_mutex_lock(&AUDIO.System.lock); rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor)); processor->process = process; rAudioProcessor *last = stream.buffer->processor; while (last && last->next) { last = last->next; } if (last) { processor->prev = last; last->next = processor; } else stream.buffer->processor = processor; ma_mutex_unlock(&AUDIO.System.lock); } // Remove processor from audio stream void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process) { ma_mutex_lock(&AUDIO.System.lock); rAudioProcessor *processor = stream.buffer->processor; while (processor) { rAudioProcessor *next = processor->next; rAudioProcessor *prev = processor->prev; if (processor->process == process) { if (stream.buffer->processor == processor) stream.buffer->processor = next; if (prev) prev->next = next; if (next) next->prev = prev; RL_FREE(processor); } processor = next; } ma_mutex_unlock(&AUDIO.System.lock); } // Add processor to audio pipeline. Order of processors is important // Works the same way as {Attach,Detach}AudioStreamProcessor() functions, except // these two work on the already mixed output just before sending it to the sound hardware void AttachAudioMixedProcessor(AudioCallback process) { ma_mutex_lock(&AUDIO.System.lock); rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor)); processor->process = process; rAudioProcessor *last = AUDIO.mixedProcessor; while (last && last->next) { last = last->next; } if (last) { processor->prev = last; last->next = processor; } else AUDIO.mixedProcessor = processor; ma_mutex_unlock(&AUDIO.System.lock); } // Remove processor from audio pipeline void DetachAudioMixedProcessor(AudioCallback process) { ma_mutex_lock(&AUDIO.System.lock); rAudioProcessor *processor = AUDIO.mixedProcessor; while (processor) { rAudioProcessor *next = processor->next; rAudioProcessor *prev = processor->prev; if (processor->process == process) { if (AUDIO.mixedProcessor == processor) AUDIO.mixedProcessor = next; if (prev) prev->next = next; if (next) next->prev = prev; RL_FREE(processor); } processor = next; } ma_mutex_unlock(&AUDIO.System.lock); } //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- // Log callback function static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage) { TRACELOG(LOG_WARNING, "miniaudio: %s", pMessage); // All log messages from miniaudio are errors } // Reads audio data from an AudioBuffer object in internal format. static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount) { // Using audio buffer callback if (audioBuffer->callback) { audioBuffer->callback(framesOut, frameCount); audioBuffer->framesProcessed += frameCount; return frameCount; } ma_uint32 subBufferSizeInFrames = (audioBuffer->sizeInFrames > 1)? audioBuffer->sizeInFrames/2 : audioBuffer->sizeInFrames; ma_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames; if (currentSubBufferIndex > 1) return 0; // Another thread can update the processed state of buffers, so // we just take a copy here to try and avoid potential synchronization problems bool isSubBufferProcessed[2] = { 0 }; isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0]; isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1]; ma_uint32 frameSizeInBytes = ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn); // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0 ma_uint32 framesRead = 0; while (1) { // We break from this loop differently depending on the buffer's usage // - For static buffers, we simply fill as much data as we can // - For streaming buffers we only fill half of the buffer that are processed // Unprocessed halves must keep their audio data in-tact if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) { if (framesRead >= frameCount) break; } else { if (isSubBufferProcessed[currentSubBufferIndex]) break; } ma_uint32 totalFramesRemaining = (frameCount - framesRead); if (totalFramesRemaining == 0) break; ma_uint32 framesRemainingInOutputBuffer; if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) { framesRemainingInOutputBuffer = audioBuffer->sizeInFrames - audioBuffer->frameCursorPos; } else { ma_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames*currentSubBufferIndex; framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer); } ma_uint32 framesToRead = totalFramesRemaining; if (framesToRead > framesRemainingInOutputBuffer) framesToRead = framesRemainingInOutputBuffer; memcpy((unsigned char *)framesOut + (framesRead*frameSizeInBytes), audioBuffer->data + (audioBuffer->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead)%audioBuffer->sizeInFrames; framesRead += framesToRead; // If we've read to the end of the buffer, mark it as processed if (framesToRead == framesRemainingInOutputBuffer) { audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true; isSubBufferProcessed[currentSubBufferIndex] = true; currentSubBufferIndex = (currentSubBufferIndex + 1)%2; // We need to break from this loop if we're not looping if (!audioBuffer->looping) { StopAudioBuffer(audioBuffer); break; } } } // Zero-fill excess ma_uint32 totalFramesRemaining = (frameCount - framesRead); if (totalFramesRemaining > 0) { memset((unsigned char *)framesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); // For static buffers we can fill the remaining frames with silence for safety, but we don't want // to report those frames as "read". The reason for this is that the caller uses the return value // to know whether a non-looping sound has finished playback. if (audioBuffer->usage != AUDIO_BUFFER_USAGE_STATIC) framesRead += totalFramesRemaining; } return framesRead; } // Reads audio data from an AudioBuffer object in device format. Returned data will be in a format appropriate for mixing. static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount) { // What's going on here is that we're continuously converting data from the AudioBuffer's internal format to the mixing format, which // should be defined by the output format of the data converter. We do this until frameCount frames have been output. The important // detail to remember here is that we never, ever attempt to read more input data than is required for the specified number of output // frames. This can be achieved with ma_data_converter_get_required_input_frame_count(). ma_uint8 inputBuffer[4096] = { 0 }; ma_uint32 inputBufferFrameCap = sizeof(inputBuffer)/ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn); ma_uint32 totalOutputFramesProcessed = 0; while (totalOutputFramesProcessed < frameCount) { ma_uint64 outputFramesToProcessThisIteration = frameCount - totalOutputFramesProcessed; ma_uint64 inputFramesToProcessThisIteration = 0; (void)ma_data_converter_get_required_input_frame_count(&audioBuffer->converter, outputFramesToProcessThisIteration, &inputFramesToProcessThisIteration); if (inputFramesToProcessThisIteration > inputBufferFrameCap) { inputFramesToProcessThisIteration = inputBufferFrameCap; } float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.channelsOut); /* At this point we can convert the data to our mixing format. */ ma_uint64 inputFramesProcessedThisIteration = ReadAudioBufferFramesInInternalFormat(audioBuffer, inputBuffer, (ma_uint32)inputFramesToProcessThisIteration); /* Safe cast. */ ma_uint64 outputFramesProcessedThisIteration = outputFramesToProcessThisIteration; ma_data_converter_process_pcm_frames(&audioBuffer->converter, inputBuffer, &inputFramesProcessedThisIteration, runningFramesOut, &outputFramesProcessedThisIteration); totalOutputFramesProcessed += (ma_uint32)outputFramesProcessedThisIteration; /* Safe cast. */ if (inputFramesProcessedThisIteration < inputFramesToProcessThisIteration) { break; /* Ran out of input data. */ } /* This should never be hit, but will add it here for safety. Ensures we get out of the loop when no input nor output frames are processed. */ if (inputFramesProcessedThisIteration == 0 && outputFramesProcessedThisIteration == 0) { break; } } return totalOutputFramesProcessed; } // Sending audio data to device callback function // This function will be called when miniaudio needs more data // NOTE: All the mixing takes place here static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount) { (void)pDevice; // Mixing is basically just an accumulation, we need to initialize the output buffer to 0 memset(pFramesOut, 0, frameCount*pDevice->playback.channels*ma_get_bytes_per_sample(pDevice->playback.format)); // Using a mutex here for thread-safety which makes things not real-time // This is unlikely to be necessary for this project, but may want to consider how you might want to avoid this ma_mutex_lock(&AUDIO.System.lock); { for (AudioBuffer *audioBuffer = AUDIO.Buffer.first; audioBuffer != NULL; audioBuffer = audioBuffer->next) { // Ignore stopped or paused sounds if (!audioBuffer->playing || audioBuffer->paused) continue; ma_uint32 framesRead = 0; while (1) { if (framesRead >= frameCount) break; // Just read as much data as we can from the stream ma_uint32 framesToRead = (frameCount - framesRead); while (framesToRead > 0) { float tempBuffer[1024] = { 0 }; // Frames for stereo ma_uint32 framesToReadRightNow = framesToRead; if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS) { framesToReadRightNow = sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS; } ma_uint32 framesJustRead = ReadAudioBufferFramesInMixingFormat(audioBuffer, tempBuffer, framesToReadRightNow); if (framesJustRead > 0) { float *framesOut = (float *)pFramesOut + (framesRead*AUDIO.System.device.playback.channels); float *framesIn = tempBuffer; // Apply processors chain if defined rAudioProcessor *processor = audioBuffer->processor; while (processor) { processor->process(framesIn, framesJustRead); processor = processor->next; } MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer); framesToRead -= framesJustRead; framesRead += framesJustRead; } if (!audioBuffer->playing) { framesRead = frameCount; break; } // If we weren't able to read all the frames we requested, break if (framesJustRead < framesToReadRightNow) { if (!audioBuffer->looping) { StopAudioBuffer(audioBuffer); break; } else { // Should never get here, but just for safety, // move the cursor position back to the start and continue the loop audioBuffer->frameCursorPos = 0; continue; } } } // If for some reason we weren't able to read every frame we'll need to break from the loop // Not doing this could theoretically put us into an infinite loop if (framesToRead > 0) break; } } } rAudioProcessor *processor = AUDIO.mixedProcessor; while (processor) { processor->process(pFramesOut, frameCount); processor = processor->next; } ma_mutex_unlock(&AUDIO.System.lock); } // Main mixing function, pretty simple in this project, just an accumulation // NOTE: framesOut is both an input and an output, it is initially filled with zeros outside of this function static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer) { const float localVolume = buffer->volume; const ma_uint32 channels = AUDIO.System.device.playback.channels; if (channels == 2) // We consider panning { const float left = buffer->pan; const float right = 1.0f - left; // Fast sine approximation in [0..1] for pan law: y = 0.5f*x*(3 - x*x); const float levels[2] = { localVolume*0.5f*left*(3.0f - left*left), localVolume*0.5f*right*(3.0f - right*right) }; float *frameOut = framesOut; const float *frameIn = framesIn; for (ma_uint32 frame = 0; frame < frameCount; frame++) { frameOut[0] += (frameIn[0]*levels[0]); frameOut[1] += (frameIn[1]*levels[1]); frameOut += 2; frameIn += 2; } } else // We do not consider panning { for (ma_uint32 frame = 0; frame < frameCount; frame++) { for (ma_uint32 c = 0; c < channels; c++) { float *frameOut = framesOut + (frame*channels); const float *frameIn = framesIn + (frame*channels); // Output accumulates input multiplied by volume to provided output (usually 0) frameOut[c] += (frameIn[c]*localVolume); } } } } // Some required functions for audio standalone module version #if defined(RAUDIO_STANDALONE) // Check file extension static bool IsFileExtension(const char *fileName, const char *ext) { bool result = false; const char *fileExt; if ((fileExt = strrchr(fileName, '.')) != NULL) { if (strcmp(fileExt, ext) == 0) result = true; } return result; } // Get pointer to extension for a filename string (includes the dot: .png) static const char *GetFileExtension(const char *fileName) { const char *dot = strrchr(fileName, '.'); if (!dot || dot == fileName) return NULL; return dot; } // Load data from file into a buffer static unsigned char *LoadFileData(const char *fileName, int *dataSize) { unsigned char *data = NULL; *dataSize = 0; if (fileName != NULL) { FILE *file = fopen(fileName, "rb"); if (file != NULL) { // WARNING: On binary streams SEEK_END could not be found, // using fseek() and ftell() could not work in some (rare) cases fseek(file, 0, SEEK_END); int size = ftell(file); fseek(file, 0, SEEK_SET); if (size > 0) { data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char)); // NOTE: fread() returns number of read elements instead of bytes, so we read [1 byte, size elements] unsigned int count = (unsigned int)fread(data, sizeof(unsigned char), size, file); *dataSize = count; if (count != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded", fileName); else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName); } else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName); fclose(file); } else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); } else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); return data; } // Save data to file from buffer static bool SaveFileData(const char *fileName, void *data, int dataSize) { if (fileName != NULL) { FILE *file = fopen(fileName, "wb"); if (file != NULL) { unsigned int count = (unsigned int)fwrite(data, sizeof(unsigned char), dataSize, file); if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName); else if (count != dataSize) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName); else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName); fclose(file); } else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); } else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); } // Save text data to file (write), string must be '\0' terminated static bool SaveFileText(const char *fileName, char *text) { if (fileName != NULL) { FILE *file = fopen(fileName, "wt"); if (file != NULL) { int count = fprintf(file, "%s", text); if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); fclose(file); } else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); } else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); } #endif #undef AudioBuffer #endif // SUPPORT_MODULE_RAUDIO