- added GZDoom's resource management classes.
|
@ -638,6 +638,7 @@ file( GLOB HEADER_FILES
|
||||||
common/fonts/*.h
|
common/fonts/*.h
|
||||||
common/utility/*.h
|
common/utility/*.h
|
||||||
common/console/*.h
|
common/console/*.h
|
||||||
|
common/filesystem/*.h
|
||||||
|
|
||||||
build/src/*.h
|
build/src/*.h
|
||||||
thirdparty/include/*.h
|
thirdparty/include/*.h
|
||||||
|
@ -813,13 +814,21 @@ set (PCH_SOURCES
|
||||||
common/utility/utf8.cpp
|
common/utility/utf8.cpp
|
||||||
common/utility/superfasthash.cpp
|
common/utility/superfasthash.cpp
|
||||||
common/utility/configfile.cpp
|
common/utility/configfile.cpp
|
||||||
common/utility/file_zip.cpp
|
|
||||||
common/utility/resourcefile.cpp
|
|
||||||
common/utility/matrix.cpp
|
common/utility/matrix.cpp
|
||||||
common/utility/m_png.cpp
|
common/utility/m_png.cpp
|
||||||
common/utility/memarena.cpp
|
common/utility/memarena.cpp
|
||||||
common/utility/sc_man.cpp
|
common/utility/sc_man.cpp
|
||||||
|
|
||||||
|
common/filesystem/ancientzip.cpp
|
||||||
|
common/filesystem/file_zip.cpp
|
||||||
|
common/filesystem/file_7z.cpp
|
||||||
|
common/filesystem/file_grp.cpp
|
||||||
|
common/filesystem/file_rff.cpp
|
||||||
|
common/filesystem/file_pak.cpp
|
||||||
|
common/filesystem/file_lump.cpp
|
||||||
|
common/filesystem/file_directory.cpp
|
||||||
|
common/filesystem/resourcefile.cpp
|
||||||
|
|
||||||
common/textures/bitmap.cpp
|
common/textures/bitmap.cpp
|
||||||
common/textures/buildtiles.cpp
|
common/textures/buildtiles.cpp
|
||||||
common/textures/texture.cpp
|
common/textures/texture.cpp
|
||||||
|
@ -892,6 +901,7 @@ include_directories(
|
||||||
common/utility
|
common/utility
|
||||||
common/console
|
common/console
|
||||||
common/textures
|
common/textures
|
||||||
|
common/filesystem
|
||||||
platform
|
platform
|
||||||
|
|
||||||
${CMAKE_BINARY_DIR}/libraries/gdtoa
|
${CMAKE_BINARY_DIR}/libraries/gdtoa
|
||||||
|
|
435
source/common/filesystem/ancientzip.cpp
Normal file
|
@ -0,0 +1,435 @@
|
||||||
|
/*
|
||||||
|
** ancientzip.cpp
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2010-2011 Randy Heit
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
** Based in information from
|
||||||
|
**
|
||||||
|
gunzip.c by Pasi Ojala, a1bert@iki.fi
|
||||||
|
http://www.iki.fi/a1bert/
|
||||||
|
|
||||||
|
A hopefully easier to understand guide to GZip
|
||||||
|
(deflate) decompression routine than the GZip
|
||||||
|
source code.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*----------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "ancientzip.h"
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
Bit-I/O variables and routines/macros
|
||||||
|
|
||||||
|
These routines work in the bit level because the target
|
||||||
|
environment does not have a barrel shifter. Trying to
|
||||||
|
handle several bits at once would've only made the code
|
||||||
|
slower.
|
||||||
|
|
||||||
|
If the environment supports multi-bit shifts, you should
|
||||||
|
write these routines again (see e.g. the GZIP sources).
|
||||||
|
|
||||||
|
[RH] Since the target environment is not a C64, I did as
|
||||||
|
suggested and rewrote these using zlib as a reference.
|
||||||
|
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
#define READBYTE(c) \
|
||||||
|
do { \
|
||||||
|
c = 0; \
|
||||||
|
if (InLeft) { \
|
||||||
|
InLeft--; \
|
||||||
|
if (bs < be) \
|
||||||
|
c = ReadBuf[bs++]; \
|
||||||
|
else { \
|
||||||
|
be = (decltype(be))In->Read(&ReadBuf, sizeof(ReadBuf)); \
|
||||||
|
c = ReadBuf[0]; \
|
||||||
|
bs = 1; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* Get a byte of input into the bit accumulator. */
|
||||||
|
#define PULLBYTE() \
|
||||||
|
do { \
|
||||||
|
unsigned char next; \
|
||||||
|
READBYTE(next); \
|
||||||
|
Hold += (unsigned int)(next) << Bits; \
|
||||||
|
Bits += 8; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* Assure that there are at least n bits in the bit accumulator. */
|
||||||
|
#define NEEDBITS(n) \
|
||||||
|
do { \
|
||||||
|
while (Bits < (unsigned)(n)) \
|
||||||
|
PULLBYTE(); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* Return the low n bits of the bit accumulator (n < 16) */
|
||||||
|
#define BITS(n) \
|
||||||
|
((unsigned)Hold & ((1U << (n)) - 1))
|
||||||
|
|
||||||
|
/* Remove n bits from the bit accumulator */
|
||||||
|
#define DROPBITS(n) \
|
||||||
|
do { \
|
||||||
|
Hold >>= (n); \
|
||||||
|
Bits -= (unsigned)(n); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define READBITS(c, a) \
|
||||||
|
do { \
|
||||||
|
NEEDBITS(a); \
|
||||||
|
c = BITS(a); \
|
||||||
|
DROPBITS(a); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
Shannon-Fano tree routines
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
static const unsigned char BitReverse4[] = {
|
||||||
|
0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06, 0x0e,
|
||||||
|
0x01, 0x09, 0x05, 0x0d, 0x03, 0x0b, 0x07, 0x0f
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FIRST_BIT_LEN 8
|
||||||
|
#define REST_BIT_LEN 4
|
||||||
|
|
||||||
|
void FZipExploder::InsertCode(TArray<HuffNode> &decoder, unsigned int pos, int bits, unsigned short code, int len, unsigned char value)
|
||||||
|
{
|
||||||
|
assert(len > 0);
|
||||||
|
unsigned int node = pos + (code & ((1 << bits) - 1));
|
||||||
|
|
||||||
|
if (len > bits)
|
||||||
|
{ // This code uses more bits than this level has room for. Store the bottom bits
|
||||||
|
// in this table, then proceed to the next one.
|
||||||
|
unsigned int child = decoder[node].ChildTable;
|
||||||
|
if (child == 0)
|
||||||
|
{ // Need to create child table.
|
||||||
|
child = InitTable(decoder, 1 << REST_BIT_LEN);
|
||||||
|
decoder[node].ChildTable = child;
|
||||||
|
decoder[node].Length = bits;
|
||||||
|
decoder[node].Value = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(decoder[node].Length == bits);
|
||||||
|
assert(decoder[node].Value == 0);
|
||||||
|
}
|
||||||
|
InsertCode(decoder, child, REST_BIT_LEN, code >> bits, len - bits, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // If this code uses fewer bits than this level of the table, it is
|
||||||
|
// inserted repeatedly for each value that matches it at its lower
|
||||||
|
// bits.
|
||||||
|
for (int i = 1 << (bits - len); --i >= 0; node += 1 << len)
|
||||||
|
{
|
||||||
|
decoder[node].Length = len;
|
||||||
|
decoder[node].Value = value;
|
||||||
|
assert(decoder[node].ChildTable == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int FZipExploder::InitTable(TArray<HuffNode> &decoder, int numspots)
|
||||||
|
{
|
||||||
|
unsigned int start = decoder.Size();
|
||||||
|
decoder.Reserve(numspots);
|
||||||
|
memset(&decoder[start], 0, sizeof(HuffNode)*numspots);
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FZipExploder::buildercmp(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
const TableBuilder *v1 = (const TableBuilder *)a;
|
||||||
|
const TableBuilder *v2 = (const TableBuilder *)b;
|
||||||
|
int d = v1->Length - v2->Length;
|
||||||
|
if (d == 0) {
|
||||||
|
d = v1->Value - v2->Value;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FZipExploder::BuildDecoder(TArray<HuffNode> &decoder, TableBuilder *values, int numvals)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
qsort(values, numvals, sizeof(*values), buildercmp);
|
||||||
|
|
||||||
|
// Generate the Shannon-Fano tree:
|
||||||
|
unsigned short code = 0;
|
||||||
|
unsigned short code_increment = 0;
|
||||||
|
unsigned short last_bit_length = 0;
|
||||||
|
for (i = numvals - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
code += code_increment;
|
||||||
|
if (values[i].Length != last_bit_length)
|
||||||
|
{
|
||||||
|
last_bit_length = values[i].Length;
|
||||||
|
code_increment = 1 << (16 - last_bit_length);
|
||||||
|
}
|
||||||
|
// Reverse the order of the bits in the code before storing it.
|
||||||
|
values[i].Code = BitReverse4[code >> 12] |
|
||||||
|
(BitReverse4[(code >> 8) & 0xf] << 4) |
|
||||||
|
(BitReverse4[(code >> 4) & 0xf] << 8) |
|
||||||
|
(BitReverse4[code & 0xf] << 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert each code into the hierarchical table. The top level is FIRST_BIT_LEN bits,
|
||||||
|
// and the other levels are REST_BIT_LEN bits. If a code does not completely fill
|
||||||
|
// a level, every permutation for the remaining bits is filled in to
|
||||||
|
// match this one.
|
||||||
|
InitTable(decoder, 1 << FIRST_BIT_LEN); // Set up the top level.
|
||||||
|
for (i = 0; i < numvals; ++i)
|
||||||
|
{
|
||||||
|
InsertCode(decoder, 0, FIRST_BIT_LEN, values[i].Code, values[i].Length, values[i].Value);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int FZipExploder::DecodeSFValue(const TArray<HuffNode> &decoder)
|
||||||
|
{
|
||||||
|
unsigned int bits = FIRST_BIT_LEN, table = 0, code;
|
||||||
|
const HuffNode *pos;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
NEEDBITS(bits);
|
||||||
|
code = BITS(bits);
|
||||||
|
bits = REST_BIT_LEN;
|
||||||
|
pos = &decoder[table + code];
|
||||||
|
DROPBITS(pos->Length);
|
||||||
|
table = pos->ChildTable;
|
||||||
|
}
|
||||||
|
while (table != 0);
|
||||||
|
return pos->Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int FZipExploder::DecodeSF(TArray<HuffNode> &decoder, int numvals)
|
||||||
|
{
|
||||||
|
TableBuilder builder[256];
|
||||||
|
unsigned char a, c;
|
||||||
|
int i, n, v = 0;
|
||||||
|
|
||||||
|
READBYTE(c);
|
||||||
|
n = c + 1;
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
int nv, bl;
|
||||||
|
READBYTE(a);
|
||||||
|
nv = ((a >> 4) & 15) + 1;
|
||||||
|
bl = (a & 15) + 1;
|
||||||
|
while (nv--) {
|
||||||
|
builder[v].Length = bl;
|
||||||
|
builder[v].Value = v;
|
||||||
|
v++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (v != numvals)
|
||||||
|
return 1; /* bad table */
|
||||||
|
return BuildDecoder(decoder, builder, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
int FZipExploder::Explode(unsigned char *out, unsigned int outsize,
|
||||||
|
FileReader &in, unsigned int insize,
|
||||||
|
int flags)
|
||||||
|
{
|
||||||
|
int c, i, minMatchLen = 3, len, dist;
|
||||||
|
int lowDistanceBits;
|
||||||
|
unsigned int bIdx = 0;
|
||||||
|
|
||||||
|
Hold = 0;
|
||||||
|
Bits = 0;
|
||||||
|
In = ∈
|
||||||
|
InLeft = insize;
|
||||||
|
bs = be = 0;
|
||||||
|
|
||||||
|
if ((flags & 4)) {
|
||||||
|
/* 3 trees: literals, lengths, distance top 6 */
|
||||||
|
minMatchLen = 3;
|
||||||
|
if (DecodeSF(LiteralDecoder, 256))
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
/* 2 trees: lengths, distance top 6 */
|
||||||
|
minMatchLen = 2;
|
||||||
|
}
|
||||||
|
if (DecodeSF(LengthDecoder, 64))
|
||||||
|
return 1;
|
||||||
|
if (DecodeSF(DistanceDecoder, 64))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
lowDistanceBits = (flags & 2) ? /* 8k dictionary */ 7 : /* 4k dictionary */ 6;
|
||||||
|
while (bIdx < outsize)
|
||||||
|
{
|
||||||
|
READBITS(c, 1);
|
||||||
|
if (c) {
|
||||||
|
/* literal data */
|
||||||
|
if ((flags & 4)) {
|
||||||
|
c = DecodeSFValue(LiteralDecoder);
|
||||||
|
} else {
|
||||||
|
READBITS(c, 8);
|
||||||
|
}
|
||||||
|
out[bIdx++] = c;
|
||||||
|
} else {
|
||||||
|
READBITS(dist, lowDistanceBits);
|
||||||
|
c = DecodeSFValue(DistanceDecoder);
|
||||||
|
dist |= (c << lowDistanceBits);
|
||||||
|
len = DecodeSFValue(LengthDecoder);
|
||||||
|
if (len == 63) {
|
||||||
|
READBITS(c, 8);
|
||||||
|
len += c;
|
||||||
|
}
|
||||||
|
len += minMatchLen;
|
||||||
|
dist++;
|
||||||
|
if (bIdx + len > outsize) {
|
||||||
|
throw CExplosionError("Not enough output space");
|
||||||
|
}
|
||||||
|
if ((unsigned int)dist > bIdx) {
|
||||||
|
/* Anything before the first input byte is zero. */
|
||||||
|
int zeros = dist - bIdx;
|
||||||
|
if (len < zeros)
|
||||||
|
zeros = len;
|
||||||
|
for(i = zeros; i; i--)
|
||||||
|
out[bIdx++] = 0;
|
||||||
|
len -= zeros;
|
||||||
|
}
|
||||||
|
for(i = len; i; i--, bIdx++) {
|
||||||
|
out[bIdx] = out[bIdx - dist];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* HSIZE is defined as 2^13 (8192) in unzip.h */
|
||||||
|
#define HSIZE 8192
|
||||||
|
#define BOGUSCODE 256
|
||||||
|
#define CODE_MASK (HSIZE - 1) /* 0x1fff (lower bits are parent's index) */
|
||||||
|
#define FREE_CODE HSIZE /* 0x2000 (code is unused or was cleared) */
|
||||||
|
#define HAS_CHILD (HSIZE << 1) /* 0x4000 (code has a child--do not clear) */
|
||||||
|
|
||||||
|
int ShrinkLoop(unsigned char *out, unsigned int outsize, FileReader &_In, unsigned int InLeft)
|
||||||
|
{
|
||||||
|
FileReader *In = &_In;
|
||||||
|
unsigned char ReadBuf[256];
|
||||||
|
unsigned short Parent[HSIZE];
|
||||||
|
unsigned char Value[HSIZE], Stack[HSIZE];
|
||||||
|
unsigned char *newstr;
|
||||||
|
int len;
|
||||||
|
int KwKwK, codesize = 9; /* start at 9 bits/code */
|
||||||
|
int code, oldcode, freecode, curcode;
|
||||||
|
unsigned int Bits = 0, Hold = 0;
|
||||||
|
unsigned int size = 0;
|
||||||
|
unsigned int bs = 0, be = 0;
|
||||||
|
|
||||||
|
freecode = BOGUSCODE;
|
||||||
|
for (code = 0; code < BOGUSCODE; code++)
|
||||||
|
{
|
||||||
|
Value[code] = code;
|
||||||
|
Parent[code] = BOGUSCODE;
|
||||||
|
}
|
||||||
|
for (code = BOGUSCODE+1; code < HSIZE; code++)
|
||||||
|
Parent[code] = FREE_CODE;
|
||||||
|
|
||||||
|
READBITS(oldcode, codesize);
|
||||||
|
if (size < outsize) {
|
||||||
|
out[size++] = oldcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (size < outsize)
|
||||||
|
{
|
||||||
|
READBITS(code, codesize);
|
||||||
|
if (code == BOGUSCODE) { /* possible to have consecutive escapes? */
|
||||||
|
READBITS(code, codesize);
|
||||||
|
if (code == 1) {
|
||||||
|
codesize++;
|
||||||
|
} else if (code == 2) {
|
||||||
|
/* clear leafs (nodes with no children) */
|
||||||
|
/* first loop: mark each parent as such */
|
||||||
|
for (code = BOGUSCODE+1; code < HSIZE; ++code) {
|
||||||
|
curcode = (Parent[code] & CODE_MASK);
|
||||||
|
|
||||||
|
if (curcode > BOGUSCODE)
|
||||||
|
Parent[curcode] |= HAS_CHILD; /* set parent's child-bit */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* second loop: clear all nodes *not* marked as parents; reset flag bits */
|
||||||
|
for (code = BOGUSCODE+1; code < HSIZE; ++code) {
|
||||||
|
if (Parent[code] & HAS_CHILD) { /* just clear child-bit */
|
||||||
|
Parent[code] &= ~HAS_CHILD;
|
||||||
|
} else { /* leaf: lose it */
|
||||||
|
Parent[code] = FREE_CODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
freecode = BOGUSCODE;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newstr = &Stack[HSIZE-1];
|
||||||
|
curcode = code;
|
||||||
|
|
||||||
|
if (Parent[curcode] == FREE_CODE) {
|
||||||
|
KwKwK = 1;
|
||||||
|
newstr--; /* last character will be same as first character */
|
||||||
|
curcode = oldcode;
|
||||||
|
len = 1;
|
||||||
|
} else {
|
||||||
|
KwKwK = 0;
|
||||||
|
len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
*newstr-- = Value[curcode];
|
||||||
|
len++;
|
||||||
|
curcode = (Parent[curcode] & CODE_MASK);
|
||||||
|
} while (curcode != BOGUSCODE);
|
||||||
|
|
||||||
|
newstr++;
|
||||||
|
if (KwKwK) {
|
||||||
|
Stack[HSIZE-1] = *newstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
freecode++;
|
||||||
|
} while (Parent[freecode] != FREE_CODE);
|
||||||
|
|
||||||
|
Parent[freecode] = oldcode;
|
||||||
|
Value[freecode] = *newstr;
|
||||||
|
oldcode = code;
|
||||||
|
|
||||||
|
while (len--) {
|
||||||
|
out[size++] = *newstr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
51
source/common/filesystem/ancientzip.h
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#include <stdexcept>
|
||||||
|
#include "files.h"
|
||||||
|
//#include "doomerrors.h"
|
||||||
|
|
||||||
|
class FZipExploder
|
||||||
|
{
|
||||||
|
unsigned int Hold, Bits;
|
||||||
|
FileReader *In;
|
||||||
|
unsigned int InLeft;
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
Shannon-Fano tree structures, variables and related routines
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
struct HuffNode
|
||||||
|
{
|
||||||
|
unsigned char Value;
|
||||||
|
unsigned char Length;
|
||||||
|
unsigned short ChildTable;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TableBuilder
|
||||||
|
{
|
||||||
|
unsigned char Value;
|
||||||
|
unsigned char Length;
|
||||||
|
unsigned short Code;
|
||||||
|
};
|
||||||
|
|
||||||
|
TArray<HuffNode> LiteralDecoder;
|
||||||
|
TArray<HuffNode> DistanceDecoder;
|
||||||
|
TArray<HuffNode> LengthDecoder;
|
||||||
|
unsigned char ReadBuf[256];
|
||||||
|
unsigned int bs, be;
|
||||||
|
|
||||||
|
static int buildercmp(const void *a, const void *b);
|
||||||
|
void InsertCode(TArray<HuffNode> &decoder, unsigned int pos, int bits, unsigned short code, int len, unsigned char value);
|
||||||
|
unsigned int InitTable(TArray<HuffNode> &decoder, int numspots);
|
||||||
|
int BuildDecoder(TArray<HuffNode> &decoder, TableBuilder *values, int numvals);
|
||||||
|
int DecodeSFValue(const TArray<HuffNode> ¤tTree);
|
||||||
|
int DecodeSF(TArray<HuffNode> &decoder, int numvals);
|
||||||
|
public:
|
||||||
|
int Explode(unsigned char *out, unsigned int outsize, FileReader &in, unsigned int insize, int flags);
|
||||||
|
};
|
||||||
|
|
||||||
|
class CExplosionError : std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CExplosionError(const char *message) : std::runtime_error(message) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
int ShrinkLoop(unsigned char *out, unsigned int outsize, FileReader &in, unsigned int insize);
|
383
source/common/filesystem/file_7z.cpp
Normal file
|
@ -0,0 +1,383 @@
|
||||||
|
/*
|
||||||
|
** file_7z.cpp
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2009 Randy Heit
|
||||||
|
** Copyright 2009 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Note that 7z made the unwise decision to include windows.h :(
|
||||||
|
#include "7z.h"
|
||||||
|
#include "7zCrc.h"
|
||||||
|
|
||||||
|
#include "resourcefile.h"
|
||||||
|
#include "cmdlib.h"
|
||||||
|
#include "printf.h"
|
||||||
|
//#include "v_text.h"
|
||||||
|
//#include "w_wad.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Interface classes to 7z library
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
extern ISzAlloc g_Alloc;
|
||||||
|
|
||||||
|
struct CZDFileInStream
|
||||||
|
{
|
||||||
|
ISeekInStream s;
|
||||||
|
FileReader &File;
|
||||||
|
|
||||||
|
CZDFileInStream(FileReader &_file)
|
||||||
|
: File(_file)
|
||||||
|
{
|
||||||
|
s.Read = Read;
|
||||||
|
s.Seek = Seek;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SRes Read(const ISeekInStream *pp, void *buf, size_t *size)
|
||||||
|
{
|
||||||
|
CZDFileInStream *p = (CZDFileInStream *)pp;
|
||||||
|
auto numread = p->File.Read(buf, (long)*size);
|
||||||
|
if (numread < 0)
|
||||||
|
{
|
||||||
|
*size = 0;
|
||||||
|
return SZ_ERROR_READ;
|
||||||
|
}
|
||||||
|
*size = numread;
|
||||||
|
return SZ_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SRes Seek(const ISeekInStream *pp, Int64 *pos, ESzSeek origin)
|
||||||
|
{
|
||||||
|
CZDFileInStream *p = (CZDFileInStream *)pp;
|
||||||
|
FileReader::ESeek move_method;
|
||||||
|
int res;
|
||||||
|
if (origin == SZ_SEEK_SET)
|
||||||
|
{
|
||||||
|
move_method = FileReader::SeekSet;
|
||||||
|
}
|
||||||
|
else if (origin == SZ_SEEK_CUR)
|
||||||
|
{
|
||||||
|
move_method = FileReader::SeekCur;
|
||||||
|
}
|
||||||
|
else if (origin == SZ_SEEK_END)
|
||||||
|
{
|
||||||
|
move_method = FileReader::SeekEnd;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
res = (int)p->File.Seek((long)*pos, move_method);
|
||||||
|
*pos = p->File.Tell();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct C7zArchive
|
||||||
|
{
|
||||||
|
CSzArEx DB;
|
||||||
|
CZDFileInStream ArchiveStream;
|
||||||
|
CLookToRead2 LookStream;
|
||||||
|
Byte StreamBuffer[1<<14];
|
||||||
|
UInt32 BlockIndex;
|
||||||
|
Byte *OutBuffer;
|
||||||
|
size_t OutBufferSize;
|
||||||
|
|
||||||
|
C7zArchive(FileReader &file) : ArchiveStream(file)
|
||||||
|
{
|
||||||
|
if (g_CrcTable[1] == 0)
|
||||||
|
{
|
||||||
|
CrcGenerateTable();
|
||||||
|
}
|
||||||
|
file.Seek(0, FileReader::SeekSet);
|
||||||
|
LookToRead2_CreateVTable(&LookStream, false);
|
||||||
|
LookStream.realStream = &ArchiveStream.s;
|
||||||
|
LookToRead2_Init(&LookStream);
|
||||||
|
LookStream.bufSize = sizeof(StreamBuffer);
|
||||||
|
LookStream.buf = StreamBuffer;
|
||||||
|
SzArEx_Init(&DB);
|
||||||
|
BlockIndex = 0xFFFFFFFF;
|
||||||
|
OutBuffer = NULL;
|
||||||
|
OutBufferSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~C7zArchive()
|
||||||
|
{
|
||||||
|
if (OutBuffer != NULL)
|
||||||
|
{
|
||||||
|
IAlloc_Free(&g_Alloc, OutBuffer);
|
||||||
|
}
|
||||||
|
SzArEx_Free(&DB, &g_Alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
SRes Open()
|
||||||
|
{
|
||||||
|
return SzArEx_Open(&DB, &LookStream.vt, &g_Alloc, &g_Alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
SRes Extract(UInt32 file_index, char *buffer)
|
||||||
|
{
|
||||||
|
size_t offset, out_size_processed;
|
||||||
|
SRes res = SzArEx_Extract(&DB, &LookStream.vt, file_index,
|
||||||
|
&BlockIndex, &OutBuffer, &OutBufferSize,
|
||||||
|
&offset, &out_size_processed,
|
||||||
|
&g_Alloc, &g_Alloc);
|
||||||
|
if (res == SZ_OK)
|
||||||
|
{
|
||||||
|
memcpy(buffer, OutBuffer + offset, out_size_processed);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Zip Lump
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
struct F7ZLump : public FResourceLump
|
||||||
|
{
|
||||||
|
int Position;
|
||||||
|
|
||||||
|
virtual int FillCache();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// 7-zip file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class F7ZFile : public FResourceFile
|
||||||
|
{
|
||||||
|
friend struct F7ZLump;
|
||||||
|
|
||||||
|
F7ZLump *Lumps;
|
||||||
|
C7zArchive *Archive;
|
||||||
|
|
||||||
|
public:
|
||||||
|
F7ZFile(const char * filename, FileReader &filer);
|
||||||
|
bool Open(bool quiet);
|
||||||
|
virtual ~F7ZFile();
|
||||||
|
virtual FResourceLump *GetLump(int no) { return ((unsigned)no < NumLumps)? &Lumps[no] : NULL; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// 7Z file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
F7ZFile::F7ZFile(const char * filename, FileReader &filer)
|
||||||
|
: FResourceFile(filename, filer)
|
||||||
|
{
|
||||||
|
Lumps = NULL;
|
||||||
|
Archive = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Open it
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool F7ZFile::Open(bool quiet)
|
||||||
|
{
|
||||||
|
Archive = new C7zArchive(Reader);
|
||||||
|
int skipped = 0;
|
||||||
|
SRes res;
|
||||||
|
|
||||||
|
res = Archive->Open();
|
||||||
|
if (res != SZ_OK)
|
||||||
|
{
|
||||||
|
delete Archive;
|
||||||
|
Archive = NULL;
|
||||||
|
if (!quiet)
|
||||||
|
{
|
||||||
|
Printf("\n%s: ", FileName.GetChars());
|
||||||
|
if (res == SZ_ERROR_UNSUPPORTED)
|
||||||
|
{
|
||||||
|
Printf("Decoder does not support this archive\n");
|
||||||
|
}
|
||||||
|
else if (res == SZ_ERROR_MEM)
|
||||||
|
{
|
||||||
|
Printf("Cannot allocate memory\n");
|
||||||
|
}
|
||||||
|
else if (res == SZ_ERROR_CRC)
|
||||||
|
{
|
||||||
|
Printf("CRC error\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Printf("error #%d\n", res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSzArEx* const archPtr = &Archive->DB;
|
||||||
|
|
||||||
|
NumLumps = archPtr->NumFiles;
|
||||||
|
Lumps = new F7ZLump[NumLumps];
|
||||||
|
|
||||||
|
F7ZLump *lump_p = Lumps;
|
||||||
|
TArray<UInt16> nameUTF16;
|
||||||
|
TArray<char> nameASCII;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < NumLumps; ++i)
|
||||||
|
{
|
||||||
|
// skip Directories
|
||||||
|
if (SzArEx_IsDir(archPtr, i))
|
||||||
|
{
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t nameLength = SzArEx_GetFileNameUtf16(archPtr, i, NULL);
|
||||||
|
|
||||||
|
if (0 == nameLength)
|
||||||
|
{
|
||||||
|
++skipped;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nameUTF16.Resize((unsigned)nameLength);
|
||||||
|
nameASCII.Resize((unsigned)nameLength);
|
||||||
|
SzArEx_GetFileNameUtf16(archPtr, i, &nameUTF16[0]);
|
||||||
|
for (size_t c = 0; c < nameLength; ++c)
|
||||||
|
{
|
||||||
|
nameASCII[c] = static_cast<char>(nameUTF16[c]);
|
||||||
|
}
|
||||||
|
|
||||||
|
FString name = &nameASCII[0];
|
||||||
|
name.Substitute("\\", "/");
|
||||||
|
name.ToLower();
|
||||||
|
|
||||||
|
lump_p->LumpNameSetup(name);
|
||||||
|
lump_p->LumpSize = static_cast<int>(SzArEx_GetFileSize(archPtr, i));
|
||||||
|
lump_p->Owner = this;
|
||||||
|
lump_p->Flags = LUMPF_ZIPFILE|LUMPF_COMPRESSED;
|
||||||
|
lump_p->Position = i;
|
||||||
|
lump_p++;
|
||||||
|
}
|
||||||
|
// Resize the lump record array to its actual size
|
||||||
|
NumLumps -= skipped;
|
||||||
|
|
||||||
|
if (NumLumps > 0)
|
||||||
|
{
|
||||||
|
// Quick check for unsupported compression method
|
||||||
|
|
||||||
|
TArray<char> temp;
|
||||||
|
temp.Resize(Lumps[0].LumpSize);
|
||||||
|
|
||||||
|
if (SZ_OK != Archive->Extract(Lumps[0].Position, &temp[0]))
|
||||||
|
{
|
||||||
|
if (!quiet) Printf("\n%s: unsupported 7z/LZMA file!\n", FileName.GetChars());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!quiet) Printf(", %d lumps\n", NumLumps);
|
||||||
|
|
||||||
|
PostProcessArchive(&Lumps[0], sizeof(F7ZLump));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
F7ZFile::~F7ZFile()
|
||||||
|
{
|
||||||
|
if (Lumps != NULL)
|
||||||
|
{
|
||||||
|
delete[] Lumps;
|
||||||
|
}
|
||||||
|
if (Archive != NULL)
|
||||||
|
{
|
||||||
|
delete Archive;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Fills the lump cache and performs decompression
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int F7ZLump::FillCache()
|
||||||
|
{
|
||||||
|
Cache.Resize(LumpSize);
|
||||||
|
static_cast<F7ZFile*>(Owner)->Archive->Extract(Position, (char*)Cache.Data());
|
||||||
|
RefCount = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// File open
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceFile *Check7Z(const char *filename, FileReader &file, bool quiet)
|
||||||
|
{
|
||||||
|
char head[k7zSignatureSize];
|
||||||
|
|
||||||
|
if (file.GetLength() >= k7zSignatureSize)
|
||||||
|
{
|
||||||
|
file.Seek(0, FileReader::SeekSet);
|
||||||
|
file.Read(&head, k7zSignatureSize);
|
||||||
|
file.Seek(0, FileReader::SeekSet);
|
||||||
|
if (!memcmp(head, k7zSignature, k7zSignatureSize))
|
||||||
|
{
|
||||||
|
FResourceFile *rf = new F7ZFile(filename, file);
|
||||||
|
if (rf->Open(quiet)) return rf;
|
||||||
|
|
||||||
|
file = std::move(rf->Reader); // to avoid destruction of reader
|
||||||
|
delete rf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
331
source/common/filesystem/file_directory.cpp
Normal file
|
@ -0,0 +1,331 @@
|
||||||
|
/*
|
||||||
|
** file_directory.cpp
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2008-2009 Randy Heit
|
||||||
|
** Copyright 2009 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <io.h>
|
||||||
|
#else
|
||||||
|
#include <fts.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "resourcefile.h"
|
||||||
|
#include "cmdlib.h"
|
||||||
|
#include "printf.h"
|
||||||
|
//#include "doomtype.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Zip Lump
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
struct FDirectoryLump : public FResourceLump
|
||||||
|
{
|
||||||
|
virtual FileReader NewReader();
|
||||||
|
virtual int FillCache();
|
||||||
|
|
||||||
|
FString mFullPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Zip file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FDirectory : public FResourceFile
|
||||||
|
{
|
||||||
|
TArray<FDirectoryLump> Lumps;
|
||||||
|
|
||||||
|
int AddDirectory(const char *dirpath);
|
||||||
|
void AddEntry(const char *fullpath, int size);
|
||||||
|
|
||||||
|
public:
|
||||||
|
FDirectory(const char * dirname);
|
||||||
|
bool Open(bool quiet);
|
||||||
|
virtual FResourceLump *GetLump(int no) { return ((unsigned)no < NumLumps)? &Lumps[no] : NULL; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FDirectory::FDirectory(const char * directory)
|
||||||
|
: FResourceFile(NULL)
|
||||||
|
{
|
||||||
|
FString dirname;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
directory = _fullpath(NULL, directory, _MAX_PATH);
|
||||||
|
#else
|
||||||
|
// Todo for Linux: Resolve the path before using it
|
||||||
|
#endif
|
||||||
|
dirname = directory;
|
||||||
|
#ifdef _WIN32
|
||||||
|
free((void *)directory);
|
||||||
|
#endif
|
||||||
|
dirname.Substitute("\\", "/");
|
||||||
|
if (dirname[dirname.Len()-1] != '/') dirname += '/';
|
||||||
|
FileName = dirname;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Windows version
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FDirectory::AddDirectory(const char *dirpath)
|
||||||
|
{
|
||||||
|
struct _finddata_t fileinfo;
|
||||||
|
intptr_t handle;
|
||||||
|
FString dirmatch;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
dirmatch = dirpath;
|
||||||
|
dirmatch += '*';
|
||||||
|
|
||||||
|
if ((handle = _findfirst(dirmatch, &fileinfo)) == -1)
|
||||||
|
{
|
||||||
|
Printf("Could not scan '%s': %s\n", dirpath, strerror(errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (fileinfo.attrib & _A_HIDDEN)
|
||||||
|
{
|
||||||
|
// Skip hidden files and directories. (Prevents SVN bookkeeping
|
||||||
|
// info from being included.)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (fileinfo.attrib & _A_SUBDIR)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (fileinfo.name[0] == '.' &&
|
||||||
|
(fileinfo.name[1] == '\0' ||
|
||||||
|
(fileinfo.name[1] == '.' && fileinfo.name[2] == '\0')))
|
||||||
|
{
|
||||||
|
// Do not record . and .. directories.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FString newdir = dirpath;
|
||||||
|
newdir << fileinfo.name << '/';
|
||||||
|
count += AddDirectory(newdir);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (strstr(fileinfo.name, ".orig") || strstr(fileinfo.name, ".bak"))
|
||||||
|
{
|
||||||
|
// We shouldn't add backup files to the lump directory
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddEntry(FString(dirpath) + fileinfo.name, fileinfo.size);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
} while (_findnext(handle, &fileinfo) == 0);
|
||||||
|
_findclose(handle);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// add_dirs
|
||||||
|
// 4.4BSD version
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FDirectory::AddDirectory(const char *dirpath)
|
||||||
|
{
|
||||||
|
char *argv [2] = { NULL, NULL };
|
||||||
|
argv[0] = new char[strlen(dirpath)+1];
|
||||||
|
strcpy(argv[0], dirpath);
|
||||||
|
FTS *fts;
|
||||||
|
FTSENT *ent;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
fts = fts_open(argv, FTS_LOGICAL, NULL);
|
||||||
|
if (fts == NULL)
|
||||||
|
{
|
||||||
|
Printf("Failed to start directory traversal: %s\n", strerror(errno));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t namepos = strlen(FileName);
|
||||||
|
FString pathfix;
|
||||||
|
|
||||||
|
while ((ent = fts_read(fts)) != NULL)
|
||||||
|
{
|
||||||
|
if (ent->fts_info == FTS_D && ent->fts_name[0] == '.')
|
||||||
|
{
|
||||||
|
// Skip hidden directories. (Prevents SVN bookkeeping
|
||||||
|
// info from being included.)
|
||||||
|
fts_set(fts, ent, FTS_SKIP);
|
||||||
|
}
|
||||||
|
if (ent->fts_info == FTS_D && ent->fts_level == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ent->fts_info != FTS_F)
|
||||||
|
{
|
||||||
|
// We're only interested in remembering files.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some implementations add an extra separator between
|
||||||
|
// root of the hierarchy and entity's path.
|
||||||
|
// It needs to be removed in order to resolve
|
||||||
|
// lumps' relative paths properly.
|
||||||
|
const char* path = ent->fts_path;
|
||||||
|
|
||||||
|
if ('/' == path[namepos])
|
||||||
|
{
|
||||||
|
pathfix = FString(path, namepos);
|
||||||
|
pathfix.AppendCStrPart(&path[namepos + 1], ent->fts_pathlen - namepos - 1);
|
||||||
|
|
||||||
|
path = pathfix.GetChars();
|
||||||
|
}
|
||||||
|
|
||||||
|
AddEntry(path, ent->fts_statp->st_size);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
fts_close(fts);
|
||||||
|
delete[] argv[0];
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool FDirectory::Open(bool quiet)
|
||||||
|
{
|
||||||
|
NumLumps = AddDirectory(FileName);
|
||||||
|
if (!quiet) Printf(", %d lumps\n", NumLumps);
|
||||||
|
PostProcessArchive(&Lumps[0], sizeof(FDirectoryLump));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FDirectory::AddEntry(const char *fullpath, int size)
|
||||||
|
{
|
||||||
|
FDirectoryLump *lump_p = &Lumps[Lumps.Reserve(1)];
|
||||||
|
|
||||||
|
// Store the full path here so that we can access the file later, even if it is from a filter directory.
|
||||||
|
lump_p->mFullPath = fullpath;
|
||||||
|
|
||||||
|
// [mxd] Convert name to lowercase
|
||||||
|
FString name = fullpath + strlen(FileName);
|
||||||
|
name.ToLower();
|
||||||
|
|
||||||
|
// The lump's name is only the part relative to the main directory
|
||||||
|
lump_p->LumpNameSetup(name);
|
||||||
|
lump_p->LumpSize = size;
|
||||||
|
lump_p->Owner = this;
|
||||||
|
lump_p->Flags = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FileReader FDirectoryLump::NewReader()
|
||||||
|
{
|
||||||
|
FileReader fr;
|
||||||
|
fr.OpenFile(mFullPath);
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FDirectoryLump::FillCache()
|
||||||
|
{
|
||||||
|
FileReader fr;
|
||||||
|
if (!fr.OpenFile(mFullPath))
|
||||||
|
{
|
||||||
|
memset(Cache.Data(), 0, LumpSize);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LumpSize = fr.GetLength(); // keep this updated
|
||||||
|
Cache.Resize(LumpSize);
|
||||||
|
fr.Read(Cache.Data(), LumpSize);
|
||||||
|
RefCount = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// File open
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceFile *CheckDir(const char *filename, bool quiet)
|
||||||
|
{
|
||||||
|
FResourceFile *rf = new FDirectory(filename);
|
||||||
|
if (rf->Open(quiet)) return rf;
|
||||||
|
delete rf;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
152
source/common/filesystem/file_grp.cpp
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
** file_grp.cpp
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 1998-2009 Randy Heit
|
||||||
|
** Copyright 2005-2009 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "resourcefile.h"
|
||||||
|
#include "printf.h"
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
struct GrpInfo
|
||||||
|
{
|
||||||
|
uint32_t Magic[3];
|
||||||
|
uint32_t NumLumps;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GrpLump
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
char Name[12];
|
||||||
|
uint32_t Size;
|
||||||
|
};
|
||||||
|
char NameWithZero[13];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Build GRP file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FGrpFile : public FUncompressedFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FGrpFile(const char * filename, FileReader &file);
|
||||||
|
bool Open(bool quiet);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Initializes a Build GRP file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FGrpFile::FGrpFile(const char *filename, FileReader &file)
|
||||||
|
: FUncompressedFile(filename, file)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Open it
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool FGrpFile::Open(bool quiet)
|
||||||
|
{
|
||||||
|
GrpInfo header;
|
||||||
|
|
||||||
|
Reader.Read(&header, sizeof(header));
|
||||||
|
NumLumps = LittleLong(header.NumLumps);
|
||||||
|
|
||||||
|
GrpLump *fileinfo = new GrpLump[NumLumps];
|
||||||
|
Reader.Read (fileinfo, NumLumps * sizeof(GrpLump));
|
||||||
|
|
||||||
|
Lumps.Resize(NumLumps);
|
||||||
|
|
||||||
|
int Position = sizeof(GrpInfo) + NumLumps * sizeof(GrpLump);
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < NumLumps; i++)
|
||||||
|
{
|
||||||
|
Lumps[i].Owner = this;
|
||||||
|
Lumps[i].Position = Position;
|
||||||
|
Lumps[i].LumpSize = LittleLong(fileinfo[i].Size);
|
||||||
|
Position += fileinfo[i].Size;
|
||||||
|
Lumps[i].Flags = 0;
|
||||||
|
fileinfo[i].NameWithZero[12] = '\0'; // Be sure filename is null-terminated
|
||||||
|
Lumps[i].LumpNameSetup(fileinfo[i].NameWithZero);
|
||||||
|
}
|
||||||
|
//if (!quiet) Printf(", %d lumps\n", NumLumps);
|
||||||
|
delete[] fileinfo;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// File open
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceFile *CheckGRP(const char *filename, FileReader &file, bool quiet)
|
||||||
|
{
|
||||||
|
char head[12];
|
||||||
|
|
||||||
|
if (file.GetLength() >= 12)
|
||||||
|
{
|
||||||
|
file.Seek(0, FileReader::SeekSet);
|
||||||
|
file.Read(&head, 12);
|
||||||
|
file.Seek(0, FileReader::SeekSet);
|
||||||
|
if (!memcmp(head, "KenSilverman", 12))
|
||||||
|
{
|
||||||
|
FResourceFile *rf = new FGrpFile(filename, file);
|
||||||
|
if (rf->Open(quiet)) return rf;
|
||||||
|
|
||||||
|
file = std::move(rf->Reader); // to avoid destruction of reader
|
||||||
|
delete rf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
103
source/common/filesystem/file_lump.cpp
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
** file_lump.cpp
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2009 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "resourcefile.h"
|
||||||
|
#include "cmdlib.h"
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Single lump
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FLumpFile : public FUncompressedFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FLumpFile(const char * filename, FileReader &file);
|
||||||
|
bool Open(bool quiet);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// FLumpFile::FLumpFile
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FLumpFile::FLumpFile(const char *filename, FileReader &file)
|
||||||
|
: FUncompressedFile(filename, file)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Open it
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool FLumpFile::Open(bool quiet)
|
||||||
|
{
|
||||||
|
Lumps.Resize(1);
|
||||||
|
Lumps[0].Owner = this;
|
||||||
|
Lumps[0].Position = 0;
|
||||||
|
Lumps[0].LumpSize = (int)Reader.GetLength();
|
||||||
|
Lumps[0].Flags = 0;
|
||||||
|
Lumps[0].FullName = FileName;
|
||||||
|
Lumps[0].BaseName = ExtractFileBase(FileName);
|
||||||
|
NumLumps = 1;
|
||||||
|
/*
|
||||||
|
if (!quiet)
|
||||||
|
{
|
||||||
|
Printf("\n");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// File open
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceFile *CheckLump(const char *filename, FileReader &file, bool quiet)
|
||||||
|
{
|
||||||
|
// always succeeds
|
||||||
|
FResourceFile *rf = new FLumpFile(filename, file);
|
||||||
|
if (rf->Open(quiet)) return rf;
|
||||||
|
file = std::move(rf->Reader); // to avoid destruction of reader
|
||||||
|
delete rf;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
145
source/common/filesystem/file_pak.cpp
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
** file_pak.cpp
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2009 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "resourcefile.h"
|
||||||
|
//#include "w_wad.h"
|
||||||
|
//#include "doomtype.h"
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
struct dpackfile_t
|
||||||
|
{
|
||||||
|
char name[56];
|
||||||
|
int filepos, filelen;
|
||||||
|
} ;
|
||||||
|
|
||||||
|
struct dpackheader_t
|
||||||
|
{
|
||||||
|
int ident; // == IDPAKHEADER
|
||||||
|
int dirofs;
|
||||||
|
int dirlen;
|
||||||
|
} ;
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Wad file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FPakFile : public FUncompressedFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FPakFile(const char * filename, FileReader &file);
|
||||||
|
bool Open(bool quiet);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// FWadFile::FWadFile
|
||||||
|
//
|
||||||
|
// Initializes a WAD file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FPakFile::FPakFile(const char *filename, FileReader &file)
|
||||||
|
: FUncompressedFile(filename, file)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Open it
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool FPakFile::Open(bool quiet)
|
||||||
|
{
|
||||||
|
dpackheader_t header;
|
||||||
|
|
||||||
|
Reader.Read(&header, sizeof(header));
|
||||||
|
NumLumps = LittleLong(header.dirlen) / sizeof(dpackfile_t);
|
||||||
|
header.dirofs = LittleLong(header.dirofs);
|
||||||
|
|
||||||
|
TArray<dpackfile_t> fileinfo(NumLumps, true);
|
||||||
|
Reader.Seek (header.dirofs, FileReader::SeekSet);
|
||||||
|
Reader.Read (fileinfo.Data(), NumLumps * sizeof(dpackfile_t));
|
||||||
|
|
||||||
|
Lumps.Resize(NumLumps);
|
||||||
|
|
||||||
|
//if (!quiet) Printf(", %d lumps\n", NumLumps);
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < NumLumps; i++)
|
||||||
|
{
|
||||||
|
Lumps[i].LumpNameSetup(fileinfo[i].name);
|
||||||
|
Lumps[i].Owner = this;
|
||||||
|
Lumps[i].Position = LittleLong(fileinfo[i].filepos);
|
||||||
|
Lumps[i].LumpSize = LittleLong(fileinfo[i].filelen);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// File open
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceFile *CheckPak(const char *filename, FileReader &file, bool quiet)
|
||||||
|
{
|
||||||
|
char head[4];
|
||||||
|
|
||||||
|
if (file.GetLength() >= 12)
|
||||||
|
{
|
||||||
|
file.Seek(0, FileReader::SeekSet);
|
||||||
|
file.Read(&head, 4);
|
||||||
|
file.Seek(0, FileReader::SeekSet);
|
||||||
|
if (!memcmp(head, "PACK", 4))
|
||||||
|
{
|
||||||
|
FResourceFile *rf = new FPakFile(filename, file);
|
||||||
|
if (rf->Open(quiet)) return rf;
|
||||||
|
|
||||||
|
file = std::move(rf->Reader); // to avoid destruction of reader
|
||||||
|
delete rf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
260
source/common/filesystem/file_rff.cpp
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
/*
|
||||||
|
** file_rff.cpp
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 1998-2009 Randy Heit
|
||||||
|
** Copyright 2005-2009 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
#include <algorithm>
|
||||||
|
#include "resourcefile.h"
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
struct RFFInfo
|
||||||
|
{
|
||||||
|
// Should be "RFF\x18"
|
||||||
|
uint32_t Magic;
|
||||||
|
uint32_t Version;
|
||||||
|
uint32_t DirOfs;
|
||||||
|
uint32_t NumLumps;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RFFLump
|
||||||
|
{
|
||||||
|
uint32_t DontKnow1[4];
|
||||||
|
uint32_t FilePos;
|
||||||
|
uint32_t Size;
|
||||||
|
uint32_t DontKnow2;
|
||||||
|
uint32_t Time;
|
||||||
|
uint8_t Flags;
|
||||||
|
char Extension[3];
|
||||||
|
char Name[8];
|
||||||
|
uint32_t IndexNum; // Used by .sfx, possibly others
|
||||||
|
};
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Blood RFF lump (uncompressed lump with encryption)
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
struct FRFFLump : public FUncompressedLump
|
||||||
|
{
|
||||||
|
virtual FileReader *GetReader();
|
||||||
|
virtual int FillCache();
|
||||||
|
|
||||||
|
uint32_t IndexNum;
|
||||||
|
|
||||||
|
int GetIndexNum() const { return IndexNum; }
|
||||||
|
};
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// BloodCrypt
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void BloodCrypt (void *data, int key, int len)
|
||||||
|
{
|
||||||
|
int p = (uint8_t)key, i;
|
||||||
|
|
||||||
|
for (i = 0; i < len; ++i)
|
||||||
|
{
|
||||||
|
((uint8_t *)data)[i] ^= (unsigned char)(p+(i>>1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Blood RFF file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FRFFFile : public FResourceFile
|
||||||
|
{
|
||||||
|
FRFFLump *Lumps;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FRFFFile(const char * filename, FileReader &file);
|
||||||
|
virtual ~FRFFFile();
|
||||||
|
virtual bool Open(bool quiet);
|
||||||
|
virtual FResourceLump *GetLump(int no) { return ((unsigned)no < NumLumps)? &Lumps[no] : NULL; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Initializes a Blood RFF file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FRFFFile::FRFFFile(const char *filename, FileReader &file)
|
||||||
|
: FResourceFile(filename, file)
|
||||||
|
{
|
||||||
|
Lumps = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Initializes a Blood RFF file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool FRFFFile::Open(bool quiet)
|
||||||
|
{
|
||||||
|
RFFLump *lumps;
|
||||||
|
RFFInfo header;
|
||||||
|
|
||||||
|
Reader.Read(&header, sizeof(header));
|
||||||
|
|
||||||
|
NumLumps = LittleLong(header.NumLumps);
|
||||||
|
header.DirOfs = LittleLong(header.DirOfs);
|
||||||
|
lumps = new RFFLump[header.NumLumps];
|
||||||
|
Reader.Seek (header.DirOfs, FileReader::SeekSet);
|
||||||
|
Reader.Read (lumps, header.NumLumps * sizeof(RFFLump));
|
||||||
|
BloodCrypt (lumps, header.DirOfs, header.NumLumps * sizeof(RFFLump));
|
||||||
|
|
||||||
|
Lumps = new FRFFLump[NumLumps];
|
||||||
|
|
||||||
|
//if (!quiet) Printf(", %d lumps\n", NumLumps);
|
||||||
|
for (uint32_t i = 0; i < NumLumps; ++i)
|
||||||
|
{
|
||||||
|
Lumps[i].Position = LittleLong(lumps[i].FilePos);
|
||||||
|
Lumps[i].LumpSize = LittleLong(lumps[i].Size);
|
||||||
|
Lumps[i].Owner = this;
|
||||||
|
if (lumps[i].Flags & 0x10)
|
||||||
|
{
|
||||||
|
Lumps[i].Flags |= LUMPF_BLOODCRYPT;
|
||||||
|
}
|
||||||
|
Lumps[i].IndexNum = LittleLong(lumps[i].IndexNum);
|
||||||
|
// Rearrange the name and extension to construct the fullname.
|
||||||
|
char name[13];
|
||||||
|
strncpy(name, lumps[i].Name, 8);
|
||||||
|
name[8] = 0;
|
||||||
|
size_t len = strlen(name);
|
||||||
|
assert(len + 4 <= 12);
|
||||||
|
name[len+0] = '.';
|
||||||
|
name[len+1] = lumps[i].Extension[0];
|
||||||
|
name[len+2] = lumps[i].Extension[1];
|
||||||
|
name[len+3] = lumps[i].Extension[2];
|
||||||
|
name[len+4] = 0;
|
||||||
|
Lumps[i].LumpNameSetup(name);
|
||||||
|
}
|
||||||
|
delete[] lumps;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FRFFFile::~FRFFFile()
|
||||||
|
{
|
||||||
|
if (Lumps != NULL)
|
||||||
|
{
|
||||||
|
delete[] Lumps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Get reader (only returns non-NULL if not encrypted)
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FileReader *FRFFLump::GetReader()
|
||||||
|
{
|
||||||
|
// Don't return the reader if this lump is encrypted
|
||||||
|
// In that case always force caching of the lump
|
||||||
|
if (!(Flags & LUMPF_BLOODCRYPT))
|
||||||
|
{
|
||||||
|
return FUncompressedLump::GetReader();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Fills the lump cache and performs decryption
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FRFFLump::FillCache()
|
||||||
|
{
|
||||||
|
int res = FUncompressedLump::FillCache();
|
||||||
|
|
||||||
|
if (Flags & LUMPF_BLOODCRYPT)
|
||||||
|
{
|
||||||
|
int cryptlen = std::min<int> (LumpSize, 256);
|
||||||
|
uint8_t *data = Cache.Data();
|
||||||
|
|
||||||
|
for (int i = 0; i < cryptlen; ++i)
|
||||||
|
{
|
||||||
|
data[i] ^= i >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// File open
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceFile *CheckRFF(const char *filename, FileReader &file, bool quiet)
|
||||||
|
{
|
||||||
|
char head[4];
|
||||||
|
|
||||||
|
if (file.GetLength() >= 16)
|
||||||
|
{
|
||||||
|
file.Seek(0, FileReader::SeekSet);
|
||||||
|
file.Read(&head, 4);
|
||||||
|
file.Seek(0, FileReader::SeekSet);
|
||||||
|
if (!memcmp(head, "RFF\x1a", 4))
|
||||||
|
{
|
||||||
|
FResourceFile *rf = new FRFFFile(filename, file);
|
||||||
|
if (rf->Open(quiet)) return rf;
|
||||||
|
|
||||||
|
file = std::move(rf->Reader); // to avoid destruction of reader
|
||||||
|
delete rf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
579
source/common/filesystem/file_zip.cpp
Normal file
|
@ -0,0 +1,579 @@
|
||||||
|
/*
|
||||||
|
** file_zip.cpp
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 1998-2009 Randy Heit
|
||||||
|
** Copyright 2005-2009 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "osd.h"
|
||||||
|
#include "file_zip.h"
|
||||||
|
#include "ancientzip.h"
|
||||||
|
#include "templates.h"
|
||||||
|
//#include "v_text.h"
|
||||||
|
//#include "w_wad.h"
|
||||||
|
#include "w_zip.h"
|
||||||
|
|
||||||
|
#define BUFREADCOMMENT (0x400)
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Decompression subroutine
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static bool UncompressZipLump(char *Cache, FileReader &Reader, int Method, int LumpSize, int CompressedSize, int GPFlags)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (Method)
|
||||||
|
{
|
||||||
|
case METHOD_STORED:
|
||||||
|
{
|
||||||
|
Reader.Read(Cache, LumpSize);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case METHOD_DEFLATE:
|
||||||
|
case METHOD_BZIP2:
|
||||||
|
case METHOD_LZMA:
|
||||||
|
{
|
||||||
|
FileReader frz;
|
||||||
|
if (frz.OpenDecompressor(Reader, LumpSize, Method, false, [](const char* err) { throw std::runtime_error(err); }))
|
||||||
|
{
|
||||||
|
frz.Read(Cache, LumpSize);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixme: These should also use a stream
|
||||||
|
case METHOD_IMPLODE:
|
||||||
|
{
|
||||||
|
FZipExploder exploder;
|
||||||
|
exploder.Explode((unsigned char *)Cache, LumpSize, Reader, CompressedSize, GPFlags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case METHOD_SHRINK:
|
||||||
|
{
|
||||||
|
ShrinkLoop((unsigned char *)Cache, LumpSize, Reader, CompressedSize);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error &err)
|
||||||
|
{
|
||||||
|
OSD_Printf("%s\n", err.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FCompressedBuffer::Decompress(char *destbuffer)
|
||||||
|
{
|
||||||
|
FileReader mr;
|
||||||
|
mr.OpenMemory(mBuffer, mCompressedSize);
|
||||||
|
return UncompressZipLump(destbuffer, mr, mMethod, mSize, mCompressedSize, mZipFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Finds the central directory end record in the end of the file.
|
||||||
|
// Taken from Quake3 source but the file in question is not GPL'ed. ;)
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
static uint32_t Zip_FindCentralDir(FileReader &fin)
|
||||||
|
{
|
||||||
|
unsigned char buf[BUFREADCOMMENT + 4];
|
||||||
|
uint32_t FileSize;
|
||||||
|
uint32_t uBackRead;
|
||||||
|
uint32_t uMaxBack; // maximum size of global comment
|
||||||
|
uint32_t uPosFound=0;
|
||||||
|
|
||||||
|
FileSize = (uint32_t)fin.GetLength();
|
||||||
|
uMaxBack = std::min<uint32_t>(0xffff, FileSize);
|
||||||
|
|
||||||
|
uBackRead = 4;
|
||||||
|
while (uBackRead < uMaxBack)
|
||||||
|
{
|
||||||
|
uint32_t uReadSize, uReadPos;
|
||||||
|
int i;
|
||||||
|
if (uBackRead + BUFREADCOMMENT > uMaxBack)
|
||||||
|
uBackRead = uMaxBack;
|
||||||
|
else
|
||||||
|
uBackRead += BUFREADCOMMENT;
|
||||||
|
uReadPos = FileSize - uBackRead;
|
||||||
|
|
||||||
|
uReadSize = std::min<uint32_t>((BUFREADCOMMENT + 4), (FileSize - uReadPos));
|
||||||
|
|
||||||
|
if (fin.Seek(uReadPos, FileReader::SeekSet) != 0) break;
|
||||||
|
|
||||||
|
if (fin.Read(buf, (int32_t)uReadSize) != (int32_t)uReadSize) break;
|
||||||
|
|
||||||
|
for (i = (int)uReadSize - 3; (i--) > 0;)
|
||||||
|
{
|
||||||
|
if (buf[i] == 'P' && buf[i+1] == 'K' && buf[i+2] == 5 && buf[i+3] == 6)
|
||||||
|
{
|
||||||
|
uPosFound = uReadPos + i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uPosFound != 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return uPosFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Zip file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FZipFile::FZipFile(const char * filename, FileReader &file)
|
||||||
|
: FResourceFile(filename, file)
|
||||||
|
{
|
||||||
|
Lumps = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FZipFile::Open(bool quiet)
|
||||||
|
{
|
||||||
|
uint32_t centraldir = Zip_FindCentralDir(Reader);
|
||||||
|
FZipEndOfCentralDirectory info;
|
||||||
|
int skipped = 0;
|
||||||
|
|
||||||
|
Lumps = NULL;
|
||||||
|
|
||||||
|
if (centraldir == 0)
|
||||||
|
{
|
||||||
|
if (!quiet) OSD_Printf("\n%s: ZIP file corrupt!\n", FileName.GetChars());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the central directory info.
|
||||||
|
Reader.Seek(centraldir, FileReader::SeekSet);
|
||||||
|
Reader.Read(&info, sizeof(FZipEndOfCentralDirectory));
|
||||||
|
|
||||||
|
// No multi-disk zips!
|
||||||
|
if (info.NumEntries != info.NumEntriesOnAllDisks ||
|
||||||
|
info.FirstDisk != 0 || info.DiskNumber != 0)
|
||||||
|
{
|
||||||
|
if (!quiet) OSD_Printf("\n%s: Multipart Zip files are not supported.\n", FileName.GetChars());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NumLumps = LittleShort(info.NumEntries);
|
||||||
|
Lumps = new FZipLump[NumLumps];
|
||||||
|
|
||||||
|
// Load the entire central directory. Too bad that this contains variable length entries...
|
||||||
|
int dirsize = LittleLong(info.DirectorySize);
|
||||||
|
void *directory = malloc(dirsize);
|
||||||
|
Reader.Seek(LittleLong(info.DirectoryOffset), FileReader::SeekSet);
|
||||||
|
Reader.Read(directory, dirsize);
|
||||||
|
|
||||||
|
char *dirptr = (char*)directory;
|
||||||
|
FZipLump *lump_p = Lumps;
|
||||||
|
|
||||||
|
|
||||||
|
dirptr = (char*)directory;
|
||||||
|
lump_p = Lumps;
|
||||||
|
for (uint32_t i = 0; i < NumLumps; i++)
|
||||||
|
{
|
||||||
|
FZipCentralDirectoryInfo *zip_fh = (FZipCentralDirectoryInfo *)dirptr;
|
||||||
|
|
||||||
|
int len = LittleShort(zip_fh->NameLength);
|
||||||
|
FString name(dirptr + sizeof(FZipCentralDirectoryInfo), len);
|
||||||
|
if (name.IsNotEmpty()) name = name.Mid(name.Len());
|
||||||
|
dirptr += sizeof(FZipCentralDirectoryInfo) +
|
||||||
|
LittleShort(zip_fh->NameLength) +
|
||||||
|
LittleShort(zip_fh->ExtraLength) +
|
||||||
|
LittleShort(zip_fh->CommentLength);
|
||||||
|
|
||||||
|
if (dirptr > ((char*)directory) + dirsize) // This directory entry goes beyond the end of the file.
|
||||||
|
{
|
||||||
|
free(directory);
|
||||||
|
if (!quiet) OSD_Printf("\n%s: Central directory corrupted.", FileName.GetChars());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip Directories
|
||||||
|
if (name.IsEmpty() || (name.Back() == '/' && LittleLong(zip_fh->UncompressedSize) == 0))
|
||||||
|
{
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore unknown compression formats
|
||||||
|
zip_fh->Method = LittleShort(zip_fh->Method);
|
||||||
|
if (zip_fh->Method != METHOD_STORED &&
|
||||||
|
zip_fh->Method != METHOD_DEFLATE &&
|
||||||
|
zip_fh->Method != METHOD_LZMA &&
|
||||||
|
zip_fh->Method != METHOD_BZIP2 &&
|
||||||
|
zip_fh->Method != METHOD_IMPLODE &&
|
||||||
|
zip_fh->Method != METHOD_SHRINK)
|
||||||
|
{
|
||||||
|
if (!quiet) OSD_Printf("\n%s: '%s' uses an unsupported compression algorithm (#%d).\n", FileName.GetChars(), name.GetChars(), zip_fh->Method);
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Also ignore encrypted entries
|
||||||
|
zip_fh->Flags = LittleShort(zip_fh->Flags);
|
||||||
|
if (zip_fh->Flags & ZF_ENCRYPTED)
|
||||||
|
{
|
||||||
|
if (!quiet) OSD_Printf("\n%s: '%s' is encrypted. Encryption is not supported.\n", FileName.GetChars(), name.GetChars());
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
name.Substitute("\\", "/");
|
||||||
|
name.ToLower();
|
||||||
|
|
||||||
|
lump_p->LumpNameSetup(name);
|
||||||
|
lump_p->LumpSize = LittleLong(zip_fh->UncompressedSize);
|
||||||
|
lump_p->Owner = this;
|
||||||
|
// The start of the Reader will be determined the first time it is accessed.
|
||||||
|
lump_p->Flags = LUMPF_ZIPFILE | LUMPFZIP_NEEDFILESTART;
|
||||||
|
lump_p->Method = uint8_t(zip_fh->Method);
|
||||||
|
if (lump_p->Method != METHOD_STORED) lump_p->Flags |= LUMPF_COMPRESSED;
|
||||||
|
lump_p->GPFlags = zip_fh->Flags;
|
||||||
|
lump_p->CRC32 = zip_fh->CRC32;
|
||||||
|
lump_p->CompressedSize = LittleLong(zip_fh->CompressedSize);
|
||||||
|
lump_p->Position = LittleLong(zip_fh->LocalHeaderOffset);
|
||||||
|
|
||||||
|
lump_p++;
|
||||||
|
}
|
||||||
|
// Resize the lump record array to its actual size
|
||||||
|
NumLumps -= skipped;
|
||||||
|
free(directory);
|
||||||
|
|
||||||
|
PostProcessArchive(&Lumps[0], sizeof(FZipLump));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Zip file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FZipFile::~FZipFile()
|
||||||
|
{
|
||||||
|
if (Lumps != NULL) delete [] Lumps;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FCompressedBuffer FZipLump::GetRawData()
|
||||||
|
{
|
||||||
|
FCompressedBuffer cbuf = { (unsigned)LumpSize, (unsigned)CompressedSize, Method, GPFlags, CRC32, new char[CompressedSize] };
|
||||||
|
if (Flags & LUMPFZIP_NEEDFILESTART) SetLumpAddress();
|
||||||
|
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
||||||
|
Owner->Reader.Read(cbuf.mBuffer, CompressedSize);
|
||||||
|
return cbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// SetLumpAddress
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FZipLump::SetLumpAddress()
|
||||||
|
{
|
||||||
|
// This file is inside a zip and has not been opened before.
|
||||||
|
// Position points to the start of the local file header, which we must
|
||||||
|
// read and skip so that we can get to the actual file data.
|
||||||
|
FZipLocalFileHeader localHeader;
|
||||||
|
int skiplen;
|
||||||
|
|
||||||
|
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
||||||
|
Owner->Reader.Read(&localHeader, sizeof(localHeader));
|
||||||
|
skiplen = LittleShort(localHeader.NameLength) + LittleShort(localHeader.ExtraLength);
|
||||||
|
Position += sizeof(localHeader) + skiplen;
|
||||||
|
Flags &= ~LUMPFZIP_NEEDFILESTART;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Get reader (only returns non-NULL if not encrypted)
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FileReader *FZipLump::GetReader()
|
||||||
|
{
|
||||||
|
// Don't return the reader if this lump is encrypted
|
||||||
|
// In that case always force caching of the lump
|
||||||
|
if (Method == METHOD_STORED)
|
||||||
|
{
|
||||||
|
if (Flags & LUMPFZIP_NEEDFILESTART) SetLumpAddress();
|
||||||
|
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
||||||
|
return &Owner->Reader;
|
||||||
|
}
|
||||||
|
else return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Fills the lump cache and performs decompression
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FZipLump::FillCache()
|
||||||
|
{
|
||||||
|
if (Flags & LUMPFZIP_NEEDFILESTART) SetLumpAddress();
|
||||||
|
|
||||||
|
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
||||||
|
Cache.Resize(LumpSize);
|
||||||
|
UncompressZipLump((char*)Cache.Data(), Owner->Reader, Method, LumpSize, CompressedSize, GPFlags);
|
||||||
|
RefCount = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FZipLump::GetFileOffset()
|
||||||
|
{
|
||||||
|
if (Method != METHOD_STORED) return -1;
|
||||||
|
if (Flags & LUMPFZIP_NEEDFILESTART) SetLumpAddress();
|
||||||
|
return Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// File open
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceFile *CheckZip(const char *filename, FileReader &file, bool quiet)
|
||||||
|
{
|
||||||
|
char head[4];
|
||||||
|
|
||||||
|
if (file.GetLength() >= (long)sizeof(FZipLocalFileHeader))
|
||||||
|
{
|
||||||
|
file.Seek(0, FileReader::SeekSet);
|
||||||
|
file.Read(&head, 4);
|
||||||
|
file.Seek(0, FileReader::SeekSet);
|
||||||
|
if (!memcmp(head, "PK\x3\x4", 4))
|
||||||
|
{
|
||||||
|
FResourceFile *rf = new FZipFile(filename, file);
|
||||||
|
if (rf->Open(quiet)) return rf;
|
||||||
|
|
||||||
|
file = std::move(rf->Reader); // to avoid destruction of reader
|
||||||
|
delete rf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// time_to_dos
|
||||||
|
//
|
||||||
|
// Converts time from struct tm to the DOS format used by zip files.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static std::pair<uint16_t, uint16_t> time_to_dos(struct tm *time)
|
||||||
|
{
|
||||||
|
std::pair<uint16_t, uint16_t> val;
|
||||||
|
if (time == NULL || time->tm_year < 80)
|
||||||
|
{
|
||||||
|
val.first = val.second = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
val.first = (time->tm_year - 80) * 512 + (time->tm_mon + 1) * 32 + time->tm_mday;
|
||||||
|
val.second= time->tm_hour * 2048 + time->tm_min * 32 + time->tm_sec / 2;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// append_to_zip
|
||||||
|
//
|
||||||
|
// Write a given file to the zipFile.
|
||||||
|
//
|
||||||
|
// zipfile: zip object to be written to
|
||||||
|
//
|
||||||
|
// returns: position = success, -1 = error
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int AppendToZip(FileWriter *zip_file, const char *filename, FCompressedBuffer &content, std::pair<uint16_t, uint16_t> &dostime)
|
||||||
|
{
|
||||||
|
FZipLocalFileHeader local;
|
||||||
|
int position;
|
||||||
|
|
||||||
|
local.Magic = ZIP_LOCALFILE;
|
||||||
|
local.VersionToExtract[0] = 20;
|
||||||
|
local.VersionToExtract[1] = 0;
|
||||||
|
local.Flags = content.mMethod == METHOD_DEFLATE ? LittleShort((uint16_t)2) : LittleShort((uint16_t)content.mZipFlags);
|
||||||
|
local.Method = LittleShort((uint16_t)content.mMethod);
|
||||||
|
local.ModDate = LittleShort(dostime.first);
|
||||||
|
local.ModTime = LittleShort(dostime.second);
|
||||||
|
local.CRC32 = content.mCRC32;
|
||||||
|
local.UncompressedSize = LittleLong(content.mSize);
|
||||||
|
local.CompressedSize = LittleLong(content.mCompressedSize);
|
||||||
|
local.NameLength = LittleShort((unsigned short)strlen(filename));
|
||||||
|
local.ExtraLength = 0;
|
||||||
|
|
||||||
|
// Fill in local directory header.
|
||||||
|
|
||||||
|
position = (int)zip_file->Tell();
|
||||||
|
|
||||||
|
// Write out the header, file name, and file data.
|
||||||
|
if (zip_file->Write(&local, sizeof(local)) != sizeof(local) ||
|
||||||
|
zip_file->Write(filename, strlen(filename)) != strlen(filename) ||
|
||||||
|
zip_file->Write(content.mBuffer, content.mCompressedSize) != content.mCompressedSize)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// write_central_dir
|
||||||
|
//
|
||||||
|
// Writes the central directory entry for a file.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int AppendCentralDirectory(FileWriter *zip_file, const char *filename, FCompressedBuffer &content, std::pair<uint16_t, uint16_t> &dostime, int position)
|
||||||
|
{
|
||||||
|
FZipCentralDirectoryInfo dir;
|
||||||
|
|
||||||
|
dir.Magic = ZIP_CENTRALFILE;
|
||||||
|
dir.VersionMadeBy[0] = 20;
|
||||||
|
dir.VersionMadeBy[1] = 0;
|
||||||
|
dir.VersionToExtract[0] = 20;
|
||||||
|
dir.VersionToExtract[1] = 0;
|
||||||
|
dir.Flags = content.mMethod == METHOD_DEFLATE ? LittleShort((uint16_t)2) : LittleShort((uint16_t)content.mZipFlags);
|
||||||
|
dir.Method = LittleShort((uint16_t)content.mMethod);
|
||||||
|
dir.ModTime = LittleShort(dostime.first);
|
||||||
|
dir.ModDate = LittleShort(dostime.second);
|
||||||
|
dir.CRC32 = content.mCRC32;
|
||||||
|
dir.CompressedSize = LittleLong(content.mCompressedSize);
|
||||||
|
dir.UncompressedSize = LittleLong(content.mSize);
|
||||||
|
dir.NameLength = LittleShort((unsigned short)strlen(filename));
|
||||||
|
dir.ExtraLength = 0;
|
||||||
|
dir.CommentLength = 0;
|
||||||
|
dir.StartingDiskNumber = 0;
|
||||||
|
dir.InternalAttributes = 0;
|
||||||
|
dir.ExternalAttributes = 0;
|
||||||
|
dir.LocalHeaderOffset = LittleLong(position);
|
||||||
|
|
||||||
|
if (zip_file->Write(&dir, sizeof(dir)) != sizeof(dir) ||
|
||||||
|
zip_file->Write(filename, strlen(filename)) != strlen(filename))
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteZip(const char *filename, TArray<FString> &filenames, TArray<FCompressedBuffer> &content)
|
||||||
|
{
|
||||||
|
// try to determine local time
|
||||||
|
struct tm *ltime;
|
||||||
|
time_t ttime;
|
||||||
|
ttime = time(nullptr);
|
||||||
|
ltime = localtime(&ttime);
|
||||||
|
auto dostime = time_to_dos(ltime);
|
||||||
|
|
||||||
|
TArray<int> positions;
|
||||||
|
|
||||||
|
if (filenames.Size() != content.Size()) return false;
|
||||||
|
|
||||||
|
auto f = FileWriter::Open(filename);
|
||||||
|
if (f != nullptr)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < filenames.Size(); i++)
|
||||||
|
{
|
||||||
|
int pos = AppendToZip(f, filenames[i], content[i], dostime);
|
||||||
|
if (pos == -1)
|
||||||
|
{
|
||||||
|
delete f;
|
||||||
|
remove(filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
positions.Push(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
int dirofs = (int)f->Tell();
|
||||||
|
for (unsigned i = 0; i < filenames.Size(); i++)
|
||||||
|
{
|
||||||
|
if (AppendCentralDirectory(f, filenames[i], content[i], dostime, positions[i]) < 0)
|
||||||
|
{
|
||||||
|
delete f;
|
||||||
|
remove(filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the directory terminator.
|
||||||
|
FZipEndOfCentralDirectory dirend;
|
||||||
|
dirend.Magic = ZIP_ENDOFDIR;
|
||||||
|
dirend.DiskNumber = 0;
|
||||||
|
dirend.FirstDisk = 0;
|
||||||
|
dirend.NumEntriesOnAllDisks = dirend.NumEntries = LittleShort((uint16_t)filenames.Size());
|
||||||
|
dirend.DirectoryOffset = LittleLong(dirofs);
|
||||||
|
dirend.DirectorySize = LittleLong((uint32_t)(f->Tell() - dirofs));
|
||||||
|
dirend.ZipCommentLength = 0;
|
||||||
|
if (f->Write(&dirend, sizeof(dirend)) != sizeof(dirend))
|
||||||
|
{
|
||||||
|
delete f;
|
||||||
|
remove(filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delete f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
567
source/common/filesystem/resourcefile.cpp
Normal file
|
@ -0,0 +1,567 @@
|
||||||
|
/*
|
||||||
|
** resourcefile.cpp
|
||||||
|
**
|
||||||
|
** Base classes for resource file management
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2009 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zlib.h>
|
||||||
|
#include "resourcefile.h"
|
||||||
|
|
||||||
|
extern FString LumpFilter;
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// File reader that reads from a lump's cache
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FLumpReader : public MemoryReader
|
||||||
|
{
|
||||||
|
FResourceLump *source;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FLumpReader(FResourceLump *src)
|
||||||
|
: MemoryReader(NULL, src->LumpSize), source(src)
|
||||||
|
{
|
||||||
|
src->CacheLump();
|
||||||
|
bufptr = (const char*)src->Cache.Data();
|
||||||
|
}
|
||||||
|
|
||||||
|
~FLumpReader()
|
||||||
|
{
|
||||||
|
source->ReleaseCache();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Base class for resource lumps
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceLump::~FResourceLump()
|
||||||
|
{
|
||||||
|
Owner = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Sets up the lump name information for anything not coming from a WAD file.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FResourceLump::LumpNameSetup(FString iname)
|
||||||
|
{
|
||||||
|
long slash = iname.LastIndexOf('/');
|
||||||
|
FString base = (slash >= 0) ? iname.Mid(slash + 1) : iname;
|
||||||
|
auto dot = base.LastIndexOf('.');
|
||||||
|
if (dot >= 0) base.Truncate(dot);
|
||||||
|
BaseName = base;
|
||||||
|
FullName = iname;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// this is just for completeness. For non-Zips only an uncompressed lump can
|
||||||
|
// be returned.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FCompressedBuffer FResourceLump::GetRawData()
|
||||||
|
{
|
||||||
|
FCompressedBuffer cbuf = { (unsigned)LumpSize, (unsigned)LumpSize, METHOD_STORED, 0, 0, new char[LumpSize] };
|
||||||
|
memcpy(cbuf.mBuffer, CacheLump(), LumpSize);
|
||||||
|
cbuf.mCRC32 = crc32(0, (uint8_t*)cbuf.mBuffer, LumpSize);
|
||||||
|
ReleaseCache();
|
||||||
|
return cbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Returns the owner's FileReader if it can be used to access this lump
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FileReader *FResourceLump::GetReader()
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Returns a file reader to the lump's cache
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FileReader FResourceLump::NewReader()
|
||||||
|
{
|
||||||
|
return FileReader(new FLumpReader(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Caches a lump's content and increases the reference counter
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void *FResourceLump::CacheLump()
|
||||||
|
{
|
||||||
|
if (Cache.Size())
|
||||||
|
{
|
||||||
|
if (RefCount > 0) RefCount++;
|
||||||
|
}
|
||||||
|
else if (LumpSize > 0)
|
||||||
|
{
|
||||||
|
FillCache();
|
||||||
|
}
|
||||||
|
return Cache.Data();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Decrements reference counter and frees lump if counter reaches 0
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FResourceLump::ReleaseCache()
|
||||||
|
{
|
||||||
|
if (LumpSize > 0 && RefCount > 0)
|
||||||
|
{
|
||||||
|
if (--RefCount == 0)
|
||||||
|
{
|
||||||
|
Cache.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RefCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Opens a resource file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
typedef FResourceFile * (*CheckFunc)(const char *filename, FileReader &file, bool quiet);
|
||||||
|
|
||||||
|
FResourceFile *CheckWad(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckGRP(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckRFF(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckPak(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckZip(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *Check7Z(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckLump(const char *filename,FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckDir(const char *filename, bool quiet);
|
||||||
|
|
||||||
|
static CheckFunc funcs[] = { CheckGRP, CheckRFF, CheckZip, Check7Z, CheckPak, CheckLump };
|
||||||
|
|
||||||
|
FResourceFile *FResourceFile::DoOpenResourceFile(const char *filename, FileReader &file, bool quiet, bool containeronly)
|
||||||
|
{
|
||||||
|
for(size_t i = 0; i < countof(funcs) - containeronly; i++)
|
||||||
|
{
|
||||||
|
FResourceFile *resfile = funcs[i](filename, file, quiet);
|
||||||
|
if (resfile != NULL) return resfile;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
FResourceFile *FResourceFile::OpenResourceFile(const char *filename, FileReader &file, bool quiet, bool containeronly)
|
||||||
|
{
|
||||||
|
return DoOpenResourceFile(filename, file, quiet, containeronly);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FResourceFile *FResourceFile::OpenResourceFile(const char *filename, bool quiet, bool containeronly)
|
||||||
|
{
|
||||||
|
FileReader file;
|
||||||
|
if (!file.OpenFile(filename)) return nullptr;
|
||||||
|
return DoOpenResourceFile(filename, file, quiet, containeronly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FResourceFile *FResourceFile::OpenResourceFileFromLump(int lumpnum, bool quiet, bool containeronly)
|
||||||
|
{
|
||||||
|
FileReader file = Wads.ReopenLumpReader(lumpnum);
|
||||||
|
return DoOpenResourceFile("internal", file, quiet, containeronly);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
FResourceFile *FResourceFile::OpenDirectory(const char *filename, bool quiet)
|
||||||
|
{
|
||||||
|
return CheckDir(filename, quiet);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Resource file base class
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceFile::FResourceFile(const char *filename)
|
||||||
|
: FileName(filename)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FResourceFile::FResourceFile(const char *filename, FileReader &r)
|
||||||
|
: FResourceFile(filename)
|
||||||
|
{
|
||||||
|
Reader = std::move(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
FResourceFile::~FResourceFile()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int lumpcmp(const void * a, const void * b)
|
||||||
|
{
|
||||||
|
FResourceLump * rec1 = (FResourceLump *)a;
|
||||||
|
FResourceLump * rec2 = (FResourceLump *)b;
|
||||||
|
|
||||||
|
return rec1->FullName.CompareNoCase(rec2->FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// FResourceFile :: PostProcessArchive
|
||||||
|
//
|
||||||
|
// Sorts files by name.
|
||||||
|
// For files named "filter/<game>/*": Using the same filter rules as config
|
||||||
|
// autoloading, move them to the end and rename them without the "filter/"
|
||||||
|
// prefix. Filtered files that don't match are deleted.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FResourceFile::PostProcessArchive(void *lumps, size_t lumpsize)
|
||||||
|
{
|
||||||
|
// Entries in archives are sorted alphabetically
|
||||||
|
qsort(lumps, NumLumps, lumpsize, lumpcmp);
|
||||||
|
|
||||||
|
|
||||||
|
// Filter out lumps using the same names as the Autoload.* sections
|
||||||
|
// in the ini file use. We reduce the maximum lump concidered after
|
||||||
|
// each one so that we don't risk refiltering already filtered lumps.
|
||||||
|
uint32_t max = NumLumps;
|
||||||
|
|
||||||
|
long len;
|
||||||
|
int lastpos = -1;
|
||||||
|
FString file;
|
||||||
|
|
||||||
|
while ((len = LumpFilter.IndexOf('.', lastpos+1)) > 0)
|
||||||
|
{
|
||||||
|
max -= FilterLumps(LumpFilter.Left(len), lumps, lumpsize, max);
|
||||||
|
lastpos = len;
|
||||||
|
}
|
||||||
|
JunkLeftoverFilters(lumps, lumpsize, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// FResourceFile :: FilterLumps
|
||||||
|
//
|
||||||
|
// Finds any lumps between [0,<max>) that match the pattern
|
||||||
|
// "filter/<filtername>/*" and moves them to the end of the lump list.
|
||||||
|
// Returns the number of lumps moved.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FResourceFile::FilterLumps(FString filtername, void *lumps, size_t lumpsize, uint32_t max)
|
||||||
|
{
|
||||||
|
FString filter;
|
||||||
|
uint32_t start, end;
|
||||||
|
|
||||||
|
if (filtername.IsEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
filter << "filter/" << filtername << '/';
|
||||||
|
|
||||||
|
bool found = FindPrefixRange(filter, lumps, lumpsize, max, start, end);
|
||||||
|
|
||||||
|
// Workaround for old Doom filter names.
|
||||||
|
if (!found && filtername.IndexOf("doom.id.doom") == 0)
|
||||||
|
{
|
||||||
|
filter.Substitute("doom.id.doom", "doom.doom");
|
||||||
|
found = FindPrefixRange(filter, lumps, lumpsize, max, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
void *from = (uint8_t *)lumps + start * lumpsize;
|
||||||
|
|
||||||
|
// Remove filter prefix from every name
|
||||||
|
void *lump_p = from;
|
||||||
|
for (uint32_t i = start; i < end; ++i, lump_p = (uint8_t *)lump_p + lumpsize)
|
||||||
|
{
|
||||||
|
FResourceLump *lump = (FResourceLump *)lump_p;
|
||||||
|
assert(lump->FullName.CompareNoCase(filter, (int)filter.Len()) == 0);
|
||||||
|
lump->LumpNameSetup(lump->FullName.Mid(filter.Len()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move filtered lumps to the end of the lump list.
|
||||||
|
size_t count = (end - start) * lumpsize;
|
||||||
|
void *to = (uint8_t *)lumps + NumLumps * lumpsize - count;
|
||||||
|
assert (to >= from);
|
||||||
|
|
||||||
|
if (from != to)
|
||||||
|
{
|
||||||
|
// Copy filtered lumps to a temporary buffer.
|
||||||
|
uint8_t *filteredlumps = new uint8_t[count];
|
||||||
|
memcpy(filteredlumps, from, count);
|
||||||
|
|
||||||
|
// Shift lumps left to make room for the filtered ones at the end.
|
||||||
|
memmove(from, (uint8_t *)from + count, (NumLumps - end) * lumpsize);
|
||||||
|
|
||||||
|
// Copy temporary buffer to newly freed space.
|
||||||
|
memcpy(to, filteredlumps, count);
|
||||||
|
|
||||||
|
delete[] filteredlumps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return end - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// FResourceFile :: JunkLeftoverFilters
|
||||||
|
//
|
||||||
|
// Deletes any lumps beginning with "filter/" that were not matched.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FResourceFile::JunkLeftoverFilters(void *lumps, size_t lumpsize, uint32_t max)
|
||||||
|
{
|
||||||
|
uint32_t start, end;
|
||||||
|
if (FindPrefixRange("filter/", lumps, lumpsize, max, start, end))
|
||||||
|
{
|
||||||
|
// Since the resource lumps may contain non-POD data besides the
|
||||||
|
// full name, we "delete" them by erasing their names so they
|
||||||
|
// can't be found.
|
||||||
|
void *stop = (uint8_t *)lumps + end * lumpsize;
|
||||||
|
for (void *p = (uint8_t *)lumps + start * lumpsize; p < stop; p = (uint8_t *)p + lumpsize)
|
||||||
|
{
|
||||||
|
FResourceLump *lump = (FResourceLump *)p;
|
||||||
|
lump->FullName = "";
|
||||||
|
lump->BaseName = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// FResourceFile :: FindPrefixRange
|
||||||
|
//
|
||||||
|
// Finds a range of lumps that start with the prefix string. <start> is left
|
||||||
|
// indicating the first matching one. <end> is left at one plus the last
|
||||||
|
// matching one.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool FResourceFile::FindPrefixRange(FString filter, void *lumps, size_t lumpsize, uint32_t maxlump, uint32_t &start, uint32_t &end)
|
||||||
|
{
|
||||||
|
uint32_t min, max, mid, inside;
|
||||||
|
FResourceLump *lump;
|
||||||
|
int cmp;
|
||||||
|
|
||||||
|
end = start = 0;
|
||||||
|
|
||||||
|
// Pretend that our range starts at 1 instead of 0 so that we can avoid
|
||||||
|
// unsigned overflow if the range starts at the first lump.
|
||||||
|
lumps = (uint8_t *)lumps - lumpsize;
|
||||||
|
|
||||||
|
// Binary search to find any match at all.
|
||||||
|
min = 1, max = maxlump;
|
||||||
|
while (min <= max)
|
||||||
|
{
|
||||||
|
mid = min + (max - min) / 2;
|
||||||
|
lump = (FResourceLump *)((uint8_t *)lumps + mid * lumpsize);
|
||||||
|
cmp = lump->FullName.CompareNoCase(filter, (int)filter.Len());
|
||||||
|
if (cmp == 0)
|
||||||
|
break;
|
||||||
|
else if (cmp < 0)
|
||||||
|
min = mid + 1;
|
||||||
|
else
|
||||||
|
max = mid - 1;
|
||||||
|
}
|
||||||
|
if (max < min)
|
||||||
|
{ // matched nothing
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary search to find first match.
|
||||||
|
inside = mid;
|
||||||
|
min = 1, max = mid;
|
||||||
|
while (min <= max)
|
||||||
|
{
|
||||||
|
mid = min + (max - min) / 2;
|
||||||
|
lump = (FResourceLump *)((uint8_t *)lumps + mid * lumpsize);
|
||||||
|
cmp = lump->FullName.CompareNoCase(filter, (int)filter.Len());
|
||||||
|
// Go left on matches and right on misses.
|
||||||
|
if (cmp == 0)
|
||||||
|
max = mid - 1;
|
||||||
|
else
|
||||||
|
min = mid + 1;
|
||||||
|
}
|
||||||
|
start = mid + (cmp != 0) - 1;
|
||||||
|
|
||||||
|
// Binary search to find last match.
|
||||||
|
min = inside, max = maxlump;
|
||||||
|
while (min <= max)
|
||||||
|
{
|
||||||
|
mid = min + (max - min) / 2;
|
||||||
|
lump = (FResourceLump *)((uint8_t *)lumps + mid * lumpsize);
|
||||||
|
cmp = lump->FullName.CompareNoCase(filter, (int)filter.Len());
|
||||||
|
// Go right on matches and left on misses.
|
||||||
|
if (cmp == 0)
|
||||||
|
min = mid + 1;
|
||||||
|
else
|
||||||
|
max = mid - 1;
|
||||||
|
}
|
||||||
|
end = mid - (cmp != 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Finds a lump by a given name. Used for savegames
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceLump *FResourceFile::FindLump(const char *name)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < NumLumps; i++)
|
||||||
|
{
|
||||||
|
FResourceLump *lump = GetLump(i);
|
||||||
|
if (!stricmp(name, lump->FullName))
|
||||||
|
{
|
||||||
|
return lump;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Caches a lump's content and increases the reference counter
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FileReader *FUncompressedLump::GetReader()
|
||||||
|
{
|
||||||
|
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
||||||
|
return &Owner->Reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Caches a lump's content and increases the reference counter
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FUncompressedLump::FillCache()
|
||||||
|
{
|
||||||
|
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
||||||
|
Cache.Resize(LumpSize);
|
||||||
|
Owner->Reader.Read(Cache.Data(), LumpSize);
|
||||||
|
RefCount = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Base class for uncompressed resource files
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FUncompressedFile::FUncompressedFile(const char *filename)
|
||||||
|
: FResourceFile(filename)
|
||||||
|
{}
|
||||||
|
|
||||||
|
FUncompressedFile::FUncompressedFile(const char *filename, FileReader &r)
|
||||||
|
: FResourceFile(filename, r)
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// external lump
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FExternalLump::FExternalLump(const char *_filename, int filesize)
|
||||||
|
: Filename(_filename)
|
||||||
|
{
|
||||||
|
if (filesize == -1)
|
||||||
|
{
|
||||||
|
FileReader f;
|
||||||
|
|
||||||
|
if (f.OpenFile(_filename))
|
||||||
|
{
|
||||||
|
LumpSize = (int)f.GetLength();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LumpSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LumpSize = filesize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Caches a lump's content and increases the reference counter
|
||||||
|
// For external lumps this reopens the file each time it is accessed
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FExternalLump::FillCache()
|
||||||
|
{
|
||||||
|
Cache.Resize(LumpSize);
|
||||||
|
FileReader f;
|
||||||
|
|
||||||
|
if (f.OpenFile(Filename))
|
||||||
|
{
|
||||||
|
f.Read(Cache.Data(), LumpSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memset(Cache.Data(), 0, LumpSize);
|
||||||
|
}
|
||||||
|
RefCount = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
|
@ -12,14 +12,14 @@ class FTexture;
|
||||||
|
|
||||||
enum ELumpFlags
|
enum ELumpFlags
|
||||||
{
|
{
|
||||||
LUMPF_MAYBEFLAT=1, // might be a flat outside F_START/END
|
LUMPF_ZIPFILE=1, // contains a full path
|
||||||
LUMPF_ZIPFILE=2, // contains a full path
|
LUMPF_BLOODCRYPT = 2, // encrypted
|
||||||
LUMPF_EMBEDDED=4, // from an embedded WAD
|
LUMPF_COMPRESSED = 4, // compressed
|
||||||
LUMPF_BLOODCRYPT = 8, // encrypted
|
LUMPF_SEQUENTIAL = 8, // compressed but a sequential reader can be retrieved.
|
||||||
LUMPF_COMPRESSED = 16, // compressed
|
|
||||||
LUMPF_SEQUENTIAL = 32, // compressed but a sequential reader can be retrieved.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FResourceFile;
|
||||||
|
|
||||||
// This holds a compresed Zip entry with all needed info to decompress it.
|
// This holds a compresed Zip entry with all needed info to decompress it.
|
||||||
struct FCompressedBuffer
|
struct FCompressedBuffer
|
||||||
{
|
{
|
||||||
|
@ -46,30 +46,23 @@ struct FResourceLump
|
||||||
{
|
{
|
||||||
friend class FResourceFile;
|
friend class FResourceFile;
|
||||||
|
|
||||||
int LumpSize;
|
int LumpSize = 0;
|
||||||
FString FullName; // only valid for files loaded from a non-wad archive
|
int RefCount = 0;
|
||||||
uint8_t Flags;
|
int Flags = 0;
|
||||||
int8_t RefCount;
|
FString FullName; // Name with extension and path
|
||||||
char * Cache;
|
FString BaseName; // Name without extension and path
|
||||||
FResourceFile * Owner;
|
FResourceFile * Owner = nullptr;
|
||||||
FTexture * LinkedTexture;
|
TArray<uint8_t> Cache;
|
||||||
int Namespace;
|
|
||||||
|
|
||||||
FResourceLump()
|
FResourceLump() = default;
|
||||||
{
|
|
||||||
Cache = NULL;
|
|
||||||
Owner = NULL;
|
|
||||||
Flags = 0;
|
|
||||||
RefCount = 0;
|
|
||||||
Namespace = 0; // ns_global
|
|
||||||
LinkedTexture = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~FResourceLump();
|
virtual ~FResourceLump();
|
||||||
|
virtual FileReader *GetReader();
|
||||||
virtual FileReader NewReader();
|
virtual FileReader NewReader();
|
||||||
virtual int GetFileOffset() { return -1; }
|
virtual int GetFileOffset() { return -1; }
|
||||||
virtual int GetIndexNum() const { return 0; }
|
virtual int GetIndexNum() const { return 0; }
|
||||||
void LumpNameSetup(FString iname);
|
void LumpNameSetup(FString iname);
|
||||||
|
virtual FCompressedBuffer GetRawData();
|
||||||
|
|
||||||
void *CacheLump();
|
void *CacheLump();
|
||||||
int ReleaseCache();
|
int ReleaseCache();
|
||||||
|
@ -86,24 +79,32 @@ public:
|
||||||
FString FileName;
|
FString FileName;
|
||||||
protected:
|
protected:
|
||||||
uint32_t NumLumps;
|
uint32_t NumLumps;
|
||||||
FString Hash;
|
|
||||||
|
|
||||||
FResourceFile(const char *filename);
|
FResourceFile(const char *filename);
|
||||||
FResourceFile(const char *filename, FileReader &r);
|
FResourceFile(const char *filename, FileReader &r);
|
||||||
|
|
||||||
|
// for archives that can contain directories
|
||||||
|
void PostProcessArchive(void *lumps, size_t lumpsize);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t FirstLump;
|
uint32_t FirstLump;
|
||||||
|
|
||||||
|
int FilterLumps(FString filtername, void *lumps, size_t lumpsize, uint32_t max);
|
||||||
|
bool FindPrefixRange(FString filter, void *lumps, size_t lumpsize, uint32_t max, uint32_t &start, uint32_t &end);
|
||||||
|
void JunkLeftoverFilters(void *lumps, size_t lumpsize, uint32_t max);
|
||||||
|
static FResourceFile *DoOpenResourceFile(const char *filename, FileReader &file, bool quiet, bool containeronly);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static FResourceFile *OpenResourceFile(const char *filename, FileReader &file, bool quiet = false, bool containeronly = false);
|
static FResourceFile *OpenResourceFile(const char *filename, FileReader &file, bool quiet = false, bool containeronly = false);
|
||||||
static FResourceFile *OpenResourceFile(const char *filename, bool quiet = false, bool containeronly = false);
|
static FResourceFile *OpenResourceFile(const char *filename, bool quiet = false, bool containeronly = false);
|
||||||
|
static FResourceFile *OpenDirectory(const char *filename, bool quiet = false);
|
||||||
virtual ~FResourceFile();
|
virtual ~FResourceFile();
|
||||||
// If this FResourceFile represents a directory, the Reader object is not usable so don't return it.
|
// If this FResourceFile represents a directory, the Reader object is not usable so don't return it.
|
||||||
FileReader *GetReader() { return Reader.isOpen()? &Reader : nullptr; }
|
FileReader *GetReader() { return Reader.isOpen()? &Reader : nullptr; }
|
||||||
uint32_t LumpCount() const { return NumLumps; }
|
uint32_t LumpCount() const { return NumLumps; }
|
||||||
uint32_t GetFirstLump() const { return FirstLump; }
|
uint32_t GetFirstLump() const { return FirstLump; }
|
||||||
void SetFirstLump(uint32_t f) { FirstLump = f; }
|
void SetFirstLump(uint32_t f) { FirstLump = f; }
|
||||||
const FString &GetHash() const { return Hash; }
|
|
||||||
|
|
||||||
|
|
||||||
virtual bool Open(bool quiet) = 0;
|
virtual bool Open(bool quiet) = 0;
|
||||||
|
@ -122,7 +123,7 @@ struct FUncompressedLump : public FResourceLump
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Base class for uncompressed resource files (WAD, GRP, PAK and single lumps)
|
// Base class for uncompressed resource files (GRP, PAK and single lumps)
|
||||||
class FUncompressedFile : public FResourceFile
|
class FUncompressedFile : public FResourceFile
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
@ -133,4 +134,16 @@ protected:
|
||||||
virtual FResourceLump *GetLump(int no) { return ((unsigned)no < NumLumps)? &Lumps[no] : NULL; }
|
virtual FResourceLump *GetLump(int no) { return ((unsigned)no < NumLumps)? &Lumps[no] : NULL; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct FExternalLump : public FResourceLump
|
||||||
|
{
|
||||||
|
FString Filename;
|
||||||
|
|
||||||
|
FExternalLump(const char *_filename, int filesize = -1);
|
||||||
|
virtual int FillCache();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
|
@ -187,28 +187,36 @@ void FGameConfigFile::DoAutoloadSetup (/*FIWadManager *iwad_man*/)
|
||||||
CreateSectionAtStart("Global.Autoload");
|
CreateSectionAtStart("Global.Autoload");
|
||||||
|
|
||||||
// The same goes for auto-exec files.
|
// The same goes for auto-exec files.
|
||||||
CreateStandardAutoExec("ShadowWarrior.AutoExec", true);
|
|
||||||
CreateStandardAutoExec("IonFury.AutoExec", true);
|
|
||||||
CreateStandardAutoExec("RedneckRides.AutoExec", true);
|
|
||||||
CreateStandardAutoExec("Redneck.AutoExec", true);
|
|
||||||
CreateStandardAutoExec("WW2GI.AutoExec", true);
|
|
||||||
CreateStandardAutoExec("Nam.AutoExec", true);
|
|
||||||
CreateStandardAutoExec("DukeNukem3D.AutoExec", true);
|
|
||||||
|
|
||||||
CreateStandardAutoExec("ShadowWarrior.AutoLoad", true);
|
CreateStandardAutoExec("ShadowWarrior.AutoLoad", true);
|
||||||
CreateStandardAutoExec("IonFury.AutoLoad", true);
|
CreateStandardAutoExec("IonFury.AutoLoad", true);
|
||||||
CreateStandardAutoExec("RedneckRides.AutoLoad", true);
|
CreateStandardAutoExec("Redneck.RidesAgain.AutoLoad", true);
|
||||||
CreateStandardAutoExec("Redneck.AutoLoad", true);
|
CreateStandardAutoExec("Redneck.Redneck.AutoLoad", true);
|
||||||
CreateStandardAutoExec("WW2GI.AutoLoad", true);
|
CreateStandardAutoExec("WW2GI.AutoLoad", true);
|
||||||
CreateStandardAutoExec("Nam.AutoLoad", true);
|
CreateStandardAutoExec("Nam.AutoLoad", true);
|
||||||
CreateStandardAutoExec("DukeNukem3D.AutoLoad", true);
|
CreateStandardAutoExec("DukeNukem3D.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("DukeNukem3D.DN3D.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("DukeNukem3D.DukeDC.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("DukeNukem3D.NWinter.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("DukeNukem3D.Vacation.AutoLoad", true);
|
||||||
|
|
||||||
|
CreateStandardAutoExec("ShadowWarrior.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("IonFury.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("Redneck.RidesAgain.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("Redneck.Redneck.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("WW2GI.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("Nam.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("DukeNukem3D.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("DukeNukem3D.DN3D.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("DukeNukem3D.DukeDC.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("DukeNukem3D.NWinter.AutoLoad", true);
|
||||||
|
CreateStandardAutoExec("DukeNukem3D.Vacation.AutoLoad", true);
|
||||||
|
|
||||||
// Move search paths back to the top.
|
// Move search paths back to the top.
|
||||||
MoveSectionToStart("SoundfontSearch.Directories");
|
MoveSectionToStart("SoundfontSearch.Directories");
|
||||||
MoveSectionToStart("FileSearch.Directories");
|
MoveSectionToStart("FileSearch.Directories");
|
||||||
MoveSectionToStart("IWADSearch.Directories");
|
MoveSectionToStart("IWADSearch.Directories");
|
||||||
|
|
||||||
SetSectionNote("DukeNukem3D.AutoExec",
|
SetSectionNote("DukeNukem3D.AutoLoad",
|
||||||
"# Files to automatically execute when running the corresponding game.\n"
|
"# Files to automatically execute when running the corresponding game.\n"
|
||||||
"# Each file should be on its own line, preceded by Path=\n\n");
|
"# Each file should be on its own line, preceded by Path=\n\n");
|
||||||
SetSectionNote("Global.Autoload",
|
SetSectionNote("Global.Autoload",
|
||||||
|
@ -504,12 +512,12 @@ void FGameConfigFile::AddAutoexec (FArgs *list, const char *game)
|
||||||
const char *key;
|
const char *key;
|
||||||
const char *value;
|
const char *value;
|
||||||
|
|
||||||
snprintf (section, countof(section), "%s.AutoExec", game);
|
snprintf (section, countof(section), "%s.AutoLoad", game);
|
||||||
|
|
||||||
// If <game>.AutoExec section does not exist, create it
|
// If <game>.AutoLoad section does not exist, create it
|
||||||
// with a default autoexec.cfg file present.
|
// with a default autoexec.cfg file present.
|
||||||
CreateStandardAutoExec(section, false);
|
CreateStandardAutoExec(section, false);
|
||||||
// Run any files listed in the <game>.AutoExec section
|
// Run any files listed in the <game>.AutoLoad section
|
||||||
if (!SectionIsEmpty())
|
if (!SectionIsEmpty())
|
||||||
{
|
{
|
||||||
while (NextInSection (key, value))
|
while (NextInSection (key, value))
|
||||||
|
|
|
@ -109,6 +109,7 @@ static const GameFuncNameDesc gamefuncs[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
extern FString currentGame;
|
extern FString currentGame;
|
||||||
|
FString LumpFilter;
|
||||||
|
|
||||||
static TMap<FName, int> GF_NameToNum;
|
static TMap<FName, int> GF_NameToNum;
|
||||||
static FString GF_NumToName[NUMGAMEFUNCTIONS]; // This one will preserve the original name for writing to the config (which must be loaded before CON scripts can hack around with the alias array.)
|
static FString GF_NumToName[NUMGAMEFUNCTIONS]; // This one will preserve the original name for writing to the config (which must be loaded before CON scripts can hack around with the alias array.)
|
||||||
|
@ -294,6 +295,9 @@ void UserConfig::ProcessOptions()
|
||||||
|
|
||||||
void CONFIG_Init()
|
void CONFIG_Init()
|
||||||
{
|
{
|
||||||
|
LumpFilter = currentGame;
|
||||||
|
if (LumpFilter.Compare("Redneck") == 0) LumpFilter = "Redneck.Redneck";
|
||||||
|
else if (LumpFilter.Compare("RedneckRides") == 0) LumpFilter = "Redneck.RidesAgain";
|
||||||
SetClipshapes();
|
SetClipshapes();
|
||||||
|
|
||||||
// This must be done before initializing any data, so doing it late in the startup process won't work.
|
// This must be done before initializing any data, so doing it late in the startup process won't work.
|
||||||
|
|
235
source/common/gamemain.cpp
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
#include "zstring.h"
|
||||||
|
#include "gameconfigfile.h"
|
||||||
|
#include "gamecontrol.h"
|
||||||
|
#include "resourcefile.h"
|
||||||
|
#include "sc_man.h"
|
||||||
|
#include "i_specialpaths.h"
|
||||||
|
#include "inputstate.h"
|
||||||
|
#include "c_cvars.h"
|
||||||
|
#include "../../glbackend/glbackend.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Currently there is no global state for the current game. This is a temporary workaround because the video init code needs to do a few things based on the active game.
|
||||||
|
|
||||||
|
FString currentGame;
|
||||||
|
|
||||||
|
namespace Duke
|
||||||
|
{
|
||||||
|
extern GameInterface Interface;
|
||||||
|
}
|
||||||
|
namespace Redneck
|
||||||
|
{
|
||||||
|
extern GameInterface Interface;
|
||||||
|
}
|
||||||
|
namespace Blood
|
||||||
|
{
|
||||||
|
extern GameInterface Interface;
|
||||||
|
}
|
||||||
|
namespace ShadowWarrior
|
||||||
|
{
|
||||||
|
extern GameInterface Interface;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameInterface *CheckFrontend()
|
||||||
|
{
|
||||||
|
FILE* f = fopen("blood.rff", "rb");
|
||||||
|
if (f)
|
||||||
|
{
|
||||||
|
currentGame = "Blood";
|
||||||
|
fclose(f);
|
||||||
|
return &Blood::Interface;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
f = fopen("redneck.grp", "rb");
|
||||||
|
if (f)
|
||||||
|
{
|
||||||
|
currentGame = "Redneck";
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
auto pos = ftell(f);
|
||||||
|
// Quick hack to distinguish these two. This won't survive until production but for testing it's sufficient.
|
||||||
|
if (pos > 190'000'000) currentGame = "RedneckRides";
|
||||||
|
fclose(f);
|
||||||
|
return &Redneck::Interface;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
f = fopen("sw.grp", "rb");
|
||||||
|
if (f)
|
||||||
|
{
|
||||||
|
currentGame = "ShadowWarrior";
|
||||||
|
fclose(f);
|
||||||
|
return &ShadowWarrior::Interface;
|
||||||
|
}
|
||||||
|
f = fopen("fury.grp", "rb");
|
||||||
|
if (f)
|
||||||
|
{
|
||||||
|
currentGame = "IonFury";
|
||||||
|
fclose(f);
|
||||||
|
return &Duke::Interface;
|
||||||
|
}
|
||||||
|
f = fopen("nam.grp", "rb");
|
||||||
|
if (f)
|
||||||
|
{
|
||||||
|
currentGame = "Nam";
|
||||||
|
fclose(f);
|
||||||
|
return &Duke::Interface;
|
||||||
|
}
|
||||||
|
f = fopen("ww2gi.grp", "rb");
|
||||||
|
if (f)
|
||||||
|
{
|
||||||
|
currentGame = "WW2GI";
|
||||||
|
fclose(f);
|
||||||
|
return &Duke::Interface;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentGame = "Duke";
|
||||||
|
}
|
||||||
|
return &Duke::Interface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChooseGame()
|
||||||
|
{
|
||||||
|
auto dir = Args->CheckValue("-game");
|
||||||
|
if (dir && !chdir(dir))
|
||||||
|
{
|
||||||
|
gi = CheckFrontend();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FString> paths;
|
||||||
|
std::vector<std::wstring> wgames;
|
||||||
|
TArray<TASKDIALOG_BUTTON> buttons;
|
||||||
|
char* token;
|
||||||
|
|
||||||
|
FileReader fr;
|
||||||
|
if (fr.OpenFile("./games.list"))
|
||||||
|
{
|
||||||
|
auto filedata = fr.ReadPadded(1);
|
||||||
|
|
||||||
|
auto script = scriptfile_fromstring((char*)filedata.Data());
|
||||||
|
int id = 1000;
|
||||||
|
while (!scriptfile_eof(script))
|
||||||
|
{
|
||||||
|
scriptfile_getstring(script, &token);
|
||||||
|
if (scriptfile_eof(script))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
FString game = token;
|
||||||
|
scriptfile_getstring(script, &token);
|
||||||
|
paths.Push(token);
|
||||||
|
FStringf display("%s\n%s", game.GetChars(), token);
|
||||||
|
wgames.push_back(display.WideString());
|
||||||
|
buttons.Push({ id++, wgames.back().c_str() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (paths.Size() == 0)
|
||||||
|
{
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int nResult = 0;
|
||||||
|
|
||||||
|
TASKDIALOGCONFIG stTaskConfig;
|
||||||
|
ZeroMemory(&stTaskConfig, sizeof(stTaskConfig));
|
||||||
|
|
||||||
|
stTaskConfig.cbSize = sizeof(TASKDIALOGCONFIG);
|
||||||
|
stTaskConfig.hwndParent = NULL;
|
||||||
|
stTaskConfig.hInstance = NULL;
|
||||||
|
|
||||||
|
stTaskConfig.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION| TDF_USE_COMMAND_LINKS;
|
||||||
|
|
||||||
|
if (!gi)
|
||||||
|
{
|
||||||
|
// Open a popup to select the game.
|
||||||
|
// The entire startup code just doesn't work right if this isn't checked as the very first thing.
|
||||||
|
stTaskConfig.pszWindowTitle = L"Demolition";
|
||||||
|
stTaskConfig.pszMainInstruction = L"Choose your game";
|
||||||
|
stTaskConfig.pszContent = L"";
|
||||||
|
stTaskConfig.cButtons = buttons.Size();
|
||||||
|
|
||||||
|
stTaskConfig.pButtons = buttons.Data();
|
||||||
|
stTaskConfig.nDefaultButton = 1000;
|
||||||
|
|
||||||
|
if (SUCCEEDED(TaskDialogIndirect(&stTaskConfig, &nResult, NULL, NULL)))
|
||||||
|
{
|
||||||
|
if (nResult >= 1000 && nResult < 1000 +(int)buttons.Size())
|
||||||
|
{
|
||||||
|
nResult -= 1000;
|
||||||
|
chdir(paths[nResult]);
|
||||||
|
gi = CheckFrontend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gi == nullptr) exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::unique_ptr<FResourceFile> engine_res;
|
||||||
|
|
||||||
|
// The resourge manager in cache1d is far too broken to add some arbitrary file without some adjustment.
|
||||||
|
// For now, keep this file here, until the resource management can be redone in a more workable fashion.
|
||||||
|
extern FString progdir;
|
||||||
|
|
||||||
|
void InitBaseRes()
|
||||||
|
{
|
||||||
|
if (!engine_res)
|
||||||
|
{
|
||||||
|
// If we get here for the first time, load the engine-internal data.
|
||||||
|
FString baseres = progdir + "demolition.pk3";
|
||||||
|
engine_res.reset(FResourceFile::OpenResourceFile(baseres, true, true));
|
||||||
|
if (!engine_res)
|
||||||
|
{
|
||||||
|
I_Error("Engine resources (%s) not found", baseres.GetChars());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReader openFromBaseResource(const char* fn)
|
||||||
|
{
|
||||||
|
InitBaseRes();
|
||||||
|
auto lump = engine_res->FindLump(fn);
|
||||||
|
if (lump) return lump->NewReader();
|
||||||
|
// Also look in game filtered directories.
|
||||||
|
FStringf filtername("filter/game-%s/%s", currentGame.GetChars(), fn);
|
||||||
|
lump = engine_res->FindLump(filtername);
|
||||||
|
if (lump) return lump->NewReader();
|
||||||
|
return FileReader(nullptr);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int GameMain()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Write to the DOCUMENTS directory, not the game directory
|
||||||
|
|
||||||
|
FString logpath = M_GetDocumentsPath() + "demolition.log";
|
||||||
|
OSD_SetLogFile(logpath);
|
||||||
|
CONFIG_ReadCombatMacros();
|
||||||
|
|
||||||
|
// Startup dialog must be presented here so that everything can be set up before reading the keybinds.
|
||||||
|
G_LoadConfig(currentGame);
|
||||||
|
CONFIG_Init();
|
||||||
|
r = gi->app_main(buildargc, (const char**)buildargv);
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error & err)
|
||||||
|
{
|
||||||
|
wm_msgbox("Error", "%s", err.what());
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
catch (const ExitEvent & exit)
|
||||||
|
{
|
||||||
|
// Just let the rest of the function execute.
|
||||||
|
r = exit.Reason();
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,14 +35,13 @@
|
||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include "osd.h"
|
|
||||||
#include "file_zip.h"
|
#include "file_zip.h"
|
||||||
//#include "cmdlib.h"
|
#include "cmdlib.h"
|
||||||
#include "templates.h"
|
#include "basics.h"
|
||||||
//#include "v_text.h"
|
|
||||||
//#include "w_wad.h"
|
|
||||||
#include "w_zip.h"
|
#include "w_zip.h"
|
||||||
|
|
||||||
|
#include "ancientzip.h"
|
||||||
|
|
||||||
#define BUFREADCOMMENT (0x400)
|
#define BUFREADCOMMENT (0x400)
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
@ -75,14 +74,28 @@ static bool UncompressZipLump(char *Cache, FileReader &Reader, int Method, int L
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixme: These should also use a stream
|
||||||
|
case METHOD_IMPLODE:
|
||||||
|
{
|
||||||
|
FZipExploder exploder;
|
||||||
|
exploder.Explode((unsigned char *)Cache, LumpSize, Reader, CompressedSize, GPFlags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case METHOD_SHRINK:
|
||||||
|
{
|
||||||
|
ShrinkLoop((unsigned char *)Cache, LumpSize, Reader, CompressedSize);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert(0);
|
assert(0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (std::runtime_error &err)
|
catch (const std::runtime_error &err)
|
||||||
{
|
{
|
||||||
OSD_Printf("%s\n", err.what());
|
Printf("%s\n", err.what());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -167,7 +180,7 @@ bool FZipFile::Open(bool quiet)
|
||||||
|
|
||||||
if (centraldir == 0)
|
if (centraldir == 0)
|
||||||
{
|
{
|
||||||
if (!quiet) OSD_Printf("\n%s: ZIP file corrupt!\n", FileName.GetChars());
|
if (!quiet) Printf(TEXTCOLOR_RED "\n%s: ZIP file corrupt!\n", FileName.GetChars());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +192,7 @@ bool FZipFile::Open(bool quiet)
|
||||||
if (info.NumEntries != info.NumEntriesOnAllDisks ||
|
if (info.NumEntries != info.NumEntriesOnAllDisks ||
|
||||||
info.FirstDisk != 0 || info.DiskNumber != 0)
|
info.FirstDisk != 0 || info.DiskNumber != 0)
|
||||||
{
|
{
|
||||||
if (!quiet) OSD_Printf("\n%s: Multipart Zip files are not supported.\n", FileName.GetChars());
|
if (!quiet) Printf(TEXTCOLOR_RED "\n%s: Multipart Zip files are not supported.\n", FileName.GetChars());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +228,7 @@ bool FZipFile::Open(bool quiet)
|
||||||
if (dirptr > ((char*)directory) + dirsize) // This directory entry goes beyond the end of the file.
|
if (dirptr > ((char*)directory) + dirsize) // This directory entry goes beyond the end of the file.
|
||||||
{
|
{
|
||||||
free(directory);
|
free(directory);
|
||||||
if (!quiet) OSD_Printf("\n%s: Central directory corrupted.", FileName.GetChars());
|
if (!quiet) Printf(TEXTCOLOR_RED "\n%s: Central directory corrupted.", FileName.GetChars());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,7 +299,7 @@ bool FZipFile::Open(bool quiet)
|
||||||
if (dirptr > ((char*)directory) + dirsize) // This directory entry goes beyond the end of the file.
|
if (dirptr > ((char*)directory) + dirsize) // This directory entry goes beyond the end of the file.
|
||||||
{
|
{
|
||||||
free(directory);
|
free(directory);
|
||||||
if (!quiet) OSD_Printf("\n%s: Central directory corrupted.", FileName.GetChars());
|
if (!quiet) Printf(TEXTCOLOR_RED "\n%s: Central directory corrupted.", FileName.GetChars());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,7 +319,7 @@ bool FZipFile::Open(bool quiet)
|
||||||
zip_fh->Method != METHOD_IMPLODE &&
|
zip_fh->Method != METHOD_IMPLODE &&
|
||||||
zip_fh->Method != METHOD_SHRINK)
|
zip_fh->Method != METHOD_SHRINK)
|
||||||
{
|
{
|
||||||
if (!quiet) OSD_Printf("\n%s: '%s' uses an unsupported compression algorithm (#%d).\n", FileName.GetChars(), name.GetChars(), zip_fh->Method);
|
if (!quiet) Printf(TEXTCOLOR_YELLOW "\n%s: '%s' uses an unsupported compression algorithm (#%d).\n", FileName.GetChars(), name.GetChars(), zip_fh->Method);
|
||||||
skipped++;
|
skipped++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -314,11 +327,12 @@ bool FZipFile::Open(bool quiet)
|
||||||
zip_fh->Flags = LittleShort(zip_fh->Flags);
|
zip_fh->Flags = LittleShort(zip_fh->Flags);
|
||||||
if (zip_fh->Flags & ZF_ENCRYPTED)
|
if (zip_fh->Flags & ZF_ENCRYPTED)
|
||||||
{
|
{
|
||||||
if (!quiet) OSD_Printf("\n%s: '%s' is encrypted. Encryption is not supported.\n", FileName.GetChars(), name.GetChars());
|
if (!quiet) Printf(TEXTCOLOR_YELLOW "\n%s: '%s' is encrypted. Encryption is not supported.\n", FileName.GetChars(), name.GetChars());
|
||||||
skipped++;
|
skipped++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FixPathSeperator(name);
|
||||||
name.ToLower();
|
name.ToLower();
|
||||||
|
|
||||||
lump_p->LumpNameSetup(name);
|
lump_p->LumpNameSetup(name);
|
||||||
|
@ -339,6 +353,9 @@ bool FZipFile::Open(bool quiet)
|
||||||
NumLumps -= skipped;
|
NumLumps -= skipped;
|
||||||
free(directory);
|
free(directory);
|
||||||
|
|
||||||
|
if (!quiet) Printf(TEXTCOLOR_NORMAL ", %d lumps\n", NumLumps);
|
||||||
|
|
||||||
|
PostProcessArchive(&Lumps[0], sizeof(FZipLump));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,19 +434,10 @@ FileReader *FZipLump::GetReader()
|
||||||
int FZipLump::FillCache()
|
int FZipLump::FillCache()
|
||||||
{
|
{
|
||||||
if (Flags & LUMPFZIP_NEEDFILESTART) SetLumpAddress();
|
if (Flags & LUMPFZIP_NEEDFILESTART) SetLumpAddress();
|
||||||
const char *buffer;
|
|
||||||
|
|
||||||
if (Method == METHOD_STORED && (buffer = Owner->Reader.GetBuffer()) != NULL)
|
|
||||||
{
|
|
||||||
// This is an in-memory file so the cache can point directly to the file's data.
|
|
||||||
Cache = const_cast<char*>(buffer) + Position;
|
|
||||||
RefCount = -1;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
||||||
Cache = new char[LumpSize];
|
Cache.Resize(LumpSize);
|
||||||
UncompressZipLump(Cache, Owner->Reader, Method, LumpSize, CompressedSize, GPFlags);
|
UncompressZipLump((char*)Cache.Data(), Owner->Reader, Method, LumpSize, CompressedSize, GPFlags);
|
||||||
RefCount = 1;
|
RefCount = 1;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -474,3 +482,172 @@ FResourceFile *CheckZip(const char *filename, FileReader &file, bool quiet)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// time_to_dos
|
||||||
|
//
|
||||||
|
// Converts time from struct tm to the DOS format used by zip files.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static std::pair<uint16_t, uint16_t> time_to_dos(struct tm *time)
|
||||||
|
{
|
||||||
|
std::pair<uint16_t, uint16_t> val;
|
||||||
|
if (time == NULL || time->tm_year < 80)
|
||||||
|
{
|
||||||
|
val.first = val.second = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
val.first = (time->tm_year - 80) * 512 + (time->tm_mon + 1) * 32 + time->tm_mday;
|
||||||
|
val.second= time->tm_hour * 2048 + time->tm_min * 32 + time->tm_sec / 2;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// append_to_zip
|
||||||
|
//
|
||||||
|
// Write a given file to the zipFile.
|
||||||
|
//
|
||||||
|
// zipfile: zip object to be written to
|
||||||
|
//
|
||||||
|
// returns: position = success, -1 = error
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int AppendToZip(FileWriter *zip_file, const char *filename, FCompressedBuffer &content, std::pair<uint16_t, uint16_t> &dostime)
|
||||||
|
{
|
||||||
|
FZipLocalFileHeader local;
|
||||||
|
int position;
|
||||||
|
|
||||||
|
local.Magic = ZIP_LOCALFILE;
|
||||||
|
local.VersionToExtract[0] = 20;
|
||||||
|
local.VersionToExtract[1] = 0;
|
||||||
|
local.Flags = content.mMethod == METHOD_DEFLATE ? LittleShort((uint16_t)2) : LittleShort((uint16_t)content.mZipFlags);
|
||||||
|
local.Method = LittleShort((uint16_t)content.mMethod);
|
||||||
|
local.ModDate = LittleShort(dostime.first);
|
||||||
|
local.ModTime = LittleShort(dostime.second);
|
||||||
|
local.CRC32 = content.mCRC32;
|
||||||
|
local.UncompressedSize = LittleLong(content.mSize);
|
||||||
|
local.CompressedSize = LittleLong(content.mCompressedSize);
|
||||||
|
local.NameLength = LittleShort((unsigned short)strlen(filename));
|
||||||
|
local.ExtraLength = 0;
|
||||||
|
|
||||||
|
// Fill in local directory header.
|
||||||
|
|
||||||
|
position = (int)zip_file->Tell();
|
||||||
|
|
||||||
|
// Write out the header, file name, and file data.
|
||||||
|
if (zip_file->Write(&local, sizeof(local)) != sizeof(local) ||
|
||||||
|
zip_file->Write(filename, strlen(filename)) != strlen(filename) ||
|
||||||
|
zip_file->Write(content.mBuffer, content.mCompressedSize) != content.mCompressedSize)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// write_central_dir
|
||||||
|
//
|
||||||
|
// Writes the central directory entry for a file.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int AppendCentralDirectory(FileWriter *zip_file, const char *filename, FCompressedBuffer &content, std::pair<uint16_t, uint16_t> &dostime, int position)
|
||||||
|
{
|
||||||
|
FZipCentralDirectoryInfo dir;
|
||||||
|
|
||||||
|
dir.Magic = ZIP_CENTRALFILE;
|
||||||
|
dir.VersionMadeBy[0] = 20;
|
||||||
|
dir.VersionMadeBy[1] = 0;
|
||||||
|
dir.VersionToExtract[0] = 20;
|
||||||
|
dir.VersionToExtract[1] = 0;
|
||||||
|
dir.Flags = content.mMethod == METHOD_DEFLATE ? LittleShort((uint16_t)2) : LittleShort((uint16_t)content.mZipFlags);
|
||||||
|
dir.Method = LittleShort((uint16_t)content.mMethod);
|
||||||
|
dir.ModTime = LittleShort(dostime.first);
|
||||||
|
dir.ModDate = LittleShort(dostime.second);
|
||||||
|
dir.CRC32 = content.mCRC32;
|
||||||
|
dir.CompressedSize = LittleLong(content.mCompressedSize);
|
||||||
|
dir.UncompressedSize = LittleLong(content.mSize);
|
||||||
|
dir.NameLength = LittleShort((unsigned short)strlen(filename));
|
||||||
|
dir.ExtraLength = 0;
|
||||||
|
dir.CommentLength = 0;
|
||||||
|
dir.StartingDiskNumber = 0;
|
||||||
|
dir.InternalAttributes = 0;
|
||||||
|
dir.ExternalAttributes = 0;
|
||||||
|
dir.LocalHeaderOffset = LittleLong(position);
|
||||||
|
|
||||||
|
if (zip_file->Write(&dir, sizeof(dir)) != sizeof(dir) ||
|
||||||
|
zip_file->Write(filename, strlen(filename)) != strlen(filename))
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WriteZip(const char *filename, TArray<FString> &filenames, TArray<FCompressedBuffer> &content)
|
||||||
|
{
|
||||||
|
// try to determine local time
|
||||||
|
struct tm *ltime;
|
||||||
|
time_t ttime;
|
||||||
|
ttime = time(nullptr);
|
||||||
|
ltime = localtime(&ttime);
|
||||||
|
auto dostime = time_to_dos(ltime);
|
||||||
|
|
||||||
|
TArray<int> positions;
|
||||||
|
|
||||||
|
if (filenames.Size() != content.Size()) return false;
|
||||||
|
|
||||||
|
auto f = FileWriter::Open(filename);
|
||||||
|
if (f != nullptr)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < filenames.Size(); i++)
|
||||||
|
{
|
||||||
|
int pos = AppendToZip(f, filenames[i], content[i], dostime);
|
||||||
|
if (pos == -1)
|
||||||
|
{
|
||||||
|
delete f;
|
||||||
|
remove(filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
positions.Push(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
int dirofs = (int)f->Tell();
|
||||||
|
for (unsigned i = 0; i < filenames.Size(); i++)
|
||||||
|
{
|
||||||
|
if (AppendCentralDirectory(f, filenames[i], content[i], dostime, positions[i]) < 0)
|
||||||
|
{
|
||||||
|
delete f;
|
||||||
|
remove(filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the directory terminator.
|
||||||
|
FZipEndOfCentralDirectory dirend;
|
||||||
|
dirend.Magic = ZIP_ENDOFDIR;
|
||||||
|
dirend.DiskNumber = 0;
|
||||||
|
dirend.FirstDisk = 0;
|
||||||
|
dirend.NumEntriesOnAllDisks = dirend.NumEntries = LittleShort((uint16_t)filenames.Size());
|
||||||
|
dirend.DirectoryOffset = LittleLong(dirofs);
|
||||||
|
dirend.DirectorySize = LittleLong((uint32_t)(f->Tell() - dirofs));
|
||||||
|
dirend.ZipCommentLength = 0;
|
||||||
|
if (f->Write(&dirend, sizeof(dirend)) != sizeof(dirend))
|
||||||
|
{
|
||||||
|
delete f;
|
||||||
|
remove(filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delete f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
53
source/common/resourcefiles/file_zip.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#ifndef __FILE_ZIP_H
|
||||||
|
#define __FILE_ZIP_H
|
||||||
|
|
||||||
|
#include "resourcefile.h"
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
LUMPFZIP_NEEDFILESTART = 128
|
||||||
|
};
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Zip Lump
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
struct FZipLump : public FResourceLump
|
||||||
|
{
|
||||||
|
uint16_t GPFlags;
|
||||||
|
uint8_t Method;
|
||||||
|
int CompressedSize;
|
||||||
|
int Position;
|
||||||
|
unsigned CRC32;
|
||||||
|
|
||||||
|
virtual FileReader *GetReader();
|
||||||
|
virtual int FillCache();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetLumpAddress();
|
||||||
|
virtual int GetFileOffset();
|
||||||
|
FCompressedBuffer GetRawData();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Zip file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FZipFile : public FResourceFile
|
||||||
|
{
|
||||||
|
FZipLump *Lumps;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FZipFile(const char * filename, FileReader &file);
|
||||||
|
virtual ~FZipFile();
|
||||||
|
bool Open(bool quiet);
|
||||||
|
virtual FResourceLump *GetLump(int no) { return ((unsigned)no < NumLumps)? &Lumps[no] : NULL; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
602
source/common/resourcefiles/resourcefile.cpp
Normal file
|
@ -0,0 +1,602 @@
|
||||||
|
/*
|
||||||
|
** resourcefile.cpp
|
||||||
|
**
|
||||||
|
** Base classes for resource file management
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2009 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zlib.h>
|
||||||
|
#include "resourcefile.h"
|
||||||
|
#include "cmdlib.h"
|
||||||
|
#include "basics.h"
|
||||||
|
#include "gametype.h"
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// File reader that reads from a lump's cache
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FLumpReader : public MemoryReader
|
||||||
|
{
|
||||||
|
FResourceLump *source;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FLumpReader(FResourceLump *src)
|
||||||
|
: MemoryReader(NULL, src->LumpSize), source(src)
|
||||||
|
{
|
||||||
|
src->CacheLump();
|
||||||
|
bufptr = (const char*)src->Cache.Data();
|
||||||
|
}
|
||||||
|
|
||||||
|
~FLumpReader()
|
||||||
|
{
|
||||||
|
source->ReleaseCache();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Base class for resource lumps
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceLump::~FResourceLump()
|
||||||
|
{
|
||||||
|
Owner = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Sets up the lump name information for anything not coming from a WAD file.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FResourceLump::LumpNameSetup(FString iname)
|
||||||
|
{
|
||||||
|
long slash = iname.LastIndexOf('/');
|
||||||
|
FString base = (slash >= 0) ? iname.Mid(slash + 1) : iname;
|
||||||
|
auto dot = base.LastIndexOf('.');
|
||||||
|
if (dot >= 0) base.Truncate(dot);
|
||||||
|
BaseName = base;
|
||||||
|
FullName = iname;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// this is just for completeness. For non-Zips only an uncompressed lump can
|
||||||
|
// be returned.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FCompressedBuffer FResourceLump::GetRawData()
|
||||||
|
{
|
||||||
|
FCompressedBuffer cbuf = { (unsigned)LumpSize, (unsigned)LumpSize, METHOD_STORED, 0, 0, new char[LumpSize] };
|
||||||
|
memcpy(cbuf.mBuffer, CacheLump(), LumpSize);
|
||||||
|
cbuf.mCRC32 = crc32(0, (uint8_t*)cbuf.mBuffer, LumpSize);
|
||||||
|
ReleaseCache();
|
||||||
|
return cbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Returns the owner's FileReader if it can be used to access this lump
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FileReader *FResourceLump::GetReader()
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Returns a file reader to the lump's cache
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FileReader FResourceLump::NewReader()
|
||||||
|
{
|
||||||
|
return FileReader(new FLumpReader(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Caches a lump's content and increases the reference counter
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void *FResourceLump::CacheLump()
|
||||||
|
{
|
||||||
|
if (Cache.Size())
|
||||||
|
{
|
||||||
|
if (RefCount > 0) RefCount++;
|
||||||
|
}
|
||||||
|
else if (LumpSize > 0)
|
||||||
|
{
|
||||||
|
FillCache();
|
||||||
|
}
|
||||||
|
return Cache.Data();
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Decrements reference counter and frees lump if counter reaches 0
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FResourceLump::ReleaseCache()
|
||||||
|
{
|
||||||
|
if (LumpSize > 0 && RefCount > 0)
|
||||||
|
{
|
||||||
|
if (--RefCount == 0)
|
||||||
|
{
|
||||||
|
Cache.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RefCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Opens a resource file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
typedef FResourceFile * (*CheckFunc)(const char *filename, FileReader &file, bool quiet);
|
||||||
|
|
||||||
|
FResourceFile *CheckWad(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckGRP(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckRFF(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckPak(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckZip(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *Check7Z(const char *filename, FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckLump(const char *filename,FileReader &file, bool quiet);
|
||||||
|
FResourceFile *CheckDir(const char *filename, bool quiet);
|
||||||
|
|
||||||
|
static CheckFunc funcs[] = { CheckGRP, CheckRFF, CheckZip, Check7Z, CheckPak, CheckLump };
|
||||||
|
|
||||||
|
FResourceFile *FResourceFile::DoOpenResourceFile(const char *filename, FileReader &file, bool quiet, bool containeronly)
|
||||||
|
{
|
||||||
|
for(size_t i = 0; i < countof(funcs) - containeronly; i++)
|
||||||
|
{
|
||||||
|
FResourceFile *resfile = funcs[i](filename, file, quiet);
|
||||||
|
if (resfile != NULL) return resfile;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
FResourceFile *FResourceFile::OpenResourceFile(const char *filename, FileReader &file, bool quiet, bool containeronly)
|
||||||
|
{
|
||||||
|
return DoOpenResourceFile(filename, file, quiet, containeronly);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FResourceFile *FResourceFile::OpenResourceFile(const char *filename, bool quiet, bool containeronly)
|
||||||
|
{
|
||||||
|
FileReader file;
|
||||||
|
if (!file.OpenFile(filename)) return nullptr;
|
||||||
|
return DoOpenResourceFile(filename, file, quiet, containeronly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FResourceFile *FResourceFile::OpenResourceFileFromLump(int lumpnum, bool quiet, bool containeronly)
|
||||||
|
{
|
||||||
|
FileReader file = Wads.ReopenLumpReader(lumpnum);
|
||||||
|
return DoOpenResourceFile("internal", file, quiet, containeronly);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
FResourceFile *FResourceFile::OpenDirectory(const char *filename, bool quiet)
|
||||||
|
{
|
||||||
|
return CheckDir(filename, quiet);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Resource file base class
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceFile::FResourceFile(const char *filename)
|
||||||
|
: FileName(filename)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FResourceFile::FResourceFile(const char *filename, FileReader &r)
|
||||||
|
: FResourceFile(filename)
|
||||||
|
{
|
||||||
|
Reader = std::move(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
FResourceFile::~FResourceFile()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int lumpcmp(const void * a, const void * b)
|
||||||
|
{
|
||||||
|
FResourceLump * rec1 = (FResourceLump *)a;
|
||||||
|
FResourceLump * rec2 = (FResourceLump *)b;
|
||||||
|
|
||||||
|
return rec1->FullName.CompareNoCase(rec2->FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// FResourceFile :: PostProcessArchive
|
||||||
|
//
|
||||||
|
// Sorts files by name.
|
||||||
|
// For files named "filter/<game>/*": Using the same filter rules as config
|
||||||
|
// autoloading, move them to the end and rename them without the "filter/"
|
||||||
|
// prefix. Filtered files that don't match are deleted.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FResourceFile::PostProcessArchive(void *lumps, size_t lumpsize)
|
||||||
|
{
|
||||||
|
// Entries in archives are sorted alphabetically
|
||||||
|
qsort(lumps, NumLumps, lumpsize, lumpcmp);
|
||||||
|
|
||||||
|
|
||||||
|
// Filter out lumps using the same names as the Autoload.* sections
|
||||||
|
// in the ini file use. We reduce the maximum lump concidered after
|
||||||
|
// each one so that we don't risk refiltering already filtered lumps.
|
||||||
|
uint32_t max = NumLumps;
|
||||||
|
max -= FilterLumpsByGameType(gametype, lumps, lumpsize, max);
|
||||||
|
|
||||||
|
long len;
|
||||||
|
int lastpos = -1;
|
||||||
|
FString file;
|
||||||
|
|
||||||
|
while ((len = LumpFilterIWAD.IndexOf('.', lastpos+1)) > 0)
|
||||||
|
{
|
||||||
|
max -= FilterLumps(LumpFilterIWAD.Left(len), lumps, lumpsize, max);
|
||||||
|
lastpos = len;
|
||||||
|
}
|
||||||
|
JunkLeftoverFilters(lumps, lumpsize, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// FResourceFile :: FilterLumps
|
||||||
|
//
|
||||||
|
// Finds any lumps between [0,<max>) that match the pattern
|
||||||
|
// "filter/<filtername>/*" and moves them to the end of the lump list.
|
||||||
|
// Returns the number of lumps moved.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FResourceFile::FilterLumps(FString filtername, void *lumps, size_t lumpsize, uint32_t max)
|
||||||
|
{
|
||||||
|
FString filter;
|
||||||
|
uint32_t start, end;
|
||||||
|
|
||||||
|
if (filtername.IsEmpty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
filter << "filter/" << filtername << '/';
|
||||||
|
|
||||||
|
bool found = FindPrefixRange(filter, lumps, lumpsize, max, start, end);
|
||||||
|
|
||||||
|
// Workaround for old Doom filter names.
|
||||||
|
if (!found && filtername.IndexOf("doom.id.doom") == 0)
|
||||||
|
{
|
||||||
|
filter.Substitute("doom.id.doom", "doom.doom");
|
||||||
|
found = FindPrefixRange(filter, lumps, lumpsize, max, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
void *from = (uint8_t *)lumps + start * lumpsize;
|
||||||
|
|
||||||
|
// Remove filter prefix from every name
|
||||||
|
void *lump_p = from;
|
||||||
|
for (uint32_t i = start; i < end; ++i, lump_p = (uint8_t *)lump_p + lumpsize)
|
||||||
|
{
|
||||||
|
FResourceLump *lump = (FResourceLump *)lump_p;
|
||||||
|
assert(lump->FullName.CompareNoCase(filter, (int)filter.Len()) == 0);
|
||||||
|
lump->LumpNameSetup(lump->FullName.Mid(filter.Len()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move filtered lumps to the end of the lump list.
|
||||||
|
size_t count = (end - start) * lumpsize;
|
||||||
|
void *to = (uint8_t *)lumps + NumLumps * lumpsize - count;
|
||||||
|
assert (to >= from);
|
||||||
|
|
||||||
|
if (from != to)
|
||||||
|
{
|
||||||
|
// Copy filtered lumps to a temporary buffer.
|
||||||
|
uint8_t *filteredlumps = new uint8_t[count];
|
||||||
|
memcpy(filteredlumps, from, count);
|
||||||
|
|
||||||
|
// Shift lumps left to make room for the filtered ones at the end.
|
||||||
|
memmove(from, (uint8_t *)from + count, (NumLumps - end) * lumpsize);
|
||||||
|
|
||||||
|
// Copy temporary buffer to newly freed space.
|
||||||
|
memcpy(to, filteredlumps, count);
|
||||||
|
|
||||||
|
delete[] filteredlumps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return end - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// FResourceFile :: FilterLumpsByGameType
|
||||||
|
//
|
||||||
|
// Matches any lumps that match "filter/game-<gametype>/*". Includes
|
||||||
|
// inclusive gametypes like Raven.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FResourceFile::FilterLumpsByGameType(int type, void *lumps, size_t lumpsize, uint32_t max)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (int i = GAME_MAX; i >= 0; i++)
|
||||||
|
{
|
||||||
|
if ((type & i) && GameFilters[i])
|
||||||
|
{
|
||||||
|
count += FilterLumps(GameFilters[i], lumps, lumpsize, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// FResourceFile :: JunkLeftoverFilters
|
||||||
|
//
|
||||||
|
// Deletes any lumps beginning with "filter/" that were not matched.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FResourceFile::JunkLeftoverFilters(void *lumps, size_t lumpsize, uint32_t max)
|
||||||
|
{
|
||||||
|
uint32_t start, end;
|
||||||
|
if (FindPrefixRange("filter/", lumps, lumpsize, max, start, end))
|
||||||
|
{
|
||||||
|
// Since the resource lumps may contain non-POD data besides the
|
||||||
|
// full name, we "delete" them by erasing their names so they
|
||||||
|
// can't be found.
|
||||||
|
void *stop = (uint8_t *)lumps + end * lumpsize;
|
||||||
|
for (void *p = (uint8_t *)lumps + start * lumpsize; p < stop; p = (uint8_t *)p + lumpsize)
|
||||||
|
{
|
||||||
|
FResourceLump *lump = (FResourceLump *)p;
|
||||||
|
lump->FullName = "";
|
||||||
|
lump->BaseName = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// FResourceFile :: FindPrefixRange
|
||||||
|
//
|
||||||
|
// Finds a range of lumps that start with the prefix string. <start> is left
|
||||||
|
// indicating the first matching one. <end> is left at one plus the last
|
||||||
|
// matching one.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool FResourceFile::FindPrefixRange(FString filter, void *lumps, size_t lumpsize, uint32_t maxlump, uint32_t &start, uint32_t &end)
|
||||||
|
{
|
||||||
|
uint32_t min, max, mid, inside;
|
||||||
|
FResourceLump *lump;
|
||||||
|
int cmp;
|
||||||
|
|
||||||
|
end = start = 0;
|
||||||
|
|
||||||
|
// Pretend that our range starts at 1 instead of 0 so that we can avoid
|
||||||
|
// unsigned overflow if the range starts at the first lump.
|
||||||
|
lumps = (uint8_t *)lumps - lumpsize;
|
||||||
|
|
||||||
|
// Binary search to find any match at all.
|
||||||
|
min = 1, max = maxlump;
|
||||||
|
while (min <= max)
|
||||||
|
{
|
||||||
|
mid = min + (max - min) / 2;
|
||||||
|
lump = (FResourceLump *)((uint8_t *)lumps + mid * lumpsize);
|
||||||
|
cmp = lump->FullName.CompareNoCase(filter, (int)filter.Len());
|
||||||
|
if (cmp == 0)
|
||||||
|
break;
|
||||||
|
else if (cmp < 0)
|
||||||
|
min = mid + 1;
|
||||||
|
else
|
||||||
|
max = mid - 1;
|
||||||
|
}
|
||||||
|
if (max < min)
|
||||||
|
{ // matched nothing
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary search to find first match.
|
||||||
|
inside = mid;
|
||||||
|
min = 1, max = mid;
|
||||||
|
while (min <= max)
|
||||||
|
{
|
||||||
|
mid = min + (max - min) / 2;
|
||||||
|
lump = (FResourceLump *)((uint8_t *)lumps + mid * lumpsize);
|
||||||
|
cmp = lump->FullName.CompareNoCase(filter, (int)filter.Len());
|
||||||
|
// Go left on matches and right on misses.
|
||||||
|
if (cmp == 0)
|
||||||
|
max = mid - 1;
|
||||||
|
else
|
||||||
|
min = mid + 1;
|
||||||
|
}
|
||||||
|
start = mid + (cmp != 0) - 1;
|
||||||
|
|
||||||
|
// Binary search to find last match.
|
||||||
|
min = inside, max = maxlump;
|
||||||
|
while (min <= max)
|
||||||
|
{
|
||||||
|
mid = min + (max - min) / 2;
|
||||||
|
lump = (FResourceLump *)((uint8_t *)lumps + mid * lumpsize);
|
||||||
|
cmp = lump->FullName.CompareNoCase(filter, (int)filter.Len());
|
||||||
|
// Go right on matches and left on misses.
|
||||||
|
if (cmp == 0)
|
||||||
|
min = mid + 1;
|
||||||
|
else
|
||||||
|
max = mid - 1;
|
||||||
|
}
|
||||||
|
end = mid - (cmp != 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Needs to be virtual in the base class. Implemented only for WADs
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FResourceFile::FindStrifeTeaserVoices ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Finds a lump by a given name. Used for savegames
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FResourceLump *FResourceFile::FindLump(const char *name)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < NumLumps; i++)
|
||||||
|
{
|
||||||
|
FResourceLump *lump = GetLump(i);
|
||||||
|
if (!stricmp(name, lump->FullName))
|
||||||
|
{
|
||||||
|
return lump;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Caches a lump's content and increases the reference counter
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FileReader *FUncompressedLump::GetReader()
|
||||||
|
{
|
||||||
|
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
||||||
|
return &Owner->Reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Caches a lump's content and increases the reference counter
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FUncompressedLump::FillCache()
|
||||||
|
{
|
||||||
|
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
||||||
|
Cache.Resize(LumpSize);
|
||||||
|
Owner->Reader.Read(Cache.Data(), LumpSize);
|
||||||
|
RefCount = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Base class for uncompressed resource files
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FUncompressedFile::FUncompressedFile(const char *filename)
|
||||||
|
: FResourceFile(filename)
|
||||||
|
{}
|
||||||
|
|
||||||
|
FUncompressedFile::FUncompressedFile(const char *filename, FileReader &r)
|
||||||
|
: FResourceFile(filename, r)
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// external lump
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FExternalLump::FExternalLump(const char *_filename, int filesize)
|
||||||
|
: Filename(_filename)
|
||||||
|
{
|
||||||
|
if (filesize == -1)
|
||||||
|
{
|
||||||
|
FileReader f;
|
||||||
|
|
||||||
|
if (f.OpenFile(_filename))
|
||||||
|
{
|
||||||
|
LumpSize = (int)f.GetLength();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LumpSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LumpSize = filesize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Caches a lump's content and increases the reference counter
|
||||||
|
// For external lumps this reopens the file each time it is accessed
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int FExternalLump::FillCache()
|
||||||
|
{
|
||||||
|
Cache.Resize(LumpSize);
|
||||||
|
FileReader f;
|
||||||
|
|
||||||
|
if (f.OpenFile(Filename))
|
||||||
|
{
|
||||||
|
f.Read(Cache.Data(), LumpSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memset(Cache.Data(), 0, LumpSize);
|
||||||
|
}
|
||||||
|
RefCount = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
148
source/common/resourcefiles/resourcefile.h
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef __RESFILE_H
|
||||||
|
#define __RESFILE_H
|
||||||
|
|
||||||
|
#include "zstring.h"
|
||||||
|
#include "files.h"
|
||||||
|
#include "printf.h"
|
||||||
|
|
||||||
|
enum ELumpFlags
|
||||||
|
{
|
||||||
|
LUMPF_ZIPFILE=1, // contains a full path
|
||||||
|
LUMPF_BLOODCRYPT = 2, // encrypted
|
||||||
|
LUMPF_COMPRESSED = 4, // compressed
|
||||||
|
LUMPF_SEQUENTIAL = 8, // compressed but a sequential reader can be retrieved.
|
||||||
|
};
|
||||||
|
|
||||||
|
class FResourceFile;
|
||||||
|
|
||||||
|
// This holds a compresed Zip entry with all needed info to decompress it.
|
||||||
|
struct FCompressedBuffer
|
||||||
|
{
|
||||||
|
unsigned mSize;
|
||||||
|
unsigned mCompressedSize;
|
||||||
|
int mMethod;
|
||||||
|
int mZipFlags;
|
||||||
|
unsigned mCRC32;
|
||||||
|
char *mBuffer;
|
||||||
|
|
||||||
|
bool Decompress(char *destbuffer);
|
||||||
|
void Clean()
|
||||||
|
{
|
||||||
|
mSize = mCompressedSize = 0;
|
||||||
|
if (mBuffer != nullptr)
|
||||||
|
{
|
||||||
|
delete[] mBuffer;
|
||||||
|
mBuffer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FResourceLump
|
||||||
|
{
|
||||||
|
friend class FResourceFile;
|
||||||
|
|
||||||
|
int LumpSize = 0;
|
||||||
|
int RefCount = 0;
|
||||||
|
int Flags = 0;
|
||||||
|
FString FullName; // Name with extension and path
|
||||||
|
FString BaseName; // Name without extension and path
|
||||||
|
FResourceFile * Owner = nullptr;
|
||||||
|
TArray<uint8_t> Cache;
|
||||||
|
|
||||||
|
FResourceLump() = default;
|
||||||
|
|
||||||
|
virtual ~FResourceLump();
|
||||||
|
virtual FileReader *GetReader();
|
||||||
|
virtual FileReader NewReader();
|
||||||
|
virtual int GetFileOffset() { return -1; }
|
||||||
|
virtual int GetIndexNum() const { return 0; }
|
||||||
|
void LumpNameSetup(FString iname);
|
||||||
|
virtual FCompressedBuffer GetRawData();
|
||||||
|
|
||||||
|
void *CacheLump();
|
||||||
|
int ReleaseCache();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual int FillCache() { return -1; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class FResourceFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FileReader Reader;
|
||||||
|
FString FileName;
|
||||||
|
protected:
|
||||||
|
uint32_t NumLumps;
|
||||||
|
|
||||||
|
FResourceFile(const char *filename);
|
||||||
|
FResourceFile(const char *filename, FileReader &r);
|
||||||
|
|
||||||
|
// for archives that can contain directories
|
||||||
|
void PostProcessArchive(void *lumps, size_t lumpsize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t FirstLump;
|
||||||
|
|
||||||
|
int FilterLumps(FString filtername, void *lumps, size_t lumpsize, uint32_t max);
|
||||||
|
int FilterLumpsByGameType(int gametype, void *lumps, size_t lumpsize, uint32_t max);
|
||||||
|
bool FindPrefixRange(FString filter, void *lumps, size_t lumpsize, uint32_t max, uint32_t &start, uint32_t &end);
|
||||||
|
void JunkLeftoverFilters(void *lumps, size_t lumpsize, uint32_t max);
|
||||||
|
static FResourceFile *DoOpenResourceFile(const char *filename, FileReader &file, bool quiet, bool containeronly);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static FResourceFile *OpenResourceFile(const char *filename, FileReader &file, bool quiet = false, bool containeronly = false);
|
||||||
|
static FResourceFile *OpenResourceFile(const char *filename, bool quiet = false, bool containeronly = false);
|
||||||
|
//static FResourceFile *OpenResourceFileFromLump(int lumpnum, bool quiet = false, bool containeronly = false);
|
||||||
|
static FResourceFile *OpenDirectory(const char *filename, bool quiet = false);
|
||||||
|
virtual ~FResourceFile();
|
||||||
|
// If this FResourceFile represents a directory, the Reader object is not usable so don't return it.
|
||||||
|
FileReader *GetReader() { return Reader.isOpen()? &Reader : nullptr; }
|
||||||
|
uint32_t LumpCount() const { return NumLumps; }
|
||||||
|
uint32_t GetFirstLump() const { return FirstLump; }
|
||||||
|
void SetFirstLump(uint32_t f) { FirstLump = f; }
|
||||||
|
|
||||||
|
|
||||||
|
virtual void FindStrifeTeaserVoices ();
|
||||||
|
virtual bool Open(bool quiet) = 0;
|
||||||
|
virtual FResourceLump *GetLump(int no) = 0;
|
||||||
|
FResourceLump *FindLump(const char *name);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FUncompressedLump : public FResourceLump
|
||||||
|
{
|
||||||
|
int Position;
|
||||||
|
|
||||||
|
virtual FileReader *GetReader();
|
||||||
|
virtual int FillCache();
|
||||||
|
virtual int GetFileOffset() { return Position; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Base class for uncompressed resource files (GRP, PAK and single lumps)
|
||||||
|
class FUncompressedFile : public FResourceFile
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
TArray<FUncompressedLump> Lumps;
|
||||||
|
|
||||||
|
FUncompressedFile(const char *filename);
|
||||||
|
FUncompressedFile(const char *filename, FileReader &r);
|
||||||
|
virtual FResourceLump *GetLump(int no) { return ((unsigned)no < NumLumps)? &Lumps[no] : NULL; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct FExternalLump : public FResourceLump
|
||||||
|
{
|
||||||
|
FString Filename;
|
||||||
|
|
||||||
|
FExternalLump(const char *_filename, int filesize = -1);
|
||||||
|
virtual int FillCache();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
80
source/common/resourcefiles/w_zip.h
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
#ifndef __W_ZIP
|
||||||
|
#define __W_ZIP
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
// With versions of GCC newer than 4.2, it appears it was determined that the
|
||||||
|
// cost of an unaligned pointer on PPC was high enough to add padding to the
|
||||||
|
// end of packed structs. For whatever reason __packed__ and pragma pack are
|
||||||
|
// handled differently in this regard. Note that this only needs to be applied
|
||||||
|
// to types which are used in arrays.
|
||||||
|
#define FORCE_PACKED __attribute__((__packed__))
|
||||||
|
#else
|
||||||
|
#define FORCE_PACKED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#pragma pack(1)
|
||||||
|
// FZipCentralInfo
|
||||||
|
struct FZipEndOfCentralDirectory
|
||||||
|
{
|
||||||
|
uint32_t Magic;
|
||||||
|
uint16_t DiskNumber;
|
||||||
|
uint16_t FirstDisk;
|
||||||
|
uint16_t NumEntries;
|
||||||
|
uint16_t NumEntriesOnAllDisks;
|
||||||
|
uint32_t DirectorySize;
|
||||||
|
uint32_t DirectoryOffset;
|
||||||
|
uint16_t ZipCommentLength;
|
||||||
|
} FORCE_PACKED;
|
||||||
|
|
||||||
|
// FZipFileInfo
|
||||||
|
struct FZipCentralDirectoryInfo
|
||||||
|
{
|
||||||
|
uint32_t Magic;
|
||||||
|
uint8_t VersionMadeBy[2];
|
||||||
|
uint8_t VersionToExtract[2];
|
||||||
|
uint16_t Flags;
|
||||||
|
uint16_t Method;
|
||||||
|
uint16_t ModTime;
|
||||||
|
uint16_t ModDate;
|
||||||
|
uint32_t CRC32;
|
||||||
|
uint32_t CompressedSize;
|
||||||
|
uint32_t UncompressedSize;
|
||||||
|
uint16_t NameLength;
|
||||||
|
uint16_t ExtraLength;
|
||||||
|
uint16_t CommentLength;
|
||||||
|
uint16_t StartingDiskNumber;
|
||||||
|
uint16_t InternalAttributes;
|
||||||
|
uint32_t ExternalAttributes;
|
||||||
|
uint32_t LocalHeaderOffset;
|
||||||
|
// file name and other variable length info follows
|
||||||
|
} FORCE_PACKED;
|
||||||
|
|
||||||
|
// FZipLocalHeader
|
||||||
|
struct FZipLocalFileHeader
|
||||||
|
{
|
||||||
|
uint32_t Magic;
|
||||||
|
uint8_t VersionToExtract[2];
|
||||||
|
uint16_t Flags;
|
||||||
|
uint16_t Method;
|
||||||
|
uint16_t ModTime;
|
||||||
|
uint16_t ModDate;
|
||||||
|
uint32_t CRC32;
|
||||||
|
uint32_t CompressedSize;
|
||||||
|
uint32_t UncompressedSize;
|
||||||
|
uint16_t NameLength;
|
||||||
|
uint16_t ExtraLength;
|
||||||
|
// file name and other variable length info follows
|
||||||
|
} FORCE_PACKED;
|
||||||
|
|
||||||
|
|
||||||
|
#pragma pack()
|
||||||
|
|
||||||
|
#define ZIP_LOCALFILE MAKE_ID('P','K',3,4)
|
||||||
|
#define ZIP_CENTRALFILE MAKE_ID('P','K',1,2)
|
||||||
|
#define ZIP_ENDOFDIR MAKE_ID('P','K',5,6)
|
||||||
|
|
||||||
|
// File header flags.
|
||||||
|
#define ZF_ENCRYPTED 0x1
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,6 +1,14 @@
|
||||||
#ifndef __BASICS_H
|
#ifndef __BASICS_H
|
||||||
#define __BASICS_H
|
#define __BASICS_H
|
||||||
|
|
||||||
|
#ifndef MAKE_ID
|
||||||
|
#ifndef __BIG_ENDIAN__
|
||||||
|
#define MAKE_ID(a,b,c,d) ((uint32_t)((a)|((b)<<8)|((c)<<16)|((d)<<24)))
|
||||||
|
#else
|
||||||
|
#define MAKE_ID(a,b,c,d) ((uint32_t)((d)|((c)<<8)|((b)<<16)|((a)<<24)))
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#define GCCPRINTF(stri,firstargi) __attribute__((format(printf,stri,firstargi)))
|
#define GCCPRINTF(stri,firstargi) __attribute__((format(printf,stri,firstargi)))
|
||||||
#define GCCFORMAT(stri) __attribute__((format(printf,stri,0)))
|
#define GCCFORMAT(stri) __attribute__((format(printf,stri,0)))
|
||||||
|
@ -11,5 +19,9 @@
|
||||||
#define GCCNOWARN
|
#define GCCNOWARN
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
template <typename T, size_t N>
|
||||||
|
char(&_ArraySizeHelper(T(&array)[N]))[N];
|
||||||
|
|
||||||
|
#define countof( array ) (sizeof( _ArraySizeHelper( array ) ))
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,271 +0,0 @@
|
||||||
/*
|
|
||||||
** resourcefile.cpp
|
|
||||||
**
|
|
||||||
** Base classes for resource file management
|
|
||||||
**
|
|
||||||
**---------------------------------------------------------------------------
|
|
||||||
** Copyright 2009 Christoph Oelckers
|
|
||||||
** All rights reserved.
|
|
||||||
**
|
|
||||||
** Redistribution and use in source and binary forms, with or without
|
|
||||||
** modification, are permitted provided that the following conditions
|
|
||||||
** are met:
|
|
||||||
**
|
|
||||||
** 1. Redistributions of source code must retain the above copyright
|
|
||||||
** notice, this list of conditions and the following disclaimer.
|
|
||||||
** 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
** notice, this list of conditions and the following disclaimer in the
|
|
||||||
** documentation and/or other materials provided with the distribution.
|
|
||||||
** 3. The name of the author may not be used to endorse or promote products
|
|
||||||
** derived from this software without specific prior written permission.
|
|
||||||
**
|
|
||||||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
||||||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
||||||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
||||||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
||||||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
||||||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
**---------------------------------------------------------------------------
|
|
||||||
**
|
|
||||||
**
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <zlib.h>
|
|
||||||
#include "resourcefile.h"
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// File reader that reads from a lump's cache
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
class FLumpReader : public MemoryReader
|
|
||||||
{
|
|
||||||
FResourceLump *source;
|
|
||||||
|
|
||||||
public:
|
|
||||||
FLumpReader(FResourceLump *src)
|
|
||||||
: MemoryReader(NULL, src->LumpSize), source(src)
|
|
||||||
{
|
|
||||||
src->CacheLump();
|
|
||||||
bufptr = src->Cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
~FLumpReader()
|
|
||||||
{
|
|
||||||
source->ReleaseCache();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// Base class for resource lumps
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
FResourceLump::~FResourceLump()
|
|
||||||
{
|
|
||||||
if (Cache != NULL && RefCount >= 0)
|
|
||||||
{
|
|
||||||
delete [] Cache;
|
|
||||||
Cache = NULL;
|
|
||||||
}
|
|
||||||
Owner = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// Sets up the lump name information for anything not coming from a WAD file.
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
void FResourceLump::LumpNameSetup(FString iname)
|
|
||||||
{
|
|
||||||
long slash = iname.LastIndexOf('/');
|
|
||||||
FString base = (slash >= 0) ? iname.Mid(slash + 1) : iname;
|
|
||||||
auto dot = base.LastIndexOf('.');
|
|
||||||
if (dot >= 0) base.Truncate(dot);
|
|
||||||
FullName = iname;
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// Returns a file reader to the lump's cache
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
FileReader FResourceLump::NewReader()
|
|
||||||
{
|
|
||||||
return FileReader(new FLumpReader(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// Caches a lump's content and increases the reference counter
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
void *FResourceLump::CacheLump()
|
|
||||||
{
|
|
||||||
if (Cache != NULL)
|
|
||||||
{
|
|
||||||
if (RefCount > 0) RefCount++;
|
|
||||||
}
|
|
||||||
else if (LumpSize > 0)
|
|
||||||
{
|
|
||||||
FillCache();
|
|
||||||
}
|
|
||||||
return Cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// Decrements reference counter and frees lump if counter reaches 0
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
int FResourceLump::ReleaseCache()
|
|
||||||
{
|
|
||||||
if (LumpSize > 0 && RefCount > 0)
|
|
||||||
{
|
|
||||||
if (--RefCount == 0)
|
|
||||||
{
|
|
||||||
delete [] Cache;
|
|
||||||
Cache = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return RefCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// Opens a resource file
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
typedef FResourceFile * (*CheckFunc)(const char *filename, FileReader &file, bool quiet);
|
|
||||||
|
|
||||||
FResourceFile *CheckZip(const char *filename, FileReader &file, bool quiet);
|
|
||||||
|
|
||||||
|
|
||||||
FResourceFile *FResourceFile::OpenResourceFile(const char *filename, FileReader &file, bool quiet, bool containeronly)
|
|
||||||
{
|
|
||||||
return CheckZip(filename, file, quiet);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
FResourceFile *FResourceFile::OpenResourceFile(const char *filename, bool quiet, bool containeronly)
|
|
||||||
{
|
|
||||||
FileReader file;
|
|
||||||
if (!file.OpenFile(filename)) return nullptr;
|
|
||||||
return OpenResourceFile(filename, file, quiet, containeronly);
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// Resource file base class
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
FResourceFile::FResourceFile(const char *filename)
|
|
||||||
: FileName(filename)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
FResourceFile::FResourceFile(const char *filename, FileReader &r)
|
|
||||||
: FResourceFile(filename)
|
|
||||||
{
|
|
||||||
Reader = std::move(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
FResourceFile::~FResourceFile()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
int lumpcmp(const void * a, const void * b)
|
|
||||||
{
|
|
||||||
FResourceLump * rec1 = (FResourceLump *)a;
|
|
||||||
FResourceLump * rec2 = (FResourceLump *)b;
|
|
||||||
|
|
||||||
return rec1->FullName.CompareNoCase(rec2->FullName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// Finds a lump by a given name. Used for savegames
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
FResourceLump *FResourceFile::FindLump(const char *name)
|
|
||||||
{
|
|
||||||
for (unsigned i = 0; i < NumLumps; i++)
|
|
||||||
{
|
|
||||||
FResourceLump *lump = GetLump(i);
|
|
||||||
if (!stricmp(name, lump->FullName))
|
|
||||||
{
|
|
||||||
return lump;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// Caches a lump's content and increases the reference counter
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
FileReader *FUncompressedLump::GetReader()
|
|
||||||
{
|
|
||||||
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
|
||||||
return &Owner->Reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// Caches a lump's content and increases the reference counter
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
int FUncompressedLump::FillCache()
|
|
||||||
{
|
|
||||||
const char * buffer = Owner->Reader.GetBuffer();
|
|
||||||
|
|
||||||
if (buffer != NULL)
|
|
||||||
{
|
|
||||||
// This is an in-memory file so the cache can point directly to the file's data.
|
|
||||||
Cache = const_cast<char*>(buffer) + Position;
|
|
||||||
RefCount = -1;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Owner->Reader.Seek(Position, FileReader::SeekSet);
|
|
||||||
Cache = new char[LumpSize];
|
|
||||||
Owner->Reader.Read(Cache, LumpSize);
|
|
||||||
RefCount = 1;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================
|
|
||||||
//
|
|
||||||
// Base class for uncompressed resource files
|
|
||||||
//
|
|
||||||
//==========================================================================
|
|
||||||
|
|
||||||
FUncompressedFile::FUncompressedFile(const char *filename)
|
|
||||||
: FResourceFile(filename)
|
|
||||||
{}
|
|
||||||
|
|
||||||
FUncompressedFile::FUncompressedFile(const char *filename, FileReader &r)
|
|
||||||
: FResourceFile(filename, r)
|
|
||||||
{}
|
|
||||||
|
|
||||||
|
|
489
source/zmusic/i_music.cpp
Normal file
|
@ -0,0 +1,489 @@
|
||||||
|
/*
|
||||||
|
** i_music.cpp
|
||||||
|
** Plays music
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 1998-2010 Randy Heit
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
#include "m_argv.h"
|
||||||
|
#include "w_wad.h"
|
||||||
|
#include "c_dispatch.h"
|
||||||
|
#include "templates.h"
|
||||||
|
#include "stats.h"
|
||||||
|
#include "c_cvars.h"
|
||||||
|
#include "c_console.h"
|
||||||
|
#include "vm.h"
|
||||||
|
#include "v_text.h"
|
||||||
|
#include "i_sound.h"
|
||||||
|
#include "i_soundfont.h"
|
||||||
|
#include "s_music.h"
|
||||||
|
#include "doomstat.h"
|
||||||
|
#include "zmusic/zmusic.h"
|
||||||
|
#include "zmusic/musinfo.h"
|
||||||
|
#include "streamsources/streamsource.h"
|
||||||
|
#include "filereadermusicinterface.h"
|
||||||
|
#include "../libraries/zmusic/midisources/midisource.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void I_InitSoundFonts();
|
||||||
|
|
||||||
|
EXTERN_CVAR (Int, snd_samplerate)
|
||||||
|
EXTERN_CVAR (Int, snd_mididevice)
|
||||||
|
|
||||||
|
static bool ungzip(uint8_t *data, int size, std::vector<uint8_t> &newdata);
|
||||||
|
|
||||||
|
int nomusic = 0;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
void I_InitMusicWin32();
|
||||||
|
|
||||||
|
#include "musicformats/win32/i_cd.h"
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// CVAR: cd_drive
|
||||||
|
//
|
||||||
|
// Which drive (letter) to use for CD audio. If not a valid drive letter,
|
||||||
|
// let the operating system decide for us.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
EXTERN_CVAR(Bool, cd_enabled);
|
||||||
|
|
||||||
|
CUSTOM_CVAR(String, cd_drive, "", CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG)
|
||||||
|
{
|
||||||
|
if (cd_enabled && !Args->CheckParm("-nocdaudio")) CD_Enable(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// CVAR: cd_enabled
|
||||||
|
//
|
||||||
|
// Use the CD device? Can be overridden with -nocdaudio on the command line
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, cd_enabled, true, CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG)
|
||||||
|
{
|
||||||
|
if (self && !Args->CheckParm("-nocdaudio"))
|
||||||
|
CD_Enable(cd_drive);
|
||||||
|
else
|
||||||
|
CD_Enable(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// CVAR snd_musicvolume
|
||||||
|
//
|
||||||
|
// Maximum volume of MOD/stream music.
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CUSTOM_CVAR (Float, snd_musicvolume, 0.5f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||||
|
{
|
||||||
|
if (self < 0.f)
|
||||||
|
self = 0.f;
|
||||||
|
else if (self > 1.f)
|
||||||
|
self = 1.f;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Set general music volume.
|
||||||
|
ChangeMusicSetting(ZMusic::snd_musicvolume, nullptr, self);
|
||||||
|
if (GSnd != nullptr)
|
||||||
|
{
|
||||||
|
GSnd->SetMusicVolume(clamp<float>(self * relative_volume * snd_mastervolume, 0, 1));
|
||||||
|
}
|
||||||
|
// For music not implemented through the digital sound system,
|
||||||
|
// let them know about the change.
|
||||||
|
if (mus_playing.handle != nullptr)
|
||||||
|
{
|
||||||
|
mus_playing.handle->MusicVolumeChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // If the music was stopped because volume was 0, start it now.
|
||||||
|
S_RestartMusic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Callbacks for the music system.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static void tim_printfunc(int type, int verbosity_level, const char* fmt, ...)
|
||||||
|
{
|
||||||
|
if (verbosity_level >= 3/*Timidity::VERB_DEBUG*/) return; // Don't waste time on diagnostics.
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
FString msg;
|
||||||
|
msg.VFormat(fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case 2:// Timidity::CMSG_ERROR:
|
||||||
|
Printf(TEXTCOLOR_RED "%s\n", msg.GetChars());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1://Timidity::CMSG_WARNING:
|
||||||
|
Printf(TEXTCOLOR_YELLOW "%s\n", msg.GetChars());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0://Timidity::CMSG_INFO:
|
||||||
|
DPrintf(DMSG_SPAMMY, "%s\n", msg.GetChars());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wm_printfunc(const char* wmfmt, va_list args)
|
||||||
|
{
|
||||||
|
Printf(TEXTCOLOR_RED);
|
||||||
|
VPrintf(PRINT_HIGH, wmfmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// other callbacks
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static short* dumb_decode_vorbis_(int outlen, const void* oggstream, int sizebytes)
|
||||||
|
{
|
||||||
|
return GSnd->DecodeSample(outlen, oggstream, sizebytes, CODEC_Vorbis);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string mus_NicePath(const char* str)
|
||||||
|
{
|
||||||
|
FString strv = NicePath(str);
|
||||||
|
return strv.GetChars();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* mus_pathToSoundFont(const char* sfname, int type)
|
||||||
|
{
|
||||||
|
auto info = sfmanager.FindSoundFont(sfname, type);
|
||||||
|
return info ? info->mFilename.GetChars() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MusicIO::SoundFontReaderInterface* mus_openSoundFont(const char* sfname, int type)
|
||||||
|
{
|
||||||
|
return sfmanager.OpenSoundFont(sfname, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Pass some basic working data to the music backend
|
||||||
|
// We do this once at startup for everything available.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static void SetupGenMidi()
|
||||||
|
{
|
||||||
|
// The OPL renderer should not care about where this comes from.
|
||||||
|
// Note: No I_Error here - this needs to be consistent with the rest of the music code.
|
||||||
|
auto lump = Wads.CheckNumForName("GENMIDI", ns_global);
|
||||||
|
if (lump < 0)
|
||||||
|
{
|
||||||
|
Printf("No GENMIDI lump found. OPL playback not available.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto data = Wads.OpenLumpReader(lump);
|
||||||
|
|
||||||
|
auto genmidi = data.Read();
|
||||||
|
if (genmidi.Size() < 8 + 175 * 36 || memcmp(genmidi.Data(), "#OPL_II#", 8)) return;
|
||||||
|
ZMusic_SetGenMidi(genmidi.Data()+8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetupWgOpn()
|
||||||
|
{
|
||||||
|
int lump = Wads.CheckNumForFullName("xg.wopn");
|
||||||
|
if (lump < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FMemLump data = Wads.ReadLump(lump);
|
||||||
|
ZMusic_SetWgOpn(data.GetMem(), (uint32_t)data.GetSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetupDMXGUS()
|
||||||
|
{
|
||||||
|
int lump = Wads.CheckNumForFullName("DMXGUS");
|
||||||
|
if (lump < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FMemLump data = Wads.ReadLump(lump);
|
||||||
|
ZMusic_SetDmxGus(data.GetMem(), (uint32_t)data.GetSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void I_InitMusic (void)
|
||||||
|
{
|
||||||
|
I_InitSoundFonts();
|
||||||
|
|
||||||
|
snd_musicvolume.Callback ();
|
||||||
|
|
||||||
|
nomusic = !!Args->CheckParm("-nomusic") || !!Args->CheckParm("-nosound");
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
I_InitMusicWin32 ();
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
Callbacks callbacks;
|
||||||
|
|
||||||
|
callbacks.Fluid_MessageFunc = Printf;
|
||||||
|
callbacks.GUS_MessageFunc = callbacks.Timidity_Messagefunc = tim_printfunc;
|
||||||
|
callbacks.WildMidi_MessageFunc = wm_printfunc;
|
||||||
|
callbacks.NicePath = mus_NicePath;
|
||||||
|
callbacks.PathForSoundfont = mus_pathToSoundFont;
|
||||||
|
callbacks.OpenSoundFont = mus_openSoundFont;
|
||||||
|
callbacks.DumbVorbisDecode = dumb_decode_vorbis_;
|
||||||
|
|
||||||
|
ZMusic_SetCallbacks(&callbacks);
|
||||||
|
SetupGenMidi();
|
||||||
|
SetupDMXGUS();
|
||||||
|
SetupWgOpn();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void I_SetRelativeVolume(float vol)
|
||||||
|
{
|
||||||
|
relative_volume = (float)vol;
|
||||||
|
ChangeMusicSetting(ZMusic::relative_volume, nullptr, (float)vol);
|
||||||
|
snd_musicvolume.Callback();
|
||||||
|
}
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Sets relative music volume. Takes $musicvolume in SNDINFO into consideration
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void I_SetMusicVolume (double factor)
|
||||||
|
{
|
||||||
|
factor = clamp(factor, 0., 2.0);
|
||||||
|
I_SetRelativeVolume((float)factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// test a relative music volume
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CCMD(testmusicvol)
|
||||||
|
{
|
||||||
|
if (argv.argc() > 1)
|
||||||
|
{
|
||||||
|
I_SetRelativeVolume((float)strtod(argv[1], nullptr));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Printf("Current relative volume is %1.2f\n", relative_volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// STAT music
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
ADD_STAT(music)
|
||||||
|
{
|
||||||
|
if (mus_playing.handle != nullptr)
|
||||||
|
{
|
||||||
|
return FString(mus_playing.handle->GetStats().c_str());
|
||||||
|
}
|
||||||
|
return "No song playing";
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Common loader for the dumpers.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static MIDISource *GetMIDISource(const char *fn)
|
||||||
|
{
|
||||||
|
FString src = fn;
|
||||||
|
if (src.Compare("*") == 0) src = mus_playing.name;
|
||||||
|
|
||||||
|
auto lump = Wads.CheckNumForName(src, ns_music);
|
||||||
|
if (lump < 0) lump = Wads.CheckNumForFullName(src);
|
||||||
|
if (lump < 0)
|
||||||
|
{
|
||||||
|
Printf("Cannot find MIDI lump %s.\n", src.GetChars());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto wlump = Wads.OpenLumpReader(lump);
|
||||||
|
|
||||||
|
uint32_t id[32 / 4];
|
||||||
|
|
||||||
|
if (wlump.Read(id, 32) != 32 || wlump.Seek(-32, FileReader::SeekCur) != 0)
|
||||||
|
{
|
||||||
|
Printf("Unable to read lump %s\n", src.GetChars());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto type = IdentifyMIDIType(id, 32);
|
||||||
|
if (type == MIDI_NOTMIDI)
|
||||||
|
{
|
||||||
|
Printf("%s is not MIDI-based.\n", src.GetChars());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = wlump.Read();
|
||||||
|
auto source = CreateMIDISource(data.Data(), data.Size(), type);
|
||||||
|
|
||||||
|
if (source == nullptr)
|
||||||
|
{
|
||||||
|
Printf("%s is not MIDI-based.\n", src.GetChars());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// CCMD writewave
|
||||||
|
//
|
||||||
|
// If the current song can be represented as a waveform, dump it to
|
||||||
|
// the specified file on disk. The sample rate parameter is merely a
|
||||||
|
// suggestion, and the dumper is free to ignore it.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
UNSAFE_CCMD (writewave)
|
||||||
|
{
|
||||||
|
if (argv.argc() >= 3 && argv.argc() <= 7)
|
||||||
|
{
|
||||||
|
auto source = GetMIDISource(argv[1]);
|
||||||
|
if (source == nullptr) return;
|
||||||
|
|
||||||
|
EMidiDevice dev = MDEV_DEFAULT;
|
||||||
|
|
||||||
|
if (argv.argc() >= 6)
|
||||||
|
{
|
||||||
|
if (!stricmp(argv[5], "WildMidi")) dev = MDEV_WILDMIDI;
|
||||||
|
else if (!stricmp(argv[5], "GUS")) dev = MDEV_GUS;
|
||||||
|
else if (!stricmp(argv[5], "Timidity") || !stricmp(argv[5], "Timidity++")) dev = MDEV_TIMIDITY;
|
||||||
|
else if (!stricmp(argv[5], "FluidSynth")) dev = MDEV_FLUIDSYNTH;
|
||||||
|
else if (!stricmp(argv[5], "OPL")) dev = MDEV_OPL;
|
||||||
|
else if (!stricmp(argv[5], "OPN")) dev = MDEV_OPN;
|
||||||
|
else if (!stricmp(argv[5], "ADL")) dev = MDEV_ADL;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Printf("%s: Unknown MIDI device\n", argv[5]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We must stop the currently playing music to avoid interference between two synths.
|
||||||
|
auto savedsong = mus_playing;
|
||||||
|
S_StopMusic(true);
|
||||||
|
if (dev == MDEV_DEFAULT && snd_mididevice >= 0) dev = MDEV_FLUIDSYNTH; // The Windows system synth cannot dump a wave.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MIDIDumpWave(source, dev, argv.argc() < 6 ? nullptr : argv[6], argv[2], argv.argc() < 4 ? 0 : (int)strtol(argv[3], nullptr, 10), argv.argc() < 5 ? 0 : (int)strtol(argv[4], nullptr, 10));
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& err)
|
||||||
|
{
|
||||||
|
Printf("MIDI dump failed: %s\n", err.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
S_ChangeMusic(savedsong.name, savedsong.baseorder, savedsong.loop, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Printf ("Usage: writewave <midi> <filename> [subsong] [sample rate] [synth] [soundfont]\n"
|
||||||
|
" - use '*' as song name to dump the currently playing song\n"
|
||||||
|
" - use 0 for subsong and sample rate to play the default\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// CCMD writemidi
|
||||||
|
//
|
||||||
|
// Writes a given MIDI song to disk. This does not affect playback anymore,
|
||||||
|
// like older versions did.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
UNSAFE_CCMD(writemidi)
|
||||||
|
{
|
||||||
|
if (argv.argc() != 3)
|
||||||
|
{
|
||||||
|
Printf("Usage: writemidi <midisong> <filename> - use '*' as song name to dump the currently playing song\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto source = GetMIDISource(argv[1]);
|
||||||
|
if (source == nullptr) return;
|
||||||
|
|
||||||
|
std::vector<uint8_t> midi;
|
||||||
|
bool success;
|
||||||
|
|
||||||
|
source->CreateSMF(midi, 1);
|
||||||
|
auto f = FileWriter::Open(argv[2]);
|
||||||
|
if (f == nullptr)
|
||||||
|
{
|
||||||
|
Printf("Could not open %s.\n", argv[2]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
success = (f->Write(&midi[0], midi.size()) == midi.size());
|
||||||
|
delete f;
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
Printf("Could not write to music file %s.\n", argv[2]);
|
||||||
|
}
|
||||||
|
}
|
53
source/zmusic/i_music.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
** i_music.h
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 1998-2006 Randy Heit
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __I_MUSIC_H__
|
||||||
|
#define __I_MUSIC_H__
|
||||||
|
|
||||||
|
class FileReader;
|
||||||
|
struct FOptionValues;
|
||||||
|
|
||||||
|
//
|
||||||
|
// MUSIC I/O
|
||||||
|
//
|
||||||
|
void I_InitMusic ();
|
||||||
|
void I_BuildMIDIMenuList (FOptionValues *);
|
||||||
|
|
||||||
|
// Volume.
|
||||||
|
void I_SetRelativeVolume(float);
|
||||||
|
void I_SetMusicVolume (double volume);
|
||||||
|
|
||||||
|
|
||||||
|
extern int nomusic;
|
||||||
|
|
||||||
|
#endif //__I_MUSIC_H__
|
546
source/zmusic/i_soundfont.cpp
Normal file
|
@ -0,0 +1,546 @@
|
||||||
|
/*
|
||||||
|
** i_soundfont.cpp
|
||||||
|
** The sound font manager for the MIDI synths
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 2018 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "i_soundfont.h"
|
||||||
|
#include "i_soundinternal.h"
|
||||||
|
#include "cmdlib.h"
|
||||||
|
#include "i_system.h"
|
||||||
|
#include "gameconfigfile.h"
|
||||||
|
#include "filereadermusicinterface.h"
|
||||||
|
#include "zmusic/zmusic.h"
|
||||||
|
#include "resourcefiles/resourcefile.h"
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FSoundFontManager sfmanager;
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// returns both a file reader and the full name of the looked up file
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
std::pair<FileReader, FString> FSoundFontReader::LookupFile(const char *name)
|
||||||
|
{
|
||||||
|
if (!IsAbsPath(name))
|
||||||
|
{
|
||||||
|
for(int i = mPaths.Size()-1; i>=0; i--)
|
||||||
|
{
|
||||||
|
FString fullname = mPaths[i] + name;
|
||||||
|
auto fr = OpenFile(fullname);
|
||||||
|
if (fr.isOpen()) return std::make_pair(std::move(fr), fullname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto fr = OpenFile(name);
|
||||||
|
if (!fr.isOpen()) name = "";
|
||||||
|
return std::make_pair(std::move(fr), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// This adds a directory to the path list
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FSoundFontReader::AddPath(const char *strp)
|
||||||
|
{
|
||||||
|
if (*strp == 0) return;
|
||||||
|
if (!mAllowAbsolutePaths && IsAbsPath(strp)) return; // of no use so we may just discard it right away
|
||||||
|
int i = 0;
|
||||||
|
FString str = strp;
|
||||||
|
FixPathSeperator(str);
|
||||||
|
if (str.Back() != '/') str += '/'; // always let it end with a slash.
|
||||||
|
for (auto &s : mPaths)
|
||||||
|
{
|
||||||
|
if (pathcmp(s.GetChars(), str) == 0)
|
||||||
|
{
|
||||||
|
// move string to the back.
|
||||||
|
mPaths.Delete(i);
|
||||||
|
mPaths.Push(str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
mPaths.Push(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
int FSoundFontReader::pathcmp(const char *p1, const char *p2)
|
||||||
|
{
|
||||||
|
return mCaseSensitivePaths? strcmp(p1, p2) : stricmp(p1, p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FileReader FSoundFontReader::Open(const char *name, std::string& filename)
|
||||||
|
{
|
||||||
|
FileReader fr;
|
||||||
|
if (name == nullptr)
|
||||||
|
{
|
||||||
|
fr = OpenMainConfigFile();
|
||||||
|
filename = MainConfigFileName();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto res = LookupFile(name);
|
||||||
|
fr = std::move(res.first);
|
||||||
|
filename = res.second;
|
||||||
|
}
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
MusicIO::FileInterface* FSoundFontReader::open_interface(const char* name)
|
||||||
|
{
|
||||||
|
std::string filename;
|
||||||
|
|
||||||
|
FileReader fr = Open(name, filename);
|
||||||
|
if (!fr.isOpen()) return nullptr;
|
||||||
|
auto fri = new FileReaderMusicInterface(fr);
|
||||||
|
fri->filename = std::move(filename);
|
||||||
|
return fri;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// The file interface for the backend
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
struct MusicIO::FileInterface* FSoundFontReader::open_file(const char* name)
|
||||||
|
{
|
||||||
|
return open_interface(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Note that the file type has already been checked
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FSF2Reader::FSF2Reader(const char *fn)
|
||||||
|
{
|
||||||
|
mMainConfigForSF2.Format("soundfont \"%s\"\n", fn);
|
||||||
|
mFilename = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FileReader FSF2Reader::OpenMainConfigFile()
|
||||||
|
{
|
||||||
|
FileReader fr;
|
||||||
|
if (mMainConfigForSF2.IsNotEmpty())
|
||||||
|
{
|
||||||
|
fr.OpenMemory(mMainConfigForSF2.GetChars(), mMainConfigForSF2.Len());
|
||||||
|
}
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReader FSF2Reader::OpenFile(const char *name)
|
||||||
|
{
|
||||||
|
FileReader fr;
|
||||||
|
if (mFilename.CompareNoCase(name) == 0)
|
||||||
|
{
|
||||||
|
fr.OpenFile(name);
|
||||||
|
}
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FZipPatReader::FZipPatReader(const char *filename)
|
||||||
|
{
|
||||||
|
resf = FResourceFile::OpenResourceFile(filename, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
FZipPatReader::~FZipPatReader()
|
||||||
|
{
|
||||||
|
if (resf != nullptr) delete resf;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReader FZipPatReader::OpenMainConfigFile()
|
||||||
|
{
|
||||||
|
return OpenFile("timidity.cfg");
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReader FZipPatReader::OpenFile(const char *name)
|
||||||
|
{
|
||||||
|
FileReader fr;
|
||||||
|
if (resf != nullptr)
|
||||||
|
{
|
||||||
|
auto lump = resf->FindLump(name);
|
||||||
|
if (lump != nullptr)
|
||||||
|
{
|
||||||
|
return lump->NewReader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FPatchSetReader::FPatchSetReader(const char *filename)
|
||||||
|
{
|
||||||
|
#ifndef _WIN32
|
||||||
|
mCaseSensitivePaths = true;
|
||||||
|
const char *paths[] = {
|
||||||
|
"/usr/local/lib/timidity",
|
||||||
|
"/etc/timidity",
|
||||||
|
"/etc"
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
const char *paths[] = {
|
||||||
|
"C:/TIMIDITY",
|
||||||
|
"/TIMIDITY",
|
||||||
|
progdir
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
mAllowAbsolutePaths = true;
|
||||||
|
FileReader fr;
|
||||||
|
if (fr.OpenFile(filename))
|
||||||
|
{
|
||||||
|
mFullPathToConfig = filename;
|
||||||
|
}
|
||||||
|
else if (!IsAbsPath(filename))
|
||||||
|
{
|
||||||
|
for(auto c : paths)
|
||||||
|
{
|
||||||
|
FStringf fullname("%s/%s", c, filename);
|
||||||
|
if (fr.OpenFile(fullname))
|
||||||
|
{
|
||||||
|
mFullPathToConfig = fullname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mFullPathToConfig.Len() > 0)
|
||||||
|
{
|
||||||
|
FixPathSeperator(mFullPathToConfig);
|
||||||
|
mBasePath = ExtractFilePath(mFullPathToConfig);
|
||||||
|
if (mBasePath.Len() > 0 && mBasePath.Back() != '/') mBasePath += '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FileReader FPatchSetReader::OpenMainConfigFile()
|
||||||
|
{
|
||||||
|
FileReader fr;
|
||||||
|
fr.OpenFile(mFullPathToConfig);
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReader FPatchSetReader::OpenFile(const char *name)
|
||||||
|
{
|
||||||
|
FString path;
|
||||||
|
if (IsAbsPath(name)) path = name;
|
||||||
|
else path = mBasePath + name;
|
||||||
|
FileReader fr;
|
||||||
|
fr.OpenFile(path);
|
||||||
|
return fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FLumpPatchSetReader::FLumpPatchSetReader(const char *filename)
|
||||||
|
{
|
||||||
|
mLumpIndex = Wads.CheckNumForFullName(filename);
|
||||||
|
|
||||||
|
mBasePath = filename;
|
||||||
|
FixPathSeperator(mBasePath);
|
||||||
|
mBasePath = ExtractFilePath(mBasePath);
|
||||||
|
if (mBasePath.Len() > 0 && mBasePath.Back() != '/') mBasePath += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReader FLumpPatchSetReader::OpenMainConfigFile()
|
||||||
|
{
|
||||||
|
return Wads.ReopenLumpReader(mLumpIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReader FLumpPatchSetReader::OpenFile(const char *name)
|
||||||
|
{
|
||||||
|
FString path;
|
||||||
|
if (IsAbsPath(name)) return FileReader(); // no absolute paths in the lump directory.
|
||||||
|
path = mBasePath + name;
|
||||||
|
auto index = Wads.CheckNumForFullName(path);
|
||||||
|
if (index < 0) return FileReader();
|
||||||
|
return Wads.ReopenLumpReader(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// collects everything out of the soundfonts directory.
|
||||||
|
// This may either be .sf2 files or zipped GUS patch sets with a
|
||||||
|
// 'timidity.cfg' in the root directory.
|
||||||
|
// Other compression types are not supported, in particular not 7z because
|
||||||
|
// due to the solid nature of its archives would be too slow.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FSoundFontManager::ProcessOneFile(const FString &fn)
|
||||||
|
{
|
||||||
|
auto fb = ExtractFileBase(fn, false);
|
||||||
|
auto fbe = ExtractFileBase(fn, true);
|
||||||
|
for (auto &sfi : soundfonts)
|
||||||
|
{
|
||||||
|
// We already got a soundfont with this name. Do not add again.
|
||||||
|
if (!sfi.mName.CompareNoCase(fb)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReader fr;
|
||||||
|
if (fr.OpenFile(fn))
|
||||||
|
{
|
||||||
|
// Try to identify. We only accept .sf2 and .zip by content. All other archives are intentionally ignored.
|
||||||
|
char head[16] = { 0};
|
||||||
|
fr.Read(head, 16);
|
||||||
|
if (!memcmp(head, "RIFF", 4) && !memcmp(head+8, "sfbkLIST", 8))
|
||||||
|
{
|
||||||
|
FSoundFontInfo sft = { fb, fbe, fn, SF_SF2 };
|
||||||
|
soundfonts.Push(sft);
|
||||||
|
}
|
||||||
|
if (!memcmp(head, "WOPL3-BANK\0", 11))
|
||||||
|
{
|
||||||
|
FSoundFontInfo sft = { fb, fbe, fn, SF_WOPL };
|
||||||
|
soundfonts.Push(sft);
|
||||||
|
}
|
||||||
|
if (!memcmp(head, "WOPN2-BANK\0", 11) || !memcmp(head, "WOPN2-B2NK\0", 11))
|
||||||
|
{
|
||||||
|
FSoundFontInfo sft = { fb, fbe, fn, SF_WOPN };
|
||||||
|
soundfonts.Push(sft);
|
||||||
|
}
|
||||||
|
else if (!memcmp(head, "PK", 2))
|
||||||
|
{
|
||||||
|
auto zip = FResourceFile::OpenResourceFile(fn, true);
|
||||||
|
if (zip != nullptr)
|
||||||
|
{
|
||||||
|
if (zip->LumpCount() > 1) // Anything with just one lump cannot possibly be a packed GUS patch set so skip it right away and simplify the lookup code
|
||||||
|
{
|
||||||
|
auto zipl = zip->FindLump("timidity.cfg");
|
||||||
|
if (zipl != nullptr)
|
||||||
|
{
|
||||||
|
// It seems like this is what we are looking for
|
||||||
|
FSoundFontInfo sft = { fb, fbe, fn, SF_GUS };
|
||||||
|
soundfonts.Push(sft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete zip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void FSoundFontManager::CollectSoundfonts()
|
||||||
|
{
|
||||||
|
findstate_t c_file;
|
||||||
|
void *file;
|
||||||
|
|
||||||
|
if (GameConfig != NULL && GameConfig->SetSection ("SoundfontSearch.Directories"))
|
||||||
|
{
|
||||||
|
const char *key;
|
||||||
|
const char *value;
|
||||||
|
|
||||||
|
while (GameConfig->NextInSection (key, value))
|
||||||
|
{
|
||||||
|
if (stricmp (key, "Path") == 0)
|
||||||
|
{
|
||||||
|
FString dir;
|
||||||
|
|
||||||
|
dir = NicePath(value);
|
||||||
|
FixPathSeperator(dir);
|
||||||
|
if (dir.IsNotEmpty())
|
||||||
|
{
|
||||||
|
if (dir.Back() != '/') dir += '/';
|
||||||
|
FString mask = dir + '*';
|
||||||
|
if ((file = I_FindFirst(mask, &c_file)) != ((void *)(-1)))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (!(I_FindAttr(&c_file) & FA_DIREC))
|
||||||
|
{
|
||||||
|
FStringf name("%s%s", dir.GetChars(), I_FindName(&c_file));
|
||||||
|
ProcessOneFile(name);
|
||||||
|
}
|
||||||
|
} while (I_FindNext(file, &c_file) == 0);
|
||||||
|
I_FindClose(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (soundfonts.Size() == 0)
|
||||||
|
{
|
||||||
|
ProcessOneFile(NicePath("$PROGDIR/soundfonts/gzdoom.sf2"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
const FSoundFontInfo *FSoundFontManager::FindSoundFont(const char *name, int allowed) const
|
||||||
|
{
|
||||||
|
for(auto &sfi : soundfonts)
|
||||||
|
{
|
||||||
|
// an empty name will pick the first one in a compatible format.
|
||||||
|
if (allowed & sfi.type && (name == nullptr || *name == 0 || !sfi.mName.CompareNoCase(name) || !sfi.mNameExt.CompareNoCase(name)))
|
||||||
|
{
|
||||||
|
return &sfi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We did not find what we were looking for. Let's just return the first valid item that works with the given device.
|
||||||
|
for (auto &sfi : soundfonts)
|
||||||
|
{
|
||||||
|
if (allowed & sfi.type)
|
||||||
|
{
|
||||||
|
return &sfi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
FSoundFontReader *FSoundFontManager::OpenSoundFont(const char *name, int allowed)
|
||||||
|
{
|
||||||
|
|
||||||
|
// First check if the given name is inside the loaded resources.
|
||||||
|
// To avoid clashes this will only be done if the name has the '.cfg' extension.
|
||||||
|
// Sound fonts cannot be loaded this way.
|
||||||
|
if (name != nullptr)
|
||||||
|
{
|
||||||
|
const char *p = name + strlen(name) - 4;
|
||||||
|
if (p > name && !stricmp(p, ".cfg") && Wads.CheckNumForFullName(name) >= 0)
|
||||||
|
{
|
||||||
|
return new FLumpPatchSetReader(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sfi = FindSoundFont(name, allowed);
|
||||||
|
if (sfi != nullptr)
|
||||||
|
{
|
||||||
|
if (sfi->type == SF_SF2) return new FSF2Reader(sfi->mFilename);
|
||||||
|
else return new FZipPatReader(sfi->mFilename);
|
||||||
|
}
|
||||||
|
// The sound font collection did not yield any good results.
|
||||||
|
// Next check if the file is a .sf file
|
||||||
|
if (allowed & SF_SF2)
|
||||||
|
{
|
||||||
|
FileReader fr;
|
||||||
|
if (fr.OpenFile(name))
|
||||||
|
{
|
||||||
|
char head[16] = { 0};
|
||||||
|
fr.Read(head, 16);
|
||||||
|
fr.Close();
|
||||||
|
if (!memcmp(head, "RIFF", 4) && !memcmp(head+8, "sfbkLIST", 8))
|
||||||
|
{
|
||||||
|
return new FSF2Reader(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allowed & SF_GUS)
|
||||||
|
{
|
||||||
|
FileReader fr;
|
||||||
|
if (fr.OpenFile(name))
|
||||||
|
{
|
||||||
|
char head[16] = { 0 };
|
||||||
|
fr.Read(head, 2);
|
||||||
|
fr.Close();
|
||||||
|
if (!memcmp(head, "PK", 2)) // The only reason for this check is to block non-Zips. The actual validation will be done by FZipFile.
|
||||||
|
{
|
||||||
|
auto r = new FZipPatReader(name);
|
||||||
|
if (r->isOk()) return r;
|
||||||
|
delete r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config files are only accepted if they are named '.cfg', because they are impossible to validate.
|
||||||
|
const char *p = name + strlen(name) - 4;
|
||||||
|
if (p > name && !stricmp(p, ".cfg") && FileExists(name))
|
||||||
|
{
|
||||||
|
return new FPatchSetReader(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void I_InitSoundFonts()
|
||||||
|
{
|
||||||
|
sfmanager.CollectSoundfonts();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
165
source/zmusic/i_soundfont.h
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "doomtype.h"
|
||||||
|
#include "w_wad.h"
|
||||||
|
#include "files.h"
|
||||||
|
#include "filereadermusicinterface.h"
|
||||||
|
|
||||||
|
struct FSoundFontInfo
|
||||||
|
{
|
||||||
|
FString mName; // This is what the sounfont is identified with. It's the extension-less base file name
|
||||||
|
FString mNameExt; // Same with extension. Used for comparing with input names so they can be done with or without extension.
|
||||||
|
FString mFilename; // Full path to the backing file - this is needed by FluidSynth to load the sound font.
|
||||||
|
int type;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FSoundFontReader : public MusicIO::SoundFontReaderInterface
|
||||||
|
// Yes, it's 3 copies of essentially the same interface, but since we want to keep the 3 renderers as isolated modules we have to pull in their own implementations here.
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
// This is only doable for loose config files that get set as sound fonts. All other cases read from a contained environment where this does not apply.
|
||||||
|
bool mAllowAbsolutePaths = false;
|
||||||
|
// This has only meaning if being run on a platform with a case sensitive file system and loose files.
|
||||||
|
// When reading from an archive it will always be case insensitive, just like the lump manager.
|
||||||
|
bool mCaseSensitivePaths = false;
|
||||||
|
TArray<FString> mPaths;
|
||||||
|
|
||||||
|
|
||||||
|
int pathcmp(const char *p1, const char *p2);
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual ~FSoundFontReader() {}
|
||||||
|
virtual FileReader OpenMainConfigFile() = 0; // this is special because it needs to be synthesized for .sf files and set some restrictions for patch sets
|
||||||
|
virtual FString MainConfigFileName()
|
||||||
|
{
|
||||||
|
return basePath() + "timidity.cfg";
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual FileReader OpenFile(const char *name) = 0;
|
||||||
|
std::pair<FileReader , FString> LookupFile(const char *name);
|
||||||
|
void AddPath(const char *str);
|
||||||
|
virtual FString basePath() const
|
||||||
|
{
|
||||||
|
return ""; // archived patch sets do not use paths
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual FileReader Open(const char* name, std::string &filename);
|
||||||
|
|
||||||
|
// Timidity++ interface
|
||||||
|
struct MusicIO::FileInterface* open_file(const char* name) override;
|
||||||
|
void add_search_path(const char* name) override
|
||||||
|
{
|
||||||
|
return AddPath(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
MusicIO::FileInterface* open_interface(const char* name);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FSF2Reader : public FSoundFontReader
|
||||||
|
{
|
||||||
|
FString mMainConfigForSF2;
|
||||||
|
FString mFilename;
|
||||||
|
public:
|
||||||
|
FSF2Reader(const char *filename);
|
||||||
|
virtual FileReader OpenMainConfigFile() override;
|
||||||
|
virtual FileReader OpenFile(const char *name) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FZipPatReader : public FSoundFontReader
|
||||||
|
{
|
||||||
|
FResourceFile *resf;
|
||||||
|
public:
|
||||||
|
FZipPatReader(const char *filename);
|
||||||
|
~FZipPatReader();
|
||||||
|
virtual FileReader OpenMainConfigFile() override;
|
||||||
|
virtual FileReader OpenFile(const char *name) override;
|
||||||
|
bool isOk() { return resf != nullptr; }
|
||||||
|
};
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FLumpPatchSetReader : public FSoundFontReader
|
||||||
|
{
|
||||||
|
int mLumpIndex;;
|
||||||
|
FString mBasePath;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FLumpPatchSetReader(const char *filename);
|
||||||
|
virtual FileReader OpenMainConfigFile() override;
|
||||||
|
virtual FileReader OpenFile(const char *name) override;
|
||||||
|
virtual FString basePath() const override
|
||||||
|
{
|
||||||
|
return mBasePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FPatchSetReader : public FSoundFontReader
|
||||||
|
{
|
||||||
|
FString mBasePath;
|
||||||
|
FString mFullPathToConfig;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FPatchSetReader(FileReader &reader);
|
||||||
|
FPatchSetReader(const char *filename);
|
||||||
|
virtual FileReader OpenMainConfigFile() override;
|
||||||
|
virtual FileReader OpenFile(const char *name) override;
|
||||||
|
virtual FString basePath() const override
|
||||||
|
{
|
||||||
|
return mBasePath;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
class FSoundFontManager
|
||||||
|
{
|
||||||
|
TArray<FSoundFontInfo> soundfonts;
|
||||||
|
|
||||||
|
void ProcessOneFile(const FString & fn);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void CollectSoundfonts();
|
||||||
|
const FSoundFontInfo *FindSoundFont(const char *name, int allowedtypes) const;
|
||||||
|
FSoundFontReader *OpenSoundFont(const char *name, int allowedtypes);
|
||||||
|
const auto &GetList() const { return soundfonts; } // This is for the menu
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern FSoundFontManager sfmanager;
|
490
source/zmusic/music_config.cpp
Normal file
|
@ -0,0 +1,490 @@
|
||||||
|
/*
|
||||||
|
** music_config.cpp
|
||||||
|
** This forwards all CVAR changes to the music system.
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 1999-2016 Randy Heit
|
||||||
|
** Copyright 2005-2019 Christoph Oelckers
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include "c_cvars.h"
|
||||||
|
#include "s_music.h"
|
||||||
|
#include "zmusic/zmusic.h"
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// ADL Midi device
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
#define FORWARD_CVAR(key) \
|
||||||
|
decltype(*self) newval; \
|
||||||
|
auto ret = ChangeMusicSetting(ZMusic::key, mus_playing.handle, *self, &newval); \
|
||||||
|
self = (decltype(*self))newval; \
|
||||||
|
if (ret) S_MIDIDeviceChanged(-1, true);
|
||||||
|
|
||||||
|
#define FORWARD_BOOL_CVAR(key) \
|
||||||
|
int newval; \
|
||||||
|
auto ret = ChangeMusicSetting(ZMusic::key, mus_playing.handle,*self, &newval); \
|
||||||
|
self = !!newval; \
|
||||||
|
if (ret) S_MIDIDeviceChanged(-1, true);
|
||||||
|
|
||||||
|
#define FORWARD_STRING_CVAR(key) \
|
||||||
|
auto ret = ChangeMusicSetting(ZMusic::key, mus_playing.handle,*self); \
|
||||||
|
if (ret) S_MIDIDeviceChanged(-1, true);
|
||||||
|
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, adl_chips_count, 6, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(adl_chips_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, adl_emulator_id, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(adl_emulator_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, adl_run_at_pcm_rate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(adl_run_at_pcm_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, adl_fullpan, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(adl_fullpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, adl_bank, 14, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(adl_bank);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, adl_use_custom_bank, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(adl_use_custom_bank);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(String, adl_custom_bank, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_STRING_CVAR(adl_custom_bank);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, adl_volume_model, 3/*ADLMIDI_VolumeModel_DMX*/, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(adl_bank);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Fluidsynth MIDI device
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CUSTOM_CVAR(String, fluid_lib, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_STRING_CVAR(fluid_lib);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(String, fluid_patchset, "gzdoom", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_STRING_CVAR(fluid_patchset);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, fluid_gain, 0.5, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, fluid_reverb, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(fluid_reverb);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, fluid_chorus, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(fluid_chorus);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, fluid_voices, 128, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_voices);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, fluid_interp, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_interp);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, fluid_samplerate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_samplerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, fluid_threads, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_threads);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, fluid_reverb_roomsize, 0.61f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_reverb_roomsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, fluid_reverb_damping, 0.23f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_reverb_damping);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, fluid_reverb_width, 0.76f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_reverb_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, fluid_reverb_level, 0.57f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_reverb_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, fluid_chorus_voices, 3, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_chorus_voices);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, fluid_chorus_level, 1.2f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_chorus_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, fluid_chorus_speed, 0.3f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_chorus_speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// depth is in ms and actual maximum depends on the sample rate
|
||||||
|
CUSTOM_CVAR(Float, fluid_chorus_depth, 8, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_chorus_depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, fluid_chorus_type, 0/*FLUID_CHORUS_DEFAULT_TYPE*/, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(fluid_chorus_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// OPL MIDI device
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, opl_numchips, 2, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(opl_numchips);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, opl_core, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(opl_core);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, opl_fullpan, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(opl_fullpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// OPN MIDI device
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, opn_chips_count, 8, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(opn_chips_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, opn_emulator_id, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(opn_emulator_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, opn_run_at_pcm_rate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(opn_run_at_pcm_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, opn_fullpan, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(opn_fullpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, opn_use_custom_bank, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(opn_use_custom_bank);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(String, opn_custom_bank, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_STRING_CVAR(opn_custom_bank);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// GUS MIDI device
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
|
||||||
|
CUSTOM_CVAR(String, midi_config, "gzdoom", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_STRING_CVAR(gus_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, midi_dmxgus, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) // This was 'true' but since it requires special setup that's not such a good idea.
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(gus_dmxgus);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(String, gus_patchdir, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_STRING_CVAR(gus_patchdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, midi_voices, 32, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(gus_midi_voices);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, gus_memsize, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(gus_memsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Timidity++ device
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, timidity_modulation_wheel, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(timidity_modulation_wheel);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, timidity_portamento, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(timidity_portamento);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, timidity_reverb, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(timidity_reverb);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, timidity_reverb_level, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(timidity_reverb_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, timidity_chorus, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(timidity_chorus);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, timidity_surround_chorus, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(timidity_surround_chorus);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, timidity_channel_pressure, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(timidity_channel_pressure);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, timidity_lpf_def, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(timidity_lpf_def);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, timidity_temper_control, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(timidity_temper_control);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, timidity_modulation_envelope, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(timidity_modulation_envelope);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, timidity_overlap_voice_allow, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(timidity_overlap_voice_allow);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, timidity_drum_effect, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(timidity_drum_effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, timidity_pan_delay, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(timidity_pan_delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, timidity_drum_power, 1.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) /* coef. of drum amplitude */
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(timidity_drum_power);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, timidity_key_adjust, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(timidity_key_adjust);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, timidity_tempo_adjust, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(timidity_tempo_adjust);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, min_sustain_time, 5000, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(min_sustain_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(String, timidity_config, "gzdoom", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_STRING_CVAR(timidity_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// WildMidi
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CUSTOM_CVAR(String, wildmidi_config, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_STRING_CVAR(wildmidi_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, wildmidi_reverb, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(wildmidi_reverb);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, wildmidi_enhanced_resampling, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(wildmidi_enhanced_resampling);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// This one is for Win32 MMAPI.
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, snd_midiprecache, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(snd_midiprecache);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// GME
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, gme_stereodepth, 0.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(gme_stereodepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// sndfile
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, snd_streambuffersize, 64, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(snd_streambuffersize);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// Dumb
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, mod_samplerate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(mod_samplerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, mod_volramp, 2, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(mod_volramp);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, mod_interp, 2/*DUMB_LQ_CUBIC*/, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(mod_interp);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Bool, mod_autochip, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_BOOL_CVAR(mod_autochip);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, mod_autochip_size_force, 100, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(mod_autochip_size_force);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, mod_autochip_size_scan, 500, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(mod_autochip_size_scan);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, mod_autochip_scan_threshold, 12, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(mod_autochip_scan_threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Float, mod_dumb_mastervolume, 1.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL)
|
||||||
|
{
|
||||||
|
FORWARD_CVAR(mod_dumb_mastervolume);
|
||||||
|
}
|
||||||
|
|
229
source/zmusic/music_midi_base.cpp
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
/*
|
||||||
|
** music_midi_base.cpp
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
** Copyright 1998-2010 Randy Heit
|
||||||
|
** All rights reserved.
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <mmsystem.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "c_dispatch.h"
|
||||||
|
|
||||||
|
#include "v_text.h"
|
||||||
|
#include "menu/menu.h"
|
||||||
|
#include "zmusic/zmusic.h"
|
||||||
|
#include "s_music.h"
|
||||||
|
|
||||||
|
static uint32_t nummididevices;
|
||||||
|
static bool nummididevicesset;
|
||||||
|
|
||||||
|
#define NUM_DEF_DEVICES 7
|
||||||
|
|
||||||
|
static void AddDefaultMidiDevices(FOptionValues *opt)
|
||||||
|
{
|
||||||
|
FOptionValues::Pair *pair = &opt->mValues[opt->mValues.Reserve(NUM_DEF_DEVICES)];
|
||||||
|
pair[0].Text = "FluidSynth";
|
||||||
|
pair[0].Value = -5.0;
|
||||||
|
pair[1].Text = "TiMidity++";
|
||||||
|
pair[1].Value = -2.0;
|
||||||
|
pair[2].Text = "WildMidi";
|
||||||
|
pair[2].Value = -6.0;
|
||||||
|
pair[3].Text = "GUS";
|
||||||
|
pair[3].Value = -4.0;
|
||||||
|
pair[4].Text = "OPL Synth Emulation";
|
||||||
|
pair[4].Value = -3.0;
|
||||||
|
pair[5].Text = "libADL";
|
||||||
|
pair[5].Value = -7.0;
|
||||||
|
pair[6].Text = "libOPN";
|
||||||
|
pair[6].Value = -8.0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DEF_MIDIDEV -5
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <mmsystem.h>
|
||||||
|
|
||||||
|
CUSTOM_CVAR (Int, snd_mididevice, DEF_MIDIDEV, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||||
|
{
|
||||||
|
if (!nummididevicesset)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((self >= (signed)nummididevices) || (self < -8))
|
||||||
|
{
|
||||||
|
// Don't do repeated message spam if there is no valid device.
|
||||||
|
if (self != 0)
|
||||||
|
{
|
||||||
|
Printf("ID out of range. Using default device.\n");
|
||||||
|
self = DEF_MIDIDEV;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (self == -1) self = DEF_MIDIDEV;
|
||||||
|
ChangeMusicSetting(ZMusic::snd_mididevice, nullptr, self);
|
||||||
|
S_MIDIDeviceChanged(self, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I_InitMusicWin32 ()
|
||||||
|
{
|
||||||
|
nummididevices = midiOutGetNumDevs ();
|
||||||
|
nummididevicesset = true;
|
||||||
|
snd_mididevice.Callback ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void I_BuildMIDIMenuList (FOptionValues *opt)
|
||||||
|
{
|
||||||
|
AddDefaultMidiDevices(opt);
|
||||||
|
|
||||||
|
for (uint32_t id = 0; id < nummididevices; ++id)
|
||||||
|
{
|
||||||
|
MIDIOUTCAPS caps;
|
||||||
|
MMRESULT res;
|
||||||
|
|
||||||
|
res = midiOutGetDevCaps (id, &caps, sizeof(caps));
|
||||||
|
assert(res == MMSYSERR_NOERROR);
|
||||||
|
if (res == MMSYSERR_NOERROR)
|
||||||
|
{
|
||||||
|
FOptionValues::Pair *pair = &opt->mValues[opt->mValues.Reserve(1)];
|
||||||
|
pair->Text = caps.szPname;
|
||||||
|
pair->Value = (float)id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintMidiDevice (int id, const char *name, uint16_t tech, uint32_t support)
|
||||||
|
{
|
||||||
|
if (id == snd_mididevice)
|
||||||
|
{
|
||||||
|
Printf (TEXTCOLOR_BOLD);
|
||||||
|
}
|
||||||
|
Printf ("% 2d. %s : ", id, name);
|
||||||
|
switch (tech)
|
||||||
|
{
|
||||||
|
case MIDIDEV_MIDIPORT: Printf ("MIDIPORT"); break;
|
||||||
|
case MIDIDEV_SYNTH: Printf ("SYNTH"); break;
|
||||||
|
case MIDIDEV_SQSYNTH: Printf ("SQSYNTH"); break;
|
||||||
|
case MIDIDEV_FMSYNTH: Printf ("FMSYNTH"); break;
|
||||||
|
case MIDIDEV_MAPPER: Printf ("MAPPER"); break;
|
||||||
|
case MIDIDEV_WAVETABLE: Printf ("WAVETABLE"); break;
|
||||||
|
case MIDIDEV_SWSYNTH: Printf ("SWSYNTH"); break;
|
||||||
|
}
|
||||||
|
if (support & MIDICAPS_CACHE)
|
||||||
|
{
|
||||||
|
Printf (" CACHE");
|
||||||
|
}
|
||||||
|
if (support & MIDICAPS_LRVOLUME)
|
||||||
|
{
|
||||||
|
Printf (" LRVOLUME");
|
||||||
|
}
|
||||||
|
if (support & MIDICAPS_STREAM)
|
||||||
|
{
|
||||||
|
Printf (" STREAM");
|
||||||
|
}
|
||||||
|
if (support & MIDICAPS_VOLUME)
|
||||||
|
{
|
||||||
|
Printf (" VOLUME");
|
||||||
|
}
|
||||||
|
Printf (TEXTCOLOR_NORMAL "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
CCMD (snd_listmididevices)
|
||||||
|
{
|
||||||
|
UINT id;
|
||||||
|
MIDIOUTCAPS caps;
|
||||||
|
MMRESULT res;
|
||||||
|
|
||||||
|
PrintMidiDevice(-8, "libOPN", MIDIDEV_FMSYNTH, 0);
|
||||||
|
PrintMidiDevice(-7, "libADL", MIDIDEV_FMSYNTH, 0);
|
||||||
|
PrintMidiDevice (-6, "WildMidi", MIDIDEV_SWSYNTH, 0);
|
||||||
|
PrintMidiDevice (-5, "FluidSynth", MIDIDEV_SWSYNTH, 0);
|
||||||
|
PrintMidiDevice (-4, "Gravis Ultrasound Emulation", MIDIDEV_SWSYNTH, 0);
|
||||||
|
PrintMidiDevice (-3, "Emulated OPL FM Synth", MIDIDEV_FMSYNTH, 0);
|
||||||
|
PrintMidiDevice (-2, "TiMidity++", MIDIDEV_SWSYNTH, 0);
|
||||||
|
if (nummididevices != 0)
|
||||||
|
{
|
||||||
|
for (id = 0; id < nummididevices; ++id)
|
||||||
|
{
|
||||||
|
FString text;
|
||||||
|
res = midiOutGetDevCaps (id, &caps, sizeof(caps));
|
||||||
|
if (res == MMSYSERR_NODRIVER)
|
||||||
|
text = "<Driver not installed>";
|
||||||
|
else if (res == MMSYSERR_NOMEM)
|
||||||
|
text = "<No memory for description>";
|
||||||
|
else if (res == MMSYSERR_NOERROR)
|
||||||
|
text = caps.szPname;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PrintMidiDevice (id, text, caps.wTechnology, caps.dwSupport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
// Everything but Windows uses this code.
|
||||||
|
|
||||||
|
CUSTOM_CVAR(Int, snd_mididevice, DEF_MIDIDEV, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||||||
|
{
|
||||||
|
if (self < -8)
|
||||||
|
self = -8;
|
||||||
|
else if (self > -2)
|
||||||
|
self = -2;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ChangeMusicSetting(ZMusic::snd_mididevice, nullptr, self);
|
||||||
|
S_MIDIDeviceChanged(self, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void I_BuildMIDIMenuList (FOptionValues *opt)
|
||||||
|
{
|
||||||
|
AddDefaultMidiDevices(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
CCMD (snd_listmididevices)
|
||||||
|
{
|
||||||
|
Printf("%s-8. libOPN\n", -8 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||||
|
Printf("%s-7. libADL\n", -7 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||||
|
Printf("%s-6. WildMidi\n", -6 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||||
|
Printf("%s-5. FluidSynth\n", -5 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||||
|
Printf("%s-4. Gravis Ultrasound Emulation\n", -4 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||||
|
Printf("%s-3. Emulated OPL FM Synth\n", -3 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||||
|
Printf("%s-2. TiMidity++\n", -2 == snd_mididevice ? TEXTCOLOR_BOLD : "");
|
||||||
|
}
|
||||||
|
#endif
|
633
source/zmusic/s_music.cpp
Normal file
|
@ -0,0 +1,633 @@
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Copyright 1993-1996 id Software
|
||||||
|
// Copyright 1999-2016 Randy Heit
|
||||||
|
// Copyright 2002-2016 Christoph Oelckers
|
||||||
|
//
|
||||||
|
// 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 3 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/
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// DESCRIPTION: none
|
||||||
|
//
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/* For code that originates from ZDoom the following applies:
|
||||||
|
**
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
** Redistribution and use in source and binary forms, with or without
|
||||||
|
** modification, are permitted provided that the following conditions
|
||||||
|
** are met:
|
||||||
|
**
|
||||||
|
** 1. Redistributions of source code must retain the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer.
|
||||||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
** notice, this list of conditions and the following disclaimer in the
|
||||||
|
** documentation and/or other materials provided with the distribution.
|
||||||
|
** 3. The name of the author may not be used to endorse or promote products
|
||||||
|
** derived from this software without specific prior written permission.
|
||||||
|
**
|
||||||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
**---------------------------------------------------------------------------
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <io.h>
|
||||||
|
#include "musicformats/win32/i_cd.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "i_system.h"
|
||||||
|
#include "i_sound.h"
|
||||||
|
#include "i_music.h"
|
||||||
|
|
||||||
|
#include "s_sound.h"
|
||||||
|
#include "s_sndseq.h"
|
||||||
|
#include "c_dispatch.h"
|
||||||
|
#include "m_random.h"
|
||||||
|
#include "w_wad.h"
|
||||||
|
#include "p_local.h"
|
||||||
|
#include "doomstat.h"
|
||||||
|
#include "cmdlib.h"
|
||||||
|
#include "v_video.h"
|
||||||
|
#include "v_text.h"
|
||||||
|
#include "a_sharedglobal.h"
|
||||||
|
#include "gstrings.h"
|
||||||
|
#include "gi.h"
|
||||||
|
#include "po_man.h"
|
||||||
|
#include "serializer.h"
|
||||||
|
#include "d_player.h"
|
||||||
|
#include "g_levellocals.h"
|
||||||
|
#include "vm.h"
|
||||||
|
#include "g_game.h"
|
||||||
|
#include "atterm.h"
|
||||||
|
#include "s_music.h"
|
||||||
|
#include "filereadermusicinterface.h"
|
||||||
|
#include "zmusic/musinfo.h"
|
||||||
|
#include "zmusic/zmusic.h"
|
||||||
|
|
||||||
|
// MACROS ------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||||
|
|
||||||
|
extern float S_GetMusicVolume (const char *music);
|
||||||
|
|
||||||
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||||||
|
|
||||||
|
static bool MusicPaused; // whether music is paused
|
||||||
|
MusPlayingInfo mus_playing; // music currently being played
|
||||||
|
float relative_volume = 1.f;
|
||||||
|
float saved_relative_volume = 1.0f; // this could be used to implement an ACS FadeMusic function
|
||||||
|
|
||||||
|
DEFINE_GLOBAL_NAMED(mus_playing, musplaying);
|
||||||
|
DEFINE_FIELD_X(MusPlayingInfo, MusPlayingInfo, name);
|
||||||
|
DEFINE_FIELD_X(MusPlayingInfo, MusPlayingInfo, baseorder);
|
||||||
|
DEFINE_FIELD_X(MusPlayingInfo, MusPlayingInfo, loop);
|
||||||
|
|
||||||
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||||||
|
|
||||||
|
// CODE --------------------------------------------------------------------
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Create a sound system stream for the currently playing song
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static std::unique_ptr<SoundStream> musicStream;
|
||||||
|
|
||||||
|
static bool FillStream(SoundStream* stream, void* buff, int len, void* userdata)
|
||||||
|
{
|
||||||
|
bool written = mus_playing.handle? mus_playing.handle->ServiceStream(buff, len) : 0;
|
||||||
|
if (!written)
|
||||||
|
{
|
||||||
|
memset((char*)buff, 0, len);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void S_CreateStream()
|
||||||
|
{
|
||||||
|
if (!mus_playing.handle) return;
|
||||||
|
auto fmt = mus_playing.handle->GetStreamInfo();
|
||||||
|
if (fmt.mBufferSize > 0)
|
||||||
|
{
|
||||||
|
int flags = fmt.mNumChannels < 0 ? 0 : SoundStream::Float;
|
||||||
|
if (abs(fmt.mNumChannels) < 2) flags |= SoundStream::Mono;
|
||||||
|
|
||||||
|
musicStream.reset(GSnd->CreateStream(FillStream, fmt.mBufferSize, flags, fmt.mSampleRate, nullptr));
|
||||||
|
if (musicStream) musicStream->Play(true, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void S_PauseStream(bool paused)
|
||||||
|
{
|
||||||
|
if (musicStream) musicStream->SetPaused(paused);
|
||||||
|
}
|
||||||
|
|
||||||
|
void S_StopStream()
|
||||||
|
{
|
||||||
|
if (musicStream)
|
||||||
|
{
|
||||||
|
musicStream->Stop();
|
||||||
|
musicStream.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// starts playing this song
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
static void S_StartMusicPlaying(MusInfo* song, bool loop, float rel_vol, int subsong)
|
||||||
|
{
|
||||||
|
if (rel_vol > 0.f)
|
||||||
|
{
|
||||||
|
float factor = relative_volume / saved_relative_volume;
|
||||||
|
saved_relative_volume = rel_vol;
|
||||||
|
I_SetRelativeVolume(saved_relative_volume * factor);
|
||||||
|
}
|
||||||
|
song->Stop();
|
||||||
|
song->Play(loop, subsong);
|
||||||
|
song->m_NotStartedYet = false;
|
||||||
|
|
||||||
|
// Notify the sound system of the changed relative volume
|
||||||
|
snd_musicvolume.Callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// S_PauseSound
|
||||||
|
//
|
||||||
|
// Stop music and sound effects, during game PAUSE.
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void S_PauseMusic ()
|
||||||
|
{
|
||||||
|
if (mus_playing.handle && !MusicPaused)
|
||||||
|
{
|
||||||
|
mus_playing.handle->Pause();
|
||||||
|
S_PauseStream(true);
|
||||||
|
MusicPaused = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// S_ResumeSound
|
||||||
|
//
|
||||||
|
// Resume music and sound effects, after game PAUSE.
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void S_ResumeMusic ()
|
||||||
|
{
|
||||||
|
if (mus_playing.handle && MusicPaused)
|
||||||
|
{
|
||||||
|
mus_playing.handle->Resume();
|
||||||
|
S_PauseStream(false);
|
||||||
|
MusicPaused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// S_UpdateSound
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void S_UpdateMusic ()
|
||||||
|
{
|
||||||
|
if (mus_playing.handle != nullptr)
|
||||||
|
{
|
||||||
|
mus_playing.handle->Update();
|
||||||
|
|
||||||
|
if (!mus_playing.handle->IsPlaying())
|
||||||
|
{
|
||||||
|
S_StopMusic(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// S_Start
|
||||||
|
//
|
||||||
|
// Per level startup code. Kills playing sounds at start of level
|
||||||
|
// and starts new music.
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void S_StartMusic ()
|
||||||
|
{
|
||||||
|
// stop the old music if it has been paused.
|
||||||
|
// This ensures that the new music is started from the beginning
|
||||||
|
// if it's the same as the last one and it has been paused.
|
||||||
|
if (MusicPaused) S_StopMusic(true);
|
||||||
|
|
||||||
|
// start new music for the level
|
||||||
|
MusicPaused = false;
|
||||||
|
|
||||||
|
// Don't start the music if loading a savegame, because the music is stored there.
|
||||||
|
// Don't start the music if revisiting a level in a hub for the same reason.
|
||||||
|
if (!primaryLevel->IsReentering())
|
||||||
|
{
|
||||||
|
primaryLevel->SetMusic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// S_StartMusic
|
||||||
|
//
|
||||||
|
// Starts some music with the given name.
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool S_StartMusic (const char *m_id)
|
||||||
|
{
|
||||||
|
return S_ChangeMusic (m_id, 0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// S_ChangeMusic
|
||||||
|
//
|
||||||
|
// Starts playing a music, possibly looping.
|
||||||
|
//
|
||||||
|
// [RH] If music is a MOD, starts it at position order. If name is of the
|
||||||
|
// format ",CD,<track>,[cd id]" song is a CD track, and if [cd id] is
|
||||||
|
// specified, it will only be played if the specified CD is in a drive.
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
bool S_ChangeMusic (const char *musicname, int order, bool looping, bool force)
|
||||||
|
{
|
||||||
|
if (nomusic) return false; // skip the entire procedure if music is globally disabled.
|
||||||
|
|
||||||
|
// allow specifying "*" as a placeholder to play the level's default music.
|
||||||
|
if (musicname != nullptr && !strcmp(musicname, "*"))
|
||||||
|
{
|
||||||
|
if (gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL)
|
||||||
|
{
|
||||||
|
musicname = primaryLevel->Music;
|
||||||
|
order = primaryLevel->musicorder;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
musicname = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (musicname == nullptr || musicname[0] == 0)
|
||||||
|
{
|
||||||
|
// Don't choke if the map doesn't have a song attached
|
||||||
|
S_StopMusic (true);
|
||||||
|
mus_playing.name = "";
|
||||||
|
mus_playing.LastSong = "";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString DEH_Music;
|
||||||
|
if (musicname[0] == '$')
|
||||||
|
{
|
||||||
|
// handle dehacked replacement.
|
||||||
|
// Any music name defined this way needs to be prefixed with 'D_' because
|
||||||
|
// Doom.exe does not contain the prefix so these strings don't either.
|
||||||
|
const char * mus_string = GStrings[musicname+1];
|
||||||
|
if (mus_string != nullptr)
|
||||||
|
{
|
||||||
|
DEH_Music << "D_" << mus_string;
|
||||||
|
musicname = DEH_Music;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FName *aliasp = MusicAliases.CheckKey(musicname);
|
||||||
|
if (aliasp != nullptr)
|
||||||
|
{
|
||||||
|
if (*aliasp == NAME_None)
|
||||||
|
{
|
||||||
|
return true; // flagged to be ignored
|
||||||
|
}
|
||||||
|
musicname = aliasp->GetChars();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mus_playing.name.IsEmpty() &&
|
||||||
|
mus_playing.handle != nullptr &&
|
||||||
|
stricmp (mus_playing.name, musicname) == 0 &&
|
||||||
|
mus_playing.handle->m_Looping == looping)
|
||||||
|
{
|
||||||
|
if (order != mus_playing.baseorder)
|
||||||
|
{
|
||||||
|
if (mus_playing.handle->SetSubsong(order))
|
||||||
|
{
|
||||||
|
mus_playing.baseorder = order;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!mus_playing.handle->IsPlaying())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mus_playing.handle->Play(looping, order);
|
||||||
|
S_CreateStream();
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& err)
|
||||||
|
{
|
||||||
|
Printf("Unable to start %s: %s\n", mus_playing.name.GetChars(), err.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
int lumpnum = -1;
|
||||||
|
int length = 0;
|
||||||
|
MusInfo *handle = nullptr;
|
||||||
|
MidiDeviceSetting *devp = MidiDevices.CheckKey(musicname);
|
||||||
|
|
||||||
|
// Strip off any leading file:// component.
|
||||||
|
if (strncmp(musicname, "file://", 7) == 0)
|
||||||
|
{
|
||||||
|
musicname += 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileReader reader;
|
||||||
|
if (!FileExists (musicname))
|
||||||
|
{
|
||||||
|
if ((lumpnum = Wads.CheckNumForFullName (musicname, true, ns_music)) == -1)
|
||||||
|
{
|
||||||
|
Printf ("Music \"%s\" not found\n", musicname);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (handle == nullptr)
|
||||||
|
{
|
||||||
|
if (Wads.LumpLength (lumpnum) == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
reader = Wads.ReopenLumpReader(lumpnum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Load an external file.
|
||||||
|
if (!reader.OpenFile(musicname))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutdown old music
|
||||||
|
S_StopMusic (true);
|
||||||
|
|
||||||
|
// Just record it if volume is 0
|
||||||
|
if (snd_musicvolume <= 0)
|
||||||
|
{
|
||||||
|
mus_playing.loop = looping;
|
||||||
|
mus_playing.name = musicname;
|
||||||
|
mus_playing.baseorder = order;
|
||||||
|
mus_playing.LastSong = musicname;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load & register it
|
||||||
|
if (handle != nullptr)
|
||||||
|
{
|
||||||
|
mus_playing.handle = handle;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto mreader = new FileReaderMusicInterface(reader);
|
||||||
|
mus_playing.handle = ZMusic_OpenSong(mreader, devp? (EMidiDevice)devp->device : MDEV_DEFAULT, devp? devp->args.GetChars() : "");
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& err)
|
||||||
|
{
|
||||||
|
Printf("Unable to load %s: %s\n", mus_playing.name.GetChars(), err.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mus_playing.loop = looping;
|
||||||
|
mus_playing.name = musicname;
|
||||||
|
mus_playing.baseorder = 0;
|
||||||
|
mus_playing.LastSong = "";
|
||||||
|
|
||||||
|
if (mus_playing.handle != 0)
|
||||||
|
{ // play it
|
||||||
|
try
|
||||||
|
{
|
||||||
|
S_StartMusicPlaying(mus_playing.handle, looping, S_GetMusicVolume(musicname), order);
|
||||||
|
S_CreateStream();
|
||||||
|
mus_playing.baseorder = order;
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& err)
|
||||||
|
{
|
||||||
|
Printf("Unable to start %s: %s\n", mus_playing.name.GetChars(), err.what());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// S_RestartMusic
|
||||||
|
//
|
||||||
|
// Must only be called from snd_reset in i_sound.cpp!
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void S_RestartMusic ()
|
||||||
|
{
|
||||||
|
if (!mus_playing.LastSong.IsEmpty())
|
||||||
|
{
|
||||||
|
FString song = mus_playing.LastSong;
|
||||||
|
mus_playing.LastSong = "";
|
||||||
|
S_ChangeMusic (song, mus_playing.baseorder, mus_playing.loop, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// S_MIDIDeviceChanged
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
|
||||||
|
void S_MIDIDeviceChanged(int newdev, bool force)
|
||||||
|
{
|
||||||
|
static int oldmididev = INT_MIN;
|
||||||
|
|
||||||
|
// If a song is playing, move it to the new device.
|
||||||
|
if (oldmididev != newdev || force)
|
||||||
|
{
|
||||||
|
if (mus_playing.handle != nullptr && mus_playing.handle->IsMIDI())
|
||||||
|
{
|
||||||
|
MusInfo* song = mus_playing.handle;
|
||||||
|
if (song->m_Status == MusInfo::STATE_Playing)
|
||||||
|
{
|
||||||
|
if (song->GetDeviceType() == MDEV_FLUIDSYNTH && force)
|
||||||
|
{
|
||||||
|
// FluidSynth must reload the song to change the patch set.
|
||||||
|
auto mi = mus_playing;
|
||||||
|
S_StopMusic(true);
|
||||||
|
S_ChangeMusic(mi.name, mi.baseorder, mi.loop);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
song->Stop();
|
||||||
|
S_StartMusicPlaying(song, song->m_Looping, -1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 'force'
|
||||||
|
if (!force) oldmididev = newdev;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// S_GetMusic
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
int S_GetMusic (const char **name)
|
||||||
|
{
|
||||||
|
int order;
|
||||||
|
|
||||||
|
if (mus_playing.name.IsNotEmpty())
|
||||||
|
{
|
||||||
|
*name = mus_playing.name;
|
||||||
|
order = mus_playing.baseorder;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*name = nullptr;
|
||||||
|
order = 0;
|
||||||
|
}
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// S_StopMusic
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
void S_StopMusic (bool force)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (mus_playing.handle != nullptr)
|
||||||
|
{
|
||||||
|
S_ResumeMusic();
|
||||||
|
S_StopStream();
|
||||||
|
mus_playing.handle->Stop();
|
||||||
|
delete mus_playing.handle;
|
||||||
|
mus_playing.handle = nullptr;
|
||||||
|
}
|
||||||
|
mus_playing.LastSong = std::move(mus_playing.name);
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& )
|
||||||
|
{
|
||||||
|
//Printf("Unable to stop %s: %s\n", mus_playing.name.GetChars(), err.what());
|
||||||
|
if (mus_playing.handle != nullptr)
|
||||||
|
{
|
||||||
|
delete mus_playing.handle;
|
||||||
|
mus_playing.handle = nullptr;
|
||||||
|
}
|
||||||
|
mus_playing.name = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// CCMD changemus
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CCMD (changemus)
|
||||||
|
{
|
||||||
|
if (!nomusic)
|
||||||
|
{
|
||||||
|
if (argv.argc() > 1)
|
||||||
|
{
|
||||||
|
S_ChangeMusic (argv[1], argv.argc() > 2 ? atoi (argv[2]) : 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char *currentmus = mus_playing.name.GetChars();
|
||||||
|
if(currentmus != nullptr && *currentmus != 0)
|
||||||
|
{
|
||||||
|
Printf ("currently playing %s\n", currentmus);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Printf ("no music playing\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Printf("Music is disabled\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
// CCMD stopmus
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CCMD (stopmus)
|
||||||
|
{
|
||||||
|
S_StopMusic (false);
|
||||||
|
mus_playing.LastSong = ""; // forget the last played song so that it won't get restarted if some volume changes occur
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//==========================================================================
|
||||||
|
|
||||||
|
CCMD(currentmusic)
|
||||||
|
{
|
||||||
|
if (mus_playing.name.IsNotEmpty())
|
||||||
|
{
|
||||||
|
Printf("Currently playing music '%s'\n", mus_playing.name.GetChars());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Printf("Currently no music playing\n");
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 234 B After Width: | Height: | Size: 234 B |
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 259 B |
Before Width: | Height: | Size: 285 B After Width: | Height: | Size: 285 B |
Before Width: | Height: | Size: 291 B After Width: | Height: | Size: 291 B |
Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 273 B |
Before Width: | Height: | Size: 283 B After Width: | Height: | Size: 283 B |
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
Before Width: | Height: | Size: 302 B After Width: | Height: | Size: 302 B |
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 290 B |
Before Width: | Height: | Size: 287 B After Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 276 B |
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 262 B |
Before Width: | Height: | Size: 348 B After Width: | Height: | Size: 348 B |
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 278 B |
Before Width: | Height: | Size: 314 B After Width: | Height: | Size: 314 B |
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
Before Width: | Height: | Size: 301 B After Width: | Height: | Size: 301 B |
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 337 B |
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 325 B |
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 339 B |
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 279 B |
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 363 B |
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 363 B |
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 316 B |
Before Width: | Height: | Size: 338 B After Width: | Height: | Size: 338 B |
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 355 B |
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 355 B |
Before Width: | Height: | Size: 353 B After Width: | Height: | Size: 353 B |
Before Width: | Height: | Size: 328 B After Width: | Height: | Size: 328 B |
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 268 B |
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 349 B |
Before Width: | Height: | Size: 352 B After Width: | Height: | Size: 352 B |
Before Width: | Height: | Size: 318 B After Width: | Height: | Size: 318 B |
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 342 B |
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 342 B |
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 354 B |
Before Width: | Height: | Size: 332 B After Width: | Height: | Size: 332 B |
Before Width: | Height: | Size: 347 B After Width: | Height: | Size: 347 B |
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 337 B |
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 316 B |
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 349 B |
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 342 B |
Before Width: | Height: | Size: 291 B After Width: | Height: | Size: 291 B |
Before Width: | Height: | Size: 319 B After Width: | Height: | Size: 319 B |
Before Width: | Height: | Size: 327 B After Width: | Height: | Size: 327 B |
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 326 B |
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 345 B |
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 331 B |
Before Width: | Height: | Size: 310 B After Width: | Height: | Size: 310 B |
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 363 B |
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 355 B |
Before Width: | Height: | Size: 310 B After Width: | Height: | Size: 310 B |
Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 355 B |
Before Width: | Height: | Size: 341 B After Width: | Height: | Size: 341 B |