diff --git a/plugins/impex/sai/3rdparty/libsai/include/sai.hpp b/plugins/impex/sai/3rdparty/libsai/include/sai.hpp index 4c3c8ee36b..37609beb94 100644 --- a/plugins/impex/sai/3rdparty/libsai/include/sai.hpp +++ b/plugins/impex/sai/3rdparty/libsai/include/sai.hpp @@ -1,326 +1,423 @@ /* LibSai - Library for interfacing with SystemMax PaintTool Sai files LICENSE MIT License Copyright (c) 2017 Wunkolo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include #include #include namespace sai { #pragma pack(push, 1) struct FATEntry { enum class EntryType : std::uint8_t { Folder = 0x10, File = 0x80 }; std::uint32_t Flags; char Name[32]; std::uint8_t Pad1; std::uint8_t Pad2; EntryType Type; std::uint8_t Pad4; std::uint32_t PageIndex; std::uint32_t Size; std::uint64_t TimeStamp; // Windows FILETIME std::uint64_t UnknownB; }; +/// Internal Structures + +struct ThumbnailHeader +{ + std::uint32_t Width; + std::uint32_t Height; + std::uint32_t Magic; // BM32 +}; + +enum BlendingMode : std::uint32_t +{ + PassThrough = 'pass', + Normal = 'norm', + Multiply = 'mul ', + Screen = 'scrn', + Overlay = 'over', + Luminosity = 'add ', + Shade = 'sub ', + LumiShade = 'adsb', + Binary = 'cbin' +}; + +enum class LayerClass : std::uint32_t +{ + RootLayer = 0x00, + // Parent Canvas layer object + Layer = 0x03, + Unknown4 = 0x4, + Linework = 0x05, + Mask = 0x06, + Unknown7 = 0x07, + Set = 0x08 +}; + +struct LayerReference +{ + std::uint32_t Identifier; + std::uint16_t LayerClass; + // These all get added and sent as a windows message 0x80CA for some reason + std::uint16_t Unknown; +}; + +struct LayerBounds +{ + std::int32_t X; // (X / 32) * 32 + std::int32_t Y; // (Y / 32) * 32 + std::uint32_t Width; // Width - 31 + std::uint32_t Height; // Height - 31 +}; + +struct LayerHeader +{ + std::uint32_t LayerClass; + std::uint32_t Identifier; + LayerBounds Bounds; + std::uint32_t Unknown; + std::uint8_t Opacity; + std::uint8_t Visible; + std::uint8_t PreserveOpacity; + std::uint8_t Clipping; + std::uint8_t Unknown4; + std::uint32_t Blending; +}; + union VirtualPage { static constexpr std::size_t PageSize = 0x1000; static constexpr std::size_t TableSpan = PageSize / 8; // Data std::uint8_t u8[PageSize]; std::int8_t i8[PageSize]; std::uint32_t u32[PageSize / sizeof(std::uint32_t)]; std::int32_t i32[PageSize / sizeof(std::int32_t)]; // Page Table entries struct PageEntry { std::uint32_t Checksum; std::uint32_t Flags; } PageEntries[PageSize / sizeof(PageEntry)]; void DecryptTable(std::uint32_t PageIndex); void DecryptData(std::uint32_t PageChecksum); // FAT Table Entries FATEntry FATEntries[64]; /* To checksum a table be sure to do "u32[0] = 0" first */ std::uint32_t Checksum(); }; #pragma pack(pop) /* Symmetric keys for decrupting and encrypting the virtual file system */ namespace Keys { extern const std::uint32_t User[256]; extern const std::uint32_t NotRemoveMe[256]; extern const std::uint32_t LocalState[256]; extern const std::uint32_t System[256]; } // Streambuf to read from an encrypted file class ifstreambuf : public std::streambuf { public: explicit ifstreambuf( const std::uint32_t* Key = Keys::User ); // No copy ifstreambuf(const ifstreambuf&) = delete; ifstreambuf& operator=(const ifstreambuf&) = delete; // Adhere similarly to std::basic_filebuf ifstreambuf* open( const char* Name ); ifstreambuf* open( const wchar_t* Name ); ifstreambuf* close(); bool is_open() const; // std::streambuf overrides virtual std::streambuf::int_type underflow() override; virtual std::streambuf::pos_type seekoff( std::streambuf::off_type Offset, std::ios_base::seekdir Direction, std::ios_base::openmode Mode = std::ios_base::in ) override; virtual std::streambuf::pos_type seekpos( std::streambuf::pos_type Position, std::ios_base::openmode Mode = std::ios_base::in ) override; private: std::ifstream FileIn; VirtualPage Buffer; // Decryption Key const std::uint32_t* Key; std::uint32_t CurrentPage; // Caching bool FetchPage(std::uint32_t PageIndex, VirtualPage* Dest); std::unique_ptr PageCache; std::uint32_t PageCacheIndex; std::unique_ptr TableCache; std::uint32_t TableCacheIndex; std::uint32_t PageCount; }; class ifstream : public std::istream { public: explicit ifstream( const std::string& Path ); explicit ifstream( const char* Path ); explicit ifstream( const std::wstring& Path ); explicit ifstream( const wchar_t* Path ); // Similar to ifstream member functions void open(const char* FilePath) const; void open(const std::string& FilePath) const; void open(const wchar_t* FilePath) const; void open(const std::wstring& FilePath) const; bool is_open() const; virtual ~ifstream(); private: }; // Forward declarations class VirtualFileEntry; class VirtualFileSystem; // Visitors class VirtualFileVisitor { public: virtual ~VirtualFileVisitor(); // Return false to stop iteration virtual bool VisitFolderBegin(VirtualFileEntry&); virtual bool VisitFolderEnd(VirtualFileEntry&); virtual bool VisitFile(VirtualFileEntry&); }; class VirtualFileSystem { public: explicit VirtualFileSystem(const char* FileName); explicit VirtualFileSystem(const wchar_t* FileName); ~VirtualFileSystem(); // No Copy VirtualFileSystem(const VirtualFileSystem&) = delete; VirtualFileSystem& operator=(const VirtualFileSystem&) = delete; bool IsOpen() const; bool Exists(const char* Path); std::unique_ptr GetEntry(const char* Path); std::size_t Read( std::size_t Offset, void* Destination, std::size_t Size) const; template< typename T > inline std::size_t Read(std::size_t Offset, T& Destination) { return Read( Offset, &Destination, sizeof(T) ); } void IterateFileSystem(VirtualFileVisitor& Visitor); private: void IterateFATBlock( std::size_t Index, VirtualFileVisitor& Visitor ); std::shared_ptr SaiStream; }; class VirtualFileEntry { public: ~VirtualFileEntry(); // No Copy VirtualFileEntry(const VirtualFileEntry&) = delete; VirtualFileEntry& operator=(const VirtualFileEntry&) = delete; const char* GetName() const; FATEntry::EntryType GetType() const; std::time_t GetTimeStamp() const; std::size_t GetSize() const; std::size_t GetPageIndex() const; std::size_t Tell() const; void Seek(std::size_t Offset); std::size_t Read(void* Destination, std::size_t Size); template< typename T > inline std::size_t Read(T& Destination) { return Read(&Destination, sizeof(T)); } template< typename T > inline T Read() { T temp; Read(&temp, sizeof(T)); return temp; } private: friend VirtualFileSystem; VirtualFileEntry(); std::weak_ptr FileSystem; std::size_t ReadPoint; FATEntry FATData; }; +class Layer +{ +public: + Layer(VirtualFileEntry& entry); + ~Layer(); + + LayerClass LayerType(); + std::uint32_t Identifier(); + + std::tuple< + std::int32_t, + std::int32_t + > Position(); + std::tuple< + std::uint32_t, + std::uint32_t + > Size(); + + int Opacity(); + bool IsVisible(); + bool IsPreserveOpacity(); + bool IsClipping(); + BlendingMode Blending(); + + char* LayerName(); + std::uint32_t ParentID(); +private: + + LayerHeader header; + char layerName[256]; + std::uint32_t ParentLayer; +}; + class Document : public VirtualFileSystem { public: explicit Document(const char* FileName); explicit Document(const wchar_t* FileName); ~Document(); // No Copy Document(const Document&) = delete; Document& operator=(const Document&) = delete; // Returns (Width, Height) // Returns (0,0) if an error has occured std::tuple< std::uint32_t, std::uint32_t > GetCanvasSize(); // Returns (RGBA Pixel Data, Width, Height). // Returns (null,0,0) if an error has occured. std::tuple< std::unique_ptr, std::uint32_t, std::uint32_t > GetThumbnail(); private: }; } diff --git a/plugins/impex/sai/3rdparty/libsai/samples/Tree.cpp b/plugins/impex/sai/3rdparty/libsai/samples/Tree.cpp index e7caf76f0e..5e59ccb2de 100644 --- a/plugins/impex/sai/3rdparty/libsai/samples/Tree.cpp +++ b/plugins/impex/sai/3rdparty/libsai/samples/Tree.cpp @@ -1,104 +1,210 @@ #include #include #include #include #include #include #include #include "Benchmark.hpp" class SaiTreeView : public sai::VirtualFileVisitor { public: SaiTreeView() : FolderDepth(0) { } ~SaiTreeView() { } bool VisitFolderBegin(sai::VirtualFileEntry& Entry) override { PrintVirtualFileEntry(Entry); ++FolderDepth; return true; } bool VisitFolderEnd(sai::VirtualFileEntry& /*Entry*/) override { --FolderDepth; return true; } bool VisitFile(sai::VirtualFileEntry& Entry) override - { - PrintVirtualFileEntry(Entry); + { + PrintVirtualFileEntry(Entry); + if (FolderDepth>0) { + sai::Layer layerData = sai::Layer(Entry); + PrintNestedFolder(); + PrintNestedFolder(); + std::cout << " " + <<"LayerType: "<< LayerType(layerData.LayerType()) + << ", ParentLayer: "<< layerData.ParentID() + << ", LayerID: "<< layerData.Identifier() + << ", LayerName: "<< layerData.LayerName() + << "\n"; + PrintNestedFolder(); + PrintNestedFolder(); + std::cout <<" Visibility:" + << layerData.IsVisible() + <<", Opacity:" + << layerData.Opacity() + << ", AlphaLock:" + << layerData.IsPreserveOpacity() + << ", Clipping:" + << layerData.IsClipping() + << "\n"; + PrintNestedFolder(); + PrintNestedFolder(); + std::cout << " Position: [" + << int(std::get<0>(layerData.Position())) + << ", " + << int(std::get<1>(layerData.Position())) + << "], Size: [" + << int(std::get<0>(layerData.Size())) + <<"," + << int(std::get<1>(layerData.Size())) + << "] Blending Mode: " + << BlendingMode(layerData.Blending()) + << "\n"; + + + + } return true; } private: void PrintVirtualFileEntry(const sai::VirtualFileEntry& Entry) const { const std::time_t TimeStamp = Entry.GetTimeStamp(); char TimeString[32]; std::strftime( TimeString, 32, "%D %R", std::localtime(&TimeStamp) ); PrintNestedFolder(); std::printf( "\u251C\u2500\u2500 [%12zu %s] %s\n", Entry.GetSize(), TimeString, Entry.GetName() ); } void PrintNestedFolder() const { for( std::size_t i = 0; i < FolderDepth; ++i ) { std::fputs( "\u2502 ", stdout ); } } + + std::string LayerType(sai::LayerClass type) { + std::string s = ""; + switch(type) { + case sai::LayerClass::Layer: + s = "RasterLayer"; + break; + case sai::LayerClass::Linework: + s = "LineworkLayer"; + break; + case sai::LayerClass::Set: + s = "Folder"; + break; + case sai::LayerClass::Mask: + s = "Mask"; + break; + case sai::LayerClass::RootLayer: + s = "RootLayer"; + break; + default: + s = "unknown"; + break; + } + return s; + } + std::string BlendingMode(sai::BlendingMode mode) { + std::string s = ""; + switch (mode) { + case sai::BlendingMode::Shade: + s = "Shade"; + break; + case sai::BlendingMode::Binary: + s = "Binary"; + break; + case sai::BlendingMode::Normal: + s = "Normal"; + break; + case sai::BlendingMode::Screen: + s = "Screen"; + break; + case sai::BlendingMode::Multiply: + s = "Multiply"; + break; + case sai::BlendingMode::LumiShade: + s = "LumiShade"; + break; + case sai::BlendingMode::Luminosity: + s = "Luminosity"; + break; + case sai::BlendingMode::PassThrough: + s = "Passthrough"; + break; + case sai::BlendingMode::Overlay: + s = "Overlay"; + break; + default: + s = "unknown blending mode"; + std::cout << " unknown blending mode " << mode; + printf("|%c%c%c%c|\n", + reinterpret_cast(&mode)[3], + reinterpret_cast(&mode)[2], + reinterpret_cast(&mode)[1], + reinterpret_cast(&mode)[0] + ); + break; + } + return s; + } std::uint32_t FolderDepth; }; const char* const Help = "Show virtual file system tree of a user-created .sai files:\n" "\tDecrypt.exe (filename)\n" "\tWunkolo - Wunkolo@gmail.com"; int main(int argc, char* argv[]) { if( argc < 2 ) { std::puts(Help); return EXIT_FAILURE; } sai::Document CurDocument(argv[1]); if( !CurDocument.IsOpen() ) { std::cout << "Error opening file for reading: " << argv[1] << std::endl; return EXIT_FAILURE; } const auto Bench = Benchmark::Run( [&CurDocument]() -> void { SaiTreeView TreeVisitor; CurDocument.IterateFileSystem(TreeVisitor); } ); std::printf( "Iterated VFS of %s in %zu ns\n", argv[1], Bench.count() ); return EXIT_SUCCESS; } diff --git a/plugins/impex/sai/3rdparty/libsai/source/sai.cpp b/plugins/impex/sai/3rdparty/libsai/source/sai.cpp index 45b43f4f25..59f9412491 100644 --- a/plugins/impex/sai/3rdparty/libsai/source/sai.cpp +++ b/plugins/impex/sai/3rdparty/libsai/source/sai.cpp @@ -1,1086 +1,1134 @@ #include #include #include #include #include #include -#include - -namespace sai -{ -/// Internal Structures +#include +#include -#pragma pack(push, 1) -struct ThumbnailHeader -{ - std::uint32_t Width; - std::uint32_t Height; - std::uint32_t Magic; // BM32 -}; - -enum class LayerClass -{ - RootLayer = 0x00, - // Parent Canvas layer object - Layer = 0x03, - Unknown4 = 0x4, - Linework = 0x05, - Mask = 0x06, - Unknown7 = 0x07, - Set = 0x08 -}; - -struct LayerReference -{ - std::uint32_t Identifier; - std::uint16_t LayerClass; - // These all get added and sent as a windows message 0x80CA for some reason - std::uint16_t Unknown; -}; +#include -struct LayerBounds +namespace sai { - std::int32_t X; // (X / 32) * 32 - std::int32_t Y; // (Y / 32) * 32 - std::uint32_t Width; // Width - 31 - std::uint32_t Height; // Height - 31 -}; - -struct LayerHeader -{ - std::uint32_t LayerClass; - std::uint32_t Identifier; - LayerBounds Bounds; - std::uint32_t Unknown; - std::uint8_t Opacity; - std::uint8_t Visible; - std::uint8_t PreserveOpacity; - std::uint8_t Clipping; - std::uint8_t Unknown4; - std::uint32_t Blending; -}; - -#pragma pack(pop) - /// VirtualPage #if defined(__AVX2__) inline __m256i KeySum8( __m256i Vector8, const std::uint32_t Key[256] ) { __m256i Sum = _mm256_i32gather_epi32( (const std::int32_t*)Key, _mm256_and_si256(Vector8, _mm256_set1_epi32(0xFF)), sizeof(std::uint32_t) ); Sum = _mm256_add_epi32( Sum, _mm256_i32gather_epi32( (const std::int32_t*)Key, _mm256_and_si256( _mm256_srli_epi32(Vector8, 8), _mm256_set1_epi32(0xFF) ), sizeof(std::uint32_t) ) ); Sum = _mm256_add_epi32( Sum, _mm256_i32gather_epi32( (const std::int32_t*)Key, _mm256_and_si256( _mm256_srli_epi32(Vector8, 16), _mm256_set1_epi32(0xFF) ), sizeof(std::uint32_t) ) ); Sum = _mm256_add_epi32( Sum, _mm256_i32gather_epi32( (const std::int32_t*)Key, _mm256_srli_epi32(Vector8, 24), sizeof(std::uint32_t) ) ); return Sum; } #endif void VirtualPage::DecryptTable(std::uint32_t PageIndex) { std::uint32_t PrevData = PageIndex & (~0x1FF); #if defined(__AVX2__) __m256i PrevData8 = _mm256_set1_epi32(PrevData); for( std::size_t i = 0; i < (PageSize / sizeof(std::uint32_t)); i += 8 ) { const __m256i CurData8 = _mm256_loadu_si256((__m256i*)(u32 + i)); // There is no true _mm_alignr_epi8 for AVX2 // An extra _mm256_permute2x128_si256 is needed PrevData8 = _mm256_alignr_epi8( CurData8, _mm256_permute2x128_si256(PrevData8, CurData8, _MM_SHUFFLE(0,2,0,1)), sizeof(std::uint32_t) * 3 ); __m256i CurPlain8 = _mm256_xor_si256( _mm256_xor_si256(CurData8, PrevData8), KeySum8(PrevData8, Keys::User) ); CurPlain8 = _mm256_shuffle_epi8( CurPlain8, _mm256_set_epi8( 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2, 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2 ) ); _mm256_storeu_si256((__m256i*)(u32 + i), CurPlain8); PrevData8 = CurData8; }; #else for( std::size_t i = 0; i < (PageSize / sizeof(std::uint32_t)); i++ ) { const std::uint32_t CurData = u32[i]; std::uint32_t X = PrevData ^ CurData; X ^= ( Keys::User[(PrevData >> 24) & 0xFF] + Keys::User[(PrevData >> 16) & 0xFF] + Keys::User[(PrevData >> 8) & 0xFF] + Keys::User[(PrevData >> 0) & 0xFF] ); u32[i] = static_cast((X << 16) | (X >> 16)); PrevData = CurData; }; #endif } void VirtualPage::DecryptData(std::uint32_t PageChecksum) { std::uint32_t PrevData = PageChecksum; #if defined(__AVX2__) __m256i PrevData8 = _mm256_set1_epi32(PrevData); for( std::size_t i = 0; i < (PageSize / sizeof(std::uint32_t)); i += 8 ) { const __m256i CurData8 = _mm256_loadu_si256((__m256i*)(u32 + i)); // There is no true _mm_alignr_epi8 for AVX2 // An extra _mm256_permute2x128_si256 is needed PrevData8 = _mm256_alignr_epi8( CurData8, _mm256_permute2x128_si256(PrevData8, CurData8, _MM_SHUFFLE(0,2,0,1)), sizeof(std::uint32_t) * 3 ); __m256i CurPlain8 = _mm256_sub_epi32( CurData8, _mm256_xor_si256(PrevData8, KeySum8(PrevData8, Keys::User)) ); _mm256_storeu_si256((__m256i*)(u32 + i), CurPlain8); PrevData8 = CurData8; }; #else for( std::size_t i = 0; i < (PageSize / sizeof(std::uint32_t)); i++ ) { const std::uint32_t CurData = u32[i]; u32[i] = CurData - (PrevData ^ ( Keys::User[(PrevData >> 24) & 0xFF] + Keys::User[(PrevData >> 16) & 0xFF] + Keys::User[(PrevData >> 8) & 0xFF] + Keys::User[(PrevData >> 0) & 0xFF] ) ); PrevData = CurData; } #endif } std::uint32_t VirtualPage::Checksum() { std::uint32_t Sum = 0; for( std::size_t i = 0; i < (PageSize / sizeof(std::uint32_t)); i++ ) { Sum = (2 * Sum | (Sum >> 31)) ^ u32[i]; } return Sum | 1; } /// ifstreambuf ifstreambuf::ifstreambuf(const std::uint32_t* Key) : Key(Key), CurrentPage(-1), PageCache(nullptr), PageCacheIndex(-1), TableCache(nullptr), TableCacheIndex(-1), PageCount(0) { setg( nullptr, nullptr, nullptr ); setp( nullptr, nullptr ); PageCache = std::make_unique(); TableCache = std::make_unique(); } ifstreambuf* ifstreambuf::open(const char* Name) { if( is_open() == true ) { return nullptr; } FileIn.open( Name, std::ios_base::binary | std::ios_base::ate ); if( FileIn.is_open() == false ) { close(); return nullptr; } const std::ifstream::pos_type FileSize = FileIn.tellg(); if( FileSize % VirtualPage::PageSize != 0 ) { // File size is not pagealigned close(); return nullptr; } PageCount = static_cast(FileSize) / VirtualPage::PageSize; seekpos( 0 ); return this; } ifstreambuf* ifstreambuf::open(const wchar_t* Name) { if( is_open() == true ) { return nullptr; } #if defined(_WIN32) FileIn.open( Name, std::ios_base::binary | std::ios_base::ate ); #else std::wstring_convert> Converter; std::string Name8 = Converter.to_bytes(std::wstring(Name)); FileIn.open( Name8, std::ios_base::binary | std::ios_base::ate ); #endif if( FileIn.is_open() == false ) { close(); return nullptr; } const std::ifstream::pos_type FileSize = FileIn.tellg(); if( FileSize % VirtualPage::PageSize != 0 ) { // File size is not pagealigned close(); return nullptr; } PageCount = static_cast(FileSize) / VirtualPage::PageSize; seekpos( 0 ); return this; } ifstreambuf* ifstreambuf::close() { if( FileIn.is_open() ) { FileIn.close(); return this; } return nullptr; } bool ifstreambuf::is_open() const { return FileIn.is_open(); } std::streambuf::int_type ifstreambuf::underflow() { if( FileIn.eof() ) { return traits_type::eof(); } if( gptr() == egptr() ) { // buffer depleated, get next block if( seekpos( (CurrentPage + 1) * VirtualPage::PageSize ) == std::streampos(std::streamoff(-1)) ) { // Seek position error return traits_type::eof(); } } return traits_type::to_int_type(*gptr()); } std::streambuf::pos_type ifstreambuf::seekoff( std::streambuf::off_type Offset, std::ios_base::seekdir Direction, std::ios_base::openmode /*Mode*/ ) { std::streambuf::pos_type Position; if( Direction & std::ios_base::beg ) { Position = (CurrentPage * VirtualPage::PageSize); // Current Page Position += (gptr() - egptr()); // Offset within page Position += Offset; } if( Direction & std::ios_base::cur ) { Position = Offset; } if( Direction & std::ios_base::end ) { Position = (PageCount * VirtualPage::PageSize) + Offset; } return seekpos( Position ); } std::streambuf::pos_type ifstreambuf::seekpos( std::streambuf::pos_type Position, std::ios_base::openmode Mode ) { if( Mode & std::ios_base::in ) { CurrentPage = static_cast(Position) / VirtualPage::PageSize; if( CurrentPage < PageCount ) { if( FetchPage(CurrentPage, &Buffer) ) { setg( reinterpret_cast(Buffer.u8), reinterpret_cast(Buffer.u8) + (Position % VirtualPage::PageSize), reinterpret_cast(Buffer.u8) + VirtualPage::PageSize ); return true; } } } setg( nullptr, nullptr, nullptr ); return std::streampos(std::streamoff(-1)); } bool ifstreambuf::FetchPage(std::uint32_t PageIndex, VirtualPage* Dest) { if( FileIn.fail() ) { return false; } if( PageIndex % VirtualPage::TableSpan == 0 ) // Table Block { if( PageIndex == TableCacheIndex ) { // Cache Hit if( Dest != nullptr ) { std::memcpy( Dest, TableCache.get(), VirtualPage::PageSize ); } return true; } FileIn.seekg( PageIndex * VirtualPage::PageSize, std::ios_base::beg ); FileIn.read( reinterpret_cast(TableCache.get()), VirtualPage::PageSize ); if( FileIn.fail() ) { return false; } TableCache.get()->DecryptTable(PageIndex); TableCacheIndex = PageIndex; if( Dest != nullptr ) { std::memcpy( Dest, TableCache.get(), VirtualPage::PageSize ); } } else // Data Block { if( PageIndex == PageCacheIndex ) { // Cache Hit if( Dest != nullptr ) { std::memcpy( Dest, PageCache.get(), VirtualPage::PageSize ); } return true; } // Prefetch nearest table // Ensure it is in the cache const std::uint32_t NearestTable = (PageIndex / VirtualPage::TableSpan) * VirtualPage::TableSpan; if( FetchPage(NearestTable, nullptr) == false ) { // Failed to fetch table return false; } FileIn.seekg( PageIndex * VirtualPage::PageSize, std::ios_base::beg ); FileIn.read( reinterpret_cast(PageCache.get()), VirtualPage::PageSize ); if( FileIn.fail() ) { return false; } PageCache.get()->DecryptData( TableCache.get()->PageEntries[PageIndex % VirtualPage::TableSpan].Checksum ); if( PageCache.get()->Checksum() != TableCache.get()->PageEntries[PageIndex % VirtualPage::TableSpan].Checksum ) { // Checksum mismatch, file corrupt return false; } PageCacheIndex = PageIndex; if( Dest != nullptr ) { std::memcpy( Dest, PageCache.get(), VirtualPage::PageSize ); } } return true; } /// ifstream ifstream::ifstream(const std::string& Path) : std::istream(new ifstreambuf()) { reinterpret_cast(rdbuf())->open( Path.c_str() ); } ifstream::ifstream(const char* Path) : std::istream(new ifstreambuf()) { reinterpret_cast(rdbuf())->open( Path ); } ifstream::ifstream(const std::wstring& Path) : std::istream(new ifstreambuf()) { reinterpret_cast(rdbuf())->open( Path.c_str() ); } ifstream::ifstream(const wchar_t* Path) : std::istream(new ifstreambuf()) { reinterpret_cast(rdbuf())->open( Path ); } void ifstream::open(const char* FilePath) const { reinterpret_cast(rdbuf())->close(); reinterpret_cast(rdbuf())->open( FilePath ); } void ifstream::open(const std::string& FilePath) const { open(FilePath.c_str()); } void ifstream::open(const wchar_t* FilePath) const { reinterpret_cast(rdbuf())->close(); reinterpret_cast(rdbuf())->open( FilePath ); } void ifstream::open(const std::wstring& FilePath) const { open(FilePath.c_str()); } bool ifstream::is_open() const { return reinterpret_cast(rdbuf())->is_open(); } ifstream::~ifstream() { if( rdbuf() ) { delete rdbuf(); } } VirtualFileVisitor::~VirtualFileVisitor() { } bool VirtualFileVisitor::VisitFolderBegin(VirtualFileEntry& /*Entry*/) { return true; } bool VirtualFileVisitor::VisitFolderEnd(VirtualFileEntry& /*Entry*/) { return true; } bool VirtualFileVisitor::VisitFile(VirtualFileEntry& /*Entry*/) { return true; } /// Virtual File System VirtualFileSystem::VirtualFileSystem(const char* FileName) : SaiStream(std::make_shared(FileName)) { } VirtualFileSystem::VirtualFileSystem(const wchar_t* FileName) : SaiStream(std::make_shared(FileName)) { } VirtualFileSystem::~VirtualFileSystem() { } bool VirtualFileSystem::IsOpen() const { return SaiStream->is_open(); } bool VirtualFileSystem::Exists(const char* Path) { return static_cast(GetEntry(Path)); } std::unique_ptr VirtualFileSystem::GetEntry(const char* Path) { VirtualPage CurPage; Read( 2 * VirtualPage::PageSize, CurPage ); std::string CurPath(Path); const char* PathDelim = "./"; const char* CurToken = std::strtok(&CurPath[0], PathDelim); std::size_t CurEntry = 0; while( CurEntry < 64 && CurPage.FATEntries[CurEntry].Flags && CurToken ) { if( std::strcmp(CurToken, CurPage.FATEntries[CurEntry].Name) == 0 ) { // Match if( (CurToken = std::strtok(nullptr, PathDelim)) == nullptr ) { // No more tokens, done std::unique_ptr Entry(new VirtualFileEntry()); Entry->FATData = CurPage.FATEntries[CurEntry]; Entry->FileSystem = SaiStream; return Entry; } // Try to go further if( CurPage.FATEntries[CurEntry].Type != FATEntry::EntryType::Folder ) { // Part of the path was not a folder, cant go further return nullptr; } Read( CurPage.FATEntries[CurEntry].PageIndex * VirtualPage::PageSize, CurPage ); CurEntry = 0; continue; } CurEntry++; } return nullptr; } std::size_t VirtualFileSystem::Read( std::size_t Offset, void* Destination, std::size_t Size) const { SaiStream->seekg(Offset); SaiStream->read( reinterpret_cast(Destination), Size ); return Size; } void VirtualFileSystem::IterateFileSystem(VirtualFileVisitor& Visitor) { IterateFATBlock( 2, Visitor ); } void VirtualFileSystem::IterateFATBlock( std::size_t PageIndex, VirtualFileVisitor& Visitor ) { VirtualPage CurPage = {}; Read( PageIndex * VirtualPage::PageSize, CurPage ); for( std::size_t i = 0; i < std::extent::value && CurPage.FATEntries[i].Flags; i++ ) { VirtualFileEntry CurEntry; CurEntry.FATData = CurPage.FATEntries[i]; CurEntry.FileSystem = SaiStream; switch( CurEntry.GetType() ) { case FATEntry::EntryType::File: { Visitor.VisitFile(CurEntry); break; } case FATEntry::EntryType::Folder: { Visitor.VisitFolderBegin(CurEntry); IterateFATBlock( CurEntry.GetPageIndex(), Visitor ); Visitor.VisitFolderEnd(CurEntry); break; } } } } /// VirtualFileEntry VirtualFileEntry::VirtualFileEntry() : ReadPoint(0), FATData() { } VirtualFileEntry::~VirtualFileEntry() { } const char* VirtualFileEntry::GetName() const { return FATData.Name; } FATEntry::EntryType VirtualFileEntry::GetType() const { return FATData.Type; } std::time_t VirtualFileEntry::GetTimeStamp() const { return FATData.TimeStamp / 10000000ULL - 11644473600ULL; } std::size_t VirtualFileEntry::GetSize() const { return static_cast(FATData.Size); } std::size_t VirtualFileEntry::GetPageIndex() const { return static_cast(FATData.PageIndex); } std::size_t VirtualFileEntry::Tell() const { return ReadPoint; } void VirtualFileEntry::Seek(std::size_t Offset) { ReadPoint = Offset; } std::size_t VirtualFileEntry::Read(void* Destination, std::size_t Size) { if( std::shared_ptr SaiStream = FileSystem.lock() ) { // Because this is a high-level file-level read: // all table blocks must be abstracted away and "skipped" // or else we will be reading table-block data as file-data // this is a mess right now until I become more aware of some more sound // logic to do this. // What this is basically trying to do is skip reading every table block(blocks with index 0,512,1024,etc) // so this would need to SKIP all byte-ranges: // [0,4096],[2097152,2101248],[4194304,4198400],[TableIndex * 4096,TableIndex * 4096 + 4096] // and by skipping this then file-reads will appear to be perfectly continuous. // If you're reading this and have to work with this I'm so sorry. // - Wunkolo, 10/19/17 std::uint8_t* CurDest = reinterpret_cast(Destination); std::size_t NextTableIndex = ((ReadPoint + (FATData.PageIndex * VirtualPage::PageSize)) / VirtualPage::PageSize & ~( 0x1FF)) + VirtualPage::TableSpan; while( Size ) { // Requested offset that we want to read from const std::size_t CurOffset = ReadPoint + (FATData.PageIndex * VirtualPage::PageSize); const std::size_t CurrentIndex = CurOffset / VirtualPage::PageSize; // If we find ourselves within a table block, skip. if( NextTableIndex == CurrentIndex ) { // Align to immediately at end of current block ReadPoint += VirtualPage::PageSize - (ReadPoint % VirtualPage::PageSize); NextTableIndex += VirtualPage::TableSpan; continue; } const std::size_t NextTableOffset = NextTableIndex * VirtualPage::PageSize; const std::size_t CurStride = std::min( Size, NextTableOffset - CurOffset ); SaiStream->seekg(CurOffset); SaiStream->read( reinterpret_cast(CurDest), CurStride ); ReadPoint += CurStride; CurDest += CurStride; Size -= CurStride; } return Size; } return 0; } /// SaiDocument Document::Document(const char* FileName) : VirtualFileSystem(FileName) { } Document::Document(const wchar_t* FileName) : VirtualFileSystem(FileName) { } Document::~Document() { } std::tuple Document::GetCanvasSize() { if( std::unique_ptr Canvas = GetEntry("canvas") ) { std::uint32_t Alignment; // Always seems to be 0x10, bpc? Alignment? std::uint32_t Width, Height; Canvas->Read(Alignment); Canvas->Read(Width); Canvas->Read(Height); return std::make_tuple(Width, Height); } return std::make_tuple(0, 0); } std::tuple< std::unique_ptr, std::uint32_t, std::uint32_t > Document::GetThumbnail() { if( std::unique_ptr Thumbnail = GetEntry("thumbnail") ) { ThumbnailHeader Header; Thumbnail->Read(Header.Width); Thumbnail->Read(Header.Height); Thumbnail->Read(Header.Magic); if( Header.Magic != *(uint*)"23MB" ) { return std::make_tuple(nullptr, 0, 0); } const std::size_t PixelCount = Header.Height * Header.Width; std::unique_ptr Pixels = std::make_unique(PixelCount * sizeof(std::uint32_t)); Thumbnail->Read( Pixels.get(), PixelCount * sizeof(std::uint32_t) ); //// BGRA to RGBA //std::size_t i = 0; //// Simd speedup, four pixels at a time //while( i < ((PixelCount * sizeof(std::uint32_t)) & ~0xF) ) //{ // const __m128i Swizzle = // _mm_set_epi8( // 15, 12, 13, 14, // 11, 8, 9, 10, // 7, 4, 5, 6, // 3, 0, 1, 2 // ); // __m128i QuadPixel = _mm_loadu_si128( // reinterpret_cast<__m128i*>(&Pixels[i]) // ); // QuadPixel = _mm_shuffle_epi8(QuadPixel, Swizzle); // _mm_store_si128( // reinterpret_cast<__m128i*>(&Pixels[i]), // QuadPixel // ); // i += (sizeof(std::uint32_t) * 4); //} //for( ; i < PixelCount * sizeof(std::uint32_t); i += sizeof(std::uint32_t) ) //{ // std::swap(Pixels[i], Pixels[i + 2]); //} return std::make_tuple(std::move(Pixels), Header.Width, Header.Height); } - return std::make_tuple(nullptr, 0, 0); + return std::make_tuple(nullptr, 0, 0); } /// Keys namespace Keys { const std::uint32_t User[256] = { 0x9913D29E,0x83F58D3D,0xD0BE1526,0x86442EB7,0x7EC69BFB,0x89D75F64,0xFB51B239,0xFF097C56, 0xA206EF1E,0x973D668D,0xC383770D,0x1CB4CCEB,0x36F7108B,0x40336BCD,0x84D123BD,0xAFEF5DF3, 0x90326747,0xCBFFA8DD,0x25B94703,0xD7C5A4BA,0xE40A17A0,0xEADAE6F2,0x6B738250,0x76ECF24A, 0x6F2746CC,0x9BF95E24,0x1ECA68C5,0xE71C5929,0x7817E56C,0x2F99C471,0x395A32B9,0x61438343, 0x5E3E4F88,0x80A9332C,0x1879C69F,0x7A03D354,0x12E89720,0xF980448E,0x03643576,0x963C1D7B, 0xBBED01D6,0xC512A6B1,0x51CB492B,0x44BADEC9,0xB2D54BC1,0x4E7C2893,0x1531C9A3,0x43A32CA5, 0x55B25A87,0x70D9FA79,0xEF5B4AE3,0x8AE7F495,0x923A8505,0x1D92650C,0xC94A9A5C,0x27D4BB14, 0x1372A9F7,0x0C19A7FE,0x64FA1A53,0xF1A2EB6D,0x9FEB910F,0x4CE10C4E,0x20825601,0x7DFC98C4, 0xA046C808,0x8E90E7BE,0x601DE357,0xF360F37C,0x00CD6F77,0xCC6AB9D4,0x24CC4E78,0xAB1E0BFC, 0x6A8BC585,0xFD70ABF0,0xD4A75261,0x1ABF5834,0x45DCFE17,0x5F67E136,0x948FD915,0x65AD9EF5, 0x81AB20E9,0xD36EAF42,0x0F7F45C7,0x1BAE72D9,0xBE116AC6,0xDF58B4D5,0x3F0B960E,0xC2613F98, 0xB065F8B0,0x6259F975,0xC49AEE84,0x29718963,0x0B6D991D,0x09CF7A37,0x692A6DF8,0x67B68B02, 0x2E10DBC2,0x6C34E93C,0xA84B50A1,0xAC6FC0BB,0x5CA6184C,0x34E46183,0x42B379A9,0x79883AB6, 0x08750921,0x35AF2B19,0xF7AA886A,0x49F281D3,0xA1768059,0x14568CFD,0x8B3625F6,0x3E1B2D9D, 0xF60E14CE,0x1157270A,0xDB5C7EB3,0x738A0AFA,0x19C248E5,0x590CBD62,0x7B37C312,0xFC00B148, 0xD808CF07,0xD6BD1C82,0xBD50F1D8,0x91DEA3B8,0xFA86B340,0xF5DF2A80,0x9A7BEA6E,0x1720B8F1, 0xED94A56B,0xBF02BE28,0x0D419FA8,0x073B4DBC,0x829E3144,0x029F43E1,0x71E6D51F,0xA9381F09, 0x583075E0,0xE398D789,0xF0E31106,0x75073EB5,0x5704863E,0x6EF1043B,0xBC407F33,0x8DBCFB25, 0x886C8F22,0x5AF4DD7A,0x2CEACA35,0x8FC969DC,0x9DB8D6B4,0xC65EDC2F,0xE60F9316,0x0A84519A, 0x3A294011,0xDCF3063F,0x41621623,0x228CB75B,0x28E9D166,0xAE631B7F,0x06D8C267,0xDA693C94, 0x54A5E860,0x7C2170F4,0xF2E294CB,0x5B77A0F9,0xB91522A6,0xEC549500,0x10DD78A7,0x3823E458, 0x77D3635A,0x018E3069,0xE039D055,0xD5C341BF,0x9C2400EA,0x85C0A1D1,0x66059C86,0x0416FF1A, 0xE27E05C8,0xB19C4C2D,0xFE4DF58F,0xD2F0CE2A,0x32E013C0,0xEED637D7,0xE9FEC1E8,0xA4890DCA, 0xF4180313,0x7291738C,0xE1B053A2,0x9801267E,0x2DA15BDB,0xADC4DA4F,0xCF95D474,0xC0265781, 0x1F226CED,0xA7472952,0x3C5F0273,0xC152BA68,0xDD66F09B,0x93C7EDCF,0x4F147404,0x3193425D, 0x26B5768A,0x0E683B2E,0x952FDF30,0x2A6BAE46,0xA3559270,0xB781D897,0xEB4ECB51,0xDE49394D, 0x483F629C,0x2153845E,0xB40D64E2,0x47DB0ED0,0x302D8E4B,0x4BF8125F,0x2BD2B0AC,0x3DC836EC, 0xC7871965,0xB64C5CDE,0x9EA8BC27,0xD1853490,0x3B42EC6F,0x63A4FD91,0xAA289D18,0x4D2B1E49, 0xB8A060AD,0xB5F6C799,0x6D1F7D1C,0xBA8DAAE6,0xE51A0FC3,0xD94890E7,0x167DF6D2,0x879BCD41, 0x5096AC1B,0x05ACB5DA,0x375D24EE,0x7F2EB6AA,0xA535F738,0xCAD0AD10,0xF8456E3A,0x23FD5492, 0xB3745532,0x53C1A272,0x469DFCDF,0xE897BF7D,0xA6BBE2AE,0x68CE38AF,0x5D783D0B,0x524F21E4, 0x4A257B31,0xCE7A07B2,0x562CE045,0x33B708A4,0x8CEE8AEF,0xC8FB71FF,0x74E52FAB,0xCDB18796 }; const std::uint32_t NotRemoveMe[256] = { 0xA0C62B54,0x0374CB94,0xB3A53F76,0x5B772C6B,0xF2B92931,0x80F923A9,0x7A22EF7A,0x216C7582, 0xEDFF8B71,0x8B0C6642,0xAF81AD2F,0x8E095A62,0x02926C0C,0xDD2F56B9,0xA3614155,0xF9AED6E4, 0x079C3E5E,0xE6D9E1FD,0x256F165C,0x77280767,0x5D2037A1,0x3019B3CE,0xFC13CC15,0xF457C85F, 0x728DF4E9,0x4405AA18,0x2AE0B950,0xE847316F,0xD69FA172,0x62F658E2,0xB0F21F89,0x8AFB852E, 0x1A3E924A,0xDBAD0B48,0x88ECBD5A,0xC53FC908,0x81251757,0x57D53685,0x73F463A3,0x048F4B58, 0xC36A46AC,0x9A8B6FBD,0x35DC9DC1,0xF76EABF5,0x9280D935,0xBFCC93FB,0x4B2BCA7D,0x60861DFC, 0x7C548877,0x2EA46821,0x7136998F,0x5AD45EDF,0x019BA6EF,0x6FC598C7,0x1DF383EC,0x39BAC06D, 0x5C3A5B1F,0x7827FB39,0x27FCA953,0x8601E843,0x6C429623,0xBA5DC127,0xCE659075,0x48291378, 0x5EDA6B5B,0xE355AC99,0xCF8C704D,0x965E6A29,0xF5035103,0x20582702,0x1B7909DB,0xCA974452, 0x7DB20E30,0x2807326C,0x2DF56D0E,0x084E9C41,0xA42DE39C,0x9170A5C3,0x9DB4F95D,0x53CA2068, 0x3488FC6E,0xD1BB7AE8,0xC61F81C5,0x310857E5,0xEF1694EE,0xF63067B1,0x3E621B8B,0x22523BFF, 0x0D37A4BA,0xCB83BECA,0x9BE78691,0xB7D84E2C,0x45A676DD,0x1F31F636,0x7FAB97C6,0x3CA15F33, 0xFA6DB6FE,0x67DD72DC,0x6B8948FA,0x9849FF4B,0xBE452E79,0x38AF6E7F,0x8FE211A7,0x941728B4, 0x63217749,0x70EF1280,0x13A9F201,0xACDB14A2,0x1184E73A,0x337E87B5,0xB6008EB7,0xC868C43C, 0x85F7DC83,0xD35AD519,0xF87310ED,0xA7C0D29B,0x361D2DCF,0xC1D27C3F,0x9C78DFE0,0x2C4FD8C4, 0x05357D9D,0x2B398964,0x182AC610,0xFD4A3873,0xE71E6416,0x842C4A05,0x5946F70F,0xB95FA366, 0x1C0B71CB,0x50CEFA06,0xAB9DC211,0x659ABCAE,0xD2E17FE7,0x581A0365,0xA61BE0B0,0xD460B084, 0xE21C5CF9,0x87B1D460,0x4DF8CF04,0x4C1573EA,0xCD967432,0xD58EBA12,0x5F2E9A3B,0x6A9955EB, 0x55A391AF,0xEBC1EED5,0xB59E8C7C,0x1E825946,0xAA18A04E,0x6891EDF3,0x663C542D,0xC459D37E, 0xC06453BC,0x460D223E,0x1690F8DE,0xC97580F7,0xA1F08D4F,0x56DE4381,0xEE06B5E3,0xC2FA05D1, 0x3794B488,0xEACD428E,0x7B2362C2,0xE97FDE9F,0xBB4C60D2,0xE4B3E2AB,0x74C93909,0x76AA2FDA, 0x9F049B7B,0x93BCDA8A,0x51BEC790,0x0FD6E4CC,0x8972E6AD,0xBCA70F40,0x405C2469,0x10673486, 0xBD104C97,0x49381E0D,0x063B456A,0x23D02634,0x43ACEC9E,0xE50E49F8,0x197DBF1B,0x8DF1BB9A, 0xB46B1CA6,0xD7E895A5,0xCC51A217,0xE1C2F196,0xDEB533C9,0x24FDC58D,0x32850822,0x12DF4DA8, 0x90BD3500,0x97C7F320,0xDA3450F4,0x2F534059,0xDC7B3D63,0x95B6CD98,0x09BF19D6,0xA5D15DBF, 0x42E47851,0xF07A021E,0x9ECB2A3D,0xE0C39F38,0x99714F95,0x3A5BEA4C,0xB2C4DD25,0xB13D47C0, 0xAD418A0B,0x6DEAB81C,0x83EE25F2,0x3B26AE47,0xA8B018D3,0xFF76E5F1,0xA2ED0461,0x26119ED8, 0x61EB0A74,0x15A2B187,0x4A93CE2A,0x7943A707,0x29E5B744,0x4E14F02B,0x0A698424,0xD9A03AE6, 0xEC87D7C8,0xA94021B8,0x3D95D1CD,0x6E2415BE,0x52E3F592,0x64A83CD9,0x8263C31D,0x41B87EB6, 0x8C50FD1A,0x47C80CD7,0xD844008C,0xB812E9AA,0x0B983013,0xFB7C520A,0x4F66FEBB,0x17E982D0, 0x00FE6914,0xFE0FD028,0x0C328F93,0x75021AF6,0x3FE6AFB2,0x7E330DE1,0xDF8ADB45,0x14D37B37, 0xD04D06A4,0x694B0156,0x0ECF6170,0xC756EBF0,0xF1B76526,0xF348A8B3,0xAE0A79A0,0x54D7B2D4 }; const std::uint32_t LocalState[256] = { 0x021CF107,0xE9253648,0x8AFBA619,0x8CF31842,0xBF40F860,0xA672F03E,0xFA2756AC,0x927B2E7E, 0x1E37D3C4,0x7C3A0524,0x4F284D1B,0xD8A31E9D,0xBA73B6E6,0xF399710D,0xBD8B1937,0x70FFE130, 0x056DAA4A,0xDC509CA1,0x07358DFF,0xDF30A2DC,0x67E7349F,0x49532C31,0x2393EBAA,0xE54DF202, 0x3A2C7EC9,0x98AB13EF,0x7FA52975,0x83E4792E,0x7485DA08,0x4A1823A8,0x77812011,0x8710BB89, 0x9B4E0C68,0x64125D8E,0x5F174A0E,0x33EA50E7,0xA5E168B0,0x1BD9B944,0x6D7D8FE0,0xEE66B84C, 0xF0DB530C,0xF8B06B72,0x97ED7DF8,0x126E0122,0x364BED23,0xA103B75C,0x3BC844FA,0xD0946501, 0x4E2F70F1,0x79A6F413,0x60B9E977,0xC1582F10,0x759B286A,0xE723EEF5,0x8BAC4B39,0xB074B188, 0xCC528E64,0x698700EE,0x44F9E5BB,0x7E336153,0xE2413AFD,0x91DCE2BE,0xFDCE9EC1,0xCAB2DE4F, 0x46C5A486,0xA0D630DB,0x1FCD5FCA,0xEA110891,0x3F20C6F9,0xE8F1B25D,0x6EFD10C8,0x889027AF, 0xF284AF3F,0x89EE9A61,0x58AF1421,0xE41B9269,0x260C6D71,0x5079D96E,0xD959E465,0x519CD72C, 0x73B64F5A,0x40BE5535,0x78386CBC,0x0A1A02CF,0xDBC126B6,0xAD02BC8D,0x22A85BC5,0xA28ABEC3, 0x5C643952,0xE35BC9AD,0xCBDACA63,0x4CA076A4,0x4B6121CB,0x9500BF7D,0x6F8E32BF,0xC06587E5, 0x21FAEF46,0x9C2AD2F6,0x7691D4A2,0xB13E4687,0xC7460AD6,0xDDFE54D5,0x81F516F3,0xC60D7438, 0xB9CB3BC7,0xC4770D94,0xF4571240,0x06862A50,0x30D343D3,0x5ACF52B2,0xACF4E68A,0x0FC2A59B, 0xB70AEACD,0x53AA5E80,0xCF624E8F,0xF1214CEB,0x936072DF,0x62193F18,0xF5491CDA,0x5D476958, 0xDA7A852D,0x5B053E12,0xC5A9F6D0,0xABD4A7D1,0xD25E6E82,0xA4D17314,0x2E148C4E,0x6B9F6399, 0xBC26DB47,0x8296DDCE,0x3E71D616,0x350E4083,0x2063F503,0x167833F2,0x115CDC5E,0x4208E715, 0x03A49B66,0x43A724BA,0xA3B71B8C,0x107584AE,0xC24AE0C6,0xB3FC6273,0x280F3795,0x1392C5D4, 0xD5BAC762,0xB46B5A3B,0xC9480B8B,0xC39783FC,0x17F2935B,0x9DB482F4,0xA7E9CC09,0x553F4734, 0x8DB5C3A3,0x7195EC7A,0xA8518A9A,0x0CE6CB2A,0x14D50976,0x99C077A5,0x012E1733,0x94EC3D7C, 0x3D825805,0x0E80A920,0x1D39D1AB,0xFCD85126,0x3C7F3C79,0x7A43780B,0xB26815D9,0xAF1F7F1C, 0xBB8D7C81,0xAAE5250F,0x34BC670A,0x1929C8D2,0xD6AE9FC0,0x1AE07506,0x416F3155,0x9EB38698, 0x8F22CF29,0x04E8065F,0xE07CFBDE,0x2AEF90E8,0x6CAD049C,0x4DC3A8CC,0x597E3596,0x08562B92, 0x52A21D6F,0xB6C9881D,0xFBD75784,0xF613FC32,0x54C6F757,0x66E2D57B,0xCD69FE9E,0x478CA13D, 0x2F5F6428,0x8E55913C,0xF9091185,0x0089E8B3,0x1C6A48BD,0x3844946D,0x24CC8B6B,0x6524AC2B, 0xD1F6A0F0,0x32980E51,0x8634CE17,0xED67417F,0x250BAEB9,0x84D2FD1A,0xEC6C4593,0x29D0C0B1, 0xEBDF42A9,0x0D3DCD45,0x72BF963A,0x27F0B590,0x159D5978,0x3104ABD7,0x903B1F27,0x9F886A56, 0x80540FA6,0x18F8AD1F,0xEF5A9870,0x85016FC2,0xC8362D41,0x6376C497,0xE1A15C67,0x6ABD806C, 0x569AC1E2,0xFE5D1AF7,0x61CADF59,0xCE063874,0xD4F722DD,0x37DEC2EC,0xAE70BDEA,0x0B2D99B4, 0x39B895FE,0x091E9DFB,0xA9150754,0x7D1D7A36,0x9A07B41E,0x5E8FE3B5,0xD34503A0,0xBE2BFAB7, 0x5742D0A7,0x48DDBA25,0x7BE3604D,0x2D4C66E9,0xB831FFB8,0xF7BBA343,0x451697E4,0x2C4FD84B, 0x96B17B00,0xB5C789E3,0xFFEBF9ED,0xD7C4B349,0xDE3281D8,0x689E4904,0xE683F32F,0x2B3CB0E1 }; const std::uint32_t System[256] = { 0x724FB987,0x4A3E70BE,0xCA549C50,0x34E263E1,0x2D5ED2FF,0x127F0E11,0x58A42B78,0x5F6D14AE, 0x7E2F745D,0xC3450384,0xCFBB15DE,0xDF0A6D8A,0xEF2545F3,0x6D8919DB,0xBC413C94,0xCCB0A198, 0xE42DBBD2,0x361C0B8C,0x8359731F,0x13D61E9F,0x7505F7CE,0x271D7957,0x429C0699,0xD84EC85F, 0x953391DD,0xB25DE567,0xC1BA2F97,0x2309B605,0x69A134D1,0x14A092F2,0x681500EF,0xB90148A7, 0x01AF398B,0x16FD5168,0x9E572161,0x0F7405E3,0x56AC576D,0xF275A349,0x1E8120C0,0x4BF64E3A, 0x5A90E85E,0xD27BC4F1,0x3BD2FFB1,0xD6B40FDC,0x26EC61CF,0xF744AD3F,0xCDE7C548,0x8AFFE60A, 0xE382CA47,0x87DA3E1B,0x8FA3DB36,0x5737C7E0,0xACD8CC17,0xD0CC3B66,0xD93D776B,0x37E5BE2B, 0xD38A1129,0x037E81D0,0x15B15072,0xA6493052,0x35BCD4B9,0xC4538D32,0xEC66C1D5,0xA20DF513, 0x5524EB75,0x92C10488,0xDA03D9FD,0x65168F4B,0x1902BA24,0x7439FA7D,0x1D8CB46F,0xFBC39389, 0xC5DF6A58,0x89E8FB00,0x50DBE0A1,0xAAE98AF8,0x6A7C6C9C,0x7712D6EC,0x4030D0CD,0x6052B585, 0x6132AA77,0xEB4A38C3,0x673AB1E6,0x1C3C07C6,0x91EA2C76,0x7A4C7EA0,0x10B3DCFC,0xBE7DF402, 0x2817D87A,0x25632264,0xBD8D02B0,0xF6D0F8A8,0xB1ED3AF0,0xE6C4F1CA,0x99E028B5,0xE5D48674, 0x09CF47B8,0x9D6EAF0E,0x0A721AFE,0xB6109E54,0x8D642344,0x9FEFC27C,0xF0CA520F,0x2C6BDA7E, 0x2E9DB06A,0x97DEFC2E,0x53C5F0EE,0xAD4B8C60,0xE9F36696,0xA8C68907,0x70B70A20,0x3D9F82AA, 0x7604A595,0x441A563B,0x39193D4A,0x33BF1DC7,0x31B283FB,0xA399F25B,0x642CE39E,0xF9E3B204, 0x79A87534,0x5DBE2943,0x9813E93E,0x47864AD6,0xD420D1BF,0x24A6C986,0xFE386EF7,0xD1B65AB7, 0x3A96BF2F,0x006FE1AB,0x22938E90,0x78FE7A40,0x5CE1319B,0x46F5EEF5,0xBB064BE4,0xB7271C22, 0xC0225D21,0xFA145B10,0x7C58BC33,0xF84654C2,0xEEF4691E,0x021BEC16,0xE16C1737,0x1BCB2603, 0x48A2954D,0xDD56A8FA,0xB8C8A48D,0x5277590B,0x1194E7A9,0x590F42B4,0x7B97C0D8,0x7142B714, 0xAEDD6BC8,0xBA116212,0x6B0E642C,0xF42ABDC5,0x6E76AC81,0xBF348819,0xCB790C59,0xDC6718AD, 0x80471230,0x84DC985C,0x2AEE32C1,0x4D35964F,0x0C6894AC,0x3EF2CDE5,0xB59B37A5,0x9BC9729D, 0x186A41AF,0xEA98A970,0x21F8A291,0x5487E2C9,0xE05F3F42,0xA523B86E,0x8C1E4062,0xA962F6CB, 0x0D4816E8,0x9A4DF92D,0x20439DCC,0xA0713645,0x43506FE9,0xC2EB4651,0xB4780D6C,0xAFC29B28, 0x1FCE5FD4,0x9C7385D3,0xCE00E463,0x38CD997F,0x452933DA,0xC9F7DEBA,0x0840A093,0xDB287B41, 0x90E48479,0x66FC6709,0x6C884C65,0x3FB56082,0xF5B87123,0xED367D1D,0x6F0C44F9,0x8270DD38, 0x0E314F83,0x1AE69F35,0xD5A51FB3,0xA761A671,0x850B4DED,0x06AE0892,0x5EAA2A06,0xC7FA80F6, 0xB0692D4E,0x81657F8F,0x948B0980,0xB3D97C01,0xFC80C3EA,0xFF9E53A4,0x30BD784C,0xF3AD970C, 0xA12E9A31,0x04D37646,0x072655A3,0xE8D5F353,0x4CA98BDF,0x7391FE56,0x7D5BEDA6,0x2BD7650D, 0x862B5C73,0x8B60A726,0x7F8ECB3C,0x517A49B6,0xD7B9CF5A,0x6308D5BC,0x0B3F68D7,0x62A7EA15, 0xC65AFD3D,0xAB8525B2,0xA451B308,0xE7C7AB18,0x88F91369,0x1783279A,0x4F95DF2A,0x41F158BD, 0xC8D1CEBB,0x325CD3E2,0xF1928739,0x9355AE8E,0x2FC05EC4,0x4E0735E7,0xDE3B10D9,0x8E18C61A, 0xE29AEF25,0x4984D7A2,0x051F247B,0x29AB9055,0xFD2101F4,0x96FB2E1C,0x5BF04327,0x3C8F1BEB, }; } + +Layer::Layer(VirtualFileEntry &entry): + ParentLayer(0) +{ + entry.Read(header); + + std::uint32_t CurTag = 0; + std::uint32_t CurTagSize = 0; + //std::cout << "starting tags" << entry.Tell(); + entry.Read(CurTag); + while(CurTag) + { + entry.Read(CurTagSize); + switch( CurTag ) + { + //lorg + case 'name': + { + entry.Read(layerName); + break; + } + case 'pfid': + { + entry.Read(ParentLayer); + break; + } + default: + { + // for any streams that we do not handle, + // we just skip forward in the stream + entry.Seek(entry.Tell() + CurTagSize); + break; + } + } + entry.Read(CurTag); + } + //std::cout << " end" << entry.Tell() << " "; +} + +Layer::~Layer() +{ + +} + +sai::LayerClass Layer::LayerType() +{ + return sai::LayerClass(header.LayerClass); +} + +uint32_t Layer::Identifier() +{ + return header.Identifier; +} + +std::tuple Layer::Position() +{ + return std::make_tuple(header.Bounds.X, header.Bounds.Y); +} + +std::tuple Layer::Size() +{ + return std::make_tuple(header.Bounds.Width, header.Bounds.Height); +} + +int Layer::Opacity() +{ + return int(header.Opacity); +} + +bool Layer::IsVisible() +{ + return bool(header.Visible); +} + +bool Layer::IsPreserveOpacity() +{ + return bool(header.PreserveOpacity); +} + +bool Layer::IsClipping() +{ + return bool(header.Clipping); +} + +BlendingMode Layer::Blending() +{ + return BlendingMode(header.Blending); +} + +char *Layer::LayerName() +{ + return layerName; +} + +uint32_t Layer::ParentID() +{ + return ParentLayer; +} + } diff --git a/plugins/impex/sai/kis_sai_converter.cpp b/plugins/impex/sai/kis_sai_converter.cpp index 2652e47ffa..de6b1b7cca 100644 --- a/plugins/impex/sai/kis_sai_converter.cpp +++ b/plugins/impex/sai/kis_sai_converter.cpp @@ -1,101 +1,224 @@ /* * Copyright (c) 2019 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sai.hpp" +#include + #include #include +#include #include #include #include #include +#include #include #include + #include "kis_sai_converter.h" class SaiLayerVisitor : public sai::VirtualFileVisitor { public: SaiLayerVisitor(KisImageSP image) : m_image(image) , FolderDepth(0) + , parentNodeList(QMap()) + , clippedLayers(QVector()) { } ~SaiLayerVisitor() override { } bool VisitFolderBegin(sai::VirtualFileEntry& Entry) override { qDebug() << "begin folder" << Entry.GetSize() << Entry.GetTimeStamp() << Entry.GetName(); ++FolderDepth; return true; } bool VisitFolderEnd(sai::VirtualFileEntry& /*Entry*/) override { qDebug() << "end folder"; --FolderDepth; return true; } bool VisitFile(sai::VirtualFileEntry& Entry) override { qDebug() << Entry.GetSize() << Entry.GetTimeStamp() << Entry.GetName(); + if (FolderDepth>0) { + sai::Layer layerData = sai::Layer(Entry); + + if (layerData.LayerType() == sai::LayerClass::Layer) { + KisPaintLayerSP layer = new KisPaintLayer(m_image, layerData.LayerName(), 255); + layer->setVisible(layerData.IsVisible()); + layer->setAlphaLocked(layerData.IsPreserveOpacity()); + quint8 opacity = qRound(layerData.Opacity() * 2.55); + layer->setOpacity(opacity); + layer->setCompositeOpId(BlendingMode(layerData.Blending())); + layer->setX(int(std::get<0>(layerData.Position()))); + layer->setY(int(std::get<1>(layerData.Position()))); + // Bounds; + + if (layerData.IsClipping() || !clippedLayers.isEmpty()) { + //we should add it to a list so we can make a clipping group. + //All clipped layers and the first non-clipped layer go in the group. + layer->disableAlphaChannel(layerData.IsClipping()); + clippedLayers.append(layer); + } else { + + if (layerData.ParentID() == 0) { + m_image->addNode(layer); + } else { + if (parentNodeList.contains(layerData.ParentID())) { + m_image->addNode(layer, parentNodeList.value(layerData.ParentID())); + } + } + } + parentNodeList.insert(layerData.Identifier(), layer); + + } /*else if (layerData.LayerType() == sai::LayerClass::Linework) { + break; + }*/ else if (layerData.LayerType() == sai::LayerClass::Set) { + KisGroupLayerSP layer = new KisGroupLayer(m_image, layerData.LayerName(), 255); + layer->setVisible(layerData.IsVisible()); + quint8 opacity = qRound(layerData.Opacity() * 2.55); + layer->setOpacity(opacity); + layer->setX(0); + layer->setY(0); + if (layerData.Blending() != sai::BlendingMode::PassThrough) { + layer->setCompositeOpId(BlendingMode(layerData.Blending())); + } else { + layer->setPassThroughMode(true); + } + + if (layerData.ParentID() == 0) { + m_image->addNode(layer); + } else { + if (parentNodeList.contains(layerData.ParentID())) { + m_image->addNode(layer, parentNodeList.value(layerData.ParentID())); + } + } + parentNodeList.insert(layerData.Identifier(), layer); + + } else if (layerData.LayerType() == sai::LayerClass::Mask) { + KisTransparencyMaskSP layer = new KisTransparencyMask(); + //only interesting thing here is identifying data and identifying parent layer. + if (parentNodeList.contains(layerData.ParentID())) { + m_image->addNode(layer, parentNodeList.value(layerData.ParentID())); + } + } else { + qDebug() << "unknown layer type"; + } + if (!clippedLayers.isEmpty() && layerData.IsClipping() == false) { +//XXX: Make translatable + KisGroupLayerSP clipgroup = new KisGroupLayer(m_image, "Clipping Group", 255); + KisNodeSP clippedLayer; + while(!clippedLayers.isEmpty()) { + clippedLayer = clippedLayers.takeLast(); + qDebug() << clippedLayer->name(); + m_image->addNode(clippedLayer, clipgroup); + } + m_image->addNode(clipgroup); + qDebug() < parentNodeList; + QVector clippedLayers; + + QString BlendingMode(sai::BlendingMode mode) { + QString s = ""; + switch (mode) { + case sai::BlendingMode::Shade: + s = COMPOSITE_SUBTRACT; + break; + case sai::BlendingMode::Binary: + s = COMPOSITE_DISSOLVE; + break; + case sai::BlendingMode::Normal: + s = COMPOSITE_OVER; + break; + case sai::BlendingMode::Screen: + s = COMPOSITE_SCREEN; + break; + case sai::BlendingMode::Multiply: + s = COMPOSITE_MULT; + break; + case sai::BlendingMode::LumiShade: + s = COMPOSITE_LINEAR_LIGHT; + break; + case sai::BlendingMode::Luminosity: + s = COMPOSITE_LUMINOSITY_SAI; + break; + case sai::BlendingMode::PassThrough: + s = "passthrough"; + break; + case sai::BlendingMode::Overlay: + s = COMPOSITE_OVERLAY; + break; + } + return s; + } }; KisSaiConverter::KisSaiConverter(KisDocument *doc) : QObject(0), m_doc(doc) { } KisImportExportErrorCode KisSaiConverter::buildImage(const QString &filename) { sai::Document saiFile(QFile::encodeName(filename)); if (!saiFile.IsOpen()) { dbgFile << "Could not open the file, either it does not exist, either it is not a Sai file :" << filename; return ImportExportCodes::FileFormatIncorrect; } std::tuple size = saiFile.GetCanvasSize(); m_image = new KisImage(m_doc->createUndoStore(), int(std::get<0>(size)), int(std::get<1>(size)), KoColorSpaceRegistry::instance()->rgb8(), "file"); SaiLayerVisitor visitor(m_image); saiFile.IterateFileSystem(visitor); return ImportExportCodes::OK; } KisImageSP KisSaiConverter::image() { return m_image; }