|
From: <sag...@us...> - 2012-03-31 17:46:45
|
Revision: 1234
http://modplug.svn.sourceforge.net/modplug/?rev=1234&view=rev
Author: saga-games
Date: 2012-03-31 17:46:38 +0000 (Sat, 31 Mar 2012)
Log Message:
-----------
[Imp] Rewrote UMX loader. Instead of "scanning" the file, it actually parses Unreal files now. As a nice side effect, it can now also load samples from UAX files.
[Fix] 669 Loader broke some revisions ago.
Modified Paths:
--------------
trunk/OpenMPT/soundlib/Load_669.cpp
trunk/OpenMPT/soundlib/Load_umx.cpp
trunk/OpenMPT/soundlib/Sndfile.cpp
trunk/OpenMPT/soundlib/Sndfile.h
Modified: trunk/OpenMPT/soundlib/Load_669.cpp
===================================================================
--- trunk/OpenMPT/soundlib/Load_669.cpp 2012-03-30 23:29:11 UTC (rev 1233)
+++ trunk/OpenMPT/soundlib/Load_669.cpp 2012-03-31 17:46:38 UTC (rev 1234)
@@ -17,6 +17,12 @@
struct _669FileHeader
{
+ enum MagicBytes
+ {
+ magic669 = 0x6669, // 'if'
+ magic669Ext = 0x4E4A // 'JN'
+ };
+
uint16 sig; // 'if' or 'JN'
char songmessage[108]; // Song Message
uint8 samples; // number of samples (1-64)
@@ -25,6 +31,12 @@
uint8 orders[128];
uint8 tempolist[128];
uint8 breaks[128];
+
+ // Convert all multi-byte numeric values to current platform's endianness or vice versa.
+ void ConvertEndianness()
+ {
+ SwapBytesLE(sig);
+ }
};
STATIC_ASSERT(sizeof(_669FileHeader) == 497);
@@ -32,9 +44,17 @@
struct _669Sample
{
char filename[13];
- uint32 length; // when will somebody think about DWORD align ???
+ uint32 length;
uint32 loopStart;
uint32 loopEnd;
+
+ // Convert all multi-byte numeric values to current platform's endianness or vice versa.
+ void ConvertEndianness()
+ {
+ SwapBytesLE(length);
+ SwapBytesLE(loopStart);
+ SwapBytesLE(loopEnd);
+ }
};
STATIC_ASSERT(sizeof(_669Sample) == 25);
@@ -45,21 +65,20 @@
bool CSoundFile::Read669(FileReader &file)
//----------------------------------------
{
- bool has669Ext;
_669FileHeader fileHeader;
file.Rewind();
- if(!file.Read(fileHeader))
+ if(!file.ReadConvertEndianness(fileHeader))
{
return false;
}
- if(fileHeader.sig != LittleEndianW(0x6669) && fileHeader.sig != LittleEndianW(0x4E4A))
+ if(fileHeader.sig != _669FileHeader::magic669 && fileHeader.sig != _669FileHeader::magic669Ext)
{
return false;
}
- has669Ext = fileHeader.sig == LittleEndianW(0x4E4A);
+ //bool has669Ext = fileHeader.sig == _669FileHeader::magic669Ext;
if(fileHeader.samples > 64 || fileHeader.restartpos >= 128
|| fileHeader.patterns > 128)
{
@@ -74,81 +93,76 @@
m_nDefaultSpeed = 6;
m_nChannels = 8;
- // Copy first song message line into song title
- StringFixer::ReadString<StringFixer::spacePadded>(m_szNames[0], fileHeader.songmessage, 36);
-
m_nSamples = fileHeader.samples;
- for(SAMPLEINDEX nSmp = 1; nSmp <= m_nSamples; nSmp++)
+ for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++)
{
_669Sample sample;
- if(!file.Read(sample))
+ if(!file.ReadConvertEndianness(sample))
{
return false;
}
- DWORD len = LittleEndian(sample.length);
- DWORD loopstart = LittleEndian(sample.loopStart);
- DWORD loopend = LittleEndian(sample.loopEnd);
- if (len > MAX_SAMPLE_LENGTH) len = MAX_SAMPLE_LENGTH;
- if ((loopend > len) && (!loopstart)) loopend = 0;
- if (loopend > len) loopend = len;
- if (loopstart + 4 >= loopend) loopstart = loopend = 0;
- Samples[nSmp].nLength = len;
- Samples[nSmp].nLoopStart = loopstart;
- Samples[nSmp].nLoopEnd = loopend;
- if (loopend) Samples[nSmp].uFlags |= CHN_LOOP;
- StringFixer::ReadString<StringFixer::nullTerminated>(m_szNames[nSmp], sample.filename);
- Samples[nSmp].nVolume = 256;
- Samples[nSmp].nGlobalVol = 64;
- Samples[nSmp].nPan = 128;
+ SmpLength len = sample.length;
+ SmpLength loopstart = sample.loopStart;
+ SmpLength loopend = sample.loopEnd;
+ if(len > MAX_SAMPLE_LENGTH) len = MAX_SAMPLE_LENGTH;
+ if((loopend > len) && (!loopstart)) loopend = 0;
+ if(loopend > len) loopend = len;
+ if(loopstart + 4 >= loopend) loopstart = loopend = 0;
+ Samples[smp].nLength = len;
+ Samples[smp].nLoopStart = loopstart;
+ Samples[smp].nLoopEnd = loopend;
+ if(loopend) Samples[smp].uFlags |= CHN_LOOP;
+ StringFixer::ReadString<StringFixer::maybeNullTerminated>(m_szNames[smp], sample.filename);
+ Samples[smp].nVolume = 256;
+ Samples[smp].nGlobalVol = 64;
+ Samples[smp].nPan = 128;
}
+ // Copy first song message line into song title
+ StringFixer::ReadString<StringFixer::spacePadded>(m_szNames[0], fileHeader.songmessage, 36);
// Song Message
- ReadFixedLineLengthMessage(file, 108, 36, 0);
+ ReadFixedLineLengthMessage(reinterpret_cast<const BYTE *>(fileHeader.songmessage), 108, 36, 0);
// Reading Orders
Order.ReadAsByte(fileHeader.orders, 128, 128);
m_nRestartPos = fileHeader.restartpos;
if(Order[m_nRestartPos] >= fileHeader.patterns) m_nRestartPos = 0;
- // Reading Pattern Break Locations
- for (UINT npan=0; npan<8; npan++)
+
+ // Set up panning
+ for(CHANNELINDEX chn = 0; chn < 8; chn++)
{
- ChnSettings[npan].nPan = (npan & 1) ? 0x30 : 0xD0;
- ChnSettings[npan].nVolume = 64;
+ ChnSettings[chn].nPan = (chn & 1) ? 0x30 : 0xD0;
+ ChnSettings[chn].nVolume = 64;
}
// Reading Patterns
- for (UINT npat = 0; npat < fileHeader.patterns; npat++)
+ for(PATTERNINDEX pat = 0; pat < fileHeader.patterns; pat++)
{
- if(Patterns.Insert(npat, 64))
- break;
+ if(Patterns.Insert(pat, 64))
+ {
+ continue;
+ }
- ModCommand *m = Patterns[npat];
- for (UINT row=0; row<64; row++)
+ ModCommand *m = Patterns[pat];
+ for(ROWINDEX row = 0; row < 64; row++)
{
ModCommand *mspeed = m;
- if ((row == fileHeader.breaks[npat]) && (row != 63))
+
+ for(CHANNELINDEX n = 0; n < 8; n++, m++)
{
- for (UINT i=0; i<8; i++)
- {
- m[i].command = CMD_PATTERNBREAK;
- m[i].param = 0;
- }
- }
- for(UINT n = 0; n < 8; n++, m++)
- {
- char data[3];
+ uint8 data[3];
if(!file.ReadArray(data))
{
break;
}
- UINT note = data[0] >> 2;
- UINT instr = ((data[0] & 0x03) << 4) | (data[1] >> 4);
- UINT vol = data[1] & 0x0F;
+ uint8 note = data[0] >> 2;
+ uint8 instr = ((data[0] & 0x03) << 4) | (data[1] >> 4);
+ uint8 vol = data[1] & 0x0F;
if (data[0] < 0xFE)
{
- m->note = note + 37;
+ m->note = note + 36 + NOTE_MIN;
m->instr = instr + 1;
}
if (data[0] <= 0xFE)
@@ -158,8 +172,8 @@
}
if (data[2] != 0xFF)
{
- UINT command = data[2] >> 4;
- UINT param = data[2] & 0x0F;
+ uint8 command = data[2] >> 4;
+ uint8 param = data[2] & 0x0F;
switch(command)
{
case 0x00: command = CMD_PORTAMENTOUP; break;
@@ -187,10 +201,16 @@
for (UINT i=0; i<8; i++) if (!mspeed[i].command)
{
mspeed[i].command = CMD_SPEED;
- mspeed[i].param = fileHeader.tempolist[npat] + 2;
+ mspeed[i].param = fileHeader.tempolist[pat] + 2;
break;
}
}
+
+ // Write pattern break
+ if(fileHeader.breaks[pat] < 63)
+ {
+ TryWriteEffect(pat, fileHeader.breaks[pat], CMD_PATTERNBREAK, 0, false, CHANNELINDEX_INVALID, false, weTryPreviousRow);
+ }
}
}
Modified: trunk/OpenMPT/soundlib/Load_umx.cpp
===================================================================
--- trunk/OpenMPT/soundlib/Load_umx.cpp 2012-03-30 23:29:11 UTC (rev 1233)
+++ trunk/OpenMPT/soundlib/Load_umx.cpp 2012-03-31 17:46:38 UTC (rev 1234)
@@ -1,10 +1,10 @@
/*
* Load_umx.cpp
* ------------
- * Purpose: UMX (Unreal Music) module ripper
+ * Purpose: UMX (Unreal Music) and UAX (Unreal Sounds) module ripper
* Notes : Obviously, this code only rips modules from older Unreal Engine games, such as Unreal 1, Unreal Tournament 1 and Deus Ex.
- * Authors: Olivier Lapicque
- * OpenMPT Devs
+ * For UAX sound packages, the sounds are read into module sample slots.
+ * Authors: Johannes Schultz (inspired by code from http://wiki.beyondunreal.com/Legacy:Package_File_Format)
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
@@ -12,47 +12,297 @@
#include "stdafx.h"
#include "Loaders.h"
-#define MODMAGIC_OFFSET (20 + 31 * 30 + 130)
+#pragma pack(push, 1)
+// UMX File Header
+struct UMXFileHeader
+{
+ // Magic Bytes
+ enum UMXMagic
+ {
+ magicBytes = 0x9E2A83C1,
+ };
-bool CSoundFile::ReadUMX(const BYTE *lpStream, const DWORD dwMemLength)
-//---------------------------------------------------------------------
+ uint32 magic;
+ uint16 packageVersion;
+ uint16 licenseMode;
+ uint32 flags;
+ uint32 nameCount;
+ uint32 nameOffset;
+ uint32 exportCount;
+ uint32 exportOffset;
+ uint32 importCount;
+ uint32 importOffset;
+
+ // Convert all multi-byte numeric values to current platform's endianness or vice versa.
+ void ConvertEndianness()
+ {
+ SwapBytesLE(magic);
+ SwapBytesLE(packageVersion);
+ // Don't need the rest.
+ SwapBytesLE(nameCount);
+ SwapBytesLE(nameOffset);
+ SwapBytesLE(exportCount);
+ SwapBytesLE(exportOffset);
+ SwapBytesLE(importCount);
+ SwapBytesLE(importOffset);
+ }
+};
+
+#pragma pack(pop)
+
+
+// Read compressed unreal integers - similar to MIDI integers, but signed values are possible.
+int32 ReadUMXIndex(FileReader &chunk)
+//-----------------------------------
{
- if ((!lpStream) || (dwMemLength < 0x800)) return false;
- // Rip Mods from UMX
- if ((LittleEndian(*((DWORD *)(lpStream+0x20))) < dwMemLength)
- && (LittleEndian(*((DWORD *)(lpStream+0x18))) <= dwMemLength - 0x10)
- && (LittleEndian(*((DWORD *)(lpStream+0x18))) >= dwMemLength - 0x200))
+ enum
{
- for (UINT uscan=0x40; uscan<0x500; uscan++)
+ signMask = 0x80, // Highest bit of first byte indicates if value is signed
+ valueMask1 = 0x3F, // Low 6 bits of first byte are actual value
+ continueMask1 = 0x40, // Second-highest bit of first byte indicates if further bytes follow
+ valueMask = 0x7F, // Low 7 bits of following bytes are actual value
+ continueMask = 0x80, // Highest bit of following bytes indicates if further bytes follow
+ };
+
+ // Read first byte
+ uint8 b = chunk.ReadUint8();
+ bool isSigned = (b & signMask) != 0;
+ int32 result = (b & valueMask1);
+ int shift = 6;
+
+ if(b & continueMask1)
+ {
+ // Read remaining bytes
+ do
{
- DWORD dwScan = LittleEndian(*((DWORD *)(lpStream+uscan)));
- // IT
- if (dwScan == 0x4D504D49)
+ b = chunk.ReadUint8();
+ int32 data = static_cast<int32>(b) & valueMask;
+ data <<= shift;
+ result |= data;
+ shift += 7;
+ } while((b & continueMask) != 0 && (shift < 32));
+ }
+
+ if(isSigned)
+ {
+ result = -result;
+ }
+ return result;
+}
+
+
+// Read an entry from the name table.
+std::string ReadUMXNameTableEntry(FileReader &chunk, uint16 packageVersion)
+//-------------------------------------------------------------------------
+{
+ std::string name;
+ if(packageVersion >= 64)
+ {
+ // String length
+ int32 length = ReadUMXIndex(chunk);
+ if(length <= 0)
+ {
+ return "";
+ }
+ name.reserve(length);
+ }
+
+ // Simple zero-terminated string
+ uint8 chr;
+ while((chr = chunk.ReadUint8()) != 0)
+ {
+ // Convert string to lower case
+ if(chr >= 'A' && chr <= 'Z')
+ {
+ chr = chr - 'A' + 'a';
+ }
+ name.append(1, static_cast<char>(chr));
+ }
+
+ chunk.Skip(4); // Object flags
+ return name;
+}
+
+
+// Read an entry from the import table.
+int32 ReadUMXImportTableEntry(FileReader &chunk)
+//----------------------------------------------
+{
+ ReadUMXIndex(chunk); // Class package
+ ReadUMXIndex(chunk); // Class name
+ chunk.Skip(4); // Package
+ return ReadUMXIndex(chunk); // Object name (offset into the name table)
+}
+
+
+// Read an entry from the export table.
+void ReadUMXExportTableEntry(FileReader &chunk, int32 &objClass, int32 &objOffset, int32 &objSize, int32 &objName)
+//----------------------------------------------------------------------------------------------------------------
+{
+ objClass = ReadUMXIndex(chunk); // Object class
+ ReadUMXIndex(chunk); // Object parent
+ chunk.Skip(4); // Internal package / group of the object
+ objName = ReadUMXIndex(chunk); // Object name (offset into the name table)
+ chunk.Skip(4); // Object flags
+ objSize = ReadUMXIndex(chunk);
+ if(objSize > 0)
+ {
+ objOffset = ReadUMXIndex(chunk);
+ }
+}
+
+
+bool CSoundFile::ReadUMX(FileReader &file)
+//----------------------------------------
+{
+ file.Rewind();
+ UMXFileHeader fileHeader;
+ if(!file.ReadConvertEndianness(fileHeader)
+ || fileHeader.magic != UMXFileHeader::magicBytes
+ || !file.Seek(fileHeader.nameOffset))
+ {
+ return false;
+ }
+
+ // Read name table
+ std::vector<std::string> names;
+ names.reserve(fileHeader.nameCount);
+ for(uint32 i = 0; i < fileHeader.nameCount; i++)
+ {
+ names.push_back(ReadUMXNameTableEntry(file, fileHeader.packageVersion));
+ }
+
+ // Read import table
+ if(!file.Seek(fileHeader.importOffset))
+ {
+ return false;
+ }
+
+ std::vector<int32> classes;
+ classes.reserve(fileHeader.importCount);
+ for(uint32 i = 0; i < fileHeader.importCount; i++)
+ {
+ int32 objName = ReadUMXImportTableEntry(file);
+ if(static_cast<size_t>(objName) < names.size())
+ {
+ classes.push_back(objName);
+ }
+ }
+
+ // Read export table
+ if(!file.Seek(fileHeader.exportOffset))
+ {
+ return false;
+ }
+
+ // Now we can be pretty sure that we're doing the right thing.
+ m_nType = MOD_TYPE_UMX;
+ m_nSamples = 0;
+ m_nInstruments = 0;
+
+ for(uint32 i = 0; i < fileHeader.exportCount; i++)
+ {
+ int32 objClass, objOffset, objSize, objName;
+ ReadUMXExportTableEntry(file, objClass, objOffset, objSize, objName);
+
+ if(objSize <= 0 || objClass >= 0)
+ {
+ continue;
+ }
+
+ // Look up object class name (we only want music and sounds).
+ objClass = -objClass - 1;
+ bool isMusic = false, isSound = false;
+ if(static_cast<size_t>(objClass) < classes.size())
+ {
+ const char *objClassName = names[classes[objClass]].c_str();
+ isMusic = !strcmp(objClassName, "music");
+ isSound = !strcmp(objClassName, "sound");
+ }
+ if(!isMusic && !isSound)
+ {
+ continue;
+ }
+
+ FileReader chunk = file.GetChunk(objOffset, objSize);
+
+ if(chunk.IsValid())
+ {
+ // Read object properties
+ size_t propertyName = static_cast<size_t>(ReadUMXIndex(chunk));
+ if(propertyName >= names.size() || strcmp(names[propertyName].c_str(), "none"))
{
- DWORD dwRipOfs = uscan;
- return ReadIT(lpStream + dwRipOfs, dwMemLength - dwRipOfs);
+ // Can't bother to implement property reading, as no UMX or UAX files I've seen so far use properties for the relevant objects.
+ // If it should be necessary to implement this, check CUnProperty.cpp in http://ut-files.com/index.php?dir=Utilities/&file=utcms_source.zip
+ ASSERT(false);
+ continue;
}
- // S3M
- if (dwScan == 0x4D524353)
+
+ if(fileHeader.packageVersion >= 120)
{
- DWORD dwRipOfs = uscan - 44;
- return ReadS3M(lpStream + dwRipOfs, dwMemLength - dwRipOfs);
- }
- // XM
- if (!_strnicmp((LPCSTR)(lpStream+uscan), "Extended Module", 15))
+ // UT2003 Packages
+ ReadUMXIndex(chunk);
+ chunk.Skip(8);
+ } else if(fileHeader.packageVersion >= 100)
{
- DWORD dwRipOfs = uscan;
- return ReadXM(lpStream + dwRipOfs, dwMemLength - dwRipOfs);
+ // AAO Packages
+ chunk.Skip(4);
+ ReadUMXIndex(chunk);
+ chunk.Skip(4);
+ } else if(fileHeader.packageVersion >= 62)
+ {
+ // UT Packages
+ // Mech8.umx and a few other UT tunes have packageVersion = 62.
+ // In CUnSound.cpp, the condition above reads "packageVersion >= 63" but if that is used, those tunes won't load properly.
+ ReadUMXIndex(chunk);
+ chunk.Skip(4);
+ } else
+ {
+ // Old Unreal Packagaes
+ ReadUMXIndex(chunk);
}
- // MOD
- if ((uscan > MODMAGIC_OFFSET) && (dwScan == '.K.M'))
+ int32 size = ReadUMXIndex(chunk);
+
+ FileReader fileChunk = chunk.GetChunk(size);
+ // TODO: Use FileReader for those file types
+ const BYTE *data = reinterpret_cast<const BYTE *>(fileChunk.GetRawData());
+
+ if(isMusic)
{
- DWORD dwRipOfs = uscan - MODMAGIC_OFFSET;
- return ReadMod(lpStream+dwRipOfs, dwMemLength-dwRipOfs);
+ // Read as module
+ if(ReadIT(data, fileChunk.GetLength())
+ || ReadXM(data, fileChunk.GetLength())
+ || ReadS3M(data, fileChunk.GetLength())
+ || ReadWav(data, fileChunk.GetLength())
+ || ReadMod(data, fileChunk.GetLength()))
+ {
+ return true;
+ }
+ } else if(isSound && GetNumSamples() + 1 < MAX_SAMPLES)
+ {
+ // Read as sample
+ if(ReadSampleFromFile(GetNumSamples() + 1, (LPBYTE)data, fileChunk.GetLength()))
+ {
+ m_nSamples++;
+ if(static_cast<size_t>(objName) < names.size())
+ {
+ strncpy(m_szNames[GetNumSamples()], names[objName].c_str(), MAX_SAMPLENAME - 1);
+ }
+ }
}
}
}
- return false;
+
+ if(m_nSamples != 0)
+ {
+ m_nChannels = 4;
+ Patterns.Insert(0, 64);
+ Order[0] = 0;
+ return true;
+ } else
+ {
+ m_nType = MOD_TYPE_NONE;
+ return false;
+ }
}
-
Modified: trunk/OpenMPT/soundlib/Sndfile.cpp
===================================================================
--- trunk/OpenMPT/soundlib/Sndfile.cpp 2012-03-30 23:29:11 UTC (rev 1233)
+++ trunk/OpenMPT/soundlib/Sndfile.cpp 2012-03-31 17:46:38 UTC (rev 1234)
@@ -638,7 +638,7 @@
&& (!ReadUlt(lpStream, dwMemLength))
&& (!ReadDMF(lpStream, dwMemLength))
&& (!ReadDSM(lpStream, dwMemLength))
- && (!ReadUMX(lpStream, dwMemLength))
+ && (!ReadUMX(file))
&& (!ReadAMF(lpStream, dwMemLength))
&& (!ReadPSM(file))
&& (!ReadPSM16(file))
Modified: trunk/OpenMPT/soundlib/Sndfile.h
===================================================================
--- trunk/OpenMPT/soundlib/Sndfile.h 2012-03-30 23:29:11 UTC (rev 1233)
+++ trunk/OpenMPT/soundlib/Sndfile.h 2012-03-31 17:46:38 UTC (rev 1234)
@@ -387,7 +387,7 @@
bool ReadMT2(const LPCBYTE lpStream, const DWORD dwMemLength);
bool ReadPSM(FileReader &file);
bool ReadPSM16(FileReader &file);
- bool ReadUMX(const LPCBYTE lpStream, const DWORD dwMemLength);
+ bool ReadUMX(FileReader &file);
bool ReadMO3(const LPCBYTE lpStream, const DWORD dwMemLength);
bool ReadGDM(FileReader &file);
bool ReadIMF(const LPCBYTE lpStream, const DWORD dwMemLength);
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|