mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-27 14:42:07 +00:00
4cec187465
Wav file were not read correctly when encoutering most chunk type beside the ones used by QuakeForge. This patch will fix the riff loader code so that unused but defined chunk are skipped. Most wav files should now be loaded correctly fixing some silent sound effect. Also fixed a typo in wav loader and reordered wav validity check so that format is checked first. The data chunk could be inexistant on some weird format and so an invalid format is a more helpful error text. ! Fix: Skip unsupported chunk in riff loader instead of rejecting riff file. ! Fix: typo in Microsoft name. ! Fix: ordering of wav validity to enable more helpful error text.
530 lines
12 KiB
C
530 lines
12 KiB
C
/*
|
|
riff.c
|
|
|
|
riff wav handling
|
|
|
|
Copyright (C) 2003 Bill Currie
|
|
|
|
Author: Bill Currie <bill@taniwha.org>
|
|
Date: 2003/4/10
|
|
|
|
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, write to:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
# include "string.h"
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include "strings.h"
|
|
#endif
|
|
|
|
static __attribute__ ((used)) const char rcsid[] =
|
|
"$Id$";
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "QF/dstring.h"
|
|
#include "QF/qendian.h"
|
|
#include "QF/riff.h"
|
|
|
|
static inline int
|
|
Rread (QFile *f, void *buf, int len)
|
|
{
|
|
int count;
|
|
|
|
count = Qread (f, buf, len);
|
|
if (count != len)
|
|
return 0;
|
|
return count;
|
|
}
|
|
|
|
static char *
|
|
read_string (QFile *f, int len)
|
|
{
|
|
char c[2] = {0, 0};
|
|
char *s;
|
|
int l = len;
|
|
dstring_t *str;
|
|
|
|
if (!len)
|
|
return 0;
|
|
str = dstring_newstr ();
|
|
while (l--) {
|
|
if (Qread (f, c, 1) != 1)
|
|
goto done;
|
|
if (!c[0])
|
|
break;
|
|
dstring_appendstr (str, c);
|
|
}
|
|
Qseek (f, l, SEEK_CUR);
|
|
if (len &1) {
|
|
int c;
|
|
|
|
if ((c = Qgetc (f)) && c != -1)
|
|
Qungetc (f, c);
|
|
}
|
|
done:
|
|
s = str->str;
|
|
free (str);
|
|
return s;
|
|
}
|
|
|
|
static void *
|
|
read_data (QFile *f, int len)
|
|
{
|
|
void *data;
|
|
int count;
|
|
|
|
if (!len)
|
|
return 0;
|
|
data = malloc (len);
|
|
count = Qread (f, data, len);
|
|
if (count == len && len &1) {
|
|
int c;
|
|
|
|
if ((c = Qgetc (f)) && c != -1)
|
|
Qungetc (f, c);
|
|
}
|
|
if (count && count != len)
|
|
data = realloc (data, count);
|
|
if (!count) {
|
|
free (data);
|
|
return 0;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
static int
|
|
read_ltxt (QFile *f, int len, riff_d_ltxt_t *ltxt)
|
|
{
|
|
if (!Rread (f, ltxt, len))
|
|
return 0;
|
|
ltxt->name = LittleLong (ltxt->name);
|
|
ltxt->len = LittleLong (ltxt->len);
|
|
ltxt->country = LittleLong (ltxt->country);
|
|
ltxt->language = LittleLong (ltxt->language);
|
|
ltxt->dialect = LittleLong (ltxt->dialect);
|
|
ltxt->codepage = LittleLong (ltxt->codepage);
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
read_adtl (dstring_t *list_buf, QFile *f, int len)
|
|
{
|
|
riff_d_chunk_t ck;
|
|
riff_d_chunk_t *chunk = 0;
|
|
riff_ltxt_t *ltxt;
|
|
riff_label_t *label;
|
|
riff_data_t *data;
|
|
riff_list_t *list;
|
|
|
|
list = (riff_list_t *) list_buf->str;
|
|
while (len) {
|
|
if (!Rread (f, &ck, sizeof (ck))) {
|
|
len = 0;
|
|
break;
|
|
}
|
|
len -= sizeof (ck);
|
|
ck.len = LittleLong (ck.len);
|
|
RIFF_SWITCH (ck.name) {
|
|
case RIFF_CASE ('l','t','x','t'):
|
|
ltxt = calloc (1, sizeof (riff_ltxt_t));
|
|
ltxt->ck = ck;
|
|
read_ltxt (f, ck.len, <xt->ltxt);
|
|
chunk = <xt->ck;
|
|
break;
|
|
case RIFF_CASE ('l','a','b','l'):
|
|
case RIFF_CASE ('n','o','t','e'):
|
|
label = malloc (sizeof (riff_label_t));
|
|
label->ck = ck;
|
|
if (!Rread (f, &label->ofs, 4)) {
|
|
label->ofs = 0;
|
|
}
|
|
label->label = read_string (f, ck.len - 4);
|
|
chunk = &label->ck;
|
|
break;
|
|
default:
|
|
data = malloc (sizeof (data));
|
|
data->ck = ck;
|
|
data->data = read_data (f, ck.len);
|
|
chunk = &data->ck;
|
|
break;
|
|
}
|
|
len -= ck.len + (ck.len & 1);
|
|
dstring_append (list_buf, (char *)&chunk, sizeof (chunk));
|
|
list = (riff_list_t *) list_buf->str;
|
|
chunk = 0;
|
|
}
|
|
dstring_append (list_buf, (char *)&chunk, sizeof (chunk));
|
|
list = (riff_list_t *) list_buf->str;
|
|
}
|
|
|
|
static riff_list_t *
|
|
read_list (riff_d_chunk_t *ck, QFile *f, int len)
|
|
{
|
|
riff_d_chunk_t *chunk = 0;
|
|
dstring_t *list_buf;
|
|
riff_list_t *list;
|
|
|
|
list_buf = dstring_new ();
|
|
list_buf->size = sizeof (riff_list_t);
|
|
dstring_adjust (list_buf);
|
|
list = (riff_list_t *)list_buf->str;
|
|
list->ck = *ck;
|
|
|
|
if (!Rread (f, list->name, sizeof (list->name))) {
|
|
dstring_delete (list_buf);
|
|
return 0;
|
|
}
|
|
len -= sizeof (list->name);
|
|
while (len > 0) {
|
|
RIFF_SWITCH (list->name) {
|
|
case RIFF_CASE ('I','N','F','O'):
|
|
{
|
|
riff_data_t *data = malloc (sizeof (riff_data_t));
|
|
if (!Rread (f, &data->ck, sizeof (data->ck))) {
|
|
len = 0;
|
|
free (data);
|
|
break;
|
|
}
|
|
chunk = &data->ck;
|
|
data->ck.len = LittleLong (data->ck.len);
|
|
//printf ("%.4s %d\n", data->ck.name, data->ck.len);
|
|
len -= sizeof (data->ck);
|
|
RIFF_SWITCH (data->ck.name) {
|
|
case RIFF_CASE ('I','C','R','D'):
|
|
case RIFF_CASE ('I','S','F','T'):
|
|
data->data = read_string (f, data->ck.len);
|
|
break;
|
|
default:
|
|
data->data = read_data (f, data->ck.len);
|
|
break;
|
|
}
|
|
len -= data->ck.len + (data->ck.len & 1);
|
|
}
|
|
break;
|
|
case RIFF_CASE ('a','d','t','l'):
|
|
read_adtl (list_buf, f, len);
|
|
len = 0;
|
|
break;
|
|
default:
|
|
{
|
|
riff_data_t *data = malloc (sizeof (riff_data_t));
|
|
if (!Rread (f, &data->ck, sizeof (data->ck))) {
|
|
free (data);
|
|
} else {
|
|
data->ck.len = LittleLong (data->ck.len);
|
|
data->data = read_data (f, data->ck.len);
|
|
len -= data->ck.len + sizeof (data->ck);
|
|
chunk = &data->ck;
|
|
}
|
|
}
|
|
len = 0;
|
|
break;
|
|
}
|
|
if (chunk) {
|
|
dstring_append (list_buf, (char *)&chunk, sizeof (chunk));
|
|
list = (riff_list_t *) list_buf->str;
|
|
}
|
|
chunk = 0;
|
|
}
|
|
dstring_append (list_buf, (char *)&chunk, sizeof (chunk));
|
|
list = (riff_list_t *) list_buf->str;
|
|
free (list_buf);
|
|
return list;
|
|
}
|
|
|
|
static riff_d_cue_t *
|
|
read_cue (QFile *f, int len)
|
|
{
|
|
riff_d_cue_t *cue = malloc (len);
|
|
uint32_t i;
|
|
|
|
if (!Rread (f, cue, len)) {
|
|
free (cue);
|
|
cue = 0;
|
|
} else {
|
|
cue->count = LittleLong (cue->count);
|
|
for (i = 0; i < cue->count; i++) {
|
|
cue->cue_points[i].name = LittleLong (cue->cue_points[i].name);
|
|
cue->cue_points[i].position = LittleLong (cue->cue_points[i].position);
|
|
cue->cue_points[i].chunk_start = LittleLong (cue->cue_points[i].chunk_start);
|
|
cue->cue_points[i].block_start = LittleLong (cue->cue_points[i].block_start);
|
|
cue->cue_points[i].sample_offset = LittleLong (cue->cue_points[i].sample_offset);
|
|
}
|
|
}
|
|
|
|
return cue;
|
|
}
|
|
|
|
VISIBLE riff_t *
|
|
riff_read (QFile *f)
|
|
{
|
|
dstring_t *riff_buf;
|
|
riff_list_t *riff;
|
|
riff_d_chunk_t *chunk = 0;
|
|
riff_d_chunk_t ck;
|
|
int file_len, len;
|
|
|
|
riff_buf = dstring_new ();
|
|
riff_buf->size = sizeof (riff_list_t);
|
|
dstring_adjust (riff_buf);
|
|
riff = (riff_list_t *)riff_buf->str;
|
|
|
|
file_len = Qfilesize (f);
|
|
|
|
if (!Rread (f, &riff->ck, sizeof (riff->ck))) {
|
|
dstring_delete (riff_buf);
|
|
return 0;
|
|
}
|
|
if (!Rread (f, riff->name, sizeof (riff->name))) {
|
|
dstring_delete (riff_buf);
|
|
return 0;
|
|
}
|
|
if (strncmp (riff->ck.name, "RIFF", 4) || strncmp (riff->name, "WAVE", 4)) {
|
|
dstring_delete (riff_buf);
|
|
return 0;
|
|
}
|
|
//FIXME the pos test should be in quakeio
|
|
while (Qtell (f) < file_len && Rread (f, &ck, sizeof (ck))) {
|
|
ck.len = LittleLong (ck.len);
|
|
//printf ("%.4s %d\n", ck.name, ck.len);
|
|
if (ck.len < 0x80000000)
|
|
len = ck.len;
|
|
else {
|
|
//puts ("bling");
|
|
ck.len = len = file_len - Qtell (f);
|
|
}
|
|
RIFF_SWITCH (ck.name) {
|
|
case RIFF_CASE ('c','u','e',' '):
|
|
{
|
|
riff_cue_t *cue = malloc (sizeof (riff_cue_t));
|
|
cue->ck = ck;
|
|
cue->cue = read_cue (f, len);
|
|
chunk = &cue->ck;
|
|
}
|
|
break;
|
|
case RIFF_CASE ('L','I','S','T'):
|
|
{
|
|
riff_list_t *list;
|
|
list = read_list (&ck, f, len);
|
|
chunk = &list->ck;
|
|
}
|
|
break;
|
|
case RIFF_CASE ('f','m','t',' '):
|
|
{
|
|
riff_format_t *fmt;
|
|
fmt = malloc (sizeof (riff_format_t) + ck.len);
|
|
fmt->ck = ck;
|
|
if (!Rread (f, fmt->fdata, ck.len)) {
|
|
free (fmt);
|
|
} else {
|
|
riff_d_format_t *fd = (riff_d_format_t *) fmt->fdata;
|
|
|
|
chunk = &fmt->ck;
|
|
fd->format_tag = LittleShort (fd->format_tag);
|
|
fd->channels = LittleShort (fd->channels);
|
|
fd->samples_per_sec = LittleLong (fd->samples_per_sec);
|
|
fd->bytes_per_sec = LittleLong (fd->bytes_per_sec);
|
|
fd->align = LittleShort (fd->align);
|
|
if (fd->format_tag == 1)
|
|
fd->bits_per_sample =
|
|
LittleShort (fd->bits_per_sample);
|
|
}
|
|
}
|
|
break;
|
|
case RIFF_CASE ('d','a','t','a'):
|
|
{
|
|
riff_data_t *data = malloc (sizeof (riff_data_t));
|
|
int c;
|
|
|
|
data->ck = ck;
|
|
data->data = malloc (sizeof (int));
|
|
*((int *)data->data) = Qtell (f);
|
|
Qseek (f, ck.len, SEEK_CUR);
|
|
if ((c = Qgetc (f)) && c != -1)
|
|
Qungetc (f, c);
|
|
chunk = &data->ck;
|
|
}
|
|
break;
|
|
case RIFF_CASE ('w','a','v','l'):
|
|
// FIXME: Convert wavl to data ?
|
|
case RIFF_CASE ('s','l','n','t'):
|
|
// FIXME: Add silence to data
|
|
case RIFF_CASE ('l','i','s','t'):
|
|
case RIFF_CASE ('l','a','b','l'):
|
|
case RIFF_CASE ('n','o','t','e'):
|
|
case RIFF_CASE ('l','t','x','t'):
|
|
case RIFF_CASE ('p','l','s','t'):
|
|
case RIFF_CASE ('i','n','s','t'):
|
|
case RIFF_CASE ('f','a','c','t'):
|
|
case RIFF_CASE ('s','m','p','l'):
|
|
{ // Unused chunk, still present in a lot of wav files.
|
|
int c;
|
|
|
|
Qseek(f, ck.len, SEEK_CUR);
|
|
if ((c = Qgetc (f)) && c != -1)
|
|
Qungetc (f, c);
|
|
continue; // Skip those blocks.
|
|
}
|
|
break;
|
|
default:
|
|
// unknown chunk. bail (could be corrupted file)
|
|
chunk = 0;
|
|
goto bail;
|
|
break;
|
|
}
|
|
dstring_append (riff_buf, (char *)&chunk, sizeof (chunk));
|
|
riff = (riff_list_t *) riff_buf->str;
|
|
chunk = 0;
|
|
}
|
|
bail:
|
|
dstring_append (riff_buf, (char *)&chunk, sizeof (chunk));
|
|
riff = (riff_list_t *) riff_buf->str;
|
|
free (riff_buf);
|
|
return riff;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*****************************************************************************/
|
|
|
|
static void
|
|
free_adtl (riff_d_chunk_t *adtl)
|
|
{
|
|
riff_ltxt_t *ltxt;
|
|
riff_label_t *label;
|
|
riff_data_t *data;
|
|
|
|
//printf (" %.4s\n", adtl->name);
|
|
RIFF_SWITCH (adtl->name) {
|
|
case RIFF_CASE ('l','t','x','t'):
|
|
ltxt = (riff_ltxt_t *) adtl;
|
|
/*printf (" %d %d %4s %d %d %d %d\n",
|
|
ltxt->ltxt.name,
|
|
ltxt->ltxt.len,
|
|
ltxt->ltxt.purpose,
|
|
ltxt->ltxt.country,
|
|
ltxt->ltxt.language,
|
|
ltxt->ltxt.dialect,
|
|
ltxt->ltxt.codepage);*/
|
|
free (ltxt);
|
|
break;
|
|
case RIFF_CASE ('l','a','b','l'):
|
|
case RIFF_CASE ('n','o','t','e'):
|
|
label = (riff_label_t *) adtl;
|
|
//printf (" %-8d %s\n", label->ofs, label->label);
|
|
if (label->label)
|
|
free (label->label);
|
|
free (label);
|
|
break;
|
|
default:
|
|
data = (riff_data_t *) adtl;
|
|
if (data->data)
|
|
free (data->data);
|
|
free (data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_list (riff_list_t *list)
|
|
{
|
|
riff_d_chunk_t **ck;
|
|
riff_data_t *data;
|
|
|
|
//printf (" %.4s\n", list->name);
|
|
for (ck = list->chunks; *ck; ck++) {
|
|
RIFF_SWITCH (list->name) {
|
|
case RIFF_CASE ('I','N','F','O'):
|
|
data = (riff_data_t *) *ck;
|
|
//printf (" %.4s\n", data->ck.name);
|
|
RIFF_SWITCH (data->ck.name) {
|
|
case RIFF_CASE ('I','C','R','D'):
|
|
case RIFF_CASE ('I','S','F','T'):
|
|
//printf (" %s\n", data->data);
|
|
default:
|
|
if (data->data)
|
|
free (data->data);
|
|
free (data);
|
|
break;
|
|
}
|
|
break;
|
|
case RIFF_CASE ('a','d','t','l'):
|
|
free_adtl (*ck);
|
|
break;
|
|
default:
|
|
data = (riff_data_t *) *ck;
|
|
if (data->data)
|
|
free (data->data);
|
|
free (data);
|
|
break;
|
|
}
|
|
}
|
|
free (list);
|
|
}
|
|
|
|
VISIBLE void
|
|
riff_free (riff_t *riff)
|
|
{
|
|
riff_d_chunk_t **ck;
|
|
riff_cue_t *cue;
|
|
riff_list_t *list;
|
|
riff_data_t *data;
|
|
|
|
for (ck = riff->chunks; *ck; ck++) {
|
|
//printf ("%.4s\n", (*ck)->name);
|
|
RIFF_SWITCH ((*ck)->name) {
|
|
case RIFF_CASE ('c','u','e',' '):
|
|
cue = (riff_cue_t *) *ck;
|
|
if (cue->cue) {
|
|
/*int i;
|
|
for (i = 0; i < cue->cue->count; i++) {
|
|
printf (" %08x %d %.4s %d %d %d\n",
|
|
cue->cue->cue_points[i].name,
|
|
cue->cue->cue_points[i].position,
|
|
cue->cue->cue_points[i].chunk,
|
|
cue->cue->cue_points[i].chunk_start,
|
|
cue->cue->cue_points[i].block_start,
|
|
cue->cue->cue_points[i].sample_offset);
|
|
}*/
|
|
free (cue->cue);
|
|
}
|
|
free (cue);
|
|
break;
|
|
case RIFF_CASE ('L','I','S','T'):
|
|
list = (riff_list_t *) *ck;
|
|
free_list (list);
|
|
break;
|
|
case RIFF_CASE ('f','m','t',' '):
|
|
free (*ck);
|
|
break;
|
|
default:
|
|
data = (riff_data_t *) *ck;
|
|
if (data->data)
|
|
free (data->data);
|
|
free (data);
|
|
break;
|
|
}
|
|
}
|
|
free (riff);
|
|
}
|