langsmoke/ref_c

5529 lines
190 KiB
Plaintext
Raw Normal View History

2023-11-12 13:49:57 +00:00
#include <string.h>
#include <stdint.h>
#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;
}
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("? <tab>, 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 <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>
#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;
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 <stdlib.h>
#include <string.h>
#include <stddef.h>
#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 <function name>(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 <http://www.gnu.org/licenses/>.
*/
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/time.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
#include <utime.h>
#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 <sys/sysctl.h>
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 <sys/resource.h>
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, &times)))
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 <http://www.gnu.org/licenses/>.
*/
#ifndef MRZIP_UTIL_H
#define MRZIP_UTIL_H
#include <errno.h>
#include <fcntl.h>
#include <semaphore.h>
#include <stdarg.h>
#include <stdnoreturn.h>
#include <unistd.h>
#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
//----------------------------------------------------------------------------------
// 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;
}
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
2023-11-12 17:02:04 +00:00
#endif // SUPPORT_MODULE_RAUDIO
#include <linux/compat.h>
#include <linux/jhash.h>
#include <linux/pagemap.h>
#include <linux/memblock.h>
#include <linux/fault-inject.h>
#include <linux/slab.h>
#include "futex.h"
#include "../locking/rtmutex_common.h"
/*
* The base of the bucket array and its size are always used together
* (after initialization only in futex_hash()), so ensure that they
* reside in the same cacheline.
*/
static struct {
struct futex_hash_bucket *queues;
unsigned long hashsize;
} __futex_data __read_mostly __aligned(2*sizeof(long));
#define futex_queues (__futex_data.queues)
#define futex_hashsize (__futex_data.hashsize)
/*
* Fault injections for futexes.
*/
#ifdef CONFIG_FAIL_FUTEX
static struct {
struct fault_attr attr;
bool ignore_private;
} fail_futex = {
.attr = FAULT_ATTR_INITIALIZER,
.ignore_private = false,
};
static int __init setup_fail_futex(char *str)
{
return setup_fault_attr(&fail_futex.attr, str);
}
__setup("fail_futex=", setup_fail_futex);
bool should_fail_futex(bool fshared)
{
if (fail_futex.ignore_private && !fshared)
return false;
return should_fail(&fail_futex.attr, 1);
}
#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS
static int __init fail_futex_debugfs(void)
{
umode_t mode = S_IFREG | S_IRUSR | S_IWUSR;
struct dentry *dir;
dir = fault_create_debugfs_attr("fail_futex", NULL,
&fail_futex.attr);
if (IS_ERR(dir))
return PTR_ERR(dir);
debugfs_create_bool("ignore-private", mode, dir,
&fail_futex.ignore_private);
return 0;
}
late_initcall(fail_futex_debugfs);
#endif /* CONFIG_FAULT_INJECTION_DEBUG_FS */
#endif /* CONFIG_FAIL_FUTEX */
/**
* futex_hash - Return the hash bucket in the global hash
* @key: Pointer to the futex key for which the hash is calculated
*
* We hash on the keys returned from get_futex_key (see below) and return the
* corresponding hash bucket in the global hash.
*/
struct futex_hash_bucket *futex_hash(union futex_key *key)
{
u32 hash = jhash2((u32 *)key, offsetof(typeof(*key), both.offset) / 4,
key->both.offset);
return &futex_queues[hash & (futex_hashsize - 1)];
}
/**
* futex_setup_timer - set up the sleeping hrtimer.
* @time: ptr to the given timeout value
* @timeout: the hrtimer_sleeper structure to be set up
* @flags: futex flags
* @range_ns: optional range in ns
*
* Return: Initialized hrtimer_sleeper structure or NULL if no timeout
* value given
*/
struct hrtimer_sleeper *
futex_setup_timer(ktime_t *time, struct hrtimer_sleeper *timeout,
int flags, u64 range_ns)
{
if (!time)
return NULL;
hrtimer_init_sleeper_on_stack(timeout, (flags & FLAGS_CLOCKRT) ?
CLOCK_REALTIME : CLOCK_MONOTONIC,
HRTIMER_MODE_ABS);
/*
* If range_ns is 0, calling hrtimer_set_expires_range_ns() is
* effectively the same as calling hrtimer_set_expires().
*/
hrtimer_set_expires_range_ns(&timeout->timer, *time, range_ns);
return timeout;
}
/*
* Generate a machine wide unique identifier for this inode.
*
* This relies on u64 not wrapping in the life-time of the machine; which with
* 1ns resolution means almost 585 years.
*
* This further relies on the fact that a well formed program will not unmap
* the file while it has a (shared) futex waiting on it. This mapping will have
* a file reference which pins the mount and inode.
*
* If for some reason an inode gets evicted and read back in again, it will get
* a new sequence number and will _NOT_ match, even though it is the exact same
* file.
*
* It is important that futex_match() will never have a false-positive, esp.
* for PI futexes that can mess up the state. The above argues that false-negatives
* are only possible for malformed programs.
*/
static u64 get_inode_sequence_number(struct inode *inode)
{
static atomic64_t i_seq;
u64 old;
/* Does the inode already have a sequence number? */
old = atomic64_read(&inode->i_sequence);
if (likely(old))
return old;
for (;;) {
u64 new = atomic64_add_return(1, &i_seq);
if (WARN_ON_ONCE(!new))
continue;
old = atomic64_cmpxchg_relaxed(&inode->i_sequence, 0, new);
if (old)
return old;
return new;
}
}
/**
* get_futex_key() - Get parameters which are the keys for a futex
* @uaddr: virtual address of the futex
* @flags: FLAGS_*
* @key: address where result is stored.
* @rw: mapping needs to be read/write (values: FUTEX_READ,
* FUTEX_WRITE)
*
* Return: a negative error code or 0
*
* The key words are stored in @key on success.
*
* For shared mappings (when @fshared), the key is:
*
* ( inode->i_sequence, page->index, offset_within_page )
*
* [ also see get_inode_sequence_number() ]
*
* For private mappings (or when !@fshared), the key is:
*
* ( current->mm, address, 0 )
*
* This allows (cross process, where applicable) identification of the futex
* without keeping the page pinned for the duration of the FUTEX_WAIT.
*
* lock_page() might sleep, the caller should not hold a spinlock.
*/
int get_futex_key(u32 __user *uaddr, unsigned int flags, union futex_key *key,
enum futex_access rw)
{
unsigned long address = (unsigned long)uaddr;
struct mm_struct *mm = current->mm;
struct page *page;
struct folio *folio;
struct address_space *mapping;
int err, ro = 0;
bool fshared;
fshared = flags & FLAGS_SHARED;
/*
* The futex address must be "naturally" aligned.
*/
key->both.offset = address % PAGE_SIZE;
if (unlikely((address % sizeof(u32)) != 0))
return -EINVAL;
address -= key->both.offset;
if (unlikely(!access_ok(uaddr, sizeof(u32))))
return -EFAULT;
if (unlikely(should_fail_futex(fshared)))
return -EFAULT;
/*
* PROCESS_PRIVATE futexes are fast.
* As the mm cannot disappear under us and the 'key' only needs
* virtual address, we dont even have to find the underlying vma.
* Note : We do have to check 'uaddr' is a valid user address,
* but access_ok() should be faster than find_vma()
*/
if (!fshared) {
/*
* On no-MMU, shared futexes are treated as private, therefore
* we must not include the current process in the key. Since
* there is only one address space, the address is a unique key
* on its own.
*/
if (IS_ENABLED(CONFIG_MMU))
key->private.mm = mm;
else
key->private.mm = NULL;
key->private.address = address;
return 0;
}
again:
/* Ignore any VERIFY_READ mapping (futex common case) */
if (unlikely(should_fail_futex(true)))
return -EFAULT;
err = get_user_pages_fast(address, 1, FOLL_WRITE, &page);
/*
* If write access is not required (eg. FUTEX_WAIT), try
* and get read-only access.
*/
if (err == -EFAULT && rw == FUTEX_READ) {
err = get_user_pages_fast(address, 1, 0, &page);
ro = 1;
}
if (err < 0)
return err;
else
err = 0;
/*
* The treatment of mapping from this point on is critical. The folio
* lock protects many things but in this context the folio lock
* stabilizes mapping, prevents inode freeing in the shared
* file-backed region case and guards against movement to swap cache.
*
* Strictly speaking the folio lock is not needed in all cases being
* considered here and folio lock forces unnecessarily serialization.
* From this point on, mapping will be re-verified if necessary and
* folio lock will be acquired only if it is unavoidable
*
* Mapping checks require the folio so it is looked up now. For
* anonymous pages, it does not matter if the folio is split
* in the future as the key is based on the address. For
* filesystem-backed pages, the precise page is required as the
* index of the page determines the key.
*/
folio = page_folio(page);
mapping = READ_ONCE(folio->mapping);
/*
* If folio->mapping is NULL, then it cannot be an anonymous
* page; but it might be the ZERO_PAGE or in the gate area or
* in a special mapping (all cases which we are happy to fail);
* or it may have been a good file page when get_user_pages_fast
* found it, but truncated or holepunched or subjected to
* invalidate_complete_page2 before we got the folio lock (also
* cases which we are happy to fail). And we hold a reference,
* so refcount care in invalidate_inode_page's remove_mapping
* prevents drop_caches from setting mapping to NULL beneath us.
*
* The case we do have to guard against is when memory pressure made
* shmem_writepage move it from filecache to swapcache beneath us:
* an unlikely race, but we do need to retry for folio->mapping.
*/
if (unlikely(!mapping)) {
int shmem_swizzled;
/*
* Folio lock is required to identify which special case above
* applies. If this is really a shmem page then the folio lock
* will prevent unexpected transitions.
*/
folio_lock(folio);
shmem_swizzled = folio_test_swapcache(folio) || folio->mapping;
folio_unlock(folio);
folio_put(folio);
if (shmem_swizzled)
goto again;
return -EFAULT;
}
/*
* Private mappings are handled in a simple way.
*
* If the futex key is stored in anonymous memory, then the associated
* object is the mm which is implicitly pinned by the calling process.
*
* NOTE: When userspace waits on a MAP_SHARED mapping, even if
* it's a read-only handle, it's expected that futexes attach to
* the object not the particular process.
*/
if (folio_test_anon(folio)) {
/*
* A RO anonymous page will never change and thus doesn't make
* sense for futex operations.
*/
if (unlikely(should_fail_futex(true)) || ro) {
err = -EFAULT;
goto out;
}
key->both.offset |= FUT_OFF_MMSHARED; /* ref taken on mm */
key->private.mm = mm;
key->private.address = address;
} else {
struct inode *inode;
/*
* The associated futex object in this case is the inode and
* the folio->mapping must be traversed. Ordinarily this should
* be stabilised under folio lock but it's not strictly
* necessary in this case as we just want to pin the inode, not
* update i_pages or anything like that.
*
* The RCU read lock is taken as the inode is finally freed
* under RCU. If the mapping still matches expectations then the
* mapping->host can be safely accessed as being a valid inode.
*/
rcu_read_lock();
if (READ_ONCE(folio->mapping) != mapping) {
rcu_read_unlock();
folio_put(folio);
goto again;
}
inode = READ_ONCE(mapping->host);
if (!inode) {
rcu_read_unlock();
folio_put(folio);
goto again;
}
key->both.offset |= FUT_OFF_INODE; /* inode-based key */
key->shared.i_seq = get_inode_sequence_number(inode);
key->shared.pgoff = folio->index + folio_page_idx(folio, page);
rcu_read_unlock();
}
out:
folio_put(folio);
return err;
}
/**
* fault_in_user_writeable() - Fault in user address and verify RW access
* @uaddr: pointer to faulting user space address
*
* Slow path to fixup the fault we just took in the atomic write
* access to @uaddr.
*
* We have no generic implementation of a non-destructive write to the
* user address. We know that we faulted in the atomic pagefault
* disabled section so we can as well avoid the #PF overhead by
* calling get_user_pages() right away.
*/
int fault_in_user_writeable(u32 __user *uaddr)
{
struct mm_struct *mm = current->mm;
int ret;
mmap_read_lock(mm);
ret = fixup_user_fault(mm, (unsigned long)uaddr,
FAULT_FLAG_WRITE, NULL);
mmap_read_unlock(mm);
return ret < 0 ? ret : 0;
}
/**
* futex_top_waiter() - Return the highest priority waiter on a futex
* @hb: the hash bucket the futex_q's reside in
* @key: the futex key (to distinguish it from other futex futex_q's)
*
* Must be called with the hb lock held.
*/
struct futex_q *futex_top_waiter(struct futex_hash_bucket *hb, union futex_key *key)
{
struct futex_q *this;
plist_for_each_entry(this, &hb->chain, list) {
if (futex_match(&this->key, key))
return this;
}
return NULL;
}
int futex_cmpxchg_value_locked(u32 *curval, u32 __user *uaddr, u32 uval, u32 newval)
{
int ret;
pagefault_disable();
ret = futex_atomic_cmpxchg_inatomic(curval, uaddr, uval, newval);
pagefault_enable();
return ret;
}
int futex_get_value_locked(u32 *dest, u32 __user *from)
{
int ret;
pagefault_disable();
ret = __get_user(*dest, from);
pagefault_enable();
return ret ? -EFAULT : 0;
}
/**
* wait_for_owner_exiting - Block until the owner has exited
* @ret: owner's current futex lock status
* @exiting: Pointer to the exiting task
*
* Caller must hold a refcount on @exiting.
*/
void wait_for_owner_exiting(int ret, struct task_struct *exiting)
{
if (ret != -EBUSY) {
WARN_ON_ONCE(exiting);
return;
}
if (WARN_ON_ONCE(ret == -EBUSY && !exiting))
return;
mutex_lock(&exiting->futex_exit_mutex);
/*
* No point in doing state checking here. If the waiter got here
* while the task was in exec()->exec_futex_release() then it can
* have any FUTEX_STATE_* value when the waiter has acquired the
* mutex. OK, if running, EXITING or DEAD if it reached exit()
* already. Highly unlikely and not a problem. Just one more round
* through the futex maze.
*/
mutex_unlock(&exiting->futex_exit_mutex);
put_task_struct(exiting);
}
/**
* __futex_unqueue() - Remove the futex_q from its futex_hash_bucket
* @q: The futex_q to unqueue
*
* The q->lock_ptr must not be NULL and must be held by the caller.
*/
void __futex_unqueue(struct futex_q *q)
{
struct futex_hash_bucket *hb;
if (WARN_ON_SMP(!q->lock_ptr) || WARN_ON(plist_node_empty(&q->list)))
return;
lockdep_assert_held(q->lock_ptr);
hb = container_of(q->lock_ptr, struct futex_hash_bucket, lock);
plist_del(&q->list, &hb->chain);
futex_hb_waiters_dec(hb);
}
/* The key must be already stored in q->key. */
struct futex_hash_bucket *futex_q_lock(struct futex_q *q)
__acquires(&hb->lock)
{
struct futex_hash_bucket *hb;
hb = futex_hash(&q->key);
/*
* Increment the counter before taking the lock so that
* a potential waker won't miss a to-be-slept task that is
* waiting for the spinlock. This is safe as all futex_q_lock()
* users end up calling futex_queue(). Similarly, for housekeeping,
* decrement the counter at futex_q_unlock() when some error has
* occurred and we don't end up adding the task to the list.
*/
futex_hb_waiters_inc(hb); /* implies smp_mb(); (A) */
q->lock_ptr = &hb->lock;
spin_lock(&hb->lock);
return hb;
}
void futex_q_unlock(struct futex_hash_bucket *hb)
__releases(&hb->lock)
{
spin_unlock(&hb->lock);
futex_hb_waiters_dec(hb);
}
void __futex_queue(struct futex_q *q, struct futex_hash_bucket *hb)
{
int prio;
/*
* The priority used to register this element is
* - either the real thread-priority for the real-time threads
* (i.e. threads with a priority lower than MAX_RT_PRIO)
* - or MAX_RT_PRIO for non-RT threads.
* Thus, all RT-threads are woken first in priority order, and
* the others are woken last, in FIFO order.
*/
prio = min(current->normal_prio, MAX_RT_PRIO);
plist_node_init(&q->list, prio);
plist_add(&q->list, &hb->chain);
q->task = current;
}
/**
* futex_unqueue() - Remove the futex_q from its futex_hash_bucket
* @q: The futex_q to unqueue
*
* The q->lock_ptr must not be held by the caller. A call to futex_unqueue() must
* be paired with exactly one earlier call to futex_queue().
*
* Return:
* - 1 - if the futex_q was still queued (and we removed unqueued it);
* - 0 - if the futex_q was already removed by the waking thread
*/
int futex_unqueue(struct futex_q *q)
{
spinlock_t *lock_ptr;
int ret = 0;
/* In the common case we don't take the spinlock, which is nice. */
retry:
/*
* q->lock_ptr can change between this read and the following spin_lock.
* Use READ_ONCE to forbid the compiler from reloading q->lock_ptr and
* optimizing lock_ptr out of the logic below.
*/
lock_ptr = READ_ONCE(q->lock_ptr);
if (lock_ptr != NULL) {
spin_lock(lock_ptr);
/*
* q->lock_ptr can change between reading it and
* spin_lock(), causing us to take the wrong lock. This
* corrects the race condition.
*
* Reasoning goes like this: if we have the wrong lock,
* q->lock_ptr must have changed (maybe several times)
* between reading it and the spin_lock(). It can
* change again after the spin_lock() but only if it was
* already changed before the spin_lock(). It cannot,
* however, change back to the original value. Therefore
* we can detect whether we acquired the correct lock.
*/
if (unlikely(lock_ptr != q->lock_ptr)) {
spin_unlock(lock_ptr);
goto retry;
}
__futex_unqueue(q);
BUG_ON(q->pi_state);
spin_unlock(lock_ptr);
ret = 1;
}
return ret;
}
/*
* PI futexes can not be requeued and must remove themselves from the
* hash bucket. The hash bucket lock (i.e. lock_ptr) is held.
*/
void futex_unqueue_pi(struct futex_q *q)
{
__futex_unqueue(q);
BUG_ON(!q->pi_state);
put_pi_state(q->pi_state);
q->pi_state = NULL;
}
/* Constants for the pending_op argument of handle_futex_death */
#define HANDLE_DEATH_PENDING true
#define HANDLE_DEATH_LIST false
/*
* Process a futex-list entry, check whether it's owned by the
* dying task, and do notification if so:
*/
static int handle_futex_death(u32 __user *uaddr, struct task_struct *curr,
bool pi, bool pending_op)
{
u32 uval, nval, mval;
pid_t owner;
int err;
/* Futex address must be 32bit aligned */
if ((((unsigned long)uaddr) % sizeof(*uaddr)) != 0)
return -1;
retry:
if (get_user(uval, uaddr))
return -1;
/*
* Special case for regular (non PI) futexes. The unlock path in
* user space has two race scenarios:
*
* 1. The unlock path releases the user space futex value and
* before it can execute the futex() syscall to wake up
* waiters it is killed.
*
* 2. A woken up waiter is killed before it can acquire the
* futex in user space.
*
* In the second case, the wake up notification could be generated
* by the unlock path in user space after setting the futex value
* to zero or by the kernel after setting the OWNER_DIED bit below.
*
* In both cases the TID validation below prevents a wakeup of
* potential waiters which can cause these waiters to block
* forever.
*
* In both cases the following conditions are met:
*
* 1) task->robust_list->list_op_pending != NULL
* @pending_op == true
* 2) The owner part of user space futex value == 0
* 3) Regular futex: @pi == false
*
* If these conditions are met, it is safe to attempt waking up a
* potential waiter without touching the user space futex value and
* trying to set the OWNER_DIED bit. If the futex value is zero,
* the rest of the user space mutex state is consistent, so a woken
* waiter will just take over the uncontended futex. Setting the
* OWNER_DIED bit would create inconsistent state and malfunction
* of the user space owner died handling. Otherwise, the OWNER_DIED
* bit is already set, and the woken waiter is expected to deal with
* this.
*/
owner = uval & FUTEX_TID_MASK;
if (pending_op && !pi && !owner) {
futex_wake(uaddr, 1, 1, FUTEX_BITSET_MATCH_ANY);
return 0;
}
if (owner != task_pid_vnr(curr))
return 0;
/*
* Ok, this dying thread is truly holding a futex
* of interest. Set the OWNER_DIED bit atomically
* via cmpxchg, and if the value had FUTEX_WAITERS
* set, wake up a waiter (if any). (We have to do a
* futex_wake() even if OWNER_DIED is already set -
* to handle the rare but possible case of recursive
* thread-death.) The rest of the cleanup is done in
* userspace.
*/
mval = (uval & FUTEX_WAITERS) | FUTEX_OWNER_DIED;
/*
* We are not holding a lock here, but we want to have
* the pagefault_disable/enable() protection because
* we want to handle the fault gracefully. If the
* access fails we try to fault in the futex with R/W
* verification via get_user_pages. get_user() above
* does not guarantee R/W access. If that fails we
* give up and leave the futex locked.
*/
if ((err = futex_cmpxchg_value_locked(&nval, uaddr, uval, mval))) {
switch (err) {
case -EFAULT:
if (fault_in_user_writeable(uaddr))
return -1;
goto retry;
case -EAGAIN:
cond_resched();
goto retry;
default:
WARN_ON_ONCE(1);
return err;
}
}
if (nval != uval)
goto retry;
/*
* Wake robust non-PI futexes here. The wakeup of
* PI futexes happens in exit_pi_state():
*/
if (!pi && (uval & FUTEX_WAITERS))
futex_wake(uaddr, 1, 1, FUTEX_BITSET_MATCH_ANY);
return 0;
}
/*
* Fetch a robust-list pointer. Bit 0 signals PI futexes:
*/
static inline int fetch_robust_entry(struct robust_list __user **entry,
struct robust_list __user * __user *head,
unsigned int *pi)
{
unsigned long uentry;
if (get_user(uentry, (unsigned long __user *)head))
return -EFAULT;
*entry = (void __user *)(uentry & ~1UL);
*pi = uentry & 1;
return 0;
}
/*
* Walk curr->robust_list (very carefully, it's a userspace list!)
* and mark any locks found there dead, and notify any waiters.
*
* We silently return on any sign of list-walking problem.
*/
static void exit_robust_list(struct task_struct *curr)
{
struct robust_list_head __user *head = curr->robust_list;
struct robust_list __user *entry, *next_entry, *pending;
unsigned int limit = ROBUST_LIST_LIMIT, pi, pip;
unsigned int next_pi;
unsigned long futex_offset;
int rc;
/*
* Fetch the list head (which was registered earlier, via
* sys_set_robust_list()):
*/
if (fetch_robust_entry(&entry, &head->list.next, &pi))
return;
/*
* Fetch the relative futex offset:
*/
if (get_user(futex_offset, &head->futex_offset))
return;
/*
* Fetch any possibly pending lock-add first, and handle it
* if it exists:
*/
if (fetch_robust_entry(&pending, &head->list_op_pending, &pip))
return;
next_entry = NULL; /* avoid warning with gcc */
while (entry != &head->list) {
/*
* Fetch the next entry in the list before calling
* handle_futex_death:
*/
rc = fetch_robust_entry(&next_entry, &entry->next, &next_pi);
/*
* A pending lock might already be on the list, so
* don't process it twice:
*/
if (entry != pending) {
if (handle_futex_death((void __user *)entry + futex_offset,
curr, pi, HANDLE_DEATH_LIST))
return;
}
if (rc)
return;
entry = next_entry;
pi = next_pi;
/*
* Avoid excessively long or circular lists:
*/
if (!--limit)
break;
cond_resched();
}
if (pending) {
handle_futex_death((void __user *)pending + futex_offset,
curr, pip, HANDLE_DEATH_PENDING);
}
}
#ifdef CONFIG_COMPAT
static void __user *futex_uaddr(struct robust_list __user *entry,
compat_long_t futex_offset)
{
compat_uptr_t base = ptr_to_compat(entry);
void __user *uaddr = compat_ptr(base + futex_offset);
return uaddr;
}
/*
* Fetch a robust-list pointer. Bit 0 signals PI futexes:
*/
static inline int
compat_fetch_robust_entry(compat_uptr_t *uentry, struct robust_list __user **entry,
compat_uptr_t __user *head, unsigned int *pi)
{
if (get_user(*uentry, head))
return -EFAULT;
*entry = compat_ptr((*uentry) & ~1);
*pi = (unsigned int)(*uentry) & 1;
return 0;
}
/*
* Walk curr->robust_list (very carefully, it's a userspace list!)
* and mark any locks found there dead, and notify any waiters.
*
* We silently return on any sign of list-walking problem.
*/
static void compat_exit_robust_list(struct task_struct *curr)
{
struct compat_robust_list_head __user *head = curr->compat_robust_list;
struct robust_list __user *entry, *next_entry, *pending;
unsigned int limit = ROBUST_LIST_LIMIT, pi, pip;
unsigned int next_pi;
compat_uptr_t uentry, next_uentry, upending;
compat_long_t futex_offset;
int rc;
/*
* Fetch the list head (which was registered earlier, via
* sys_set_robust_list()):
*/
if (compat_fetch_robust_entry(&uentry, &entry, &head->list.next, &pi))
return;
/*
* Fetch the relative futex offset:
*/
if (get_user(futex_offset, &head->futex_offset))
return;
/*
* Fetch any possibly pending lock-add first, and handle it
* if it exists:
*/
if (compat_fetch_robust_entry(&upending, &pending,
&head->list_op_pending, &pip))
return;
next_entry = NULL; /* avoid warning with gcc */
while (entry != (struct robust_list __user *) &head->list) {
/*
* Fetch the next entry in the list before calling
* handle_futex_death:
*/
rc = compat_fetch_robust_entry(&next_uentry, &next_entry,
(compat_uptr_t __user *)&entry->next, &next_pi);
/*
* A pending lock might already be on the list, so
* dont process it twice:
*/
if (entry != pending) {
void __user *uaddr = futex_uaddr(entry, futex_offset);
if (handle_futex_death(uaddr, curr, pi,
HANDLE_DEATH_LIST))
return;
}
if (rc)
return;
uentry = next_uentry;
entry = next_entry;
pi = next_pi;
/*
* Avoid excessively long or circular lists:
*/
if (!--limit)
break;
cond_resched();
}
if (pending) {
void __user *uaddr = futex_uaddr(pending, futex_offset);
handle_futex_death(uaddr, curr, pip, HANDLE_DEATH_PENDING);
}
}
#endif
#ifdef CONFIG_FUTEX_PI
/*
* This task is holding PI mutexes at exit time => bad.
* Kernel cleans up PI-state, but userspace is likely hosed.
* (Robust-futex cleanup is separate and might save the day for userspace.)
*/
static void exit_pi_state_list(struct task_struct *curr)
{
struct list_head *next, *head = &curr->pi_state_list;
struct futex_pi_state *pi_state;
struct futex_hash_bucket *hb;
union futex_key key = FUTEX_KEY_INIT;
/*
* We are a ZOMBIE and nobody can enqueue itself on
* pi_state_list anymore, but we have to be careful
* versus waiters unqueueing themselves:
*/
raw_spin_lock_irq(&curr->pi_lock);
while (!list_empty(head)) {
next = head->next;
pi_state = list_entry(next, struct futex_pi_state, list);
key = pi_state->key;
hb = futex_hash(&key);
/*
* We can race against put_pi_state() removing itself from the
* list (a waiter going away). put_pi_state() will first
* decrement the reference count and then modify the list, so
* its possible to see the list entry but fail this reference
* acquire.
*
* In that case; drop the locks to let put_pi_state() make
* progress and retry the loop.
*/
if (!refcount_inc_not_zero(&pi_state->refcount)) {
raw_spin_unlock_irq(&curr->pi_lock);
cpu_relax();
raw_spin_lock_irq(&curr->pi_lock);
continue;
}
raw_spin_unlock_irq(&curr->pi_lock);
spin_lock(&hb->lock);
raw_spin_lock_irq(&pi_state->pi_mutex.wait_lock);
raw_spin_lock(&curr->pi_lock);
/*
* We dropped the pi-lock, so re-check whether this
* task still owns the PI-state:
*/
if (head->next != next) {
/* retain curr->pi_lock for the loop invariant */
raw_spin_unlock(&pi_state->pi_mutex.wait_lock);
spin_unlock(&hb->lock);
put_pi_state(pi_state);
continue;
}
WARN_ON(pi_state->owner != curr);
WARN_ON(list_empty(&pi_state->list));
list_del_init(&pi_state->list);
pi_state->owner = NULL;
raw_spin_unlock(&curr->pi_lock);
raw_spin_unlock_irq(&pi_state->pi_mutex.wait_lock);
spin_unlock(&hb->lock);
rt_mutex_futex_unlock(&pi_state->pi_mutex);
put_pi_state(pi_state);
raw_spin_lock_irq(&curr->pi_lock);
}
raw_spin_unlock_irq(&curr->pi_lock);
}
#else
static inline void exit_pi_state_list(struct task_struct *curr) { }
#endif
static void futex_cleanup(struct task_struct *tsk)
{
if (unlikely(tsk->robust_list)) {
exit_robust_list(tsk);
tsk->robust_list = NULL;
}
#ifdef CONFIG_COMPAT
if (unlikely(tsk->compat_robust_list)) {
compat_exit_robust_list(tsk);
tsk->compat_robust_list = NULL;
}
#endif
if (unlikely(!list_empty(&tsk->pi_state_list)))
exit_pi_state_list(tsk);
}
/**
* futex_exit_recursive - Set the tasks futex state to FUTEX_STATE_DEAD
* @tsk: task to set the state on
*
* Set the futex exit state of the task lockless. The futex waiter code
* observes that state when a task is exiting and loops until the task has
* actually finished the futex cleanup. The worst case for this is that the
* waiter runs through the wait loop until the state becomes visible.
*
* This is called from the recursive fault handling path in make_task_dead().
*
* This is best effort. Either the futex exit code has run already or
* not. If the OWNER_DIED bit has been set on the futex then the waiter can
* take it over. If not, the problem is pushed back to user space. If the
* futex exit code did not run yet, then an already queued waiter might
* block forever, but there is nothing which can be done about that.
*/
void futex_exit_recursive(struct task_struct *tsk)
{
/* If the state is FUTEX_STATE_EXITING then futex_exit_mutex is held */
if (tsk->futex_state == FUTEX_STATE_EXITING)
mutex_unlock(&tsk->futex_exit_mutex);
tsk->futex_state = FUTEX_STATE_DEAD;
}
static void futex_cleanup_begin(struct task_struct *tsk)
{
/*
* Prevent various race issues against a concurrent incoming waiter
* including live locks by forcing the waiter to block on
* tsk->futex_exit_mutex when it observes FUTEX_STATE_EXITING in
* attach_to_pi_owner().
*/
mutex_lock(&tsk->futex_exit_mutex);
/*
* Switch the state to FUTEX_STATE_EXITING under tsk->pi_lock.
*
* This ensures that all subsequent checks of tsk->futex_state in
* attach_to_pi_owner() must observe FUTEX_STATE_EXITING with
* tsk->pi_lock held.
*
* It guarantees also that a pi_state which was queued right before
* the state change under tsk->pi_lock by a concurrent waiter must
* be observed in exit_pi_state_list().
*/
raw_spin_lock_irq(&tsk->pi_lock);
tsk->futex_state = FUTEX_STATE_EXITING;
raw_spin_unlock_irq(&tsk->pi_lock);
}
static void futex_cleanup_end(struct task_struct *tsk, int state)
{
/*
* Lockless store. The only side effect is that an observer might
* take another loop until it becomes visible.
*/
tsk->futex_state = state;
/*
* Drop the exit protection. This unblocks waiters which observed
* FUTEX_STATE_EXITING to reevaluate the state.
*/
mutex_unlock(&tsk->futex_exit_mutex);
}
void futex_exec_release(struct task_struct *tsk)
{
/*
* The state handling is done for consistency, but in the case of
* exec() there is no way to prevent further damage as the PID stays
* the same. But for the unlikely and arguably buggy case that a
* futex is held on exec(), this provides at least as much state
* consistency protection which is possible.
*/
futex_cleanup_begin(tsk);
futex_cleanup(tsk);
/*
* Reset the state to FUTEX_STATE_OK. The task is alive and about
* exec a new binary.
*/
futex_cleanup_end(tsk, FUTEX_STATE_OK);
}
void futex_exit_release(struct task_struct *tsk)
{
futex_cleanup_begin(tsk);
futex_cleanup(tsk);
futex_cleanup_end(tsk, FUTEX_STATE_DEAD);
}
static int __init futex_init(void)
{
unsigned int futex_shift;
unsigned long i;
#if CONFIG_BASE_SMALL
futex_hashsize = 16;
#else
futex_hashsize = roundup_pow_of_two(256 * num_possible_cpus());
#endif
futex_queues = alloc_large_system_hash("futex", sizeof(*futex_queues),
futex_hashsize, 0, 0,
&futex_shift, NULL,
futex_hashsize, futex_hashsize);
futex_hashsize = 1UL << futex_shift;
for (i = 0; i < futex_hashsize; i++) {
atomic_set(&futex_queues[i].waiters, 0);
plist_head_init(&futex_queues[i].chain);
spin_lock_init(&futex_queues[i].lock);
}
return 0;
}
core_initcall(futex_init);