Skip to content

Commit

Permalink
Finished adding support for MP3 based SMFs, other minor changes
Browse files Browse the repository at this point in the history
- Enabled Hi DPI compat mode for better display higher desnity screens
- Changed case of audio related classes to standard cammel case for
consistancy
- Corrected accidental +1 in length for MP3 signatures
- Updated README
  • Loading branch information
oblivioncth committed Feb 20, 2021
1 parent d6a511e commit edeecbc
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 154 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# BSCWorks

BSCWorks is a somewhat feature rich editor for the Burnt Sound Container format, version 14, that Company of Heroes uses and also provides several tools for handling related files. This editor is about a decade too late to be relevant, but was made to more easily edit sound related aspects of the game in the unreleased quality-of-life mod I develop in my spare time for use with a small group of friends/family that still play the original. I am sharing it here in the hopes that it could be useful the remnants of CoH modders or research purposes, such as a starting point for examining other versions of this format from other Relic Games (i.e. CoH 2, Dawn of War, Warhammer 40k, etc.), or if someone ever decides to make a source port of the original game (if we are lucky enough to ever get the source, instead of just a really random licensed iPad port >>) .
Expand All @@ -15,7 +14,7 @@ BSCWorks is a somewhat feature rich editor for the Burnt Sound Container format,
- Sound Container settings
- Mostly complete editing of Sound Containers including sound container order insertion/removal of new/existing Sound Containers
- Basic editing of *uninterpreted* Effect Container data
- Single and batch conversion of both SMF->WAV and WAV->SMF
- Single and batch conversion of both SMF->WAV/MP3 and WAV/MP3->SMF
- Creation and updating of Speechmanager Caches that performs byte-level validity checking, avoids adding redundant entries and removes any existing duplicates from the cache

## Usage
Expand Down Expand Up @@ -63,7 +62,7 @@ There are still considerable unknowns regarding the BSC format that unfortunatel
- [ ] Add support for window resizing/fullscreen

## Source
This tool was written in C++ 17 along with Qt 5 and currently only targets Windows Vista and above; however, this tool can easily be ported to Linux with minimal changes, though to what end I am not sure since this is for a Windows game. The source includes an easy-to-use .pro file if you wish to build the application in Qt Creator and the available latest release was compiled in Qt Creator 4.12.0 using MSVC 2019 and a static compilation of Qt 5.14.0. Other than a C++ 17 capable compiler and Qt 5.14.x+ all files required to compile this software are included, with the exception of a standard make file.
This tool was written in C++ 17 along with Qt 5 and currently only targets Windows Vista and above; however, this tool can easily be ported to Linux with minimal changes, though to what end I am not sure since this is for a Windows game. The source includes an easy-to-use .pro file if you wish to build the application in Qt Creator and the available latest release was compiled in Qt Creator 4.12.0 using MSVC 2019 and a static compilation of Qt 5.15.0. Other than a C++ 17 capable compiler and Qt 5.15.x+ all files required to compile this software are included, with the exception of a standard make file.

All functions/variables under the "Qx" (QExtended) namespace belong to a small, personal library I maintain to always have access to frequently used functionality in my projects. A pre-compiled static version of this library is provided with the source for this tool. If anyone truly needs it, I can provide the source for this library as well.

Expand Down
1 change: 1 addition & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

int main(int argc, char *argv[])
{
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication a(argc, argv);
MainWindow w;
w.show();
Expand Down
148 changes: 79 additions & 69 deletions src/mainwindow.cpp

Large diffs are not rendered by default.

47 changes: 25 additions & 22 deletions src/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include "bsc.h"
#include "smc.h"
#include "smf.h"
#include "wav.h"

#include <QMainWindow>
#include <QTreeWidget>
Expand Down Expand Up @@ -41,7 +40,7 @@ class MainWindow : public QMainWindow
"BSC inoperable if performed incorrectly. Only edit this section if you know what you're doing.";
static inline const QString MSG_EFF_CON_EDIT_WARN_INFO_TXT = "Effect Container count must be 1 or greater before any data can be changed.";

static inline const QString MENU_CACHE_FILE_FILTER = "Speechmanager Cache (*." + SMC::FILE_EXT + ")";
static inline const QString MENU_CACHE_FILE_FILTER = "Speechmanager Cache (*." + Smc::FILE_EXT + ")";
static inline const QString MENU_CACHE_OPEN_TITLE = "Select Speechmanager Cache";
static inline const QString MENU_CACHE_FOLDER_TITLE = "Select folder to scan for BSC files";
static inline const QString MSG_CACHE_INVALID = """%1"" is not a valid Speechmanager Cache!";
Expand All @@ -64,20 +63,24 @@ class MainWindow : public QMainWindow
static inline const QString MSG_CACHE_SCAN_ABORT = "Abort";
static inline const QString MSG_CACHE_SCAN_ABORTED = "Speechmanager cache update aborted by user.";

static inline const QString MENU_SMF_FILE_FILTER = "SMF Audio (*." + SMF::FILE_EXT + ")";
static inline const QString MENU_WAV_FILE_FILTER = "WAVE Audio (*." + WAV::FILE_EXT + ")";
static inline const QString MENU_SMF_FILE_FILTER = "SMF Audio (*." + Smf::FILE_EXT + ")";
static inline const QString MENU_WAV_FILE_FILTER = "WAVE Audio (*." + Wav::FILE_EXT + ")";
static inline const QString MENU_MP3_FILE_FILTER = "MP3 Audio (*." + Mp3::FILE_EXT + ")";
static inline const QString MENU_WAV_MP3_FILE_FILTER = "WAVE/MP3 Audio (*." + Wav::FILE_EXT + ", *." + Mp3::FILE_EXT + ")";

static inline const QString MENU_CONV_SMF_IN_TITLE = "Select SMF to convert";
static inline const QString MENU_CONV_SMF_OUT_TITLE = "Enter output WAV destination";
static inline const QString MENU_CONV_SMF_OUT_WAV_TITLE = "Enter output WAV destination";
static inline const QString MENU_CONV_SMF_OUT_MP3_TITLE = "Enter output MP3 destination";
static inline const QString MSG_CONV_SMF_INVALID = """%1"" is not a valid SMF file!";
static inline const QString MSG_CONV_SMF_SUCCESS = "Conversion completed successfully.";
static inline const QString MSG_CONV_SMF_FAIL = "Error writting ""%1""";

static inline const QString MENU_CONV_WAV_IN_TITLE = "Select WAV to convert";
static inline const QString MENU_CONV_WAV_OUT_TITLE = "Enter output SMF destination";
static inline const QString MENU_CONV_WAV_MP3_IN_TITLE = "Select WAV/MP3 to convert";
static inline const QString MENU_CONV_WAV_MP3_OUT_TITLE = "Enter output SMF destination";
static inline const QString MSG_CONV_WAV_INVALID = """%1"" is not a valid WAVE file!";
static inline const QString MSG_CONV_WAV_SUCCESS = "Conversion completed successfully.";
static inline const QString MSG_CONV_WAV_FAIL = "Error writting ""%1""";
static inline const QString MSG_CONV_MP3_INVALID = """%1"" is not a valid MP3 file!";
static inline const QString MSG_CONV_WAV_MP3_SUCCESS = "Conversion completed successfully.";
static inline const QString MSG_CONV_WAV_MP3_FAIL = "Error writting ""%1""";

static inline const QString MSG_BATCH_ALL_FILE_EXST = """%1"" already exists. Do you want to overwrite this file?";
static inline const QString MSG_BATCH_ALL_ABORT = "Abort Conversion";
Expand All @@ -94,19 +97,19 @@ class MainWindow : public QMainWindow
static inline const QString MSG_BATCH_SMF_INV_FILES_INFO = "Some files were skipped due to read/write issues. See details below";
static inline const QString MSG_BATCH_SMF_INV_FILES_DETAILS = "The following files were skipped:";
static inline const QString MSG_BATCH_SMF_INV_FILES_CAT_BAD = "(Invalid data)";
static inline const QString MSG_BATCH_SMF_IN_PROGRESS = "Converting SMF files to WAV...";

static inline const QString MENU_BATCH_WAV_IN_TITLE = "Select folder to scan for WAV files";
static inline const QString MENU_BATCH_WAV_OUT_TITLE = "Select output folder";
static inline const QString MSG_BATCH_WAV_SUB_PROMPT = "Include sub-directories?";
static inline const QString MSG_BATCH_WAV_NO_FILES = "No WAV files found in %1";
static inline const QString MSG_BATCH_WAV_NO_VALID_FILES_TXT = "No valid/convertable WAV files found in %1";
static inline const QString MSG_BATCH_WAV_NO_VALID_FILES_INFO = "All present WAV files are corrupt, or experienced IO errors during conversion!";
static inline const QString MSG_BATCH_WAV_SUCCESS_TXT = "Batch WAV conversion completed successfully.";
static inline const QString MSG_BATCH_WAV_INV_FILES_INFO = "Some files were skipped due to read/write issues. See details below";
static inline const QString MSG_BATCH_WAV_INV_FILES_DETAILS = "The following files were skipped:";
static inline const QString MSG_BATCH_WAV_INV_FILES_CAT_BAD = "(Invalid data)";
static inline const QString MSG_BATCH_WAV_IN_PROGRESS = "Converting WAV files to SMF...";
static inline const QString MSG_BATCH_SMF_IN_PROGRESS = "Converting SMF files to WAV/MP3...";

static inline const QString MENU_BATCH_WAV_MP3_IN_TITLE = "Select folder to scan for WAV/MP3 files";
static inline const QString MENU_BATCH_WAV_MP3_OUT_TITLE = "Select output folder";
static inline const QString MSG_BATCH_WAV_MP3_SUB_PROMPT = "Include sub-directories?";
static inline const QString MSG_BATCH_WAV_MP3_NO_FILES = "No WAV/MP3 files found in %1";
static inline const QString MSG_BATCH_WAV_MP3_NO_VALID_FILES_TXT = "No valid/convertable WAV/MP3 files found in %1";
static inline const QString MSG_BATCH_WAV_MP3_NO_VALID_FILES_INFO = "All present WAV/MP3 files are corrupt, or experienced IO errors during conversion!";
static inline const QString MSG_BATCH_WAV_MP3_SUCCESS_TXT = "Batch WAV/MP3 conversion completed successfully.";
static inline const QString MSG_BATCH_WAV_MP3_INV_FILES_INFO = "Some files were skipped due to read/write issues. See details below";
static inline const QString MSG_BATCH_WAV_MP3_INV_FILES_DETAILS = "The following files were skipped:";
static inline const QString MSG_BATCH_WAV_MP3_INV_FILES_CAT_BAD = "(Invalid data)";
static inline const QString MSG_BATCH_WAV_MP3_IN_PROGRESS = "Converting WAV/MP3 files to SMF...";

static inline const QString MSG_ABOUT_TXT = "BSCWorks " + QString::fromStdString(VER_PRODUCTVERSION_STR) + " @oblivioncth";
static inline const QString MSG_ABOUT_INFO_TXT = "Distrubted under the GNU General Public License V3.0. See " + QString::fromStdString(VER_COMPANYDOMAIN_STR) + "/BSCWorks for more information.";
Expand Down
4 changes: 2 additions & 2 deletions src/mainwindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1594,9 +1594,9 @@
</property>
<property name="geometry">
<rect>
<x>208</x>
<x>29</x>
<y>380</y>
<width>542</width>
<width>901</width>
<height>51</height>
</rect>
</property>
Expand Down
14 changes: 8 additions & 6 deletions src/mp3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@

//-Constructor------------------------------------------------------------------------------------------------
//Public:
MP3::MP3(QByteArray rawMP3Data) : mFileDataF(rawMP3Data)
Mp3::Mp3() {}

Mp3::Mp3(QByteArray rawMP3Data) : mFileDataF(rawMP3Data)
{
if(!fileIsValidMP3())
if(!fileIsValidMp3())
mFileDataF = QByteArray();
}

//-Instance Functions------------------------------------------------------------------------------------------------
//Private:
bool MP3::fileIsValidMP3()
bool Mp3::fileIsValidMp3()
{
// Get header region
QByteArray header = mFileDataF.left(0x04);
QByteArray header = mFileDataF.left(0x03);

QByteArray mp3OldSigRegion = header.left(L_MP3_OLD_SIG);
QByteArray mp3NewSigRegion = header;
Expand All @@ -29,5 +31,5 @@ bool MP3::fileIsValidMP3()
}

//Public:
bool MP3::isValid() { return !mFileDataF.isNull(); }
QByteArray MP3::getFullData() { return mFileDataF; }
bool Mp3::isValid() { return !mFileDataF.isNull(); }
QByteArray Mp3::getFullData() { return mFileDataF; }
11 changes: 6 additions & 5 deletions src/mp3.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "qx.h"
#include "qx-io.h"

class MP3
class Mp3
{
//-Class Variables-----------------------------------------------------------------------------------------------
public:
Expand All @@ -13,20 +13,21 @@ class MP3
static inline const QByteArray MP3_OLD_SIG_2 = Qx::ByteArray::RAWFromStringHex("FFF3");
static inline const QByteArray MP3_OLD_SIG_3 = Qx::ByteArray::RAWFromStringHex("FFF2");
static inline const QByteArray MP3_NEW_SIG = Qx::ByteArray::RAWFromStringHex("494433");
static const int L_MP3_OLD_SIG = 0x03;
static const int L_MP3_NEW_SIG = 0x04;
static const int L_MP3_OLD_SIG = 0x02;
static const int L_MP3_NEW_SIG = 0x03;

//-Instance Variables--------------------------------------------------------------------------------------------
private:
QByteArray mFileDataF;

//-Constructor-------------------------------------------------------------------------------------------------
public:
MP3(QByteArray rawMP3Data);
Mp3();
Mp3(QByteArray rawMP3Data);

//-Instance Functions---------------------------------------------------------------------------------------------------
private:
bool fileIsValidMP3();
bool fileIsValidMp3();

public:
bool isValid();
Expand Down
20 changes: 10 additions & 10 deletions src/smc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,34 @@
//-Constructor------------------------------------------------------------------------------------------------
//Public:

SMC::SMC(QByteArray rawData)
Smc::Smc(QByteArray rawData)
: mFileDataF(rawData)
{
mValidFile = separateData();
if(mValidFile)
interpretData();
}

SMC::SMC()
Smc::Smc()
{
mValidFile = true;
}

//-Instance Functions------------------------------------------------------------------------------------------------
//Public:
QStringList& SMC::getInterpretedCacheListR() { return mCacheListI; }
QStringList SMC::getInterpretedCacheListV() { return mCacheListI; }
bool SMC::isValidCache() { return mValidFile; }
QStringList& Smc::getInterpretedCacheListR() { return mCacheListI; }
QStringList Smc::getInterpretedCacheListV() { return mCacheListI; }
bool Smc::isValidCache() { return mValidFile; }

void SMC::deinterpretData()
void Smc::deinterpretData()
{
mCacheListR.clear();

for (int i = 0; i< mCacheListI.length(); i++)
mCacheListR.append(Qx::ByteArray::RAWFromString(mCacheListI.value(i)));
}

QByteArray SMC::rebuildRawFile()
QByteArray Smc::rebuildRawFile()
{
QByteArray rawFile;

Expand All @@ -51,7 +51,7 @@ QByteArray SMC::rebuildRawFile()
}

//Private:
bool SMC::separateData()
bool Smc::separateData()
{
bool fileIsValid = true;
mDataCursor = 0x00; // Start of data
Expand Down Expand Up @@ -80,13 +80,13 @@ bool SMC::separateData()
return true;
}

void SMC::interpretData()
void Smc::interpretData()
{
for (int i = 0; i< mCacheListR.length(); i++)
mCacheListI.append(Qx::String::fromByteArrayDirectly(mCacheListR.value(i)));
}

bool SMC::rawEntryIsValid(QByteArray entry)
bool Smc::rawEntryIsValid(QByteArray entry)
{
for(int i = 0; i < entry.length(); i++)
{
Expand Down
6 changes: 3 additions & 3 deletions src/smc.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include <QByteArray>
#include <QByteArrayList>

class SMC
class Smc
{
//-Class Variables-----------------------------------------------------------------------------------------------
public:
Expand Down Expand Up @@ -34,8 +34,8 @@ class SMC

//-Constructor-------------------------------------------------------------------------------------------------
public:
SMC(QByteArray rawData);
SMC();
Smc(QByteArray rawData);
Smc();

//-Class Functions------------------------------------------------------------------------------------------------------
public:
Expand Down
29 changes: 15 additions & 14 deletions src/smf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@

//-Constructor------------------------------------------------------------------------------------------------
//Public:
SMF::SMF(QByteArray rawData) : mFileDataF(rawData)
Smf::Smf(QByteArray rawData) : mFileDataF(rawData)
{
if(!fileIsValidSMF())
mFileDataF = QByteArray();
}

//-Class Functions---------------------------------------------------------------------------------------------------
SMF SMF::fromStandard(WAV wavData) { return addSMFHeader(wavData.getFullData()); }
SMF SMF::fromStandard(MP3 mp3Data) { return addSMFHeader(mp3Data.getFullData()); }
Smf Smf::fromStandard(Wav wavData) { return addSMFHeader(wavData.getFullData()); }
Smf Smf::fromStandard(Mp3 mp3Data) { return addSMFHeader(mp3Data.getFullData()); }

//-Instance Functions------------------------------------------------------------------------------------------------
//Private:
bool SMF::fileIsValidSMF()
bool Smf::fileIsValidSMF()
{
// Get header region
QByteArray headers = mFileDataF.left(0x1B);
Expand All @@ -34,14 +34,14 @@ bool SMF::fileIsValidSMF()
if(smfSigRegion == SMF_SIG)
{
// WAV Check
if(toWAV().isValid())
if(toWav().isValid())
{
mType = Wav;
mType = WAVE;
return true;
}
else if(toMP3().isValid()) // MP3 Check
else if(toMp3().isValid()) // MP3 Check
{
mType = Mp3;
mType = MP3;
return true;
}
}
Expand All @@ -50,14 +50,15 @@ bool SMF::fileIsValidSMF()
return false;
}

QByteArray SMF::addSMFHeader(const QByteArray& fileData)
QByteArray Smf::addSMFHeader(const QByteArray& fileData)
{
return Qx::ByteArray::RAWFromString(SMF::SMF_SIG) + QByteArray(SMF::SMF_CMN_FLAGS, 8) + fileData;
return Qx::ByteArray::RAWFromString(Smf::SMF_SIG) + QByteArray(Smf::SMF_CMN_FLAGS, 8) + fileData;
}

//Public:
bool SMF::isValid() { return !mFileDataF.isNull(); }
QByteArray SMF::getFullData() { return mFileDataF; }
WAV SMF::toWAV() { return WAV(mFileDataF.mid(L_SMF_SIG + L_SMF_CMN_FLAGS)); } // Uses unknown flag values that seem to be common to a huge majority of SMFs
MP3 SMF::toMP3() { return MP3(mFileDataF.mid(L_SMF_SIG + L_SMF_CMN_FLAGS)); } // Uses unknown flag values that seem to be common to a huge majority of SMFs
bool Smf::isValid() { return !mFileDataF.isNull(); }
Smf::Type Smf::getType() { return mType; }
QByteArray Smf::getFullData() { return mFileDataF; }
Wav Smf::toWav() { return Wav(mFileDataF.mid(L_SMF_SIG + L_SMF_CMN_FLAGS)); } // Uses unknown flag values that seem to be common to a huge majority of SMFs
Mp3 Smf::toMp3() { return Mp3(mFileDataF.mid(L_SMF_SIG + L_SMF_CMN_FLAGS)); } // Uses unknown flag values that seem to be common to a huge majority of SMFs

15 changes: 8 additions & 7 deletions src/smf.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
#include "mp3.h"
#include "qx-io.h"

class SMF
class Smf
{
//-Class Enums---------------------------------------------------------------------------------------------------
public:
enum Type {Wav, Mp3};
enum Type {WAVE, MP3};

//-Class Variables-----------------------------------------------------------------------------------------------
public:
Expand All @@ -33,25 +33,26 @@ class SMF

//-Constructor-------------------------------------------------------------------------------------------------
public:
SMF(QByteArray rawSMFData);
Smf(QByteArray rawSMFData);

//-Class Functions-------------------------------------------------------------------------------------------------------
private:
static QByteArray addSMFHeader(const QByteArray& fileData);

public:
static SMF fromStandard(WAV wavData);
static SMF fromStandard(MP3 mp3Data);
static Smf fromStandard(Wav wavData);
static Smf fromStandard(Mp3 mp3Data);

//-Instance Functions---------------------------------------------------------------------------------------------------
private:
bool fileIsValidSMF();

public:
bool isValid();
Type getType();
QByteArray getFullData();
WAV toWAV();
MP3 toMP3();
Wav toWav();
Mp3 toMp3();
};

#endif // SMF_H
Loading

0 comments on commit edeecbc

Please sign in to comment.