quakeforge/libs/util/riff.c
Bill Currie 4cec187465 zixthree's wav file patch
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.
2010-11-21 14:18:15 +09:00

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, &ltxt->ltxt);
chunk = &ltxt->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);
}