diff --git a/include/ZQuestFile.hpp b/include/ZQuestFile.hpp index 76608a4..590d44d 100644 --- a/include/ZQuestFile.hpp +++ b/include/ZQuestFile.hpp @@ -20,10 +20,13 @@ #include #include +#define ZQUEST_VERSION_CHECK(major, minor, revision) \ + (major | (minor << 8) | (revision << 16)) + namespace zelda { /*! - * \brief The ZQuest class + * \brief ZQuestFile is an export format for save data. */ class ZQuestFile { @@ -40,10 +43,6 @@ public: * \brief The current revision of the ZQuest format */ static const Uint32 Revision; - /*! - * \brief The current build of the ZQuest format - */ - static const Uint32 Build; /*! * \brief The current version of the ZQuest format */ @@ -97,7 +96,7 @@ public: * \param data * \param length */ - ZQuestFile(Game game, Endian endian, Uint8* data, Uint32 length); + ZQuestFile(Game game, Endian endian, Uint8* data, Uint32 length, const std::string& gameString = std::string()); ~ZQuestFile(); /*! @@ -143,20 +142,22 @@ public: */ Uint32 length() const; + void setGameString(const std::string& gameString); /*! * \brief gameString * \return */ std::string gameString() const; + + static const std::vector gameStringList(); private: - Game m_game; - Endian m_endian; - Uint8* m_data; - Uint32 m_length; + Game m_game; + std::string m_gameString; + Endian m_endian; + Uint8* m_data; + Uint32 m_length; // Game strings support - std::vector m_gameStrings; - void initGameStrings(); }; } // zelda diff --git a/src/Stream.cpp b/src/Stream.cpp index 8b5a2e6..0be7544 100644 --- a/src/Stream.cpp +++ b/src/Stream.cpp @@ -110,7 +110,10 @@ void Stream::writeBit(bool val) else if (m_position > m_length) throw error::IOException("Stream::writeBit() -> Position outside stream bounds"); - *(Uint8*)(m_data + m_position) |= ((Uint32)val << m_bitPosition); + if (val) + *(Uint8*)(m_data + m_position) |= (1 << m_bitPosition); + else + *(Uint8*)(m_data + m_position) &= ~(1 << m_bitPosition); m_bitPosition++; if (m_bitPosition > 7) { diff --git a/src/ZQuestFile.cpp b/src/ZQuestFile.cpp index 1e637b0..954e854 100644 --- a/src/ZQuestFile.cpp +++ b/src/ZQuestFile.cpp @@ -18,15 +18,38 @@ namespace zelda { +std::vector GameStrings; +void initGameStrings() +{ + // Populate game strings + GameStrings.push_back("No Game"); + GameStrings.push_back("Legend Of Zelda"); + GameStrings.push_back("Adventure of Link"); + GameStrings.push_back("A Link to the Past"); + GameStrings.push_back("Links Awakening"); + GameStrings.push_back("Ocarina of Time"); + GameStrings.push_back("Ocarina of Time 3D"); + GameStrings.push_back("Majora's Mask"); + GameStrings.push_back("Oracle of Seasons"); + GameStrings.push_back("Oracle of Ages"); + GameStrings.push_back("For Swords"); + GameStrings.push_back("Wind Waker"); + GameStrings.push_back("Four Swords Adventures"); + GameStrings.push_back("Minish Cap"); + GameStrings.push_back("Twilight Princess"); + GameStrings.push_back("Phantom Hourglass"); + GameStrings.push_back("Spirit Tracks"); + GameStrings.push_back("Skyward Sword"); + GameStrings.push_back("A Link Between Worlds"); +} -const Uint32 ZQuestFile::Major = 1; +const Uint32 ZQuestFile::Major = 2; const Uint32 ZQuestFile::Minor = 0; const Uint32 ZQuestFile::Revision = 0; -const Uint32 ZQuestFile::Build = 0; -const Uint32 ZQuestFile::Version = Major | (Minor << 8) | (Revision << 16) | (Build << 24); +const Uint32 ZQuestFile::Version = Major | (Minor << 8) | (Revision << 16); -const Uint32 ZQuestFile::Magic = 'Z' | ('Q' << 8) | ('S' << 16) | ('1' << 24); +const Uint32 ZQuestFile::Magic = 'Z' | ('Q' << 8) | ('S' << 16) | (('0' + ZQuestFile::Major) << 24); ZQuestFile::ZQuestFile() : m_game(NoGame), @@ -37,13 +60,16 @@ ZQuestFile::ZQuestFile() initGameStrings(); } -ZQuestFile::ZQuestFile(ZQuestFile::Game game, Endian endian, Uint8* data, Uint32 length) +ZQuestFile::ZQuestFile(ZQuestFile::Game game, Endian endian, Uint8* data, Uint32 length, const std::string& gameString) : m_game(game), + m_gameString(gameString), m_endian(endian), m_data(data), m_length(length) { initGameStrings(); + if (gameString.empty() && (m_game < GameStrings.size() - 1)) + m_gameString = GameStrings[m_game]; } ZQuestFile::~ZQuestFile() @@ -56,6 +82,10 @@ ZQuestFile::~ZQuestFile() void ZQuestFile::setGame(ZQuestFile::Game game) { m_game = game; + if (m_game > GameStrings.size() - 1) + return; + + m_gameString = GameStrings[m_game]; } ZQuestFile::Game ZQuestFile::game() const @@ -99,36 +129,20 @@ Uint32 ZQuestFile::length() const return m_length; } +void ZQuestFile::setGameString(const std::string& gameString) +{ + m_gameString = gameString; +} + std::string ZQuestFile::gameString() const { - if (m_game > m_gameStrings.size() - 1) - return "Unsupported Game"; - - return m_gameStrings[m_game]; + return m_gameString; } -void ZQuestFile::initGameStrings() +const std::vector ZQuestFile::gameStringList() { - // Populate game strings - m_gameStrings.push_back("No Game"); - m_gameStrings.push_back("Legend Of Zelda"); - m_gameStrings.push_back("Adventure of Link"); - m_gameStrings.push_back("A Link to the Past"); - m_gameStrings.push_back("Links Awakening"); - m_gameStrings.push_back("Ocarina of Time"); - m_gameStrings.push_back("Ocarina of Time 3D"); - m_gameStrings.push_back("Majora's Mask"); - m_gameStrings.push_back("Oracle of Seasons"); - m_gameStrings.push_back("Oracle of Ages"); - m_gameStrings.push_back("For Swords"); - m_gameStrings.push_back("Wind Waker"); - m_gameStrings.push_back("Four Swords Adventures"); - m_gameStrings.push_back("Minish Cap"); - m_gameStrings.push_back("Twilight Princess"); - m_gameStrings.push_back("Phantom Hourglass"); - m_gameStrings.push_back("Spirit Tracks"); - m_gameStrings.push_back("Skyward Sword"); - m_gameStrings.push_back("A Link Between Worlds (Unreleased)"); // Probably should have this, but..... + if (GameStrings.size() <= 0) + initGameStrings(); + return GameStrings; } - } diff --git a/src/ZQuestFileReader.cpp b/src/ZQuestFileReader.cpp index d8b81c1..f6ef93c 100644 --- a/src/ZQuestFileReader.cpp +++ b/src/ZQuestFileReader.cpp @@ -17,6 +17,11 @@ #include "ZQuestFile.hpp" #include "InvalidOperationException.hpp" #include "Compression.hpp" +#include "InvalidDataException.hpp" +#include "Checksums.hpp" +#include +#include +#include namespace zelda { @@ -36,27 +41,64 @@ ZQuestFileReader::ZQuestFileReader(const std::string &filename) ZQuestFile *ZQuestFileReader::read() { Uint32 magic, version, compressedLen, uncompressedLen; - ZQuestFile::Game game; + ZQuestFile::Game game = ZQuestFile::NoGame; + std::string gameString; Uint16 BOM; + Uint32 checksum; Uint8* data; magic = base::readUInt32(); - if (magic != ZQuestFile::Magic) - throw error::InvalidOperationException("ZQuestFileReader::read -> Not a valid ZQuest file"); + if ((magic & 0x00FFFFFF) != (ZQuestFile::Magic & 0x00FFFFFF)) + THROW_INVALID_DATA("Not a valid ZQuest file"); version = base::readUInt32(); - if (version != ZQuestFile::Version) - throw error::InvalidOperationException("ZQuestFileReader::read -> Unsupported ZQuest version"); + if (version > ZQuestFile::Version) + THROW_INVALID_DATA("Unsupported ZQuest version"); compressedLen = base::readUInt32(); uncompressedLen = base::readUInt32(); - game = (ZQuestFile::Game)base::readUInt32(); - BOM = base::readUInt16(); - base::seek(0x0A); + + if (version >= ZQUEST_VERSION_CHECK(2, 0, 0)) + { + gameString = ((const char*)base::readBytes(0x0A), 0x0A); + for (size_t i = 0; i < ZQuestFile::gameStringList().size(); i++) + { + if (!ZQuestFile::gameStringList().at(i).substr(0, 0x0A).compare(gameString)) + { + gameString = ZQuestFile::gameStringList().at(i); + break; + } + + } + BOM = base::readUInt16(); + checksum = base::readUInt32(); + } + else + { + game = (ZQuestFile::Game)base::readUInt32(); + BOM = base::readUInt16(); + std::cerr << "Test" << std::endl; + base::seek(0x0A); + } + data = (Uint8*)base::readBytes(compressedLen); // compressedLen is always the total file size + if (version >= ZQUEST_VERSION_CHECK(2, 0, 0)) + { + if (checksum != zelda::Checksums::crc32(data, compressedLen)) + { + delete[] data; + THROW_INVALID_DATA("Checksum mismatch, data corrupt"); + } + } + else + { + std::clog << "ZQuest version 0x" << std::uppercase << std::setw(8) << std::setfill('0') << std::hex << zelda::utility::swapU32(version); + std::clog << " has no checksum field" << std::endl; + } + if (compressedLen != uncompressedLen) { Uint8* dst = new Uint8[uncompressedLen]; @@ -67,14 +109,14 @@ ZQuestFile *ZQuestFileReader::read() delete[] dst; delete[] data; // TODO: Make proper exception - throw error::InvalidOperationException("ZQuestFileReader::read -> Error decompressing data"); + THROW_INVALID_DATA("Error decompressing data"); } delete[] data; data = dst; } - return new ZQuestFile(game, BOM == 0xFEFF ? BigEndian : LittleEndian, data, uncompressedLen); + return new ZQuestFile(game, BOM == 0xFEFF ? BigEndian : LittleEndian, data, uncompressedLen, gameString); } } // io diff --git a/src/ZQuestFileWriter.cpp b/src/ZQuestFileWriter.cpp index ff85e5d..e0f70e1 100644 --- a/src/ZQuestFileWriter.cpp +++ b/src/ZQuestFileWriter.cpp @@ -17,6 +17,7 @@ #include "InvalidOperationException.hpp" #include "ZQuestFile.hpp" #include "Compression.hpp" +#include "Checksums.hpp" namespace zelda { @@ -36,7 +37,7 @@ ZQuestFileWriter::ZQuestFileWriter(const std::string& filename) void ZQuestFileWriter::write(ZQuestFile* quest, bool compress) { if (!quest) - throw error::InvalidOperationException("ZQuestFileWriter::write -> quest cannot be NULL"); + THROW_INVALIDOPERATION_EXCEPTION("quest cannot be NULL"); base::writeUInt32(ZQuestFile::Magic); base::writeUInt32(ZQuestFile::Version); @@ -71,9 +72,9 @@ void ZQuestFileWriter::write(ZQuestFile* quest, bool compress) } base::writeUInt32(quest->length()); - base::writeUInt32(quest->game()); + base::writeBytes((Int8*)quest->gameString().substr(0, 0x0A).c_str(), 0x0A); base::writeUInt16(quest->endian() == BigEndian ? 0xFFFE : 0xFEFF); - base::seek(0x0A); + base::writeUInt32(zelda::Checksums::crc32(questData, compLen)); base::writeUBytes(questData, compLen); base::save();