diff --git a/NODLib.pro b/NODLib.pro index 79ee67a..bf15ff2 100644 --- a/NODLib.pro +++ b/NODLib.pro @@ -1,13 +1,10 @@ -TEMPLATE = app -CONFIG += console c++11 +TEMPLATE = subdirs CONFIG -= app_bundle CONFIG -= qt QT = -QMAKE_CXXFLAGS += -maes -QMAKE_LFLAGS += -maes - -INCLUDEPATH += include +SUBDIRS += lib driver +driver.depends += lib HEADERS += \ include/Util.hpp \ @@ -19,14 +16,3 @@ HEADERS += \ include/DiscWii.hpp \ include/aes.hpp -SOURCES += \ - lib/NODLib.cpp \ - lib/FileIOFILE.cpp \ - lib/FileIOMEM.cpp \ - lib/DiscBase.cpp \ - lib/DiscGCN.cpp \ - lib/DiscWii.cpp \ - lib/DiscIOWBFS.cpp \ - lib/DiscIOISO.cpp \ - main.cpp \ - lib/aes.cpp diff --git a/driver/driver.pro b/driver/driver.pro new file mode 100644 index 0000000..c8fd4c6 --- /dev/null +++ b/driver/driver.pro @@ -0,0 +1,11 @@ +TEMPLATE = app +CONFIG += console c++11 +CONFIG -= app_bundle +CONFIG -= qt +QT = + +INCLUDEPATH += ../include +QMAKE_LFLAGS += -maes +LIBS += -L$$OUT_PWD/../lib -lNOD + +SOURCES += main.cpp diff --git a/driver/main.cpp b/driver/main.cpp new file mode 100644 index 0000000..c99cda2 --- /dev/null +++ b/driver/main.cpp @@ -0,0 +1,37 @@ +#include +#include +#include "NODLib.hpp" + +int main(int argc, char* argv[]) +{ + if (argc < 2) + { + fprintf(stderr, "Usage: nodlib \n"); + return -1; + } + + std::unique_ptr disc = NOD::OpenDiscFromImage(argv[1]); + if (!disc) + return -1; + + disc->extractToDirectory("/home/jacko/Desktop/TrilogyDump2"); + + const NOD::DiscBase::IPartition* dataPart = disc->getDataPartition(); + if (dataPart) + { + for (const NOD::DiscBase::IPartition::Node& node : dataPart->getFSTRoot()) + { + if (node.getKind() == NOD::DiscBase::IPartition::Node::NODE_FILE) + printf("FILE: %s\n", node.getName().c_str()); + else if (node.getKind() == NOD::DiscBase::IPartition::Node::NODE_DIRECTORY) + { + printf("DIR: %s\n", node.getName().c_str()); + for (const NOD::DiscBase::IPartition::Node& subnode : node) + printf("SUBFILE: %s\n", subnode.getName().c_str()); + } + } + } + + return 0; +} + diff --git a/include/DiscBase.hpp b/include/DiscBase.hpp index 044f931..c48dc8e 100644 --- a/include/DiscBase.hpp +++ b/include/DiscBase.hpp @@ -36,17 +36,6 @@ public: } }; - struct IPartReadStream - { - virtual void seek(size_t offset, int whence=SEEK_SET)=0; - virtual size_t read(void* buf, size_t length)=0; - }; - - struct IPartWriteStream - { - virtual size_t write(void* buf, size_t length)=0; - }; - class FSTNode { uint32_t typeAndNameOffset; @@ -86,8 +75,8 @@ public: size_t m_discLength; std::string m_name; - std::vector::const_iterator m_childrenBegin; - std::vector::const_iterator m_childrenEnd; + std::vector::iterator m_childrenBegin; + std::vector::iterator m_childrenEnd; public: Node(const IPartition& parent, const FSTNode& node, const char* name) @@ -107,15 +96,15 @@ public: } return m_parent.beginReadStream(m_discOffset); } - inline std::vector::const_iterator rawBegin() const {return m_childrenBegin;} - inline std::vector::const_iterator rawEnd() const {return m_childrenEnd;} + inline std::vector::iterator rawBegin() const {return m_childrenBegin;} + inline std::vector::iterator rawEnd() const {return m_childrenEnd;} - class DirectoryIterator : std::iterator + class DirectoryIterator : std::iterator { friend class Node; - std::vector::const_iterator m_it; - DirectoryIterator(std::vector::const_iterator&& it) - : m_it(std::move(it)) {} + std::vector::iterator m_it; + DirectoryIterator(const std::vector::iterator& it) + : m_it(it) {} public: inline bool operator!=(const DirectoryIterator& other) {return m_it != other.m_it;} inline DirectoryIterator& operator++() @@ -126,10 +115,12 @@ public: ++m_it; return *this; } - inline const Node& operator*() {return *m_it;} + inline Node& operator*() {return *m_it;} }; - inline DirectoryIterator begin() const {return DirectoryIterator(rawBegin());} - inline DirectoryIterator end() const {return DirectoryIterator(rawEnd());} + inline DirectoryIterator begin() const {return DirectoryIterator(m_childrenBegin);} + inline DirectoryIterator end() const {return DirectoryIterator(m_childrenEnd);} + + void extractToDirectory(const std::string& basePath, bool force=false); }; protected: uint32_t m_dolOff; @@ -148,6 +139,9 @@ public: inline Kind getKind() const {return m_kind;} virtual std::unique_ptr beginReadStream(size_t offset=0) const=0; inline const Node& getFSTRoot() const {return m_nodes[0];} + inline Node& getFSTRoot() {return m_nodes[0];} + inline void extractToDirectory(const std::string& path, bool force=false) + {m_nodes[0].extractToDirectory(path, force);} }; protected: @@ -173,6 +167,11 @@ public: return part.get(); return nullptr; } + inline void extractToDirectory(const std::string& path, bool force=false) + { + for (std::unique_ptr& part : m_partitions) + part->extractToDirectory(path, force); + } }; } diff --git a/include/IDiscIO.hpp b/include/IDiscIO.hpp index 71a17f7..bedec1f 100644 --- a/include/IDiscIO.hpp +++ b/include/IDiscIO.hpp @@ -27,6 +27,17 @@ public: virtual std::unique_ptr beginWriteStream(size_t offset=0) const=0; }; +struct IPartReadStream +{ + virtual void seek(size_t offset, int whence=SEEK_SET)=0; + virtual size_t read(void* buf, size_t length)=0; +}; + +struct IPartWriteStream +{ + virtual size_t write(void* buf, size_t length)=0; +}; + } #endif // __NOD_IDISC_IO__ diff --git a/include/IFileIO.hpp b/include/IFileIO.hpp index a2520f2..5d8260d 100644 --- a/include/IFileIO.hpp +++ b/include/IFileIO.hpp @@ -17,18 +17,21 @@ public: struct IWriteStream { virtual ~IWriteStream() {} - virtual size_t copyFromDisc(IDiscIO::IReadStream& discio, size_t length)=0; + virtual size_t copyFromDisc(struct IPartReadStream& discio, size_t length)=0; }; - virtual std::unique_ptr beginWriteStream(); + virtual std::unique_ptr beginWriteStream() const=0; struct IReadStream { virtual ~IReadStream() {} - virtual size_t copyToDisc(IDiscIO::IWriteStream& discio, size_t length)=0; + virtual size_t copyToDisc(struct IPartWriteStream& discio, size_t length)=0; }; - virtual std::unique_ptr beginReadStream(); + virtual std::unique_ptr beginReadStream() const=0; }; +std::unique_ptr NewFileIO(const std::string& path); +std::unique_ptr NewMemIO(void* buf, size_t size); + } #endif // __NOD_IFILE_IO__ diff --git a/include/aes.hpp b/include/aes.hpp index 962d580..93a9960 100644 --- a/include/aes.hpp +++ b/include/aes.hpp @@ -3,7 +3,6 @@ #include #include -#include #include namespace NOD diff --git a/lib/DiscBase.cpp b/lib/DiscBase.cpp index 645b1df..e321aba 100644 --- a/lib/DiscBase.cpp +++ b/lib/DiscBase.cpp @@ -1,4 +1,6 @@ +#include #include "DiscBase.hpp" +#include "IFileIO.hpp" namespace NOD { @@ -26,7 +28,7 @@ void DiscBase::IPartition::parseFST(IPartReadStream& s) for (uint32_t n=0 ; n"); + m_nodes.emplace_back(*this, node, n ? names + node.getNameOffset() : ""); } /* Setup dir-child iterators */ @@ -43,4 +45,27 @@ void DiscBase::IPartition::parseFST(IPartReadStream& s) } } +void DiscBase::IPartition::Node::extractToDirectory(const std::string& basePath, bool force) +{ + std::string path = basePath + "/" + getName(); + if (m_kind == NODE_DIRECTORY) + { + if (mkdir(path.c_str(), 0755) && errno != EEXIST) + throw std::runtime_error("unable to mkdir '" + path + "'"); + for (DiscBase::IPartition::Node& subnode : *this) + subnode.extractToDirectory(path); + } + else if (m_kind == NODE_FILE) + { + struct stat theStat; + if (force || stat(path.c_str(), &theStat)) + { + m_hddFile = NewFileIO(path); + std::unique_ptr rs = beginReadStream(); + std::unique_ptr ws = m_hddFile->beginWriteStream(); + ws->copyFromDisc(*rs.get(), m_discLength); + } + } +} + } diff --git a/lib/DiscGCN.cpp b/lib/DiscGCN.cpp index d483df5..bc0572f 100644 --- a/lib/DiscGCN.cpp +++ b/lib/DiscGCN.cpp @@ -10,7 +10,7 @@ public: : IPartition(parent, kind, offset) { /* GCN-specific header reads */ - std::unique_ptr s = beginReadStream(0x420); + std::unique_ptr s = beginReadStream(0x420); uint32_t vals[3]; s->read(vals, 12); m_dolOff = SBig(vals[0]); @@ -21,7 +21,7 @@ public: parseFST(*s.get()); } - class PartReadStream : public DiscBase::IPartReadStream + class PartReadStream : public IPartReadStream { const PartitionGCN& m_parent; std::unique_ptr m_dio; @@ -36,9 +36,9 @@ public: {return m_dio->read(buf, length);} }; - std::unique_ptr beginReadStream(size_t offset) const + std::unique_ptr beginReadStream(size_t offset) const { - return std::unique_ptr(new PartReadStream(*this, offset)); + return std::unique_ptr(new PartReadStream(*this, offset)); } }; diff --git a/lib/DiscWii.cpp b/lib/DiscWii.cpp index 5dc71dd..238aad5 100644 --- a/lib/DiscWii.cpp +++ b/lib/DiscWii.cpp @@ -1,4 +1,5 @@ #include +#include #include "DiscWii.hpp" #include "aes.hpp" @@ -12,6 +13,19 @@ static const uint8_t COMMON_KEY[] = {0xeb, 0xe4, 0x2a, 0x22, class PartitionWii : public DiscBase::IPartition { + enum SigType : uint32_t + { + SRSA_4096 = 0x00010000, + SRSA_2048 = 0x00010001, + SELIPTICAL_CURVE = 0x00010002 + }; + + enum KeyType : uint32_t + { + KRSA_4096 = 0x00000000, + KRSA_2048 = 0x00000001 + }; + struct Ticket { uint32_t sigType; @@ -57,7 +71,7 @@ class PartitionWii : public DiscBase::IPartition struct TMD { - uint32_t sigType; + SigType sigType; char sig[256]; char padding[60]; char sigIssuer[64]; @@ -68,7 +82,7 @@ class PartitionWii : public DiscBase::IPartition uint32_t iosIdMajor; uint32_t iosIdMinor; uint32_t titleIdMajor; - uint32_t titleIdMinor; + char titleIdMinor[4]; uint32_t titleType; uint16_t groupId; char padding2[62]; @@ -100,11 +114,10 @@ class PartitionWii : public DiscBase::IPartition void read(IDiscIO::IReadStream& s) { s.read(this, 484); - sigType = SBig(sigType); + sigType = (SigType)SBig(sigType); iosIdMajor = SBig(iosIdMajor); iosIdMinor = SBig(iosIdMinor); titleIdMajor = SBig(titleIdMajor); - titleIdMinor = SBig(titleIdMinor); titleType = SBig(titleType); groupId = SBig(groupId); accessFlags = SBig(accessFlags); @@ -121,10 +134,10 @@ class PartitionWii : public DiscBase::IPartition struct Certificate { - uint32_t sigType; + SigType sigType; char sig[512]; char issuer[64]; - uint32_t keyType; + KeyType keyType; char subject[64]; char key[512]; uint32_t modulus; @@ -133,22 +146,22 @@ class PartitionWii : public DiscBase::IPartition void read(IDiscIO::IReadStream& s) { s.read(&sigType, 4); - sigType = SBig(sigType); - if (sigType == 0x00010000) + sigType = (SigType)SBig(sigType); + if (sigType == SRSA_4096) s.read(sig, 512); - else if (sigType == 0x00010001) + else if (sigType == SRSA_2048) s.read(sig, 256); - else if (sigType == 0x00010002) + else if (sigType == SELIPTICAL_CURVE) s.read(sig, 64); s.seek(60, SEEK_CUR); s.read(issuer, 64); s.read(&keyType, 4); s.read(subject, 64); - keyType = SBig(keyType); - if (keyType == 0x00000000) + keyType = (KeyType)SBig(keyType); + if (keyType == KRSA_4096) s.read(key, 512); - else if (keyType == 0x00000001) + else if (keyType == KRSA_2048) s.read(key, 256); s.read(&modulus, 8); @@ -214,7 +227,7 @@ public: aes->decrypt(iv, m_ticket.encKey, m_decKey, 16); /* Wii-specific header reads (now using title key to decrypt) */ - std::unique_ptr ds = beginReadStream(0x420); + std::unique_ptr ds = beginReadStream(0x420); uint32_t vals[3]; ds->read(vals, 12); m_dolOff = SBig(vals[0]) << 2; @@ -225,7 +238,7 @@ public: parseFST(*ds.get()); } - class PartReadStream : public DiscBase::IPartReadStream + class PartReadStream : public IPartReadStream { std::unique_ptr m_aes; const PartitionWii& m_parent; @@ -233,6 +246,7 @@ public: size_t m_offset; std::unique_ptr m_dio; + size_t m_curBlock = SIZE_MAX; uint8_t m_encBuf[0x8000]; uint8_t m_decBuf[0x7c00]; @@ -248,6 +262,8 @@ public: m_aes->setKey(parent.m_decKey); size_t block = m_offset / 0x7c00; m_dio = m_parent.m_parent.getDiscIO().beginReadStream(m_baseOffset + block * 0x8000); + decryptBlock(); + m_curBlock = block; } void seek(size_t offset, int whence) { @@ -259,16 +275,26 @@ public: return; size_t block = m_offset / 0x7c00; m_dio->seek(m_baseOffset + block * 0x8000); + if (block != m_curBlock) + { + decryptBlock(); + m_curBlock = block; + } } size_t read(void* buf, size_t length) { + size_t block = m_offset / 0x7c00; size_t cacheOffset = m_offset % 0x7c00; size_t cacheSize; uint8_t* dst = (uint8_t*)buf; while (length) { - decryptBlock(); + if (block != m_curBlock) + { + decryptBlock(); + m_curBlock = block; + } cacheSize = length; if (cacheSize + cacheOffset > 0x7c00) @@ -278,6 +304,7 @@ public: dst += cacheSize; length -= cacheSize; cacheOffset = 0; + ++block; } m_offset += length; @@ -285,9 +312,9 @@ public: } }; - std::unique_ptr beginReadStream(size_t offset) const + std::unique_ptr beginReadStream(size_t offset) const { - return std::unique_ptr(new PartReadStream(*this, m_dataOff, offset)); + return std::unique_ptr(new PartReadStream(*this, m_dataOff, offset)); } }; diff --git a/lib/FileIOFILE.cpp b/lib/FileIOFILE.cpp index a8ed071..01fc7ff 100644 --- a/lib/FileIOFILE.cpp +++ b/lib/FileIOFILE.cpp @@ -1,21 +1,99 @@ #include +#include +#include #include #include "IFileIO.hpp" +/* Macros for min/max */ +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + namespace NOD { class FileIOFILE : public IFileIO { - const char* filepath; + std::string m_path; public: - FileIOFILE(const char* path) - : filepath(path) + FileIOFILE(const std::string& path) + : m_path(path) {} + + size_t size() { - struct stat theStat; - if (stat(path, &theStat)) - throw std::runtime_error("unable to "); + FILE* fp = fopen(m_path.c_str(), "rb"); + if (!fp) + return 0; + fseek(fp, 0, SEEK_END); + size_t result = ftell(fp); + fclose(fp); + return result; } + + struct WriteStream : public IFileIO::IWriteStream + { + FILE* fp; + uint8_t buf[0x7c00]; + WriteStream(const std::string& path) + { + fp = fopen(path.c_str(), "wb"); + if (!fp) + throw std::runtime_error("unable to open '" + path + "' for writing"); + } + ~WriteStream() {fclose(fp);} + size_t copyFromDisc(IPartReadStream& discio, size_t length) + { + size_t read = 0; + while (length) + { + size_t thisSz = MIN(0x7c00, length); + size_t readSz = discio.read(buf, thisSz); + if (thisSz != readSz) + throw std::runtime_error("unable to read enough from disc"); + if (fwrite(buf, 1, readSz, fp) != readSz) + throw std::runtime_error("unable to write in file"); + length -= thisSz; + read += thisSz; + } + return read; + } + }; + std::unique_ptr beginWriteStream() const + {return std::unique_ptr(new WriteStream(m_path));} + + struct ReadStream : public IFileIO::IReadStream + { + FILE* fp; + uint8_t buf[0x7c00]; + ReadStream(const std::string& path) + { + fp = fopen(path.c_str(), "rb"); + if (!fp) + throw std::runtime_error("unable to open '" + path + "' for reading"); + } + ~ReadStream() {fclose(fp);} + size_t copyToDisc(IPartWriteStream& discio, size_t length) + { + size_t written = 0; + while (length) + { + size_t thisSz = MIN(0x7c00, length); + if (fread(buf, 1, thisSz, fp) != thisSz) + throw std::runtime_error("unable to read enough from file"); + if (discio.write(buf, thisSz) != thisSz) + throw std::runtime_error("unable to write enough to disc"); + length -= thisSz; + written += thisSz; + } + return written; + } + }; + std::unique_ptr beginReadStream() const + {return std::unique_ptr(new ReadStream(m_path));} }; +std::unique_ptr NewFileIO(const std::string& path) +{ + return std::unique_ptr(new FileIOFILE(path)); +} + } diff --git a/lib/aes.cpp b/lib/aes.cpp index e905321..ff7a16d 100644 --- a/lib/aes.cpp +++ b/lib/aes.cpp @@ -1,16 +1,5 @@ -/* Rijndael Block Cipher - aes.c - - Written by Mike Scott 21st April 1999 - mike@compapp.dcu.ie - - Permission for free direct or derivative use is granted subject - to compliance with any conditions that the originators of the - algorithm place on its exploitation. - -*/ #include "aes.hpp" #include -//#include #include #include @@ -605,8 +594,6 @@ public: #endif static int HAS_AES_NI = -1; - - std::unique_ptr NewAES() { #if __AES__ @@ -614,10 +601,7 @@ std::unique_ptr NewAES() { unsigned int a,b,c,d; __cpuid(1, a,b,c,d); - if (c & 0x2000000) - HAS_AES_NI = 1; - else - HAS_AES_NI = 0; + HAS_AES_NI = ((c & 0x2000000) != 0); } if (HAS_AES_NI) return std::unique_ptr(new NiAES); diff --git a/lib/lib.pro b/lib/lib.pro new file mode 100644 index 0000000..c658d82 --- /dev/null +++ b/lib/lib.pro @@ -0,0 +1,21 @@ +TEMPLATE = lib +CONFIG += staticlib c++11 +CONFIG -= app_bundle +CONFIG -= qt +QT = +TARGET = NOD + +QMAKE_CXXFLAGS += -maes +INCLUDEPATH += ../include + +SOURCES += \ + NODLib.cpp \ + FileIOFILE.cpp \ + FileIOMEM.cpp \ + DiscBase.cpp \ + DiscGCN.cpp \ + DiscWii.cpp \ + DiscIOWBFS.cpp \ + DiscIOISO.cpp \ + aes.cpp +