diff --git a/include/BinaryReader.hpp b/include/BinaryReader.hpp index 5810351..a549359 100644 --- a/include/BinaryReader.hpp +++ b/include/BinaryReader.hpp @@ -18,6 +18,7 @@ #include "Stream.hpp" #include +#include namespace zelda { @@ -64,6 +65,12 @@ public: */ std::string filepath() const; + bool readBit(); + Int8 readByte(); + Uint8 readUByte(); + Int8* readBytes(Int64 length); + Uint8* readUBytes(Int64 length); + /*! \brief Reads a Int16 and swaps to proper endianness depending on platform * and Stream settings, and advances the current position * @@ -163,7 +170,9 @@ public: */ std::string readString(); + void setProgressCallback(std::function cb); protected: + void loadData(); /*! \brief Overload of isOpenForWriting in Stream * * \return false @@ -180,6 +189,9 @@ protected: */ void writeBytes(Int8*, Int64); std::string m_filepath; //!< Path to the target file + Uint32 m_currentLength; + FILE* m_file; + std::function m_progressCallback; }; } } diff --git a/include/Compression.hpp b/include/Compression.hpp index 4509aa0..3e35d3f 100644 --- a/include/Compression.hpp +++ b/include/Compression.hpp @@ -28,6 +28,9 @@ namespace Compression Int32 decompressZlib(Uint8* src, Uint32 srcLen, Uint8* dst, Uint32 dstLen); Int32 compressZlib(const Uint8* src, Uint32 srcLen, Uint8* dst, Uint32 dstLen); + // lzo compression + Int32 decompressLZO(Uint8* source, Int32 sourceSize, Uint8* dest, Int32& dstSize); + // Yaz0 encoding Uint32 yaz0Decode(Uint8* src, Uint8* dst, Uint32 uncompressedSize); Uint32 yaz0Encode(Uint8* src, Uint32 srcSize, Uint8* data); diff --git a/include/Exception.hpp b/include/Exception.hpp index ae6d614..cea139e 100644 --- a/include/Exception.hpp +++ b/include/Exception.hpp @@ -18,6 +18,10 @@ #include +#define __STRX(x) #x +#define __STR(x) __STRX(x) +#define __LINE_STRING__ __STR(__LINE__) + namespace zelda { namespace error @@ -53,4 +57,10 @@ protected: } // error } // zelda +#define THROW_EXCEPTION(msg) \ + do \ + { \ + throw zelda::error::Exception(__LINE_STRING__ " " __FILE__ " " msg); \ + } while(0) + #endif diff --git a/include/IOException.hpp b/include/IOException.hpp index 1939922..ab3521c 100644 --- a/include/IOException.hpp +++ b/include/IOException.hpp @@ -40,7 +40,7 @@ public: /*! \brief The constructor for an IOException * \param message The error message to throw */ - IOException(const std::string& message) : + inline IOException(const std::string& message) : Exception("IOException: " + message) {} }; diff --git a/include/InvalidDataException.hpp b/include/InvalidDataException.hpp new file mode 100644 index 0000000..d585a3f --- /dev/null +++ b/include/InvalidDataException.hpp @@ -0,0 +1,37 @@ +#ifndef INVALIDDATAEXCEPTION_HPP +#define INVALIDDATAEXCEPTION_HPP + +#include "Exception.hpp" +#include + +namespace zelda +{ +namespace error +{ +/*! \class InvalidDataException + * \brief An exception thrown on Invalid Data calls. + * + * This should only be thrown when the library tries to + * e.g pass a NULL pointer to a function which requires a valid pointer. + *
+ * It is NOT appropriate to use throw new so avoid doing so, + * keeping things on the stack as much as possible is very important for speed. + */ +class InvalidDataException : public Exception +{ +public: + inline InvalidDataException(const std::string& error) + : Exception(error) + { + } +}; + +} +} + +#define THROW_INVALID_DATA(msg) \ + do \ + { \ + throw zelda::error::InvalidDataException(__LINE_STRING__ " " __FILE__ " " msg); \ + } while(0) +#endif // INVALIDDATAEXCEPTION_HPP diff --git a/include/Stream.hpp b/include/Stream.hpp index 2d6e36e..d7c23ec 100644 --- a/include/Stream.hpp +++ b/include/Stream.hpp @@ -122,6 +122,14 @@ public: * \throw IOException */ virtual Int8 readByte(); + + /*! \brief Reads a byte at the current position and advances the current position + * + * \return Uint8 The value at the current position + * \throw IOException + */ + virtual Uint8 readUByte(); + /*! \brief Reads a byte at the current position and advances the current position. * * \return Uint8* The buffer at the current position from the given length. diff --git a/include/lzo.h b/include/lzo.h new file mode 100644 index 0000000..c2dbedf --- /dev/null +++ b/include/lzo.h @@ -0,0 +1,46 @@ +/* + * LZO 1x decompression + * copyright (c) 2006 Reimar Doeffinger + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LZO_H +#define LZO_H + +#include "Types.hpp" + +#define LZO_INPUT_DEPLETED 1 +#define LZO_OUTPUT_FULL 2 +#define LZO_INVALID_BACKPTR 4 +#define LZO_ERROR 8 + +#define LZO_INPUT_PADDING 4 +#define LZO_OUTPUT_PADDING 12 + +typedef unsigned char uint8_t; + +#ifdef __cplusplus +extern "C" { +#endif + +int lzo1x_decode(Uint8 *out, Int32 *outlen, Uint8 *in, Int32 *inlen); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/libzelda.pri b/libzelda.pri new file mode 100644 index 0000000..7d02833 --- /dev/null +++ b/libzelda.pri @@ -0,0 +1,100 @@ +INCLUDEPATH += $$PWD/include + +QMAKE_CXXFLAGS = -std=c++0x + +unix:LIBS += -lz +win32:LIBS += -lzlib +HEADERS += \ + $$PWD/include/utility.hpp \ + $$PWD/include/utf8.h \ + $$PWD/include/utf8/unchecked.h \ + $$PWD/include/utf8/core.h \ + $$PWD/include/utf8/checked.h \ + $$PWD/include/Types.hpp \ + $$PWD/include/TextStream.hpp \ + $$PWD/include/Stream.hpp \ + $$PWD/include/Mainpage.hpp \ + $$PWD/include/InvalidOperationException.hpp \ + $$PWD/include/IOException.hpp \ + $$PWD/include/FileNotFoundException.hpp \ + $$PWD/include/Exception.hpp \ + $$PWD/include/BinaryWriter.hpp \ + $$PWD/include/BinaryReader.hpp \ + $$PWD/include/WiiBanner.hpp \ + $$PWD/include/WiiFile.hpp \ + $$PWD/include/WiiSave.hpp \ + $$PWD/include/WiiSaveReader.hpp \ + $$PWD/include/WiiSaveWriter.hpp \ + $$PWD/include/aes.h \ + $$PWD/include/bn.h \ + $$PWD/include/ec.h \ + $$PWD/include/md5.h \ + $$PWD/include/sha1.h \ + $$PWD/include/ALTTPStructs.hpp \ + $$PWD/include/ALTTPQuest.hpp \ + $$PWD/include/ALTTPFileWriter.hpp \ + $$PWD/include/ALTTPFileReader.hpp \ + $$PWD/include/ALTTPFile.hpp \ + $$PWD/include/ALTTPEnums.hpp \ + $$PWD/include/MCFileReader.hpp \ + $$PWD/include/MCFile.hpp \ + $$PWD/include/MCFileWriter.hpp \ + $$PWD/include/ZQuestFileWriter.hpp \ + $$PWD/include/ZQuestFileReader.hpp \ + $$PWD/include/Compression.hpp \ + $$PWD/include/lzo.h \ + $$PWD/include/WiiImage.hpp \ + $$PWD/include/ZQuestFile.hpp \ + $$PWD/include/Checksums.hpp \ + $$PWD/include/SkywardSwordFile.hpp \ + $$PWD/include/SkywardSwordFileReader.hpp \ + $$PWD/include/SkywardSwordFileWriter.hpp \ + $$PWD/include/SkywardSwordQuest.hpp \ + $$PWD/include/Sprite.hpp \ + $$PWD/include/SpriteFile.hpp \ + $$PWD/include/SpriteFileReader.hpp \ + $$PWD/include/SpriteFileWriter.hpp \ + $$PWD/include/SpriteFrame.hpp \ + $$PWD/include/SpritePart.hpp \ + ../libzelda/include/InvalidDataException.hpp + +SOURCES += \ + $$PWD/src/utility.cpp \ + $$PWD/src/TextStream.cpp \ + $$PWD/src/Stream.cpp \ + $$PWD/src/BinaryWriter.cpp \ + $$PWD/src/BinaryReader.cpp \ + $$PWD/src/WiiBanner.cpp \ + $$PWD/src/WiiFile.cpp \ + $$PWD/src/WiiSave.cpp \ + $$PWD/src/WiiSaveReader.cpp \ + $$PWD/src/WiiSaveWriter.cpp \ + $$PWD/src/aes.c \ + $$PWD/src/bn.cpp \ + $$PWD/src/ec.cpp \ + $$PWD/src/md5.cpp \ + $$PWD/src/sha1.cpp \ + $$PWD/src/ALTTPQuest.cpp \ + $$PWD/src/ALTTPFileWriter.cpp \ + $$PWD/src/ALTTPFileReader.cpp \ + $$PWD/src/ALTTPFile.cpp \ + $$PWD/src/MCFileReader.cpp \ + $$PWD/src/MCFile.cpp \ + $$PWD/src/MCFileWriter.cpp \ + $$PWD/src/ZQuestFileWriter.cpp \ + $$PWD/src/ZQuestFileReader.cpp \ + $$PWD/src/Compression.cpp \ + $$PWD/src/lzo.c \ + $$PWD/src/WiiImage.cpp \ + $$PWD/src/ZQuestFile.cpp \ + $$PWD/src/Checksums.cpp \ + $$PWD/src/SkywardSwordFile.cpp \ + $$PWD/src/SkywardSwordFileReader.cpp \ + $$PWD/src/SkywardSwordFileWriter.cpp \ + $$PWD/src/SkywardSwordQuest.cpp \ + $$PWD/src/Sprite.cpp \ + $$PWD/src/SpriteFile.cpp \ + $$PWD/src/SpriteFileReader.cpp \ + $$PWD/src/SpriteFileWriter.cpp \ + $$PWD/src/SpriteFrame.cpp \ + $$PWD/src/SpritePart.cpp diff --git a/libzelda.pro b/libzelda.pro index 45f57ff..1e6a599 100644 --- a/libzelda.pro +++ b/libzelda.pro @@ -68,6 +68,7 @@ HEADERS += \ include/ZQuestFileWriter.hpp \ include/ZQuestFileReader.hpp \ include/Compression.hpp \ + include/lzo.h \ include/WiiImage.hpp \ include/ZQuestFile.hpp \ include/Checksums.hpp \ @@ -108,6 +109,7 @@ SOURCES += \ src/ZQuestFileWriter.cpp \ src/ZQuestFileReader.cpp \ src/Compression.cpp \ + src/lzo.c \ src/WiiImage.cpp \ src/ZQuestFile.cpp \ src/Checksums.cpp \ diff --git a/src/BinaryReader.cpp b/src/BinaryReader.cpp index 6b08300..cc9a18e 100644 --- a/src/BinaryReader.cpp +++ b/src/BinaryReader.cpp @@ -49,43 +49,46 @@ BinaryReader::BinaryReader(const std::string& filename) : m_filepath(filename) { Stream::setAutoResizing(false); - FILE* in; - Uint32 length; - in = fopen(filename.c_str(), "rb"); +} - if (!in) - throw error::FileNotFoundException(filename); +bool BinaryReader::readBit() +{ + if (!m_data) + loadData(); - fseek(in, 0, SEEK_END); - length = ftell(in); - fseek(in, 0, SEEK_SET); -#ifdef HW_RVL - m_data = (Uint8*)memalign(32, length); -#else - m_data = new Uint8[length]; -#endif + return Stream::readBit(); +} - Uint32 done = 0; - Uint32 blocksize = BLOCKSZ; - do - { - if (blocksize > length - done) - blocksize = length - done; +Int8 BinaryReader::readByte() +{ + if (!m_data) + loadData(); - Int32 ret = fread(m_data + done, 1, blocksize, in); + return Stream::readByte(); +} - if (ret < 0) - throw error::IOException("BinaryReader::BinaryReader -> reading data from disk"); - else if (ret == 0) - break; +Uint8 BinaryReader::readUByte() +{ + if (!m_data) + loadData(); - done += blocksize; - } while (done < length); + return Stream::readUByte(); +} - fclose(in); - m_length = length; - m_position = 0; - m_bitPosition = 0; +Int8* BinaryReader::readBytes(Int64 length) +{ + if (!m_data) + loadData(); + + return Stream::readBytes(length); +} + +Uint8* BinaryReader::readUBytes(Int64 length) +{ + if (!m_data) + loadData(); + + return Stream::readUBytes(length); } void BinaryReader::writeByte(Int8) @@ -100,6 +103,9 @@ void BinaryReader::writeBytes(Int8*, Int64) Int16 BinaryReader::readInt16() { + if (!m_data) + loadData(); + if (m_bitPosition > 0) { m_bitPosition = 0; @@ -118,6 +124,9 @@ Int16 BinaryReader::readInt16() Uint16 BinaryReader::readUInt16() { + if (!m_data) + loadData(); + if (m_bitPosition > 0) { m_bitPosition = 0; @@ -136,6 +145,9 @@ Uint16 BinaryReader::readUInt16() Int32 BinaryReader::readInt32() { + if (!m_data) + loadData(); + if (m_bitPosition > 0) { m_bitPosition = 0; @@ -153,6 +165,9 @@ Int32 BinaryReader::readInt32() Uint32 BinaryReader::readUInt32() { + if (!m_data) + loadData(); + if (m_bitPosition > 0) { m_bitPosition = 0; @@ -171,6 +186,9 @@ Uint32 BinaryReader::readUInt32() Int64 BinaryReader::readInt64() { + if (!m_data) + loadData(); + if (m_bitPosition > 0) { m_bitPosition = 0; @@ -189,6 +207,9 @@ Int64 BinaryReader::readInt64() Uint64 BinaryReader::readUInt64() { + if (!m_data) + loadData(); + if (m_bitPosition > 0) { m_bitPosition = 0; @@ -206,6 +227,9 @@ Uint64 BinaryReader::readUInt64() float BinaryReader::readFloat() { + if (!m_data) + loadData(); + if (m_bitPosition > 0) { m_bitPosition = 0; @@ -224,6 +248,9 @@ float BinaryReader::readFloat() double BinaryReader::readDouble() { + if (!m_data) + loadData(); + if (m_bitPosition > 0) { m_bitPosition = 0; @@ -243,6 +270,9 @@ double BinaryReader::readDouble() bool BinaryReader::readBool() { + if (!m_data) + loadData(); + if (m_bitPosition > 0) { m_bitPosition = 0; @@ -258,6 +288,8 @@ bool BinaryReader::readBool() std::string BinaryReader::readUnicode() { + if (!m_data) + loadData(); std::string ret; std::vector tmp; @@ -288,6 +320,56 @@ std::string BinaryReader::readString() return ret; } +void BinaryReader::setProgressCallback(std::function cb) +{ + m_progressCallback = cb; +} + +void BinaryReader::loadData() +{ + FILE* in; + Uint32 length; + in = fopen(m_filepath.c_str(), "rb"); + + if (!in) + throw error::FileNotFoundException(m_filepath); + + fseek(in, 0, SEEK_END); + length = ftell(in); + fseek(in, 0, SEEK_SET); +#ifdef HW_RVL + m_data = (Uint8*)memalign(32, length); +#else + m_data = new Uint8[length]; +#endif + + Uint32 done = 0; + Uint32 blocksize = BLOCKSZ; + do + { + if (blocksize > length - done) + blocksize = length - done; + + Int32 ret = fread(m_data + done, 1, blocksize, in); + + if (ret < 0) + throw error::IOException("BinaryReader::BinaryReader -> reading data from disk"); + else if (ret == 0) + break; + + done += ret; + + if (m_progressCallback) + m_progressCallback((int)((float)(done* 100.f)/length)); + + } while (done < length); + + fclose(in); + m_length = length; + m_position = 0; + m_bitPosition = 0; +} + bool BinaryReader::isOpenForWriting() { return false; diff --git a/src/Compression.cpp b/src/Compression.cpp index 55c1f39..2d268bf 100644 --- a/src/Compression.cpp +++ b/src/Compression.cpp @@ -15,8 +15,8 @@ #include "Compression.hpp" #include "Exception.hpp" +#include "lzo.h" #include - #include namespace zelda @@ -103,6 +103,13 @@ Int32 compressZlib(const Uint8 *src, Uint32 srcLen, Uint8 *dst, Uint32 dstLen) return ret; } +Int32 decompressLZO(Uint8* source, Int32 sourceSize, Uint8* dest, Int32& dstSize) +{ + int size = dstSize; + int result = lzo1x_decode(dest, &size, source, &sourceSize); + dstSize = size; + return result; +} //src points to the yaz0 source data (to the "real" source data, not at the header!) //dst points to a buffer uncompressedSize bytes large (you get uncompressedSize from @@ -317,6 +324,8 @@ Uint32 simpleEnc(Uint8* src, Int32 size, Int32 pos, Uint32 *pMatchPos) return numBytes; } + + } // Compression } // io } // zelda diff --git a/src/SkywardSwordFileReader.cpp b/src/SkywardSwordFileReader.cpp index 8257e07..afc2886 100644 --- a/src/SkywardSwordFileReader.cpp +++ b/src/SkywardSwordFileReader.cpp @@ -24,32 +24,41 @@ SkywardSwordFileReader::SkywardSwordFileReader(const std::string& filename) SkywardSwordFile* SkywardSwordFileReader::read() { SkywardSwordFile* file = NULL; - if (base::length() != 0xFBE0) - throw zelda::error::InvalidOperationException("SSFileReader::read -> File not the expected size of 0xFBE0"); - - Uint32 magic = base::readUInt32(); - - if (magic != SkywardSwordFile::USMagic && magic != SkywardSwordFile::JAMagic && magic != SkywardSwordFile::EUMagic) - throw zelda::error::InvalidOperationException("SSFileReader::read -> Not a valid Skyward Sword save file"); - - base::seek(0x01C, base::Beginning); - Uint32 headerSize = base::readUInt32(); // Seems to be (headerSize - 1) - - if (headerSize != 0x1D) - throw zelda::error::InvalidOperationException("SSFileHeader::read -> Invalid header size, Corrupted data?"); - - // Time to read in each slot - file = new SkywardSwordFile; - file->setRegion((magic == SkywardSwordFile::USMagic ? NTSCURegion : (magic == SkywardSwordFile::JAMagic ? NTSCJRegion : PALRegion))); - for (int i = 0; i < 3; i++) + try { - SkywardSwordQuest* q = new SkywardSwordQuest((Uint8*)base::readBytes(0x53C0), 0x53C0); - Uint64 pos = base::position(); - // seek to the skip data for this particular quest - base::seek(0xFB60 + (i * 0x24), base::Beginning); - q->setSkipData(base::readUBytes(0x24)); - base::seek(pos, base::Beginning); - file->addQuest(q); + if (base::length() != 0xFBE0) + throw zelda::error::InvalidOperationException("SSFileReader::read -> File not the expected size of 0xFBE0"); + + Uint32 magic = base::readUInt32(); + + if (magic != SkywardSwordFile::USMagic && magic != SkywardSwordFile::JAMagic && magic != SkywardSwordFile::EUMagic) + throw zelda::error::InvalidOperationException("SSFileReader::read -> Not a valid Skyward Sword save file"); + + base::seek(0x01C, base::Beginning); + Uint32 headerSize = base::readUInt32(); // Seems to be (headerSize - 1) + + if (headerSize != 0x1D) + throw zelda::error::InvalidOperationException("SSFileHeader::read -> Invalid header size, Corrupted data?"); + + // Time to read in each slot + file = new SkywardSwordFile; + file->setRegion((magic == SkywardSwordFile::USMagic ? NTSCURegion : (magic == SkywardSwordFile::JAMagic ? NTSCJRegion : PALRegion))); + for (int i = 0; i < 3; i++) + { + SkywardSwordQuest* q = new SkywardSwordQuest((Uint8*)base::readBytes(0x53C0), 0x53C0); + Uint64 pos = base::position(); + // seek to the skip data for this particular quest + base::seek(0xFB60 + (i * 0x24), base::Beginning); + q->setSkipData(base::readUBytes(0x24)); + base::seek(pos, base::Beginning); + file->addQuest(q); + } + } + catch(...) + { + delete file; + file = NULL; + throw; } return file; diff --git a/src/SpriteFileReader.cpp b/src/SpriteFileReader.cpp index c5a0620..a3eb035 100644 --- a/src/SpriteFileReader.cpp +++ b/src/SpriteFileReader.cpp @@ -24,167 +24,177 @@ SpriteFileReader::SpriteFileReader(const std::string& filepath) Sakura::SpriteFile* SpriteFileReader::readFile() { - Uint32 magic = base::readUInt32(); - - if (magic != Sakura::SpriteFile::Magic) - throw zelda::error::InvalidOperationException("Not a valid Sakura Sprite container"); - - Uint32 version = base::readUInt32(); - - // TODO: Make this more verbose - if (version != Sakura::SpriteFile::Version) - throw zelda::error::InvalidOperationException("Unsupported version"); - - // After reading in the magic and version we need to load some - // metadata about the file. - // Such as the texture count, it's dimensions, and it's origin. - // After that we have the number of sprites contained in this - // sprite container. - Uint16 textureCount = base::readUInt16(); // Having it as a Uint16 gives us the ability to have up to 65536 different states - // This is probably overkill, but it's better safe than sorry. - Uint32 width = base::readUInt32(); - Uint32 height = base::readUInt32(); - float originX = base::readFloat(); - float originY = base::readFloat(); - Uint16 spriteCount = base::readUInt16(); - - // Lets go ahead and create or new container. - Sakura::SpriteFile* ret = new Sakura::SpriteFile(width, height, originX, originY); - - // The next four bytes are reserved to keep the header 32 byte aligned. - // This isn't necessary for most systems, but it's eventually planned - // to migrate this code to Big Endian based systems, such as the wii - // which require data to be 32 byte aligned, or it causes some issues. - // It's also convenient to have this, for later expansion. - Uint32 reserved = base::readUInt32(); - UNUSED(reserved); - - // Next we have to load the textures - // If we tried to add them one at a time to the sprite container - // it will be slow as hell, so we store them in a vector locally - // then give that vector the the container, this bypasses the de-reference - // for each texture -#ifndef LIBZELDA_USE_QT - std::vector textures; -#else - QList textures; -#endif - - for (Uint16 i = 0; i < textureCount; i++) + Sakura::SpriteFile* ret = NULL; + try { - Sakura::STexture* texture = new Sakura::STexture; - texture->Filepath = base::readString(); - texture->Preload = base::readBool(); - textures.push_back(texture); - } + Uint32 magic = base::readUInt32(); - ret->setTextures(textures); + if (magic != Sakura::SpriteFile::Magic) + throw zelda::error::InvalidOperationException("Not a valid Sakura Sprite container"); - // Now for the sprites - // The sprites are a bit more difficult, they are stored in an unordered_map - // with it's name as the key, this means we can't have two sprites with the same name - // Normally this isn't a problem, but someone may decide to copy and paste a sprite - // and forget to change the name, that needs to be handled, but it's outside the scope - // of this reader. + Uint32 version = base::readUInt32(); + + // TODO: Make this more verbose + if (version != Sakura::SpriteFile::Version) + throw zelda::error::InvalidOperationException("Unsupported version"); + + // After reading in the magic and version we need to load some + // metadata about the file. + // Such as the texture count, it's dimensions, and it's origin. + // After that we have the number of sprites contained in this + // sprite container. + Uint16 textureCount = base::readUInt16(); // Having it as a Uint16 gives us the ability to have up to 65536 different states + // This is probably overkill, but it's better safe than sorry. + Uint32 width = base::readUInt32(); + Uint32 height = base::readUInt32(); + float originX = base::readFloat(); + float originY = base::readFloat(); + Uint16 spriteCount = base::readUInt16(); + + // Lets go ahead and create or new container. + ret = new Sakura::SpriteFile(width, height, originX, originY); + + // The next four bytes are reserved to keep the header 32 byte aligned. + // This isn't necessary for most systems, but it's eventually planned + // to migrate this code to Big Endian based systems, such as the wii + // which require data to be 32 byte aligned, or it causes some issues. + // It's also convenient to have this, for later expansion. + Uint32 reserved = base::readUInt32(); + UNUSED(reserved); + + // Next we have to load the textures + // If we tried to add them one at a time to the sprite container + // it will be slow as hell, so we store them in a vector locally + // then give that vector the the container, this bypasses the de-reference + // for each texture #ifndef LIBZELDA_USE_QT - std::unordered_map sprites; + std::vector textures; #else - QMap sprites; + QList textures; #endif - for (Uint16 i = 0; i < spriteCount; i++) - { - Sakura::Sprite* sprite = new Sakura::Sprite(ret); -#ifndef LIBZELDA_USE_QT - std::string name = base::readString(); -#else - QString name = QString::fromStdString(base::readString()); -#endif - sprite->setName(name); - Uint16 frameCount = base::readUInt16(); - Uint16 stateCount = base::readUInt16(); - - // Each state id corresponds to a texture held in the parent class - std::vector stateIds; - for (int j = 0; j < stateCount; j++) - stateIds.push_back(base::readUInt16()); - - - sprite->setStateIds(stateIds); - - // Now to read the sprite parts. - // The parts allow us to build retro style sprites very easily - // making it possible to use one texture atlas for all possible - // frame combinations, this reduces the amount of memory overhead - // and the storage footprint, while Sakura supports packs and zips - // it's still a bad idea to have a metric ton of texture resources - // littering the place -#ifndef LIBZELDA_USE_QT - std::vector frames; -#else - QList frames; -#endif - - for (Uint32 k = 0; k < frameCount; k++) + for (Uint16 i = 0; i < textureCount; i++) { - Sakura::SpriteFrame* frame = new Sakura::SpriteFrame(sprite); - frame->setFrameTime(base::readFloat()); - Uint16 partCount = base::readUInt16(); + Sakura::STexture* texture = new Sakura::STexture; + texture->Filepath = base::readString(); + texture->Preload = base::readBool(); + textures.push_back(texture); + } + ret->setTextures(textures); + // Now for the sprites + // The sprites are a bit more difficult, they are stored in an unordered_map + // with it's name as the key, this means we can't have two sprites with the same name + // Normally this isn't a problem, but someone may decide to copy and paste a sprite + // and forget to change the name, that needs to be handled, but it's outside the scope + // of this reader. #ifndef LIBZELDA_USE_QT - std::vector parts; + std::unordered_map sprites; #else - QList parts; + QMap sprites; #endif - for (Uint8 j = 0; j < partCount; j++) + + for (Uint16 i = 0; i < spriteCount; i++) + { + Sakura::Sprite* sprite = new Sakura::Sprite(ret); +#ifndef LIBZELDA_USE_QT + std::string name = base::readString(); +#else + QString name = QString::fromStdString(base::readString()); +#endif + sprite->setName(name); + Uint16 frameCount = base::readUInt16(); + Uint16 stateCount = base::readUInt16(); + + // Each state id corresponds to a texture held in the parent class + std::vector stateIds; + for (int j = 0; j < stateCount; j++) + stateIds.push_back(base::readUInt16()); + + + sprite->setStateIds(stateIds); + + // Now to read the sprite parts. + // The parts allow us to build retro style sprites very easily + // making it possible to use one texture atlas for all possible + // frame combinations, this reduces the amount of memory overhead + // and the storage footprint, while Sakura supports packs and zips + // it's still a bad idea to have a metric ton of texture resources + // littering the place +#ifndef LIBZELDA_USE_QT + std::vector frames; +#else + QList frames; +#endif + + for (Uint32 k = 0; k < frameCount; k++) { - Sakura::SpritePart* part = new Sakura::SpritePart(frame); + Sakura::SpriteFrame* frame = new Sakura::SpriteFrame(sprite); + frame->setFrameTime(base::readFloat()); + Uint16 partCount = base::readUInt16(); + + #ifndef LIBZELDA_USE_QT - std::string name = base::readString(); + std::vector parts; #else - QString name = QString::fromStdString(base::readString()); + QList parts; #endif - part->setName(name); - part->setCollision(base::readBool()); + for (Uint8 j = 0; j < partCount; j++) + { + Sakura::SpritePart* part = new Sakura::SpritePart(frame); +#ifndef LIBZELDA_USE_QT + std::string name = base::readString(); +#else + QString name = QString::fromStdString(base::readString()); +#endif + part->setName(name); + part->setCollision(base::readBool()); - float xOff = base::readFloat(); - float yOff = base::readFloat(); - part->setOffset(xOff, yOff); - float texXOff = base::readFloat(); - float texYOff = base::readFloat(); - part->setTextureOffset(texXOff, texYOff); - Uint32 width = base::readUInt32(); - Uint32 height = base::readUInt32(); - part->setSize(width, height); - bool flippedH = base::readBool(); - part->setFlippedHorizontally(flippedH); - bool flippedV = base::readBool(); - part->setFlippedVertically(flippedV); + float xOff = base::readFloat(); + float yOff = base::readFloat(); + part->setOffset(xOff, yOff); + float texXOff = base::readFloat(); + float texYOff = base::readFloat(); + part->setTextureOffset(texXOff, texYOff); + Uint32 width = base::readUInt32(); + Uint32 height = base::readUInt32(); + part->setSize(width, height); + bool flippedH = base::readBool(); + part->setFlippedHorizontally(flippedH); + bool flippedV = base::readBool(); + part->setFlippedVertically(flippedV); - parts.push_back(part); + parts.push_back(part); + } + frame->setParts(parts); + frames.push_back(frame); } - frame->setParts(parts); - frames.push_back(frame); - } - sprite->setFrames(frames); + sprite->setFrames(frames); #ifndef LIBZELDA_USE_QT - if (sprite->name() != std::string()) - { - std::string nameLow(sprite->name()); - zelda::utility::tolower(nameLow); - sprites[nameLow] = sprite; - } + if (sprite->name() != std::string()) + { + std::string nameLow(sprite->name()); + zelda::utility::tolower(nameLow); + sprites[nameLow] = sprite; + } #else - if (!sprite->name().isEmpty()) - sprites[sprite->name().toLower()] = sprite; + if (!sprite->name().isEmpty()) + sprites[sprite->name().toLower()] = sprite; #endif - else - throw zelda::error::IOException("SSpriteFileReader::readFile -> Sprite names cannot be empty"); - } + else + throw zelda::error::IOException("SSpriteFileReader::readFile -> Sprite names cannot be empty"); + } - ret->setSprites(sprites); + ret->setSprites(sprites); + } + catch(...) + { + delete ret; + ret = NULL; + throw; + } return ret; } diff --git a/src/Stream.cpp b/src/Stream.cpp index 4bca8e6..8b5a2e6 100644 --- a/src/Stream.cpp +++ b/src/Stream.cpp @@ -28,7 +28,7 @@ namespace zelda namespace io { -const Uint32 Stream::BLOCKSZ = 512; +const Uint32 Stream::BLOCKSZ = (32*1024); Stream::Stream() : m_bitPosition(0), @@ -198,6 +198,19 @@ Int8 Stream::readByte() return *(Int8*)(m_data + m_position++); } +Uint8 Stream::readUByte() +{ + if (m_bitPosition > 0) + { + m_bitPosition = 0; + m_position += sizeof(Uint8); + } + if (m_position + 1 > m_length) + throw error::IOException("Stream::readUByte -> Position passed stream bounds"); + + return *(Uint8*)(m_data + m_position++); +} + Uint8 *Stream::readUBytes(Int64 length) { return (Uint8*)readBytes(length); diff --git a/src/WiiSaveReader.cpp b/src/WiiSaveReader.cpp index 6afc361..77cb578 100644 --- a/src/WiiSaveReader.cpp +++ b/src/WiiSaveReader.cpp @@ -54,50 +54,60 @@ WiiSaveReader::WiiSaveReader(const std::string& filename) WiiSave* WiiSaveReader::readSave() { WiiSave* ret = new WiiSave; - if (length() < 0xF0C0) - throw error::InvalidOperationException("WiiSaveReader::readSave -> Not a valid WiiSave"); - - WiiBanner* banner = this->readBanner(); - if (!banner) - throw error::InvalidOperationException("WiiSaveReader::readSave -> Invalid banner"); - - ret->setBanner(banner); - Uint32 bkVer = base::readUInt32(); - - if (bkVer != 0x00000070) - throw error::InvalidOperationException("WiiSaveReader::readSave -> Invalid BacKup header size"); - - Uint32 bkMagic = base::readUInt32(); - bkMagic = bkMagic; - if (bkMagic != 0x426B0001) - throw error::InvalidOperationException("WiiSaveReader::readSave -> Invalid BacKup header magic"); - - Uint32 ngId = base::readUInt32(); - ngId = ngId; - - Uint32 numFiles = base::readUInt32(); - - /*int fileSize =*/ base::readUInt32(); - base::seek(8); // skip unknown data; - - Uint32 totalSize = base::readUInt32(); - base::seek(64); // Unknown (Most likely padding) - base::seek(8); - base::seek(6); - base::seek(2); - base::seek(0x10); - - std::unordered_map files; - for (Uint32 i = 0; i < numFiles; ++i) + try { - WiiFile* file = readFile(); - if (file) - files["/"+file->filename()] = file; + if (length() < 0xF0C0) + throw error::InvalidOperationException("WiiSaveReader::readSave -> Not a valid WiiSave"); + + WiiBanner* banner = this->readBanner(); + if (!banner) + throw error::InvalidOperationException("WiiSaveReader::readSave -> Invalid banner"); + + ret->setBanner(banner); + Uint32 bkVer = base::readUInt32(); + + if (bkVer != 0x00000070) + throw error::InvalidOperationException("WiiSaveReader::readSave -> Invalid BacKup header size"); + + Uint32 bkMagic = base::readUInt32(); + bkMagic = bkMagic; + if (bkMagic != 0x426B0001) + throw error::InvalidOperationException("WiiSaveReader::readSave -> Invalid BacKup header magic"); + + Uint32 ngId = base::readUInt32(); + ngId = ngId; + + Uint32 numFiles = base::readUInt32(); + + /*int fileSize =*/ base::readUInt32(); + base::seek(8); // skip unknown data; + + Uint32 totalSize = base::readUInt32(); + base::seek(64); // Unknown (Most likely padding) + base::seek(8); + base::seek(6); + base::seek(2); + base::seek(0x10); + + std::unordered_map files; + for (Uint32 i = 0; i < numFiles; ++i) + { + WiiFile* file = readFile(); + if (file) + files["/"+file->filename()] = file; + } + + ret->setFiles(files); + + readCerts(totalSize); + } + catch(...) + { + delete ret; + ret = NULL; + throw; } - ret->setFiles(files); - - readCerts(totalSize); return ret; } @@ -154,7 +164,7 @@ WiiBanner* WiiSaveReader::readBanner() gameId = base::readUInt64(); bannerSize = base::readUInt32(); permissions = base::readByte(); -/* unk =*/ base::readByte(); + /* unk =*/ base::readByte(); base::seek(0x10); // skip padding base::seek(2); diff --git a/src/lzo.c b/src/lzo.c new file mode 100644 index 0000000..3866e5a --- /dev/null +++ b/src/lzo.c @@ -0,0 +1,225 @@ +/* + * LZO 1x decompression + * Copyright (c) 2006 Reimar Doeffinger + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +//! avoid e.g. MPlayers fast_memcpy, it slows things down here + +#undef memcpy +#include +#include "lzo.h" + +//! define if we may write up to 12 bytes beyond the output buffer +//#define OUTBUF_PADDED 1 +//! define if we may read up to 4 bytes beyond the input buffer +//#define INBUF_PADDED 1 +typedef struct LZOContext { + Uint8 *in, *in_end; + Uint8 *out_start, *out, *out_end; + int error; +} LZOContext; + +/** + * \brief read one byte from input buffer, avoiding overrun + * \return byte read + */ +static int get_byte(LZOContext *c) { + if (c->in < c->in_end) + return *c->in++; + c->error |= LZO_INPUT_DEPLETED; + return 1; +} + +/** + * \brief decode a length value in the coding used by lzo + * \param x previous byte value + * \param mask bits used from x + * \return decoded length value + */ +static int get_len(LZOContext *c, int x, int mask) { + int cnt = x & mask; + if (!cnt) { + while (!(x = get_byte(c))) cnt += 255; + cnt += mask + x; + } + return cnt; +} + +/** + * \brief copy bytes from input to output buffer with checking + * \param cnt number of bytes to copy, must be > 0 + */ +static void copy(LZOContext *c, int cnt) { + register uint8_t *src = c->in; + register uint8_t *dst = c->out; + if (src + cnt > c->in_end) { + cnt = c->in_end - src; + c->error |= LZO_INPUT_DEPLETED; + } + if (dst + cnt > c->out_end) { + cnt = c->out_end - dst; + c->error |= LZO_OUTPUT_FULL; + } +#if defined(INBUF_PADDED) && defined(OUTBUF_PADDED) + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + src += 4; + dst += 4; + cnt -= 4; + if (cnt > 0) +#endif + memcpy(dst, src, cnt); + c->in = src + cnt; + c->out = dst + cnt; +} + +/** + * \brief copy previously decoded bytes to current position + * \param back how many bytes back we start + * \param cnt number of bytes to copy, must be > 0 + * + * cnt > back is valid, this will copy the bytes we just copied, + * thus creating a repeating pattern with a period length of back. + */ +static void copy_backptr(LZOContext *c, int back, int cnt) { + register uint8_t *src = &c->out[-back]; + register uint8_t *dst = c->out; + if (src < c->out_start) { + c->error |= LZO_INVALID_BACKPTR; + return; + } + if (dst + cnt > c->out_end) { + cnt = c->out_end - dst; + c->error |= LZO_OUTPUT_FULL; + } + if (back == 1) { + memset(dst, *src, cnt); + dst += cnt; + } else { +#ifdef OUTBUF_PADDED + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + src += 4; + dst += 4; + cnt -= 4; + if (cnt > 0) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + dst[4] = src[4]; + dst[5] = src[5]; + dst[6] = src[6]; + dst[7] = src[7]; + src += 8; + dst += 8; + cnt -= 8; + } +#endif + if (cnt > 0) { + int blocklen = back; + while (cnt > blocklen) { + memcpy(dst, src, blocklen); + dst += blocklen; + cnt -= blocklen; + blocklen <<= 1; + } + memcpy(dst, src, cnt); + } + dst += cnt; + } + c->out = dst; +} + +/** + * \brief decode LZO 1x compressed data + * \param out output buffer + * \param outlen size of output buffer, number of bytes left are returned here + * \param in input buffer + * \param inlen size of input buffer, number of bytes left are returned here + * \return 0 on success, otherwise error flags, see lzo.h + * + * make sure all buffers are appropriately padded, in must provide + * LZO_INPUT_PADDING, out must provide LZO_OUTPUT_PADDING additional bytes + */ +int lzo1x_decode(Uint8 *out, Int32 *outlen, Uint8 *in, Int32 *inlen) { + enum {COPY, BACKPTR} state = COPY; + Int32 x; + LZOContext c; + c.in = in; + c.in_end = in + *inlen; + c.out = c.out_start = out; + c.out_end = out + * outlen; + c.error = 0; + x = get_byte(&c); + if (x > 17) { + copy(&c, x - 17); + x = get_byte(&c); + if (x < 16) c.error |= LZO_ERROR; + } + while (!c.error) { + int cnt, back; + if (x >> 4) { + if (x >> 6) { + cnt = (x >> 5) - 1; + back = (get_byte(&c) << 3) + ((x >> 2) & 7) + 1; + } else if (x >> 5) { + cnt = get_len(&c, x, 31); + x = get_byte(&c); + back = (get_byte(&c) << 6) + (x >> 2) + 1; + } else { + cnt = get_len(&c, x, 7); + back = (1 << 14) + ((x & 8) << 11); + x = get_byte(&c); + back += (get_byte(&c) << 6) + (x >> 2); + if (back == (1 << 14)) { + if (cnt != 1) + c.error |= LZO_ERROR; + break; + } + } + } else + switch (state) { + case COPY: + cnt = get_len(&c, x, 15); + copy(&c, cnt + 3); + x = get_byte(&c); + if (x >> 4) + continue; + cnt = 1; + back = (1 << 11) + (get_byte(&c) << 2) + (x >> 2) + 1; + break; + case BACKPTR: + cnt = 0; + back = (get_byte(&c) << 2) + (x >> 2) + 1; + break; + } + copy_backptr(&c, back, cnt + 2); + cnt = x & 3; + state = cnt ? BACKPTR : COPY; + if (cnt) + copy(&c, cnt); + x = get_byte(&c); + } + *inlen = c.in_end - c.in; + *outlen = c.out_end - c.out; + return c.error; +}