Skip to main content
Commonmark migration
Source Link

###adf2mp3.cpp:

adf2mp3.cpp:

###adf2mp3.cpp:

adf2mp3.cpp:

Notice removed Improve details by glampert
Bounty Ended with edmz's answer chosen by glampert
Notice added Improve details by glampert
Bounty Started worth 50 reputation by glampert
Small improvements in the question's text.
Source Link
glampert
  • 17.2k
  • 4
  • 30
  • 89

I wrote a tiny command line tool that converts ADF audio files offrom the Grand Theft Auto - Vice City game to MP3 files that can be played outside the game.

The ADF file format stores the game soundtracks and radio stations. These in turn are just MP3 files that had every byte XORedXORed with the decimal constant 34.

// Standard C++ includes:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

// Standard C / POSIX includes:
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>

namespace adf2mp3
{

using ubyte = unsigned char;
using ulong = unsigned long;

//
// The GTA Vice City ADF files are MP3 files that
// had each byte XORed with this magic constant.
// 34 is 22 in hexadecimal and 42 in octal...
//
// Not sure who figured this out, but I got this
// info from the Xentax File Format Wiki.
//
constexpr ubyte GtaMagic = 34;

//
// Process the input file in chunks of this size in bytes.
// The chunk buffer is allocated dynamically (it is a std::vector)
// so this can be a fairly large value.
//
constexpr ulong ChunkSize = 8192;

// ========================================================

struct FileStats
{
    ulong fileLength;   // File length in bytes.
    bool  isDirectory;  // True if the path provided pointed to a directory.
    bool  isNormalFile; // True is the path provided pointed to a file.
};

FileStats fileStatsForPath(const std::string & pathname)
{
    errno = 0; // Clear the errno global, just in case.
    struct stat statBuf;

    if (stat(pathname.c_str(), &statBuf) != 0)
    {
        throw std::runtime_error("Path '" + pathname + "': " + std::string(std::strerror(errno)));
    }

    FileStats fs;
    fs.fileLength = static_cast<ulong>(statBuf.st_size);

#if _WIN32
    fs.isDirectory  = (statBuf.st_mode & _S_IFDIR) ? true : false;
    fs.isNormalFile = (statBuf.st_mode & _S_IFREG) ? true : false;
#else // !_WIN32
    fs.isDirectory  = S_ISDIR(statBuf.st_mode) ? true : false;
    fs.isNormalFile = S_ISREG(statBuf.st_mode) ? true : false;
#endif // _WIN32

    return fs;
}

std::string removeExtension(const std::string & filename)
{
    const auto lastDot = filename.find_last_of('.');
    if (lastDot == std::string::npos)
    {
        return filename;
    }
    return filename.substr(0, lastDot);
}

void printHelpText(const char * programName)
{
    std::cout << "\n";
    std::cout << "Usage:\n";
    std::cout << "$ " << programName << " <input_file> [output_file]\n";
    std::cout << "  Runs the tool normally. If the output filename is not provided\n" <<
        "  the input filename is used but the extension is replaced with '.mp3'.\n";

    std::cout << "\n";
    std::cout << "Usage:\n";
    std::cout << "$ " << programName << " --help | -h\n";
    std::cout << "  Prints this help text.\n";
    std::cout << "\n";
}

void handleCommandLine(int argc, const char * argv[],
                       std::string & inFileName, std::string & outFileName,
                       bool & printHelpOnly, bool & missingArgs)
{
    if (argc < 2)
    {
        missingArgs = true;
        return;
    }

    if (std::strcmp(argv[1], "--help") == 0 || std::strcmp(argv[1], "-h") == 0)
    {
        printHelpOnly = true;
        return;
    }

    // input_file + output_file
    if (argc >= 3)
    {
        inFileName  = argv[1];
        outFileName = argv[2];
    }
    else // Just input_file
    {
        inFileName = argv[1];
        outFileName.clear();
    }

    printHelpOnly = false;
    missingArgs   = false;
}

void processChunk(std::vector<ubyte> & chunk, ulong numBytes)
{
    for (ulong b = 0; b < numBytes; ++b)
    {
        chunk[b] ^= GtaMagic;
    }
}

void processFiles(const std::string & inFileName, const std::string & outFileName)
{
    const FileStats fileStats = fileStatsForPath(inFileName);

    if (fileStats.fileLength == 0)
    {
        throw std::runtime_error("Input file is empty!");
    }

    if (fileStats.isDirectory)
    {
        throw std::runtime_error("Input file '" + inFileName + "' is a directory!");
    }

    std::ifstream inFile(inFileName,   std::ifstream::in  | std::ifstream::binary);
    std::ofstream outFile(outFileName, std::ofstream::out | std::ofstream::binary);

    ulong bytesProcessed = 0;
    std::vector<ubyte> chunk(ChunkSize, 0);

    while (bytesProcessed != fileStats.fileLength)
    {
        const ulong bytesLeft = fileStats.fileLength - bytesProcessed;
        const ulong bytesThisIteration = std::min<ulong>(chunk.size(), bytesLeft);

        inFile.read(reinterpret_cast<char *>(chunk.data()), bytesThisIteration);
        processChunk(chunk, bytesThisIteration);
        outFile.write(reinterpret_cast<char *>(chunk.data()), bytesThisIteration);

        bytesProcessed += bytesThisIteration;
    }
}

void run(int argc, const char * argv[])
{
    bool printHelpOnly, missingArgs;
    std::string inFileName, outFileName;
    handleCommandLine(argc, argv, inFileName, outFileName, printHelpOnly, missingArgs);

    if (printHelpOnly)
    {
        printHelpText(argv[0]);
        return;
    }

    if (missingArgs)
    {
        std::cout << "Not enough arguments!\n";
        printHelpText(argv[0]);
        return;
    }

    // Replace .adf extension of source file with .mp3 and use
    // it for the output if no explicit filename was provided.
    if (outFileName.empty())
    {
        outFileName = removeExtension(inFileName) + ".mp3";
    }

    processFiles(inFileName, outFileName);
}

} // namespace adf2mp3 {}

int main(int argc, const char * argv[])
{
    try
    {
        adf2mp3::run(argc, argv);
        return 0;
    }
    catch (std::exception & e)
    {
        std::cerr << "ERROR: " << e.what() << std::endl;
        return -1;EXIT_FAILURE;
    }
}

Repository link. I'm looking for second opinions mostly. Please let me know if you seeIs there anything in the code that looks objectionableI can do to improve this? Nitpicking is also welcome.

I wrote a tiny command line tool that converts ADF audio files of the Grand Theft Auto - Vice City game to MP3 files that can be played outside the game.

The ADF file format stores the game soundtracks and radio stations. These in turn are just MP3 files that had every byte XORed with the constant 34.

// Standard C++ includes:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

// Standard C / POSIX includes:
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>

namespace adf2mp3
{

using ubyte = unsigned char;
using ulong = unsigned long;

//
// The GTA Vice City ADF files are MP3 files that
// had each byte XORed with this magic constant.
// 34 is 22 in hexadecimal and 42 in octal...
//
// Not sure who figured this out, but I got this
// info from the Xentax File Format Wiki.
//
constexpr ubyte GtaMagic = 34;

//
// Process the input file in chunks of this size in bytes.
// The chunk buffer is allocated dynamically (it is a std::vector)
// so this can be a fairly large value.
//
constexpr ulong ChunkSize = 8192;

// ========================================================

struct FileStats
{
    ulong fileLength;   // File length in bytes.
    bool  isDirectory;  // True if the path provided pointed to a directory.
    bool  isNormalFile; // True is the path provided pointed to a file.
};

FileStats fileStatsForPath(const std::string & pathname)
{
    errno = 0; // Clear the errno global, just in case.
    struct stat statBuf;

    if (stat(pathname.c_str(), &statBuf) != 0)
    {
        throw std::runtime_error("Path '" + pathname + "': " + std::string(std::strerror(errno)));
    }

    FileStats fs;
    fs.fileLength = static_cast<ulong>(statBuf.st_size);

#if _WIN32
    fs.isDirectory  = (statBuf.st_mode & _S_IFDIR) ? true : false;
    fs.isNormalFile = (statBuf.st_mode & _S_IFREG) ? true : false;
#else // !_WIN32
    fs.isDirectory  = S_ISDIR(statBuf.st_mode) ? true : false;
    fs.isNormalFile = S_ISREG(statBuf.st_mode) ? true : false;
#endif // _WIN32

    return fs;
}

std::string removeExtension(const std::string & filename)
{
    const auto lastDot = filename.find_last_of('.');
    if (lastDot == std::string::npos)
    {
        return filename;
    }
    return filename.substr(0, lastDot);
}

void printHelpText(const char * programName)
{
    std::cout << "\n";
    std::cout << "Usage:\n";
    std::cout << "$ " << programName << " <input_file> [output_file]\n";
    std::cout << "  Runs the tool normally. If the output filename is not provided\n" <<
        "  the input filename is used but the extension is replaced with '.mp3'.\n";

    std::cout << "\n";
    std::cout << "Usage:\n";
    std::cout << "$ " << programName << " --help | -h\n";
    std::cout << "  Prints this help text.\n";
    std::cout << "\n";
}

void handleCommandLine(int argc, const char * argv[],
                       std::string & inFileName, std::string & outFileName,
                       bool & printHelpOnly, bool & missingArgs)
{
    if (argc < 2)
    {
        missingArgs = true;
        return;
    }

    if (std::strcmp(argv[1], "--help") == 0 || std::strcmp(argv[1], "-h") == 0)
    {
        printHelpOnly = true;
        return;
    }

    // input_file + output_file
    if (argc >= 3)
    {
        inFileName  = argv[1];
        outFileName = argv[2];
    }
    else // Just input_file
    {
        inFileName = argv[1];
        outFileName.clear();
    }

    printHelpOnly = false;
    missingArgs   = false;
}

void processChunk(std::vector<ubyte> & chunk, ulong numBytes)
{
    for (ulong b = 0; b < numBytes; ++b)
    {
        chunk[b] ^= GtaMagic;
    }
}

void processFiles(const std::string & inFileName, const std::string & outFileName)
{
    const FileStats fileStats = fileStatsForPath(inFileName);

    if (fileStats.fileLength == 0)
    {
        throw std::runtime_error("Input file is empty!");
    }

    if (fileStats.isDirectory)
    {
        throw std::runtime_error("Input file '" + inFileName + "' is a directory!");
    }

    std::ifstream inFile(inFileName,   std::ifstream::in  | std::ifstream::binary);
    std::ofstream outFile(outFileName, std::ofstream::out | std::ofstream::binary);

    ulong bytesProcessed = 0;
    std::vector<ubyte> chunk(ChunkSize, 0);

    while (bytesProcessed != fileStats.fileLength)
    {
        const ulong bytesLeft = fileStats.fileLength - bytesProcessed;
        const ulong bytesThisIteration = std::min<ulong>(chunk.size(), bytesLeft);

        inFile.read(reinterpret_cast<char *>(chunk.data()), bytesThisIteration);
        processChunk(chunk, bytesThisIteration);
        outFile.write(reinterpret_cast<char *>(chunk.data()), bytesThisIteration);

        bytesProcessed += bytesThisIteration;
    }
}

void run(int argc, const char * argv[])
{
    bool printHelpOnly, missingArgs;
    std::string inFileName, outFileName;
    handleCommandLine(argc, argv, inFileName, outFileName, printHelpOnly, missingArgs);

    if (printHelpOnly)
    {
        printHelpText(argv[0]);
        return;
    }

    if (missingArgs)
    {
        std::cout << "Not enough arguments!\n";
        printHelpText(argv[0]);
        return;
    }

    // Replace .adf extension of source file with .mp3 and use
    // it for the output if no explicit filename was provided.
    if (outFileName.empty())
    {
        outFileName = removeExtension(inFileName) + ".mp3";
    }

    processFiles(inFileName, outFileName);
}

} // namespace adf2mp3 {}

int main(int argc, const char * argv[])
{
    try
    {
        adf2mp3::run(argc, argv);
        return 0;
    }
    catch (std::exception & e)
    {
        std::cerr << "ERROR: " << e.what() << std::endl;
        return -1;
    }
}

Repository link. I'm looking for second opinions. Please let me know if you see anything in the code that looks objectionable.

I wrote a tiny command line tool that converts ADF audio files from the Grand Theft Auto - Vice City game to MP3 files that can be played outside the game.

The ADF file format stores the game soundtracks and radio stations. These in turn are just MP3 files that had every byte XORed with the decimal constant 34.

// Standard C++ includes:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

// Standard C / POSIX includes:
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>

namespace adf2mp3
{

using ubyte = unsigned char;
using ulong = unsigned long;

//
// The GTA Vice City ADF files are MP3 files that
// had each byte XORed with this magic constant.
// 34 is 22 in hexadecimal and 42 in octal...
//
// Not sure who figured this out, but I got this
// info from the Xentax File Format Wiki.
//
constexpr ubyte GtaMagic = 34;

//
// Process the input file in chunks of this size in bytes.
// The chunk buffer is allocated dynamically (it is a std::vector)
// so this can be a fairly large value.
//
constexpr ulong ChunkSize = 8192;

// ========================================================

struct FileStats
{
    ulong fileLength;   // File length in bytes.
    bool  isDirectory;  // True if the path provided pointed to a directory.
    bool  isNormalFile; // True is the path provided pointed to a file.
};

FileStats fileStatsForPath(const std::string & pathname)
{
    errno = 0; // Clear the errno global, just in case.
    struct stat statBuf;

    if (stat(pathname.c_str(), &statBuf) != 0)
    {
        throw std::runtime_error("Path '" + pathname + "': " + std::string(std::strerror(errno)));
    }

    FileStats fs;
    fs.fileLength = static_cast<ulong>(statBuf.st_size);

#if _WIN32
    fs.isDirectory  = (statBuf.st_mode & _S_IFDIR) ? true : false;
    fs.isNormalFile = (statBuf.st_mode & _S_IFREG) ? true : false;
#else // !_WIN32
    fs.isDirectory  = S_ISDIR(statBuf.st_mode) ? true : false;
    fs.isNormalFile = S_ISREG(statBuf.st_mode) ? true : false;
#endif // _WIN32

    return fs;
}

std::string removeExtension(const std::string & filename)
{
    const auto lastDot = filename.find_last_of('.');
    if (lastDot == std::string::npos)
    {
        return filename;
    }
    return filename.substr(0, lastDot);
}

void printHelpText(const char * programName)
{
    std::cout << "\n";
    std::cout << "Usage:\n";
    std::cout << "$ " << programName << " <input_file> [output_file]\n";
    std::cout << "  Runs the tool normally. If the output filename is not provided\n" <<
        "  the input filename is used but the extension is replaced with '.mp3'.\n";

    std::cout << "\n";
    std::cout << "Usage:\n";
    std::cout << "$ " << programName << " --help | -h\n";
    std::cout << "  Prints this help text.\n";
    std::cout << "\n";
}

void handleCommandLine(int argc, const char * argv[],
                       std::string & inFileName, std::string & outFileName,
                       bool & printHelpOnly, bool & missingArgs)
{
    if (argc < 2)
    {
        missingArgs = true;
        return;
    }

    if (std::strcmp(argv[1], "--help") == 0 || std::strcmp(argv[1], "-h") == 0)
    {
        printHelpOnly = true;
        return;
    }

    // input_file + output_file
    if (argc >= 3)
    {
        inFileName  = argv[1];
        outFileName = argv[2];
    }
    else // Just input_file
    {
        inFileName = argv[1];
        outFileName.clear();
    }

    printHelpOnly = false;
    missingArgs   = false;
}

void processChunk(std::vector<ubyte> & chunk, ulong numBytes)
{
    for (ulong b = 0; b < numBytes; ++b)
    {
        chunk[b] ^= GtaMagic;
    }
}

void processFiles(const std::string & inFileName, const std::string & outFileName)
{
    const FileStats fileStats = fileStatsForPath(inFileName);

    if (fileStats.fileLength == 0)
    {
        throw std::runtime_error("Input file is empty!");
    }

    if (fileStats.isDirectory)
    {
        throw std::runtime_error("Input file '" + inFileName + "' is a directory!");
    }

    std::ifstream inFile(inFileName,   std::ifstream::in  | std::ifstream::binary);
    std::ofstream outFile(outFileName, std::ofstream::out | std::ofstream::binary);

    ulong bytesProcessed = 0;
    std::vector<ubyte> chunk(ChunkSize, 0);

    while (bytesProcessed != fileStats.fileLength)
    {
        const ulong bytesLeft = fileStats.fileLength - bytesProcessed;
        const ulong bytesThisIteration = std::min<ulong>(chunk.size(), bytesLeft);

        inFile.read(reinterpret_cast<char *>(chunk.data()), bytesThisIteration);
        processChunk(chunk, bytesThisIteration);
        outFile.write(reinterpret_cast<char *>(chunk.data()), bytesThisIteration);

        bytesProcessed += bytesThisIteration;
    }
}

void run(int argc, const char * argv[])
{
    bool printHelpOnly, missingArgs;
    std::string inFileName, outFileName;
    handleCommandLine(argc, argv, inFileName, outFileName, printHelpOnly, missingArgs);

    if (printHelpOnly)
    {
        printHelpText(argv[0]);
        return;
    }

    if (missingArgs)
    {
        std::cout << "Not enough arguments!\n";
        printHelpText(argv[0]);
        return;
    }

    // Replace .adf extension of source file with .mp3 and use
    // it for the output if no explicit filename was provided.
    if (outFileName.empty())
    {
        outFileName = removeExtension(inFileName) + ".mp3";
    }

    processFiles(inFileName, outFileName);
}

} // namespace adf2mp3 {}

int main(int argc, const char * argv[])
{
    try
    {
        adf2mp3::run(argc, argv);
        return 0;
    }
    catch (std::exception & e)
    {
        std::cerr << "ERROR: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
}

Repository link. I'm looking for second opinions mostly. Is there anything I can do to improve this? Nitpicking is also welcome.

added 108 characters in body
Source Link
glampert
  • 17.2k
  • 4
  • 30
  • 89

Repository link. I'm looking for second opinions. Please let me know if you see anything in the code that looks objectionable.

Repository link. I'm looking for second opinions. Please let me know if you see anything in the code that looks objectionable.

Tweeted twitter.com/#!/StackCodeReview/status/548966896931180545
Source Link
glampert
  • 17.2k
  • 4
  • 30
  • 89
Loading