├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── dependencies.cmake ├── supercell-flash ├── CMakeLists.txt ├── sc2_schemas │ ├── DataStorage.fbs │ ├── ExportNames.fbs │ ├── FileDescriptor.fbs │ ├── MovieClipModifiers.fbs │ ├── MovieClips.fbs │ ├── Shapes.fbs │ ├── TextFields.fbs │ ├── Textures.fbs │ └── Types.fbs └── source │ └── flash │ ├── display_object │ ├── DisplayObject.h │ ├── MovieClip.cpp │ ├── MovieClip.h │ ├── MovieClipFrame.cpp │ ├── MovieClipFrame.h │ ├── MovieClipModifier.cpp │ ├── MovieClipModifier.h │ ├── Shape.cpp │ ├── Shape.h │ ├── ShapeDrawBitmapCommand.cpp │ ├── ShapeDrawBitmapCommand.h │ ├── TextField.cpp │ └── TextField.h │ ├── flash.h │ ├── flash_tags.h │ ├── objects │ ├── ExportName.cpp │ ├── ExportName.h │ ├── SWFTexture.cpp │ ├── SWFTexture.h │ ├── SupercellSWF.cpp │ ├── SupercellSWF.h │ ├── SupercellSWF2.cpp │ └── SupercellSWF2.h │ ├── transform │ ├── ColorTransform.cpp │ ├── ColorTransform.h │ ├── Matrix2D.cpp │ ├── Matrix2D.h │ ├── MatrixBank.cpp │ └── MatrixBank.h │ └── types │ ├── SWFContainer.hpp │ ├── SWFStream.hpp │ └── SWFString.hpp └── tools ├── test-tool ├── CMakeLists.txt └── source │ └── main.cpp └── texture-tool ├── CMakeLists.txt └── source ├── SWFFile.hpp ├── SpriteData.hpp └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | android_build 3 | *.sln 4 | .* 5 | *.vcxproj* 6 | Makefile 7 | *.make -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | # workspace 4 | project(supercell-sdk) 5 | 6 | # options 7 | option(SC_FLASH_BUILD_TOOLS "Build tools of supercell::flash library" OFF) 8 | 9 | # includes 10 | include(FetchContent) 11 | include(cmake/dependencies.cmake) 12 | include(WkFlatbuffers) 13 | 14 | # projects 15 | add_subdirectory(supercell-flash) 16 | 17 | if(SC_FLASH_BUILD_TOOLS) 18 | include(WkJson) 19 | add_subdirectory(tools/test-tool) 20 | add_subdirectory(tools/texture-tool) 21 | endif() 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 SCW Make 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SupercellFlash 2 | 3 | Supercell Flash is a C++ library that allows you to quickly read, modify and save .sc files 4 | 5 | ## Example 6 | 7 | ``` 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | using namespace sc; 13 | 14 | int main(int argc, char* argv[]) { 15 | if (argc != 1) { 16 | cout << "Failed to get filename"; 17 | } 18 | 19 | SupercellSWF swf; 20 | 21 | try { 22 | swf.load(argv[0]); 23 | } 24 | catch (const std::exception& err) { 25 | cout << "Failed to load file. Error: " << err.what() << endl; 26 | } 27 | 28 | cout << "File has: " << endl; 29 | cout << swf.exports.size() << " export names" << endl; 30 | cout << swf.textures.size() << " textures" << endl; 31 | cout << swf.textFields.size() << " textfields" << endl; 32 | cout << swf.shapes.size() << " shapes" << endl; 33 | cout << swf.movieClips.size() << " movieclips" << endl; 34 | 35 | return 0; 36 | } 37 | ``` 38 | 39 | ## Building 40 | Before starting, install all necessary submodules 41 | ``` 42 | git update --remote 43 | ``` 44 | 45 | CMake build system is used. Make sure you have but installed and you can use it. Then just run ```cmake``` command from project root directory with specified generator type and output directory. 46 | Example for Visual Studio 2019 for Windows (also you can use 2022 version): 47 | ``` 48 | cmake -G "Visual Studio 16 2019" -B ./build 49 | ``` 50 | -------------------------------------------------------------------------------- /cmake/dependencies.cmake: -------------------------------------------------------------------------------- 1 | 2 | # install supercell-core 3 | FetchContent_Declare( 4 | WorkshopCore 5 | GIT_REPOSITORY https://github.com/sc-workshop/Workshop-Core.git 6 | GIT_TAG main 7 | ) 8 | 9 | FetchContent_MakeAvailable(WorkshopCore) 10 | 11 | # install supercell-compression 12 | FetchContent_Declare( 13 | supercell-compression 14 | GIT_REPOSITORY https://github.com/sc-workshop/SupercellCompression.git 15 | GIT_TAG main 16 | ) 17 | 18 | FetchContent_MakeAvailable(supercell-compression) 19 | 20 | # install supercell-texture 21 | FetchContent_Declare( 22 | supercell-texture 23 | GIT_REPOSITORY https://github.com/sc-workshop/SupercellTexture 24 | GIT_TAG main 25 | ) 26 | 27 | FetchContent_MakeAvailable(supercell-texture) -------------------------------------------------------------------------------- /supercell-flash/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project: supercell-flash 2 | # C++ headers and source files 3 | file(GLOB_RECURSE HEADERS source/*.h) 4 | file(GLOB_RECURSE SOURCES source/*.cpp source/*.hpp) 5 | file(GLOB SC2_SCHEMAS sc2_schemas/*.fbs) 6 | 7 | # choosing build type 8 | if(WK_BUILD_SHARED) 9 | # shared library 10 | set(TARGET supercell-flash-shared) 11 | add_library(${TARGET} SHARED) 12 | 13 | else() 14 | # static library 15 | set(TARGET supercell-flash-static) 16 | add_library(${TARGET} STATIC) 17 | 18 | endif() 19 | 20 | add_library(supercell::flash ALIAS ${TARGET}) 21 | 22 | # add *.h and *.cpp files 23 | target_sources(${TARGET} PRIVATE ${HEADERS} ${SOURCES}) 24 | 25 | # include directories 26 | target_include_directories(${TARGET} PUBLIC source) 27 | 28 | target_link_libraries(${TARGET} PUBLIC wk::core) 29 | target_link_libraries(${TARGET} PUBLIC supercell::compression) 30 | target_link_libraries(${TARGET} PUBLIC supercell::texture) 31 | 32 | wk_flatbuffers_generate_headers( 33 | TARGET SC2_API 34 | INCLUDE_PREFIX flash/SC2 35 | SCHEMAS ${SC2_SCHEMAS} 36 | FLAGS --cpp-std=c++17 37 | ) 38 | 39 | set_target_properties(${TARGET} PROPERTIES 40 | FOLDER SupercellSDK/core 41 | ) 42 | 43 | target_link_libraries(${TARGET} 44 | PUBLIC SC2_API 45 | ) 46 | 47 | # setup project 48 | wk_project_setup(${TARGET}) 49 | wk_include_flatbuffers() 50 | -------------------------------------------------------------------------------- /supercell-flash/sc2_schemas/DataStorage.fbs: -------------------------------------------------------------------------------- 1 | include "Types.fbs"; 2 | namespace sc.flash.SC2; 3 | 4 | // A table that represents a "bank" that contains matrices and color transformations arrays 5 | table MatrixBank 6 | { 7 | matrices: [Typing.Matrix2x3]; 8 | colors: [Typing.ColorTransform]; 9 | half_matrices: [Typing.HalfMatrix2x3]; 10 | } 11 | 12 | // The very first chunk that contains all important binary data such as strings, shapes points, MovieClip frame elements 13 | table DataStorage 14 | { 15 | /// An array of strings that objects from other chunks access by their number 16 | /// Example: strings[ref_id - 1] 17 | strings: [string]; 18 | 19 | unk2: [ubyte]; // most likely custom properties 20 | unk3: [ubyte]; 21 | 22 | // An array of rectangles that is currently only used for MovieClip Scaling Grids. 23 | // Objects can be accessed by their index 24 | rectangles: [Typing.Rect]; 25 | 26 | // Raw buffer that represents array of MovieClipFrameElement. 27 | // MovieClipFrameElement structure is accessible by byte offset in MovieClip 28 | // Example: MovieClipFrameElement* element = movieclips_frame_elements + ref_offset; 29 | movieclips_frame_elements: [ushort]; 30 | 31 | // Raw buffer that represents array of ShapeDrawBitmapCommandVertex. 32 | // Objects can be accessed by index of ShapeDrawBitmapCommandVertex structure 33 | // Example: ShapeDrawBitmapCommandVertex* vertex = shapes_bitmap_poins + (sizeof(shapes_bitmap_poins) * ref_index) 34 | shapes_bitmap_poins: [ubyte]; 35 | 36 | // An array of small transform bank objects 37 | matrix_banks: [MatrixBank]; 38 | } 39 | 40 | root_type DataStorage; -------------------------------------------------------------------------------- /supercell-flash/sc2_schemas/ExportNames.fbs: -------------------------------------------------------------------------------- 1 | namespace sc.flash.SC2; 2 | 3 | // The second chunk. Сontains Export Names and their IDs, for use by the game or external tools 4 | table ExportNames 5 | { 6 | // MovieClip ids 7 | object_ids: [ushort]; 8 | 9 | // Name string reference ids 10 | name_ref_ids: [uint]; 11 | } 12 | 13 | root_type ExportNames; -------------------------------------------------------------------------------- /supercell-flash/sc2_schemas/FileDescriptor.fbs: -------------------------------------------------------------------------------- 1 | namespace sc.flash.SC2; 2 | 3 | // A table that represents a hash of export names that is most likely used when compiling SC to speed up some processes like sprite packaging 4 | table ExportNameHash 5 | { 6 | // Export name it self 7 | name: string; 8 | 9 | // Export name hash. Usually contains 16 bytes hash. 10 | hash: [ubyte]; 11 | } 12 | 13 | enum Precision : uint 14 | { 15 | None = 0, 16 | 17 | // value / 1.0f 18 | Default = 1, 19 | 20 | // value / 20.0f 21 | Twip = 2, 22 | 23 | // value / 1000.0f 24 | Optimized = 3 25 | } 26 | 27 | // A table that describes some properties of SC2 file that help to make some optimizations when loading decompressed file content 28 | table FileDescriptor 29 | { 30 | // Im not sure but these values ​​are different only in files where matrices are normalized 31 | translation_precision: Precision = None; 32 | scale_precision: Precision = None; 33 | 34 | shape_count: uint; 35 | movie_clips_count: uint; 36 | texture_count: uint; 37 | text_fields_count: uint; 38 | 39 | unk3: uint; // Always 0 40 | 41 | // Offset to DataStorage table? 42 | header_offset: uint; 43 | 44 | // Offset to ExportNames table. But it is probably also used to read all subsequent tables 45 | resources_offset: uint; 46 | 47 | // Length of texture chunk buffer 48 | textures_length: uint; 49 | 50 | // Export names hash array 51 | exports: [ExportNameHash]; 52 | } 53 | 54 | root_type FileDescriptor; -------------------------------------------------------------------------------- /supercell-flash/sc2_schemas/MovieClipModifiers.fbs: -------------------------------------------------------------------------------- 1 | namespace sc.flash.SC2; 2 | 3 | // A modifier is an object that currently serves as a mask for Movie Clips. 4 | // When objects are drawn, modifiers set desired state in stencil buffer in frame, achieving masking 5 | struct MovieClipModifier 6 | { 7 | // DisplayObject id 8 | id: ushort; 9 | 10 | // Type of modifier 11 | // Just tags from sc1 12 | type: ubyte; 13 | } 14 | 15 | table MovieClipModifiers 16 | { 17 | modifiers: [MovieClipModifier]; 18 | } 19 | 20 | root_type MovieClipModifiers; -------------------------------------------------------------------------------- /supercell-flash/sc2_schemas/MovieClips.fbs: -------------------------------------------------------------------------------- 1 | namespace sc.flash.SC2; 2 | 3 | // Structure for describing properties of each frame of a MovieClip 4 | struct MovieClipFrame 5 | { 6 | // Count of MovieClipFrameElement to be displayed in current frame 7 | used_transform: uint; 8 | 9 | // Label name of current frame 10 | label_ref_id: uint; 11 | } 12 | 13 | // Same as MovieClipFrame but without frame label and with smaller possible frame elements count 14 | struct MovieClipShortFrame 15 | { 16 | // Count of MovieClipFrameElement to be displayed in current frame 17 | used_transform: ushort; 18 | } 19 | 20 | // A movieclip is a timeline and/or animation object with frames and children that are used in the object 21 | // It can transform properties of children objecs, and create animation in that way 22 | // It can have an export name for use in a game or external tools 23 | // Inherited from DisplayObject 24 | table MovieClip 25 | { 26 | // Display Object Id 27 | id: ushort; 28 | 29 | // Export Name Reference Id 30 | export_name_ref_id: uint = null; 31 | 32 | // MovieClip Framerate 33 | framerate: ubyte; 34 | 35 | // Count of frames in MovieClip // idk what this is doing here but ok :\ 36 | frames_count: ushort; 37 | 38 | // Also known as tag 49 39 | unknown_bool: ubyte; 40 | 41 | // Array of children ids 42 | children_ids: [ushort]; 43 | 44 | // Array of children name reference ids 45 | children_name_ref_ids: [uint]; 46 | 47 | // Array of children blend modes 48 | children_blending: [ubyte]; 49 | 50 | // Classic MovieClip frames 51 | frames: [MovieClipFrame]; 52 | 53 | // Ushort offset to frame elements in DataStorage MovieClip frame elements buffer 54 | frame_elements_offset: uint = 0xFFFFFFFF; 55 | 56 | // Index of target bank with transformations 57 | matrix_bank_index: uint; 58 | 59 | // Index of Rect for 9-slice scaling 60 | scaling_grid_index: uint = null; 61 | 62 | // MovieClip frames but without frame labels and with lower possible count of elements 63 | short_frames: [MovieClipShortFrame]; 64 | } 65 | 66 | table MovieClips 67 | { 68 | movieclips: [MovieClip]; 69 | } 70 | 71 | root_type MovieClips; -------------------------------------------------------------------------------- /supercell-flash/sc2_schemas/Shapes.fbs: -------------------------------------------------------------------------------- 1 | namespace sc.flash.SC2; 2 | 3 | // A command that contains polygon vertices and texture index on which sprite is placed 4 | struct ShapeDrawBitmapCommand 5 | { 6 | unk1: uint; // Maybe flags 7 | 8 | // Index of target texture 9 | texture_index: uint; 10 | 11 | // Count of vertices in command 12 | points_count: uint; 13 | 14 | // Offset in DataStorage shape commands buffer 15 | points_offset: uint; 16 | } 17 | 18 | // A Shape is an object that can be composed of several commands and form a "sprite" 19 | // Inherited from DisplayObject 20 | table Shape 21 | { 22 | // DisplayObject id 23 | id: ushort; 24 | 25 | // Array of bitmap draw commands 26 | commands: [ShapeDrawBitmapCommand]; 27 | } 28 | 29 | // The fourth chunk that contains an array of shapes 30 | table Shapes 31 | { 32 | shapes: [Shape]; 33 | } 34 | 35 | root_type Shapes; -------------------------------------------------------------------------------- /supercell-flash/sc2_schemas/TextFields.fbs: -------------------------------------------------------------------------------- 1 | namespace sc.flash.SC2; 2 | 3 | enum TextFieldStyle : ubyte(bit_flags) { 4 | unknown_flag3, 5 | has_outline, 6 | bold, 7 | italic, 8 | is_multiline, 9 | unknown_flag, 10 | auto_kern 11 | } 12 | 13 | // A TextField is used to display text or simply as a bound 14 | // Inherited from DisplayObject 15 | struct TextField 16 | { 17 | // Display Object Id 18 | id: ushort; 19 | 20 | unused1: ushort; 21 | 22 | // Font Name Reference Id 23 | font_name_ref_id: uint; 24 | 25 | // Bound 26 | left: short; 27 | top: short; 28 | right: short; 29 | bottom: short; 30 | 31 | // Color of text 32 | font_color: uint; 33 | 34 | // Color of text outline 35 | outline_color: uint; 36 | 37 | // Placeholder text reference Id 38 | text_ref_id: uint; 39 | 40 | typography_ref_id: uint; // Maybe typography path 41 | 42 | styles: ubyte; 43 | 44 | align: ubyte; 45 | 46 | // Size of text 47 | font_size: ubyte; 48 | 49 | unused2: ubyte; 50 | unknown_short: ushort; // unknown_short 51 | } 52 | 53 | // Third chunk. Contains TextField structures 54 | table TextFields 55 | { 56 | textfields: [TextField]; 57 | } 58 | 59 | root_type TextFields; -------------------------------------------------------------------------------- /supercell-flash/sc2_schemas/Textures.fbs: -------------------------------------------------------------------------------- 1 | namespace sc.flash.SC2; 2 | 3 | // Basic texture info and data 4 | table TextureData 5 | { 6 | flags: uint; 7 | 8 | type: ubyte; // ScPixel ? 9 | 10 | // Texture Width 11 | width: ushort; 12 | 13 | // Texture Height 14 | height: ushort; 15 | 16 | // Texture Data 17 | data: [ubyte]; 18 | 19 | // Path to external texture (zktx, ktx, sctx) 20 | external_texture: string; 21 | } 22 | 23 | // Each set can have a texture and optionally a lowres variant which is 2 times smaller 24 | table TextureSet 25 | { 26 | lowres: TextureData; 27 | highres: TextureData (required); 28 | } 29 | 30 | // Texture atlases on which sprites are placed 31 | table Textures 32 | { 33 | textures: [TextureSet]; 34 | } 35 | 36 | root_type Textures; -------------------------------------------------------------------------------- /supercell-flash/sc2_schemas/Types.fbs: -------------------------------------------------------------------------------- 1 | namespace sc.flash.SC2.Typing; 2 | 3 | // A structure that represents Rectangle 4 | // Used for MovieClip Scaling Grid 5 | struct Rect 6 | { 7 | left: float; 8 | top: float; 9 | right: float; 10 | bottom: float; 11 | } 12 | 13 | // A structure that represents Matrix 2x3 14 | // Used for transforming Display Objects in 2D space 15 | struct Matrix2x3 16 | { 17 | // Scale X 18 | a: float; 19 | 20 | // Skew X 21 | b: float; 22 | 23 | // Skew Y 24 | c: float; 25 | 26 | // Scale Y 27 | d: float; 28 | 29 | // Translation X 30 | tx: float; 31 | 32 | // Translation Y 33 | ty: float; 34 | } 35 | 36 | // A structure that represents Normalized Half Precision Matrix 2x3 37 | // Used to save resources when accuracy is not very important 38 | struct HalfMatrix2x3 39 | { 40 | // Scale X 41 | a: short; 42 | 43 | // Skew X 44 | b: short; 45 | 46 | // Skew Y 47 | c: short; 48 | 49 | // Scale Y 50 | d: short; 51 | 52 | // Translation X 53 | tx: short; 54 | 55 | // Translation Y 56 | ty: short; 57 | } 58 | 59 | // A structure that represents Multiply and Addition color 60 | // Used for transforming Display Objects color 61 | struct ColorTransform 62 | { 63 | // Red Multiply 64 | r_mul: ubyte; 65 | // Greenn Multiply 66 | g_mul: ubyte; 67 | // Blue Multiply 68 | b_mul: ubyte; 69 | 70 | // Alpha Multiply 71 | alpha: ubyte; 72 | 73 | // Red Addition 74 | r_add: ubyte; 75 | // Green Addition 76 | g_add: ubyte; 77 | // Blue Addition 78 | b_add: ubyte; 79 | } 80 | 81 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/DisplayObject.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace sc 6 | { 7 | namespace flash 8 | { 9 | class SupercellSWF; 10 | 11 | class DisplayObject 12 | { 13 | public: 14 | virtual ~DisplayObject() = default; 15 | 16 | public: 17 | uint16_t id = 0; 18 | 19 | public: 20 | virtual void load(SupercellSWF& swf, uint8_t tag) = 0; 21 | virtual void save(SupercellSWF& swf) const = 0; 22 | 23 | virtual bool is_shape() const { return false; }; 24 | virtual bool is_movieclip() const { return false; }; 25 | virtual bool is_modifier() const { return false; }; 26 | virtual bool is_textfield() const { return false; }; 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/MovieClip.cpp: -------------------------------------------------------------------------------- 1 | #include "MovieClip.h" 2 | #include "flash/objects/SupercellSWF.h" 3 | 4 | namespace sc 5 | { 6 | namespace flash { 7 | void MovieClip::load(SupercellSWF& swf, uint8_t tag) 8 | { 9 | id = swf.stream.read_unsigned_short(); 10 | frame_rate = swf.stream.read_unsigned_byte(); 11 | unknown_flag = tag == TAG_MOVIE_CLIP_5; 12 | 13 | if (tag == TAG_MOVIE_CLIP || tag == TAG_MOVIE_CLIP_4) 14 | throw wk::Exception("TAG_MOVIE_CLIP and TAG_MOVIE_CLIP_4 is unsupported"); 15 | 16 | uint16_t frame_count = swf.stream.read_unsigned_short(); 17 | frames.resize(frame_count); 18 | 19 | if (tag == TAG_MOVIE_CLIP_6) 20 | { 21 | uint8_t properties_count = swf.stream.read_unsigned_byte(); 22 | 23 | for (uint8_t i = 0; properties_count > i; i++) 24 | { 25 | uint8_t property_type = swf.stream.read_unsigned_byte(); 26 | 27 | switch (property_type) 28 | { 29 | case 0: 30 | custom_properties.emplace_back( 31 | std::make_any(swf.stream.read_bool()) 32 | ); 33 | break; 34 | default: 35 | throw wk::Exception("Unknown or unsupported custom property"); 36 | } 37 | } 38 | } 39 | 40 | int32_t frame_elements_count = swf.stream.read_int(); 41 | frame_elements.resize(frame_elements_count); 42 | 43 | for (int32_t i = 0; i < frame_elements_count; i++) 44 | { 45 | MovieClipFrameElement& element = frame_elements[i]; 46 | 47 | element.instance_index = swf.stream.read_unsigned_short(); 48 | element.matrix_index = swf.stream.read_unsigned_short(); 49 | element.colorTransform_index = swf.stream.read_unsigned_short(); 50 | } 51 | 52 | uint16_t instance_count = swf.stream.read_unsigned_short(); 53 | childrens.resize(instance_count); 54 | 55 | for (int16_t i = 0; i < instance_count; i++) 56 | { 57 | childrens[i].id = swf.stream.read_unsigned_short(); 58 | } 59 | 60 | if (tag == TAG_MOVIE_CLIP_3 || tag >= TAG_MOVIE_CLIP_5) 61 | { 62 | for (int16_t i = 0; i < instance_count; i++) 63 | { 64 | childrens[i].blend_mode = (DisplayObjectInstance::BlendMode)swf.stream.read_unsigned_byte(); 65 | } 66 | } 67 | 68 | for (int16_t i = 0; i < instance_count; i++) 69 | { 70 | swf.stream.read_string(childrens[i].name); 71 | } 72 | 73 | uint16_t frames_total = 0; 74 | while (true) 75 | { 76 | uint8_t frame_tag = swf.stream.read_unsigned_byte(); 77 | int32_t frame_tag_length = swf.stream.read_int(); 78 | 79 | if (frame_tag == 0) 80 | break; 81 | 82 | if (frame_tag_length < 0) 83 | throw wk::Exception("Negative tag length"); 84 | 85 | switch (frame_tag) 86 | { 87 | case TAG_MOVIE_CLIP_FRAME_2: 88 | frames[frames_total].load(swf); 89 | frames_total++; 90 | break; 91 | 92 | case TAG_SCALING_GRID: 93 | { 94 | wk::RectF grid; 95 | grid.left = swf.stream.read_twip(); 96 | grid.top = swf.stream.read_twip(); 97 | float width = swf.stream.read_twip(); 98 | float height = swf.stream.read_twip(); 99 | 100 | grid.right = grid.left + width; 101 | grid.bottom = grid.top + height; 102 | 103 | scaling_grid = grid; 104 | } 105 | break; 106 | 107 | case TAG_MATRIX_BANK_INDEX: 108 | bank_index = swf.stream.read_unsigned_byte(); 109 | break; 110 | 111 | default: 112 | swf.stream.seek(frame_tag_length, wk::Stream::SeekMode::Add); 113 | break; 114 | } 115 | } 116 | } 117 | 118 | void MovieClip::save(SupercellSWF& swf) const 119 | { 120 | swf.stream.write_unsigned_short(id); 121 | swf.stream.write_unsigned_byte(frame_rate); 122 | swf.stream.write_unsigned_short(frames.size()); 123 | 124 | if (swf.save_custom_property) 125 | { 126 | swf.stream.write_unsigned_byte(custom_properties.size()); 127 | for (std::any custom_property : custom_properties) 128 | { 129 | uint8_t property_type = 0xFF; 130 | 131 | if (custom_property.type() == typeid(bool)) 132 | { 133 | property_type = 0; 134 | } 135 | else 136 | { 137 | throw wk::Exception("Unknown or unsupported custom property type"); 138 | } 139 | 140 | swf.stream.write_unsigned_byte(property_type); 141 | switch (property_type) 142 | { 143 | case 0: 144 | swf.stream.write_bool(std::any_cast(custom_property)); 145 | break; 146 | default: 147 | break; 148 | } 149 | } 150 | } 151 | 152 | swf.stream.write_unsigned_int(frame_elements.size()); 153 | write_frame_elements_buffer(swf.stream); 154 | 155 | swf.stream.write_short(childrens.size()); 156 | 157 | for (const DisplayObjectInstance& children : childrens) 158 | { 159 | swf.stream.write_unsigned_short(children.id); 160 | } 161 | 162 | for (const DisplayObjectInstance& children : childrens) 163 | { 164 | swf.stream.write_unsigned_byte((uint8_t)children.blend_mode); 165 | } 166 | 167 | for (const DisplayObjectInstance& children : childrens) 168 | { 169 | swf.stream.write_string(children.name); 170 | } 171 | 172 | if (bank_index != 0) 173 | { 174 | swf.stream.write_unsigned_byte(TAG_MATRIX_BANK_INDEX); 175 | swf.stream.write_int(1); 176 | swf.stream.write_unsigned_byte((uint8_t)bank_index); 177 | } 178 | 179 | for (const MovieClipFrame& frame : frames) 180 | { 181 | size_t position = swf.stream.write_tag_header(frame.tag(swf)); 182 | frame.save(swf); 183 | swf.stream.write_tag_final(position); 184 | } 185 | 186 | if (scaling_grid != std::nullopt) 187 | { 188 | swf.stream.write_unsigned_byte(TAG_SCALING_GRID); 189 | swf.stream.write_int(sizeof(int) * 4); 190 | 191 | swf.stream.write_twip(scaling_grid->left); 192 | swf.stream.write_twip(scaling_grid->top); 193 | swf.stream.write_twip(std::abs(scaling_grid->left - scaling_grid->right)); 194 | swf.stream.write_twip(std::abs(scaling_grid->top - scaling_grid->bottom)); 195 | } 196 | 197 | swf.stream.write_tag_flag(TAG_END); 198 | } 199 | 200 | uint8_t MovieClip::tag(SupercellSWF& swf) const 201 | { 202 | if (swf.save_custom_property) 203 | { 204 | return TAG_MOVIE_CLIP_6; 205 | } 206 | 207 | return unknown_flag ? TAG_MOVIE_CLIP_5 : TAG_MOVIE_CLIP_3; 208 | } 209 | 210 | bool MovieClip::is_movieclip() const 211 | { 212 | return true; 213 | } 214 | 215 | void MovieClip::load_sc2(SupercellSWF& swf, const SC2::DataStorage* storage, const uint8_t* data) 216 | { 217 | auto movieclips_data = SC2::GetMovieClips(data); 218 | 219 | auto movieclips_vector = movieclips_data->movieclips(); 220 | if (!movieclips_vector) return; 221 | 222 | uint16_t movieclips_count = (uint16_t)movieclips_vector->size(); 223 | swf.movieclips.reserve(movieclips_count); 224 | 225 | auto strings_vector = storage->strings(); 226 | auto elements_vector = storage->movieclips_frame_elements(); 227 | auto rectangles_vector = storage->rectangles(); 228 | 229 | for (uint16_t i = 0; movieclips_count > i; i++) 230 | { 231 | auto movieclip_data = movieclips_vector->Get(i); 232 | MovieClip& movieclip = swf.movieclips[i]; 233 | 234 | movieclip.id = movieclip_data->id(); 235 | movieclip.frame_rate = movieclip_data->framerate(); 236 | 237 | movieclip.bank_index = movieclip_data->matrix_bank_index(); 238 | movieclip.unknown_flag = (bool)movieclip_data->unknown_bool(); 239 | 240 | { 241 | auto scaling_grid = movieclip_data->scaling_grid_index(); 242 | if (scaling_grid.has_value()) 243 | { 244 | auto rectangle = rectangles_vector->Get(scaling_grid.value()); 245 | movieclip.scaling_grid = wk::RectF( 246 | rectangle->left(), 247 | rectangle->top(), 248 | rectangle->right(), 249 | rectangle->bottom() 250 | ); 251 | } 252 | } 253 | 254 | auto children_ids_vector = movieclip_data->children_ids(); 255 | auto children_blending_vector = movieclip_data->children_blending(); 256 | auto children_names_vector = movieclip_data->children_name_ref_ids(); 257 | 258 | uint16_t children_count = 0; 259 | if (children_ids_vector) 260 | { 261 | children_count = (uint16_t)children_ids_vector->size(); 262 | movieclip.childrens.resize(children_count); 263 | 264 | for (uint16_t c = 0; children_count > c; c++) 265 | { 266 | movieclip.childrens[c].id = children_ids_vector->Get(c); 267 | } 268 | } 269 | 270 | 271 | if (children_blending_vector) 272 | { 273 | for (uint16_t c = 0; children_blending_vector->size() > c && children_count > c; c++) 274 | { 275 | movieclip.childrens[c].blend_mode = (DisplayObjectInstance::BlendMode)children_blending_vector->Get(c); 276 | } 277 | } 278 | 279 | if (children_names_vector) 280 | { 281 | for (uint16_t c = 0; children_names_vector->size() > c && children_count > c; c++) 282 | { 283 | movieclip.childrens[c].name = SWFString( 284 | strings_vector->Get( 285 | children_names_vector->Get(c) 286 | )->c_str() 287 | ); 288 | } 289 | } 290 | 291 | auto frames_vector = movieclip_data->frames(); 292 | auto short_frames_vector = movieclip_data->short_frames(); 293 | uint16_t frames_count = (uint16_t)movieclip_data->frames_count(); 294 | movieclip.frames.reserve(frames_count); 295 | swf.sc2_compile_settings.use_short_frames |= short_frames_vector != nullptr; 296 | 297 | if (frames_vector) 298 | { 299 | for (auto frame_data : *frames_vector) 300 | { 301 | MovieClipFrame& frame = movieclip.frames.emplace_back(); 302 | uint32_t label_id = frame_data->label_ref_id(); 303 | frame.elements_count = frame_data->used_transform(); 304 | frame.label = SWFString( 305 | strings_vector->Get(label_id)->c_str() 306 | ); 307 | } 308 | } 309 | else if (short_frames_vector) 310 | { 311 | for (auto frame_data : *short_frames_vector) 312 | { 313 | MovieClipFrame& frame = movieclip.frames.emplace_back(); 314 | frame.elements_count = frame_data->used_transform(); 315 | } 316 | } 317 | 318 | 319 | uint32_t elements_count = 0; 320 | uint32_t elements_offset = movieclip_data->frame_elements_offset(); 321 | for (MovieClipFrame& frame : movieclip.frames) 322 | { 323 | elements_count += frame.elements_count; 324 | } 325 | 326 | movieclip.frame_elements.reserve(elements_count); 327 | for (uint32_t e = 0; elements_count > e; e++) 328 | { 329 | MovieClipFrameElement& element = movieclip.frame_elements.emplace_back(); 330 | 331 | element.instance_index = elements_vector->Get(elements_offset++); 332 | element.matrix_index = elements_vector->Get(elements_offset++); 333 | element.colorTransform_index = elements_vector->Get(elements_offset++); 334 | } 335 | } 336 | } 337 | 338 | void MovieClip::write_frame_elements_buffer(wk::Stream& stream) const 339 | { 340 | for (const MovieClipFrameElement& element : frame_elements) 341 | { 342 | stream.write_unsigned_short(element.instance_index); 343 | stream.write_unsigned_short(element.matrix_index); 344 | stream.write_unsigned_short(element.colorTransform_index); 345 | } 346 | } 347 | } 348 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/MovieClip.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "flash/types/SWFString.hpp" 9 | #include "flash/types/SWFContainer.hpp" 10 | 11 | #include "flash/display_object/DisplayObject.h" 12 | #include "flash/display_object/MovieClipFrame.h" 13 | 14 | #include "flash/SC2/DataStorage_generated.h" 15 | #include "flash/SC2/MovieClips_generated.h" 16 | 17 | namespace sc 18 | { 19 | namespace flash { 20 | class SupercellSWF; 21 | 22 | struct MovieClipFrameElement 23 | { 24 | uint16_t instance_index; 25 | uint16_t matrix_index = 0xFFFF; 26 | uint16_t colorTransform_index = 0xFFFF; 27 | }; 28 | 29 | struct DisplayObjectInstance 30 | { 31 | enum class BlendMode : uint8_t 32 | { 33 | Normal = 0, 34 | // Normal1 = 1, 35 | Layer = 2, 36 | Multiply, 37 | Screen, 38 | Lighten, 39 | Darken, 40 | Difference, 41 | Add, 42 | Subtract, 43 | Invert, 44 | Alpha, 45 | Erase, 46 | Overlay, 47 | HardLight, 48 | }; 49 | 50 | uint16_t id; 51 | BlendMode blend_mode = BlendMode::Normal; 52 | SWFString name; 53 | }; 54 | 55 | typedef SWFVector MovieClipFrameElementsArray; 56 | typedef SWFVector MovieClipChildrensArray; 57 | typedef SWFVector MovieClipFrameArray; 58 | 59 | class MovieClip : public DisplayObject 60 | { 61 | public: 62 | MovieClip() {}; 63 | virtual ~MovieClip() = default; 64 | MovieClip(const MovieClip&) = default; 65 | MovieClip(MovieClip&&) = default; 66 | MovieClip& operator=(const MovieClip&) = default; 67 | MovieClip& operator=(MovieClip&&) = default; 68 | 69 | public: 70 | MovieClipFrameElementsArray frame_elements; 71 | MovieClipChildrensArray childrens; 72 | MovieClipFrameArray frames; 73 | 74 | public: 75 | uint8_t frame_rate = 24; 76 | 77 | uint32_t bank_index = 0; 78 | 79 | std::optional scaling_grid; 80 | 81 | SWFVector custom_properties; 82 | 83 | public: 84 | bool unknown_flag = false; 85 | 86 | public: 87 | virtual void load(SupercellSWF& swf, uint8_t tag); 88 | virtual void save(SupercellSWF& swf) const; 89 | 90 | virtual uint8_t tag(SupercellSWF& swf) const; 91 | 92 | virtual bool is_movieclip() const; 93 | 94 | void write_frame_elements_buffer(wk::Stream& stream) const; 95 | 96 | public: 97 | static void load_sc2(SupercellSWF&, const SC2::DataStorage*, const uint8_t*); 98 | }; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/MovieClipFrame.cpp: -------------------------------------------------------------------------------- 1 | #include "MovieClipFrame.h" 2 | #include "flash/objects/SupercellSWF.h" 3 | 4 | namespace sc 5 | { 6 | namespace flash { 7 | void MovieClipFrame::load(SupercellSWF& swf) 8 | { 9 | elements_count = swf.stream.read_unsigned_short(); 10 | swf.stream.read_string(label); 11 | } 12 | 13 | void MovieClipFrame::save(SupercellSWF& swf) const 14 | { 15 | swf.stream.write_unsigned_short((uint16_t)elements_count); 16 | swf.stream.write_string(label); 17 | } 18 | 19 | uint8_t MovieClipFrame::tag(SupercellSWF&) const 20 | { 21 | return TAG_MOVIE_CLIP_FRAME_2; 22 | }; 23 | } 24 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/MovieClipFrame.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "flash/types/SWFString.hpp" 4 | 5 | namespace sc 6 | { 7 | namespace flash { 8 | class SupercellSWF; 9 | 10 | struct MovieClipFrame 11 | { 12 | public: 13 | MovieClipFrame() {}; 14 | virtual ~MovieClipFrame() = default; 15 | MovieClipFrame(const MovieClipFrame&) = default; 16 | MovieClipFrame(MovieClipFrame&&) = default; 17 | MovieClipFrame& operator=(const MovieClipFrame&) = default; 18 | MovieClipFrame& operator=(MovieClipFrame&&) = default; 19 | 20 | public: 21 | uint32_t elements_count = 0; 22 | SWFString label; 23 | 24 | public: 25 | virtual void load(SupercellSWF& swf); 26 | virtual void save(SupercellSWF& swf) const; 27 | 28 | virtual uint8_t tag(SupercellSWF& swf) const; 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/MovieClipModifier.cpp: -------------------------------------------------------------------------------- 1 | #include "MovieClipModifier.h" 2 | 3 | #include "flash/objects/SupercellSWF.h" 4 | 5 | namespace sc { 6 | namespace flash { 7 | void MovieClipModifier::load(SupercellSWF& swf, uint8_t tag) 8 | { 9 | id = swf.stream.read_unsigned_short(); 10 | type = (Type)tag; 11 | } 12 | 13 | void MovieClipModifier::save(SupercellSWF& swf) const 14 | { 15 | swf.stream.write_unsigned_short(id); 16 | } 17 | 18 | uint8_t MovieClipModifier::tag(SupercellSWF&) const 19 | { 20 | return (uint8_t)type; 21 | } 22 | 23 | bool MovieClipModifier::is_modifier() const 24 | { 25 | return true; 26 | } 27 | 28 | void MovieClipModifier::load_sc2(SupercellSWF& swf, const SC2::DataStorage*, const uint8_t* data) 29 | { 30 | auto modifiers_data = SC2::GetMovieClipModifiers(data); 31 | 32 | auto modifiers_vector = modifiers_data->modifiers(); 33 | if (!modifiers_vector) return; 34 | 35 | uint16_t modifiers_count = (uint16_t)modifiers_vector->size(); 36 | swf.movieclip_modifiers.reserve(modifiers_count); 37 | 38 | for (uint16_t i = 0; modifiers_count > i; i++) 39 | { 40 | auto modifier_data = modifiers_vector->Get(i); 41 | MovieClipModifier& modifier = swf.movieclip_modifiers.emplace_back(); 42 | 43 | modifier.id = modifier_data->id(); 44 | modifier.type = (MovieClipModifier::Type)modifier_data->type(); 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/MovieClipModifier.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DisplayObject.h" 4 | #include "flash/flash_tags.h" 5 | 6 | #include "flash/SC2/DataStorage_generated.h" 7 | #include "flash/SC2/MovieClipModifiers_generated.h" 8 | 9 | namespace sc 10 | { 11 | namespace flash { 12 | class SupercellSWF; 13 | 14 | class MovieClipModifier : public DisplayObject 15 | { 16 | public: 17 | MovieClipModifier() {}; 18 | virtual ~MovieClipModifier() = default; 19 | MovieClipModifier(const MovieClipModifier&) = default; 20 | MovieClipModifier(MovieClipModifier&&) = default; 21 | MovieClipModifier& operator=(const MovieClipModifier&) = default; 22 | MovieClipModifier& operator=(MovieClipModifier&&) = default; 23 | 24 | public: 25 | enum class Type : uint8_t 26 | { 27 | Mask = TAG_MOVIE_CLIP_MODIFIER, 28 | Masked = TAG_MOVIE_CLIP_MODIFIER_2, 29 | Unmasked = TAG_MOVIE_CLIP_MODIFIER_3, 30 | }; 31 | 32 | public: 33 | Type type = Type::Mask; 34 | 35 | public: 36 | virtual void load(SupercellSWF& swf, uint8_t tag); 37 | virtual void save(SupercellSWF& swf) const; 38 | 39 | virtual uint8_t tag(SupercellSWF& swf) const; 40 | 41 | virtual bool is_modifier() const; 42 | 43 | public: 44 | static void load_sc2(SupercellSWF&, const SC2::DataStorage*, const uint8_t*); 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/Shape.cpp: -------------------------------------------------------------------------------- 1 | #include "Shape.h" 2 | 3 | #include "flash/objects/SupercellSWF.h" 4 | 5 | namespace sc 6 | { 7 | namespace flash { 8 | void Shape::load(SupercellSWF& swf, uint8_t tag) 9 | { 10 | id = swf.stream.read_unsigned_short(); 11 | 12 | uint16_t commands_count = swf.stream.read_unsigned_short(); 13 | commands.resize(commands_count); 14 | 15 | uint16_t vertices_count = 4; 16 | if (tag == TAG_SHAPE_2) 17 | vertices_count = swf.stream.read_unsigned_short(); 18 | 19 | uint16_t commands_total = 0; 20 | uint16_t vertices_total = 0; 21 | while (true) 22 | { 23 | uint8_t command_tag = swf.stream.read_unsigned_byte(); 24 | int32_t command_tag_length = swf.stream.read_int(); 25 | 26 | if (command_tag_length < 0) 27 | throw wk::Exception("Negative tag length"); 28 | 29 | if (command_tag == TAG_END) 30 | break; 31 | 32 | switch (command_tag) 33 | { 34 | case TAG_SHAPE_DRAW_BITMAP_COMMAND: 35 | case TAG_SHAPE_DRAW_BITMAP_COMMAND_2: 36 | case TAG_SHAPE_DRAW_BITMAP_COMMAND_3: 37 | commands[commands_total].load(swf, command_tag); 38 | vertices_count += (uint8_t)commands[commands_total].vertices.size(); 39 | if (vertices_count < vertices_total) 40 | { 41 | throw wk::Exception("Trying to load too many vertices"); 42 | } 43 | 44 | commands_total++; 45 | break; 46 | 47 | default: 48 | swf.stream.seek(command_tag_length, wk::Stream::SeekMode::Add); 49 | break; 50 | } 51 | } 52 | } 53 | 54 | void Shape::save(SupercellSWF& swf) const 55 | { 56 | if (commands.size() >= std::numeric_limits().max()) 57 | { 58 | throw wk::Exception("Too many Shape commands in shape %i", id); 59 | } 60 | 61 | uint16_t commands_count = (uint16_t)commands.size(); 62 | 63 | swf.stream.write_unsigned_short(id); 64 | swf.stream.write_unsigned_short(commands_count); 65 | 66 | uint16_t vertices_count = 0; 67 | for (uint16_t i = 0; commands_count > i; i++) { 68 | vertices_count += static_cast(commands[i].vertices.size()); 69 | } 70 | 71 | swf.stream.write_unsigned_short(vertices_count); 72 | 73 | if (vertices_count != 0) { 74 | for (const ShapeDrawBitmapCommand& command : commands) 75 | { 76 | size_t position = swf.stream.write_tag_header(command.tag(swf)); 77 | command.save(swf); 78 | swf.stream.write_tag_final(position); 79 | } 80 | } 81 | 82 | swf.stream.write_tag_flag(TAG_END); 83 | } 84 | 85 | uint8_t Shape::tag(SupercellSWF&) const 86 | { 87 | return TAG_SHAPE_2; 88 | } 89 | 90 | bool Shape::is_shape() const 91 | { 92 | return true; 93 | } 94 | 95 | bool Shape::operator==(const Shape& other) const 96 | { 97 | return id == other.id && commands == other.commands; 98 | } 99 | 100 | void Shape::load_sc2(SupercellSWF& swf, const SC2::DataStorage* storage, const uint8_t* data) 101 | { 102 | auto shapes_data = SC2::GetShapes(data); 103 | 104 | auto shapes_vector = shapes_data->shapes(); 105 | if (!shapes_vector) return; 106 | 107 | auto shape_vertex_buffer = storage->shapes_bitmap_poins(); 108 | 109 | uint16_t shapes_count = (uint16_t)shapes_vector->size(); 110 | swf.shapes.reserve(shapes_count); 111 | 112 | for (uint16_t i = 0; shapes_count > i; i++) 113 | { 114 | auto shape_data = shapes_vector->Get(i); 115 | Shape& shape = swf.shapes[i]; 116 | shape.id = shape_data->id(); 117 | 118 | auto commands_vector = shape_data->commands(); 119 | if (!commands_vector) continue; 120 | 121 | uint32_t commands_count = commands_vector->size(); 122 | shape.commands.reserve(commands_count); 123 | 124 | for (uint32_t c = 0; commands_count > c; c++) 125 | { 126 | auto command_data = commands_vector->Get(c); 127 | ShapeDrawBitmapCommand& command = shape.commands.emplace_back(); 128 | command.texture_index = command_data->texture_index(); 129 | 130 | uint32_t vertex_count = command_data->points_count(); 131 | command.vertices.reserve(vertex_count); 132 | 133 | uint32_t vertex_offset = command_data->points_offset(); 134 | for (uint32_t v = 0; vertex_count > v; v++) 135 | { 136 | const uint8_t* vertex_data = shape_vertex_buffer->data() + ((vertex_offset + v) * 12); 137 | ShapeDrawBitmapCommandVertex& vertex = command.vertices.emplace_back(); 138 | 139 | vertex.x = *(const float*)vertex_data; 140 | vertex.y = *(const float*)(vertex_data + sizeof(float)); 141 | 142 | vertex.u = (float)(*(const uint16_t*)(vertex_data + (sizeof(float) * 2))) / 0xFFFF; 143 | vertex.v = (float)(*(const uint16_t*)(vertex_data + (sizeof(float) * 2) + sizeof(uint16_t))) / 0xFFFF; 144 | } 145 | 146 | command.create_triangle_indices(true); 147 | } 148 | } 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/Shape.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DisplayObject.h" 4 | #include "ShapeDrawBitmapCommand.h" 5 | 6 | #include "flash/types/SWFContainer.hpp" 7 | 8 | #include "flash/SC2/DataStorage_generated.h" 9 | #include "flash/SC2/Shapes_generated.h" 10 | 11 | namespace sc 12 | { 13 | namespace flash { 14 | using ShapeDrawBitmapCommandArray = SWFVector; 15 | 16 | class Shape : public DisplayObject 17 | { 18 | public: 19 | Shape() {}; 20 | virtual ~Shape() = default; 21 | Shape(const Shape&) = default; 22 | Shape(Shape&&) = default; 23 | Shape& operator=(const Shape&) = default; 24 | Shape& operator=(Shape&&) = default; 25 | 26 | public: 27 | ShapeDrawBitmapCommandArray commands; 28 | 29 | public: 30 | void load(SupercellSWF& swf, uint8_t tag); 31 | void save(SupercellSWF& swf) const; 32 | 33 | virtual uint8_t tag(SupercellSWF& swf) const; 34 | 35 | virtual bool is_shape() const; 36 | 37 | bool operator==(const Shape& other) const; 38 | public: 39 | static void load_sc2(SupercellSWF&, const SC2::DataStorage*, const uint8_t*); 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/ShapeDrawBitmapCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "ShapeDrawBitmapCommand.h" 2 | #include "flash/objects/SupercellSWF.h" 3 | 4 | namespace sc 5 | { 6 | namespace flash { 7 | void ShapeDrawBitmapCommand::load(SupercellSWF& swf, uint8_t tag) 8 | { 9 | texture_index = swf.stream.read_unsigned_byte(); 10 | 11 | uint8_t vertices_count = tag == 4 ? 4 : swf.stream.read_unsigned_byte(); 12 | vertices.resize(vertices_count); 13 | 14 | for (ShapeDrawBitmapCommandVertex& vertex : vertices) 15 | { 16 | vertex.x = swf.stream.read_twip(); 17 | vertex.y = swf.stream.read_twip(); 18 | } 19 | 20 | for (ShapeDrawBitmapCommandVertex& vertex : vertices) 21 | { 22 | vertex.u = (float)swf.stream.read_unsigned_short() / 65535.0f; 23 | vertex.v = (float)swf.stream.read_unsigned_short() / 65535.0f; 24 | } 25 | 26 | create_triangle_indices(false); 27 | } 28 | 29 | void ShapeDrawBitmapCommand::save(SupercellSWF& swf) const 30 | { 31 | if (vertices.size() >= std::numeric_limits().max()) 32 | { 33 | throw wk::Exception("Too many vertices in shape draw command"); 34 | } 35 | 36 | uint8_t verticesCount = static_cast(vertices.size()); 37 | 38 | swf.stream.write_unsigned_byte((uint8_t)texture_index); 39 | swf.stream.write_unsigned_byte(verticesCount); 40 | 41 | swf.stream.reserve(swf.stream.position() + (vertices.size() * ShapeDrawBitmapCommandVertex::Size)); 42 | 43 | write_buffer(swf.stream, true, false); 44 | } 45 | 46 | void ShapeDrawBitmapCommand::create_triangle_indices(bool advanced) 47 | { 48 | uint32_t triangles_count = vertices.size() - 2; 49 | 50 | triangle_indices.resize(triangles_count * 3); 51 | for (uint32_t i = 0; triangles_count > i; i++) 52 | { 53 | if (advanced) 54 | { 55 | triangle_indices[i * 3] = i; 56 | triangle_indices[i * 3 + 1] = i + 1; 57 | triangle_indices[i * 3 + 2] = i + 1; 58 | } 59 | else 60 | { 61 | triangle_indices[i * 3] = 0; 62 | triangle_indices[i * 3 + 1] = i + 1; 63 | triangle_indices[i * 3 + 2] = i + 2; 64 | } 65 | } 66 | 67 | for (uint32_t i = 0; triangle_indices.size() > i; i++) 68 | { 69 | if (triangle_indices[i] >= vertices.size()) 70 | { 71 | triangle_indices[i] = 0; 72 | } 73 | } 74 | } 75 | 76 | void ShapeDrawBitmapCommand::sort_advanced_vertices(bool forward) 77 | { 78 | if (4 > vertices.size()) return; 79 | 80 | ShapeDrawBitmapCommandVertexArray temp = vertices; 81 | ShapeDrawBitmapCommandTrianglesArray indices; 82 | indices.reserve(vertices.size()); 83 | 84 | indices.push_back(0); 85 | for (uint16_t i = 1; i < floor((float)vertices.size() / 2) * 2; i += 2) { 86 | indices.push_back(i); 87 | } 88 | for (uint16_t i = (uint16_t)floor(((float)vertices.size() - 1) / 2) * 2; i > 0; i -= 2) { 89 | indices.push_back(i); 90 | } 91 | 92 | for (uint16_t i = 0; vertices.size() > i; i++) 93 | { 94 | if (forward) 95 | { 96 | vertices[indices[i]] = temp[i]; 97 | } 98 | else 99 | { 100 | vertices[i] = temp[indices[i]]; 101 | } 102 | } 103 | } 104 | 105 | uint8_t ShapeDrawBitmapCommand::tag(SupercellSWF&) const 106 | { 107 | return TAG_SHAPE_DRAW_BITMAP_COMMAND_3; 108 | } 109 | 110 | void ShapeDrawBitmapCommand::write_buffer(wk::Stream& stream, bool normalized, bool ordered) const 111 | { 112 | auto write_xy = [&stream, &normalized](float value) 113 | { 114 | if (normalized) 115 | { 116 | stream.write_int((int)(value / 0.05f)); 117 | } 118 | else 119 | { 120 | stream.write_float(value); 121 | } 122 | 123 | }; 124 | 125 | auto write_uv = [&stream](float value) 126 | { 127 | stream.write_unsigned_short((uint16_t)(value * 65535.0f)); 128 | }; 129 | 130 | for (const ShapeDrawBitmapCommandVertex& vertex : vertices) 131 | { 132 | write_xy(vertex.x); 133 | write_xy(vertex.y); 134 | 135 | if (ordered) 136 | { 137 | write_uv(vertex.u); 138 | write_uv(vertex.v); 139 | } 140 | } 141 | 142 | if (ordered) return; 143 | 144 | for (const ShapeDrawBitmapCommandVertex& vertex : vertices) 145 | { 146 | write_uv(vertex.u); 147 | write_uv(vertex.v); 148 | } 149 | } 150 | 151 | bool ShapeDrawBitmapCommandVertex::operator==(const ShapeDrawBitmapCommandVertex& other) const 152 | { 153 | return uv_equal(other) && xy_equal(other); 154 | } 155 | 156 | bool ShapeDrawBitmapCommandVertex::uv_equal(const ShapeDrawBitmapCommandVertex& other) const 157 | { 158 | return u == other.u && v == other.v; 159 | } 160 | 161 | bool ShapeDrawBitmapCommandVertex::xy_equal(const ShapeDrawBitmapCommandVertex& other) const 162 | { 163 | return x == other.x && y == other.y; 164 | } 165 | 166 | bool ShapeDrawBitmapCommand::operator==(const ShapeDrawBitmapCommand& other) const 167 | { 168 | if (texture_index != other.texture_index) return false; 169 | if (triangle_indices != other.triangle_indices) return false; 170 | 171 | for (uint32_t i = 0; other.triangle_indices.size() > i; i++) 172 | { 173 | const ShapeDrawBitmapCommandVertex& v1 = vertices[other.triangle_indices[i]]; 174 | const ShapeDrawBitmapCommandVertex& v2 = vertices[other.triangle_indices[i]]; 175 | 176 | if (!(v1 == v2)) return false; 177 | } 178 | 179 | return true; 180 | } 181 | } 182 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/ShapeDrawBitmapCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "flash/types/SWFContainer.hpp" 4 | #include "core/io/stream.h" 5 | 6 | namespace sc 7 | { 8 | namespace flash { 9 | class SupercellSWF; 10 | 11 | struct ShapeDrawBitmapCommandVertex 12 | { 13 | public: 14 | static const size_t Size = ((sizeof(float) * 2) + (sizeof(uint16_t) * 2)); 15 | public: 16 | float x; 17 | float y; 18 | 19 | float u; 20 | float v; 21 | 22 | bool operator==(const ShapeDrawBitmapCommandVertex& other) const; 23 | bool uv_equal(const ShapeDrawBitmapCommandVertex& other) const; 24 | bool xy_equal(const ShapeDrawBitmapCommandVertex& other) const; 25 | }; 26 | 27 | typedef SWFVector ShapeDrawBitmapCommandVertexArray; 28 | typedef SWFVector ShapeDrawBitmapCommandTrianglesArray; 29 | 30 | class ShapeDrawBitmapCommand 31 | { 32 | public: 33 | ShapeDrawBitmapCommand() {}; 34 | virtual ~ShapeDrawBitmapCommand() = default; 35 | ShapeDrawBitmapCommand(const ShapeDrawBitmapCommand&) = default; 36 | ShapeDrawBitmapCommand(ShapeDrawBitmapCommand&&) = default; 37 | ShapeDrawBitmapCommand& operator=(const ShapeDrawBitmapCommand&) = default; 38 | ShapeDrawBitmapCommand& operator=(ShapeDrawBitmapCommand&&) = default; 39 | 40 | public: 41 | uint32_t texture_index = 0; 42 | ShapeDrawBitmapCommandVertexArray vertices; 43 | ShapeDrawBitmapCommandTrianglesArray triangle_indices; 44 | 45 | public: 46 | void create_triangle_indices(bool advanced); 47 | 48 | public: 49 | void sort_advanced_vertices(bool forward = false); 50 | 51 | public: 52 | virtual void load(SupercellSWF& swf, uint8_t tag); 53 | virtual void save(SupercellSWF& swf) const; 54 | 55 | virtual uint8_t tag(SupercellSWF& swf) const; 56 | 57 | void write_buffer(wk::Stream& stream, bool normalized = false, bool ordered = false) const; 58 | 59 | public: 60 | bool operator==(const ShapeDrawBitmapCommand& other) const; 61 | }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/TextField.cpp: -------------------------------------------------------------------------------- 1 | #include "TextField.h" 2 | #include "flash/objects/SupercellSWF.h" 3 | 4 | using namespace std; 5 | 6 | namespace sc 7 | { 8 | namespace flash 9 | { 10 | void TextField::load(SupercellSWF& swf, uint8_t tag) 11 | { 12 | id = swf.stream.read_unsigned_short(); 13 | 14 | swf.stream.read_string(font_name); 15 | font_color.set_value(swf.stream.read_unsigned_int()); 16 | 17 | is_bold = swf.stream.read_bool(); 18 | is_italic = swf.stream.read_bool(); 19 | is_multiline = swf.stream.read_bool(); 20 | unknown_flag3 = swf.stream.read_bool(); 21 | 22 | set_align_flags(swf.stream.read_unsigned_byte()); 23 | 24 | font_size = swf.stream.read_unsigned_byte(); 25 | 26 | left = swf.stream.read_short(); 27 | top = swf.stream.read_short(); 28 | right = swf.stream.read_short(); 29 | bottom = swf.stream.read_short(); 30 | 31 | is_outlined = swf.stream.read_bool(); 32 | 33 | swf.stream.read_string(text); 34 | 35 | if (tag == TAG_TEXT_FIELD) 36 | return; 37 | 38 | use_device_font = swf.stream.read_bool(); 39 | 40 | if (tag > TAG_TEXT_FIELD_2) 41 | { 42 | unknown_flag = (tag != 25); 43 | } 44 | 45 | if (tag > TAG_TEXT_FIELD_3) 46 | { 47 | outline_color = swf.stream.read_unsigned_int(); 48 | } 49 | 50 | if (tag > TAG_TEXT_FIELD_5) 51 | { 52 | unknown_short = swf.stream.read_short(); 53 | unknown_short2 = swf.stream.read_short(); 54 | } 55 | 56 | if (tag > TAG_TEXT_FIELD_6) 57 | { 58 | bend_angle = swf.stream.read_short() * 91.019f; 59 | } 60 | 61 | if (tag > TAG_TEXT_FIELD_7) 62 | { 63 | auto_kern = swf.stream.read_bool(); 64 | } 65 | 66 | if (tag > TAG_TEXT_FIELD_8) 67 | { 68 | swf.stream.read_string(typography_file); 69 | } 70 | } 71 | 72 | void TextField::save(SupercellSWF& swf) const 73 | { 74 | swf.stream.write_unsigned_short(id); 75 | 76 | swf.stream.write_string(font_name); 77 | swf.stream.write_unsigned_int(font_color.as_value()); 78 | 79 | swf.stream.write_bool(is_bold); 80 | swf.stream.write_bool(is_italic); 81 | swf.stream.write_bool(is_multiline); 82 | swf.stream.write_bool(unknown_flag3); 83 | 84 | swf.stream.write_unsigned_byte(get_align_flags()); 85 | swf.stream.write_unsigned_byte(font_size); 86 | 87 | swf.stream.write_short(left); 88 | swf.stream.write_short(top); 89 | swf.stream.write_short(right); 90 | swf.stream.write_short(bottom); 91 | 92 | swf.stream.write_bool(is_outlined); 93 | 94 | swf.stream.write_string(text); 95 | 96 | save_data(swf, tag(swf)); 97 | } 98 | 99 | uint8_t TextField::tag(SupercellSWF&) const 100 | { 101 | uint8_t tag = TAG_TEXT_FIELD; 102 | 103 | if (use_device_font) 104 | tag = TAG_TEXT_FIELD_2; 105 | 106 | if (unknown_flag) 107 | tag = TAG_TEXT_FIELD_3; 108 | 109 | if (outline_color != 0x000000FF) 110 | tag = TAG_TEXT_FIELD_5; 111 | 112 | if (unknown_short != 0xFFFF || unknown_short2 != 0xFFFF) 113 | tag = TAG_TEXT_FIELD_6; 114 | 115 | if (bend_angle != 0.0f) 116 | tag = TAG_TEXT_FIELD_7; 117 | 118 | if (auto_kern) 119 | tag = TAG_TEXT_FIELD_8; 120 | 121 | if (!typography_file.empty()) 122 | tag = TAG_TEXT_FIELD_9; 123 | 124 | return tag; 125 | } 126 | 127 | void TextField::save_data(SupercellSWF& swf, uint8_t tag) const 128 | { 129 | if (tag == TAG_TEXT_FIELD) return; 130 | 131 | swf.stream.write_bool(use_device_font); 132 | 133 | if (tag == TAG_TEXT_FIELD_2 || tag == TAG_TEXT_FIELD_3) return; 134 | 135 | swf.stream.write_unsigned_int(outline_color); 136 | 137 | if (tag == TAG_TEXT_FIELD_4 || tag == TAG_TEXT_FIELD_5) return; 138 | 139 | swf.stream.write_short(unknown_short); 140 | swf.stream.write_short(unknown_short2); 141 | 142 | if (tag == TAG_TEXT_FIELD_6) return; 143 | 144 | swf.stream.write_short((int16_t)(bend_angle / 91.019f)); 145 | 146 | if (tag == TAG_TEXT_FIELD_7) return; 147 | 148 | swf.stream.write_bool(auto_kern); 149 | 150 | if (tag == TAG_TEXT_FIELD_8) return; 151 | 152 | swf.stream.write_string(typography_file); 153 | } 154 | 155 | TextField::Align TextField::get_horizontal_align(uint8_t flags) 156 | { 157 | if ((flags & 1) != 0) { 158 | return Align::Right; 159 | } 160 | if ((flags & (1 << 1)) != 0) { 161 | return Align::Center; 162 | } 163 | if ((flags & (1 << 2)) != 0) { 164 | return Align::Justify; 165 | } 166 | 167 | return Align::Left; 168 | } 169 | 170 | TextField::Align TextField::get_vertical_align(uint8_t flags) 171 | { 172 | if ((flags & (1 << 3)) != 0) { 173 | return Align::Right; 174 | } 175 | if ((flags & (1 << 4)) != 0) { 176 | return Align::Center; 177 | } 178 | if ((flags & (1 << 5)) != 0) { 179 | return Align::Justify; 180 | } 181 | 182 | return Align::Left; 183 | } 184 | 185 | bool TextField::is_textfield() const 186 | { 187 | return true; 188 | } 189 | 190 | void TextField::load_sc2(SupercellSWF& swf, const SC2::DataStorage* storage, const uint8_t* data) 191 | { 192 | using Style = SC2::TextFieldStyle; 193 | auto textfields_data = SC2::GetTextFields(data); 194 | 195 | auto textfields_vector = textfields_data->textfields(); 196 | if (!textfields_vector) return; 197 | 198 | auto strings_vector = storage->strings(); 199 | uint16_t textfields_count = (uint16_t)textfields_vector->size(); 200 | swf.textfields.reserve(textfields_count); 201 | 202 | for (uint16_t i = 0; textfields_count > i; i++) 203 | { 204 | auto textfield_data = textfields_vector->Get(i); 205 | TextField& textfield = swf.textfields[i]; 206 | 207 | textfield.id = textfield_data->id(); 208 | textfield.font_name = SWFString( 209 | strings_vector->Get( 210 | textfield_data->font_name_ref_id() 211 | )->c_str() 212 | ); 213 | 214 | textfield.set_style_flags(textfield_data->styles()); 215 | 216 | textfield.left = textfield_data->left(); 217 | textfield.right = textfield_data->right(); 218 | textfield.top = textfield_data->top(); 219 | textfield.bottom = textfield_data->bottom(); 220 | 221 | textfield.font_color.set_value(textfield_data->font_color()); 222 | textfield.outline_color = textfield_data->outline_color(); 223 | 224 | textfield.text = SWFString( 225 | strings_vector->Get( 226 | textfield_data->text_ref_id() 227 | )->c_str() 228 | ); 229 | 230 | textfield.typography_file = SWFString( 231 | strings_vector->Get( 232 | textfield_data->typography_ref_id() 233 | )->c_str() 234 | ); 235 | 236 | textfield.set_align_flags(textfield_data->align()); 237 | 238 | textfield.font_size = textfield_data->font_size(); 239 | textfield.unknown_short = textfield_data->unknown_short(); 240 | } 241 | } 242 | 243 | uint8_t TextField::get_style_flags() const 244 | { 245 | using Style = SC2::TextFieldStyle; 246 | 247 | uint8_t result = 0; 248 | 249 | if (is_bold) result |= (uint8_t)Style::bold; 250 | if (is_italic) result |= (uint8_t)Style::italic; 251 | if (is_multiline) result |= (uint8_t)Style::is_multiline; 252 | if (is_outlined) result |= (uint8_t)Style::has_outline; 253 | if (unknown_flag3) result |= (uint8_t)Style::unknown_flag3; 254 | if (unknown_flag) result |= (uint8_t)Style::unknown_flag; 255 | if (auto_kern) result |= (uint8_t)Style::auto_kern; 256 | 257 | return result; 258 | } 259 | 260 | void TextField::set_style_flags(uint8_t style) 261 | { 262 | using Style = SC2::TextFieldStyle; 263 | 264 | is_bold = (style & (uint8_t)Style::bold) > 0; 265 | is_italic = (style & (uint8_t)Style::italic) > 0; 266 | is_multiline = (style & (uint8_t)Style::is_multiline) > 0; 267 | is_outlined = (style & (uint8_t)Style::has_outline) > 0; 268 | unknown_flag3 = (style & (uint8_t)Style::unknown_flag3) > 0; 269 | unknown_flag = (style & (uint8_t)Style::unknown_flag) > 0; 270 | auto_kern = (style & (uint8_t)Style::auto_kern) > 0; 271 | } 272 | 273 | uint8_t TextField::get_align_flags() const 274 | { 275 | uint8_t result = 0; 276 | if (font_horizontal_align != Align::Left) 277 | { 278 | result |= (1 << (uint8_t)font_horizontal_align); 279 | } 280 | 281 | if (font_vertical_align != Align::Left) 282 | { 283 | result |= ((1 << 3) << (uint8_t)font_vertical_align); 284 | } 285 | 286 | result |= (unknown_align6 << 6); 287 | result |= (unknown_align7 << 7); 288 | 289 | return result; 290 | } 291 | 292 | void TextField::set_align_flags(uint8_t flags) 293 | { 294 | font_horizontal_align = TextField::get_horizontal_align(flags); 295 | font_vertical_align = TextField::get_vertical_align(flags); 296 | 297 | unknown_align6 = (flags & (1 << 6)) != 0; 298 | unknown_align7 = (flags & (1 << 7)) != 0; 299 | } 300 | } 301 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/display_object/TextField.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DisplayObject.h" 4 | #include "flash/types/SWFString.hpp" 5 | 6 | #include "flash/SC2/DataStorage_generated.h" 7 | #include "flash/SC2/TextFields_generated.h" 8 | 9 | #include "core/math/color_rgba.h" 10 | 11 | namespace sc 12 | { 13 | namespace flash { 14 | class SupercellSWF; 15 | 16 | class TextField : public DisplayObject 17 | { 18 | public: 19 | TextField() {}; 20 | virtual ~TextField() = default; 21 | TextField(const TextField&) = default; 22 | TextField(TextField&&) = default; 23 | TextField& operator=(const TextField&) = default; 24 | TextField& operator=(TextField&&) = default; 25 | 26 | public: 27 | enum class Align : uint8_t 28 | { 29 | Right, 30 | Center, 31 | Justify, 32 | Left, 33 | }; 34 | 35 | static Align get_horizontal_align(uint8_t flags); 36 | static Align get_vertical_align(uint8_t flags); 37 | 38 | public: 39 | SWFString text = ""; 40 | 41 | SWFString font_name = ""; 42 | wk::ColorRGBA font_color = {0xFF, 0xFF, 0xFF}; 43 | uint8_t font_size = 0; 44 | Align font_horizontal_align = Align::Left; 45 | Align font_vertical_align = Align::Left; 46 | bool unknown_align6 = false; 47 | bool unknown_align7 = false; 48 | 49 | int16_t left = 0; 50 | int16_t top = 0; 51 | int16_t right = 0; 52 | int16_t bottom = 0; 53 | 54 | bool is_bold = false; 55 | bool is_italic = false; 56 | bool is_multiline = false; 57 | bool is_outlined = false; 58 | bool unknown_flag3 = false; 59 | 60 | uint32_t outline_color = 0x000000FF; 61 | bool use_device_font = false; 62 | bool auto_kern = false; 63 | 64 | float bend_angle = 0.0f; 65 | 66 | bool unknown_flag = false; 67 | uint16_t unknown_short = 0xFFFF; 68 | uint16_t unknown_short2 = 0xFFFF; 69 | 70 | SWFString typography_file = ""; 71 | 72 | public: 73 | virtual void load(SupercellSWF& swf, uint8_t tag); 74 | virtual void save(SupercellSWF& swf) const; 75 | virtual void save_data(SupercellSWF& swf, uint8_t tag) const; 76 | 77 | virtual uint8_t tag(SupercellSWF& swf) const; 78 | 79 | virtual bool is_textfield() const; 80 | 81 | uint8_t get_style_flags() const; 82 | void set_style_flags(uint8_t style); 83 | 84 | uint8_t get_align_flags() const; 85 | void set_align_flags(uint8_t style); 86 | 87 | public: 88 | static void load_sc2(SupercellSWF&, const SC2::DataStorage*, const uint8_t*); 89 | }; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/flash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "flash/objects/SupercellSWF.h" 4 | #include "flash/objects/ExportName.h" 5 | #include "flash/objects/SWFTexture.h" 6 | 7 | #include "flash/display_object/MovieClip.h" 8 | #include "flash/display_object/MovieClipFrame.h" 9 | #include "flash/display_object/MovieClipModifier.h" 10 | #include "flash/display_object/Shape.h" 11 | #include "flash/display_object/ShapeDrawBitmapCommand.h" 12 | #include "flash/display_object/TextField.h" 13 | 14 | #include "flash/transform/ColorTransform.h" 15 | #include "flash/transform/Matrix2D.h" 16 | #include "flash/transform/MatrixBank.h" 17 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/flash_tags.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace sc 5 | { 6 | namespace flash { 7 | constexpr uint8_t TAG_END = 0; 8 | 9 | constexpr uint8_t TAG_TEXTURE = 1; 10 | constexpr uint8_t TAG_SHAPE = 2; 11 | constexpr uint8_t TAG_MOVIE_CLIP = 3; // deprecated 12 | constexpr uint8_t TAG_SHAPE_DRAW_BITMAP_COMMAND = 4; 13 | constexpr uint8_t TAG_MOVIE_CLIP_FRAME = 5; // deprecated 14 | constexpr uint8_t TAG_SHAPE_DRAW_COLOR_FILL_COMMAND = 6; // deprecated 15 | constexpr uint8_t TAG_TEXT_FIELD = 7; 16 | 17 | constexpr uint8_t TAG_MATRIX_2x3 = 8; 18 | constexpr uint8_t TAG_COLOR_TRANSFORM = 9; 19 | 20 | constexpr uint8_t TAG_MOVIE_CLIP_2 = 10; 21 | constexpr uint8_t TAG_MOVIE_CLIP_FRAME_2 = 11; 22 | constexpr uint8_t TAG_MOVIE_CLIP_3 = 12; 23 | constexpr uint8_t TAG_TIMELINE_INDEXES = 13; // deprecated 24 | constexpr uint8_t TAG_MOVIE_CLIP_4 = 14; // deprecated 25 | 26 | constexpr uint8_t TAG_TEXT_FIELD_2 = 15; 27 | constexpr uint8_t TAG_TEXTURE_2 = 16; 28 | 29 | constexpr uint8_t TAG_SHAPE_DRAW_BITMAP_COMMAND_2 = 17; 30 | constexpr uint8_t TAG_SHAPE_2 = 18; 31 | 32 | constexpr uint8_t TAG_TEXTURE_3 = 19; 33 | constexpr uint8_t TAG_TEXT_FIELD_3 = 20; 34 | constexpr uint8_t TAG_TEXT_FIELD_4 = 21; 35 | constexpr uint8_t TAG_SHAPE_DRAW_BITMAP_COMMAND_3 = 22; 36 | constexpr uint8_t TAG_USE_LOW_RES_TEXTURE = 23; 37 | constexpr uint8_t TAG_TEXTURE_4 = 24; 38 | constexpr uint8_t TAG_TEXT_FIELD_5 = 25; 39 | constexpr uint8_t TAG_USE_EXTERNAL_TEXTURE = 26; 40 | constexpr uint8_t TAG_TEXTURE_5 = 27; 41 | constexpr uint8_t TAG_TEXTURE_6 = 28; 42 | constexpr uint8_t TAG_TEXTURE_7 = 29; 43 | constexpr uint8_t TAG_USE_MULTI_RES_TEXTURE = 30; 44 | constexpr uint8_t TAG_SCALING_GRID = 31; 45 | constexpr uint8_t TAG_TEXTURE_FILE_SUFFIXES = 32; 46 | constexpr uint8_t TAG_TEXT_FIELD_6 = 33; 47 | constexpr uint8_t TAG_TEXTURE_8 = 34; 48 | constexpr uint8_t TAG_MOVIE_CLIP_5 = 35; 49 | 50 | constexpr uint8_t TAG_MATRIX_2x3_2 = 36; 51 | 52 | constexpr uint8_t TAG_MOVIE_CLIP_MODIFIERS_COUNT = 37; 53 | constexpr uint8_t TAG_MOVIE_CLIP_MODIFIER = 38; // Mask layer 54 | constexpr uint8_t TAG_MOVIE_CLIP_MODIFIER_2 = 39; // Masked layers start 55 | constexpr uint8_t TAG_MOVIE_CLIP_MODIFIER_3 = 40; // Masked layers end 56 | 57 | constexpr uint8_t TAG_MATRIX_BANK_INDEX = 41; // Movieclip frame tag 58 | constexpr uint8_t TAG_MATRIX_BANK = 42; 59 | 60 | constexpr uint8_t TAG_TEXT_FIELD_7 = 43; 61 | constexpr uint8_t TAG_TEXT_FIELD_8 = 44; 62 | 63 | constexpr uint8_t TAG_TEXTURE_9 = 45; // SWFTexture Khronos Texture 64 | constexpr uint8_t TAG_TEXT_FIELD_9 = 46; // Typograph 65 | 66 | constexpr uint8_t TAG_TEXTURE_10 = 47; // External Texture File 67 | constexpr uint8_t TAG_STREAMING_TEXTURE_ID_1 = 48; // Some Kind Of Flag 68 | constexpr uint8_t TAG_MOVIE_CLIP_6 = 49; // Custom Property Index 69 | } 70 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/objects/ExportName.cpp: -------------------------------------------------------------------------------- 1 | #include "ExportName.h" 2 | #include "flash/objects/SupercellSWF.h" 3 | 4 | namespace sc::flash 5 | { 6 | void ExportName::load_sc2(SupercellSWF& swf, const SC2::DataStorage* storage, const uint8_t* data) 7 | { 8 | auto exports_data = SC2::GetExportNames(data); 9 | auto exports_ids = exports_data->object_ids(); 10 | auto exports_name_ref_ids = exports_data->name_ref_ids(); 11 | 12 | // Return if some of vectors are empty 13 | if (!exports_ids || !exports_name_ref_ids) return; 14 | if (exports_ids->size() != exports_name_ref_ids->size()) 15 | { 16 | throw wk::Exception(); 17 | } 18 | 19 | auto strings_vector = storage->strings(); 20 | uint16_t export_names_count = (uint16_t)exports_ids->size(); 21 | swf.exports.reserve(export_names_count); 22 | for (uint16_t i = 0; export_names_count > i; i++) 23 | { 24 | SWFString name( 25 | strings_vector->Get( 26 | exports_name_ref_ids->Get(i) 27 | )->c_str() 28 | ); 29 | 30 | swf.CreateExportName(name, exports_ids->Get(i)); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/objects/ExportName.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "flash/types/SWFString.hpp" 4 | #include "flash/types/SWFContainer.hpp" 5 | 6 | #include "flash/SC2/DataStorage_generated.h" 7 | #include "flash/SC2/ExportNames_generated.h" 8 | 9 | namespace sc { 10 | namespace flash { 11 | class SupercellSWF; 12 | 13 | struct ExportName 14 | { 15 | ExportName() : id(0) {}; 16 | 17 | SWFString name; 18 | uint16_t id; 19 | SWFVector hash; 20 | 21 | static void load_sc2(SupercellSWF&, const SC2::DataStorage*, const uint8_t*); 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/objects/SWFTexture.cpp: -------------------------------------------------------------------------------- 1 | #include "SWFTexture.h" 2 | #include "flash/objects/SupercellSWF.h" 3 | #include "compression/compression.h" 4 | 5 | using namespace sc::texture; 6 | using namespace sc::compression; 7 | using namespace wk; 8 | 9 | namespace sc 10 | { 11 | namespace flash { 12 | const SWFVector SWFTexture::pixel_format_table = 13 | { 14 | SWFTexture::PixelFormat::RGBA8, // 0 15 | SWFTexture::PixelFormat::RGBA8, 16 | SWFTexture::PixelFormat::RGBA4, // 2 17 | SWFTexture::PixelFormat::RGB5_A1, // 3 18 | SWFTexture::PixelFormat::RGB565, // 4 19 | SWFTexture::PixelFormat::RGBA8, 20 | SWFTexture::PixelFormat::LUMINANCE8_ALPHA8, // 6 21 | SWFTexture::PixelFormat::RGBA8, 22 | SWFTexture::PixelFormat::RGBA8, 23 | SWFTexture::PixelFormat::RGBA8, 24 | SWFTexture::PixelFormat::LUMINANCE8 // 10 25 | }; 26 | 27 | const SWFVector SWFTexture::pixel_depth_table = 28 | { 29 | Image::PixelDepth::RGBA8, // 0 30 | Image::PixelDepth::RGBA8, 31 | Image::PixelDepth::RGBA4, // 2 32 | Image::PixelDepth::RGB5_A1, // 3 33 | Image::PixelDepth::RGB565, // 4 34 | Image::PixelDepth::RGBA8, 35 | Image::PixelDepth::LUMINANCE8_ALPHA8, // 6 36 | Image::PixelDepth::RGBA8, 37 | Image::PixelDepth::RGBA8, 38 | Image::PixelDepth::RGBA8, 39 | Image::PixelDepth::LUMINANCE8 // 10 40 | }; 41 | 42 | bool SWFTexture::linear() const 43 | { 44 | return m_linear; 45 | } 46 | 47 | void SWFTexture::linear(bool status) 48 | { 49 | if (status == m_linear) return; 50 | if (m_encoding != TextureEncoding::Raw) return; 51 | 52 | wk::MemoryStream buffer(m_image->data_length()); 53 | wk::Memory::copy(m_image->data(), buffer.data(), buffer.length()); 54 | 55 | convert_tiled_data( 56 | (uint8_t*)buffer.data(), m_image->data(), 57 | m_image->width(), m_image->height(), 58 | m_pixel_format, m_linear 59 | ); 60 | } 61 | 62 | SWFTexture::TextureEncoding SWFTexture::encoding() const 63 | { 64 | return m_encoding; 65 | } 66 | 67 | void SWFTexture::encoding(SWFTexture::TextureEncoding encoding) 68 | { 69 | if (m_encoding == encoding) return; 70 | 71 | wk::RawImageRef target_image = raw_image(); 72 | 73 | if (!target_image) 74 | { 75 | throw Exception("Failed to get raw texture!"); 76 | } 77 | 78 | switch (encoding) 79 | { 80 | case SWFTexture::TextureEncoding::Raw: 81 | m_image = target_image; 82 | break; 83 | 84 | case SWFTexture::TextureEncoding::KhronosTexture: 85 | m_image = CreateRef(*target_image, KhronosTexture::glInternalFormat::GL_COMPRESSED_RGBA_ASTC_4x4); 86 | break; 87 | 88 | case SWFTexture::TextureEncoding::SupercellTexture: 89 | m_image = CreateRef(*target_image, ScPixel::Type::ASTC_RGBA8_4x4, false); 90 | break; 91 | 92 | default: 93 | break; 94 | } 95 | 96 | m_encoding = encoding; 97 | }; 98 | 99 | SWFTexture::PixelFormat SWFTexture::pixel_format() const 100 | { 101 | return m_pixel_format; 102 | } 103 | 104 | void SWFTexture::pixel_format(SWFTexture::PixelFormat format) 105 | { 106 | if (m_pixel_format == format) return; 107 | if (m_encoding != TextureEncoding::Raw) return; 108 | 109 | m_pixel_format = format; 110 | 111 | Image::PixelDepth destination_depth = SWFTexture::pixel_depth_table[static_cast(std::find( 112 | SWFTexture::pixel_format_table.begin(), 113 | SWFTexture::pixel_format_table.end(), 114 | format 115 | ) - SWFTexture::pixel_format_table.begin())]; 116 | 117 | Ref texture = CreateRef(m_image->width(), m_image->height(), destination_depth); 118 | 119 | Image::remap(m_image->data(), texture->data(), m_image->width(), m_image->height(), m_image->depth(), destination_depth); 120 | 121 | m_image = texture; 122 | } 123 | 124 | const wk::Ref SWFTexture::image() const { 125 | return m_image; 126 | } 127 | 128 | wk::Ref SWFTexture::raw_image() const 129 | { 130 | Ref texture = CreateRef(m_image->width(), m_image->height(), m_image->depth()); 131 | 132 | if (m_encoding == TextureEncoding::Raw) 133 | { 134 | RawImage* raw_image = (RawImage*)m_image.get(); 135 | raw_image->copy(*texture); 136 | if (!m_linear) 137 | { 138 | convert_tiled_data(raw_image->data(), texture->data(), texture->width(), texture->height(), m_pixel_format, false); 139 | } 140 | } 141 | else 142 | { 143 | CompressedImage* compressed_image = (CompressedImage*)m_image.get(); 144 | wk::SharedMemoryStream texture_data(texture->data(), texture->data_length()); 145 | compressed_image->decompress_data(texture_data); 146 | } 147 | 148 | return texture; 149 | } 150 | 151 | void SWFTexture::load(SupercellSWF& swf, uint8_t tag, bool has_data) 152 | { 153 | bool has_khronos_texture = false; 154 | int khronos_texture_length = 0; 155 | SWFString external_texture_path; 156 | 157 | if (tag == TAG_TEXTURE_10) 158 | { 159 | swf.stream.read_string(external_texture_path); 160 | } 161 | 162 | if (has_data && tag == TAG_TEXTURE_9) 163 | { 164 | has_khronos_texture = true; 165 | khronos_texture_length = swf.stream.read_int(); 166 | 167 | if (khronos_texture_length <= 0) 168 | { 169 | throw wk::Exception("Khronos Texture has wrong length"); 170 | } 171 | } 172 | 173 | uint8_t pixel_format_index = swf.stream.read_unsigned_byte(); 174 | PixelFormat type = SWFTexture::pixel_format_table[pixel_format_index]; 175 | 176 | uint16_t width = swf.stream.read_unsigned_short(); 177 | uint16_t height = swf.stream.read_unsigned_short(); 178 | 179 | if (!external_texture_path.empty()) 180 | { 181 | return load_from_file(swf, fs::path(external_texture_path.data())); 182 | } 183 | 184 | if (has_data) 185 | { 186 | if (has_khronos_texture) 187 | { 188 | wk::SharedMemoryStream khronos_texture_data((uint8_t*)swf.stream.data() + swf.stream.position(), khronos_texture_length); 189 | load_from_khronos_texture(khronos_texture_data); 190 | swf.stream.seek(khronos_texture_length, Stream::SeekMode::Add); 191 | return; 192 | } 193 | else 194 | { 195 | m_encoding = TextureEncoding::Raw; 196 | 197 | filtering = Filter::LINEAR_NEAREST; 198 | if (tag == TAG_TEXTURE_2 || tag == TAG_TEXTURE_3 || tag == TAG_TEXTURE_7) { 199 | filtering = Filter::LINEAR_MIPMAP_NEAREST; 200 | } 201 | else if (tag == TAG_TEXTURE_8) { 202 | filtering = Filter::NEAREST_NEAREST; 203 | } 204 | 205 | m_linear = true; 206 | if (tag == TAG_TEXTURE_5 || tag == TAG_TEXTURE_6 || tag == TAG_TEXTURE_7) 207 | m_linear = false; 208 | 209 | downscaling = false; 210 | if (tag == TAG_TEXTURE || tag == TAG_TEXTURE_2 || tag == TAG_TEXTURE_6 || tag == TAG_TEXTURE_7) 211 | downscaling = true; 212 | } 213 | } 214 | 215 | load_from_buffer(swf.stream, width, height, type, has_data); 216 | }; 217 | 218 | void SWFTexture::save(SupercellSWF& swf, bool has_data, bool is_lowres) const 219 | { 220 | uint8_t texture_tag = tag(swf, has_data); 221 | 222 | uint16_t width = is_lowres ? (uint16_t)(round(m_image->width() / 2)) : m_image->width(); 223 | uint16_t height = is_lowres ? (uint16_t)(round(m_image->height() / 2)) : m_image->height(); 224 | 225 | bool has_khronos_texture = texture_tag == TAG_TEXTURE_9; 226 | size_t khronos_texture_size_position = swf.stream.position(); 227 | if (has_khronos_texture) 228 | { 229 | swf.stream.write_int(-1); 230 | } 231 | 232 | swf.stream.write_unsigned_byte((uint8_t)m_pixel_format); 233 | swf.stream.write_unsigned_short(width); 234 | swf.stream.write_unsigned_short(height); 235 | 236 | if (has_data && !swf.use_external_textures) 237 | { 238 | size_t current_position = swf.stream.position(); 239 | save_buffer(swf.stream, is_lowres); 240 | if (has_khronos_texture) 241 | { 242 | int* khronos_texture_length = (int*)((ptrdiff_t)swf.stream.data() + khronos_texture_size_position); 243 | *khronos_texture_length = (int)(swf.stream.position() - current_position); 244 | } 245 | } 246 | }; 247 | 248 | void SWFTexture::save_buffer(Stream& stream, bool is_lowres) const 249 | { 250 | uint16_t target_width = m_image->width(); 251 | uint16_t target_height = m_image->height(); 252 | if (is_lowres) 253 | { 254 | target_width = (uint16_t)(round(target_width / 2)); 255 | target_height = (uint16_t)(round(target_height / 2)); 256 | } 257 | 258 | if (m_encoding == TextureEncoding::Raw) 259 | { 260 | RawImageRef image = raw_image(); 261 | 262 | if (is_lowres) 263 | { 264 | MemoryStream buffer( 265 | Image::calculate_image_length(target_width, target_height, m_image->depth()) 266 | ); 267 | 268 | RawImage lowres_image( 269 | (uint8_t*)buffer.data(), 270 | target_width, target_height, 271 | m_image->depth(), m_image->colorspace() 272 | ); 273 | image->copy(lowres_image); 274 | 275 | if (!m_linear) 276 | { 277 | MemoryStream result(lowres_image.data_length()); 278 | 279 | convert_tiled_data( 280 | lowres_image.data(), (uint8_t*)result.data(), 281 | lowres_image.width(), lowres_image.height(), 282 | m_pixel_format, true 283 | ); 284 | 285 | stream.write(result.data(), result.length()); 286 | } 287 | else 288 | { 289 | stream.write(lowres_image.data(), lowres_image.data_length()); 290 | } 291 | } 292 | else 293 | { 294 | stream.write(m_image->data(), m_image->data_length()); 295 | } 296 | } 297 | else 298 | { 299 | CompressedImage* image = (CompressedImage*)m_image.get(); 300 | 301 | if (is_lowres) 302 | { 303 | RawImage texture(image->width(), image->height(), image->depth()); 304 | wk::SharedMemoryStream texture_data(texture.data(), texture.data_length()); 305 | image->decompress_data(texture_data); 306 | 307 | RawImage lowres_texture( 308 | target_width, 309 | target_height, 310 | image->depth() 311 | ); 312 | texture.copy(lowres_texture); 313 | 314 | switch (m_encoding) 315 | { 316 | case SWFTexture::TextureEncoding::KhronosTexture: 317 | { 318 | KhronosTexture1 compressed_lowres(lowres_texture, KhronosTexture::glInternalFormat::GL_COMPRESSED_RGBA_ASTC_4x4); 319 | compressed_lowres.write(stream); 320 | } 321 | break; 322 | case SWFTexture::TextureEncoding::SupercellTexture: 323 | { 324 | SupercellTexture compressed_lowres(lowres_texture, ScPixel::Type::ASTC_RGBA8_4x4); 325 | compressed_lowres.write(stream); 326 | } 327 | break; 328 | default: 329 | break; 330 | } 331 | } 332 | else 333 | { 334 | image->write(stream); 335 | } 336 | } 337 | }; 338 | 339 | void SWFTexture::load_from_image(RawImage& image) 340 | { 341 | SWFTexture::PixelFormat image_format{}; 342 | Ref image_data = CreateRef(image.data(), image.data_length()); 343 | 344 | switch (image.depth()) 345 | { 346 | case RawImage::PixelDepth::RGBA8: 347 | case RawImage::PixelDepth::RGBA4: 348 | case RawImage::PixelDepth::RGB5_A1: 349 | case RawImage::PixelDepth::RGB565: 350 | case RawImage::PixelDepth::LUMINANCE8_ALPHA8: 351 | case RawImage::PixelDepth::LUMINANCE8: 352 | image_format = SWFTexture::pixel_format_table[ 353 | static_cast( 354 | std::find( 355 | SWFTexture::pixel_depth_table.begin(), 356 | SWFTexture::pixel_depth_table.end(), image.depth() 357 | ) - SWFTexture::pixel_depth_table.begin()) 358 | ]; 359 | break; 360 | 361 | case RawImage::PixelDepth::RGB8: 362 | { 363 | image_format = SWFTexture::PixelFormat::RGB565; 364 | RawImage::PixelDepth output_depth = RawImage::PixelDepth::RGB565; 365 | 366 | image_data = CreateRef(Image::calculate_image_length(image.width(), image.height(), output_depth)); 367 | Image::remap( 368 | image.data(), (uint8_t*)image_data->data(), 369 | image.width(), image.height(), 370 | image.depth(), output_depth 371 | ); 372 | } 373 | break; 374 | 375 | default: 376 | break; 377 | } 378 | 379 | load_from_buffer( 380 | *image_data, 381 | image.width(), image.height(), 382 | image_format 383 | ); 384 | } 385 | 386 | void SWFTexture::load_from_buffer(Stream& data, uint16_t width, uint16_t height, PixelFormat type, bool has_data) 387 | { 388 | m_pixel_format = type; 389 | 390 | Image::PixelDepth depth = SWFTexture::pixel_depth_table[(uint8_t)m_pixel_format]; 391 | m_image = CreateRef(width, height, depth); 392 | 393 | if (has_data) 394 | { 395 | data.read(m_image->data(), m_image->data_length()); 396 | } 397 | } 398 | 399 | void SWFTexture::load_from_file(const SupercellSWF& swf, const fs::path& path) 400 | { 401 | fs::path texture_path = swf.current_file.parent_path() / path; 402 | return load_from_file(texture_path); 403 | } 404 | 405 | void SWFTexture::load_from_file(const fs::path& path) 406 | { 407 | fs::path texture_extension = path.extension(); 408 | if (texture_extension == ".zktx") 409 | { 410 | InputFileStream texture_stream(path); 411 | load_from_compressed_khronos_texture(texture_stream); 412 | } 413 | else if (texture_extension == ".ktx") 414 | { 415 | InputFileStream texture_stream(path); 416 | load_from_khronos_texture(texture_stream); 417 | } 418 | else if (texture_extension == ".sctx") 419 | { 420 | load_from_supercell_texture(path); 421 | } 422 | } 423 | 424 | void SWFTexture::load_from_khronos_texture(Stream& data) 425 | { 426 | m_encoding = TextureEncoding::KhronosTexture; 427 | m_image = CreateRef(data); 428 | } 429 | 430 | void SWFTexture::load_from_supercell_texture(const std::filesystem::path& path) 431 | { 432 | m_encoding = TextureEncoding::SupercellTexture; 433 | m_image = CreateRef(path); 434 | } 435 | 436 | void SWFTexture::load_from_compressed_khronos_texture(Stream& data) 437 | { 438 | BufferStream texture_data; 439 | 440 | ZstdDecompressor dctx; 441 | dctx.decompress(data, texture_data); 442 | 443 | texture_data.seek(0); 444 | load_from_khronos_texture(texture_data); 445 | } 446 | 447 | void SWFTexture::convert_tiled_data(uint8_t* input_data, uint8_t* output_data, uint16_t width, uint16_t height, PixelFormat type, bool is_linear) { 448 | uint8_t pixel_size = Image::PixelDepthTable[(uint8_t)pixel_depth_table[(uint8_t)type]].byte_count; 449 | 450 | const uint16_t x_blocks = static_cast(floor(width / SWFTEXTURE_BLOCK_SIZE)); 451 | const uint16_t y_blocks = static_cast(floor(height / SWFTEXTURE_BLOCK_SIZE)); 452 | 453 | uint32_t pixel_index = 0; 454 | 455 | for (uint16_t y_block = 0; y_blocks + 1 > y_block; y_block++) { 456 | for (uint16_t x_block = 0; x_blocks + 1 > x_block; x_block++) { 457 | for (uint8_t y = 0; SWFTEXTURE_BLOCK_SIZE > y; y++) { 458 | uint16_t pixel_y = (y_block * SWFTEXTURE_BLOCK_SIZE) + y; 459 | if (pixel_y >= height) break; 460 | 461 | for (uint8_t x = 0; SWFTEXTURE_BLOCK_SIZE > x; x++) { 462 | uint16_t pixel_x = (x_block * SWFTEXTURE_BLOCK_SIZE) + x; 463 | if (pixel_x >= width) break; 464 | 465 | uint32_t source = (pixel_y * width + pixel_x) * pixel_size; 466 | uint32_t target = pixel_index * pixel_size; 467 | if (!is_linear) // blocks to image 468 | { 469 | wk::Memory::copy(input_data + target, output_data + source, pixel_size); 470 | } 471 | else // image to blocks 472 | { 473 | wk::Memory::copy(input_data + source, output_data + target, pixel_size); 474 | } 475 | 476 | pixel_index++; 477 | } 478 | } 479 | } 480 | } 481 | } 482 | 483 | uint8_t SWFTexture::tag(SupercellSWF& swf, bool has_data) const 484 | { 485 | uint8_t tag = TAG_TEXTURE; 486 | 487 | if (!has_data) 488 | { 489 | return tag; 490 | } 491 | 492 | if (swf.use_external_textures) 493 | { 494 | return TAG_TEXTURE_10; 495 | } 496 | 497 | if (m_encoding == TextureEncoding::KhronosTexture) { 498 | return TAG_TEXTURE_9; 499 | } 500 | else if (m_encoding == TextureEncoding::Raw) 501 | { 502 | switch (filtering) 503 | { 504 | case SWFTexture::Filter::LINEAR_NEAREST: 505 | if (!m_linear) { 506 | tag = downscaling ? TAG_TEXTURE_6 : TAG_TEXTURE_5; 507 | } 508 | else if (!downscaling) { 509 | tag = TAG_TEXTURE_4; 510 | } 511 | break; 512 | case SWFTexture::Filter::LINEAR_MIPMAP_NEAREST: 513 | if (!m_linear && downscaling) { 514 | tag = TAG_TEXTURE_7; 515 | } 516 | else { 517 | tag = downscaling ? TAG_TEXTURE_2 : TAG_TEXTURE_3; 518 | } 519 | break; 520 | case SWFTexture::Filter::NEAREST_NEAREST: 521 | if (!m_linear) 522 | { 523 | tag = TAG_TEXTURE_8; 524 | } 525 | 526 | break; 527 | default: 528 | break; 529 | } 530 | } 531 | else 532 | { 533 | throw wk::Exception("Unsuported encoding method"); 534 | } 535 | 536 | return tag; 537 | } 538 | 539 | std::filesystem::path SWFTexture::save_to_external_file(const SupercellSWF& swf, uint32_t index, bool is_lowres) const 540 | { 541 | fs::path output_filepath = swf.current_file.parent_path(); 542 | fs::path filename = get_external_filename(swf, index, is_lowres); 543 | output_filepath /= filename; 544 | save_to_external_file(swf, output_filepath, is_lowres); 545 | 546 | return filename; 547 | } 548 | 549 | void SWFTexture::save_to_external_file(const SupercellSWF& swf, const std::filesystem::path& path, bool is_lowres) const 550 | { 551 | wk::OutputFileStream file(path); 552 | 553 | switch (m_encoding) 554 | { 555 | case SWFTexture::TextureEncoding::KhronosTexture: 556 | { 557 | if (swf.compress_external_textures) 558 | { 559 | wk::BufferStream input_data; 560 | save_buffer(input_data, is_lowres); 561 | 562 | ZstdCompressor::Props props; 563 | ZstdCompressor cctx(props); 564 | 565 | input_data.seek(0); 566 | cctx.compress(input_data, file); 567 | } 568 | else 569 | { 570 | save_buffer(file, is_lowres); 571 | } 572 | } 573 | break; 574 | 575 | case SWFTexture::TextureEncoding::SupercellTexture: 576 | { 577 | SupercellTexture* texture = (SupercellTexture*)m_image.get(); 578 | texture->use_compression = swf.compress_external_textures; 579 | save_buffer(file, is_lowres); 580 | } 581 | break; 582 | 583 | case SWFTexture::TextureEncoding::Raw: 584 | default: 585 | break; 586 | } 587 | } 588 | 589 | void SWFTexture::load_sc2(SupercellSWF& swf, const SC2::DataStorage*, const uint8_t* data) 590 | { 591 | auto textures_data = SC2::GetTextures(data); 592 | 593 | auto textures_vector = textures_data->textures(); 594 | if (!textures_vector) return; 595 | 596 | uint32_t texture_count = textures_vector->size(); 597 | 598 | for (uint32_t i = 0; texture_count > i; i++) 599 | { 600 | SWFTexture& texture = swf.textures[i]; 601 | auto texture_set = textures_vector->Get(i); 602 | swf.use_low_resolution = texture_set->lowres(); 603 | swf.use_multi_resolution = swf.use_low_resolution; 604 | 605 | auto selected_texture = swf.low_memory_usage_mode && texture_set->lowres() ? 606 | texture_set->lowres() : 607 | texture_set->highres(); 608 | 609 | if (selected_texture->external_texture()) 610 | { 611 | swf.use_external_textures = true; 612 | fs::path texture_path = selected_texture->external_texture()->str(); 613 | texture.load_from_file(swf, texture_path); 614 | } 615 | else 616 | { 617 | // Hardcode Khronos texture for now 618 | wk::SharedMemoryStream texture_stream((uint8_t*)selected_texture->data()->data(), selected_texture->data()->size()); 619 | texture.load_from_khronos_texture(texture_stream); 620 | } 621 | } 622 | } 623 | 624 | std::filesystem::path SWFTexture::get_external_filename(const SupercellSWF& swf, uint32_t index, bool is_lowres) const 625 | { 626 | std::filesystem::path result = swf.current_file.stem(); 627 | 628 | if (is_lowres) 629 | { 630 | result += swf.low_resolution_suffix.string(); 631 | } 632 | result += "_"; 633 | result += std::to_string(index); 634 | 635 | switch (m_encoding) 636 | { 637 | case sc::flash::SWFTexture::TextureEncoding::Raw: 638 | result += ".bin"; 639 | break; 640 | case sc::flash::SWFTexture::TextureEncoding::KhronosTexture: 641 | result += swf.compress_external_textures ? ".zktx" : ".ktx"; 642 | break; 643 | case sc::flash::SWFTexture::TextureEncoding::SupercellTexture: 644 | result += ".sctx"; 645 | break; 646 | default: 647 | break; 648 | } 649 | 650 | return result; 651 | } 652 | } 653 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/objects/SWFTexture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "flash/types/SWFContainer.hpp" 7 | 8 | constexpr auto SWFTEXTURE_BLOCK_SIZE = 32; 9 | 10 | #include "flash/SC2/DataStorage_generated.h" 11 | #include "flash/SC2/Textures_generated.h" 12 | 13 | namespace sc 14 | { 15 | namespace flash { 16 | class SupercellSWF; 17 | 18 | class SWFTexture 19 | { 20 | public: 21 | SWFTexture() {}; 22 | virtual ~SWFTexture() = default; 23 | SWFTexture(const SWFTexture&) = default; 24 | SWFTexture(SWFTexture&&) = default; 25 | SWFTexture& operator=(const SWFTexture&) = default; 26 | SWFTexture& operator=(SWFTexture&&) = default; 27 | 28 | public: 29 | enum class Filter : uint8_t 30 | { 31 | LINEAR_NEAREST, 32 | NEAREST_NEAREST, 33 | LINEAR_MIPMAP_NEAREST 34 | }; 35 | 36 | enum class PixelFormat : uint8_t 37 | { 38 | RGBA8 = 0, 39 | RGBA4 = 2, 40 | RGB5_A1 = 3, 41 | RGB565 = 4, 42 | LUMINANCE8_ALPHA8 = 6, 43 | LUMINANCE8 = 10 44 | }; 45 | 46 | enum class TextureEncoding : uint8_t { 47 | Raw, 48 | KhronosTexture, 49 | SupercellTexture, 50 | }; 51 | 52 | public: 53 | static const SWFVector pixel_format_table; 54 | static const SWFVector pixel_depth_table; 55 | 56 | public: 57 | TextureEncoding encoding() const; 58 | PixelFormat pixel_format() const; 59 | 60 | bool linear() const; 61 | 62 | public: 63 | 64 | void encoding(TextureEncoding encoding); 65 | void pixel_format(PixelFormat format); 66 | void linear(bool status); 67 | 68 | const wk::Ref image() const; 69 | 70 | /// 71 | /// Decodes texture to RawImage 72 | /// 73 | /// Raw texture data without compression or anything like that 74 | wk::Ref raw_image() const; 75 | 76 | public: 77 | /// 78 | /// Groups image data to blocks 32x32 79 | /// 80 | /// Image data 81 | /// Output Image data. Size if buffer must be the same as input 82 | /// 83 | /// 84 | /// Pixel type 85 | /// If true, converts image to block. Otherwise converts blocks to image 86 | static void convert_tiled_data(uint8_t* inout_data, uint8_t* output_data, uint16_t width, uint16_t height, PixelFormat type, bool is_linear); 87 | 88 | public: 89 | void load_from_image(wk::RawImage& image); 90 | void load_from_buffer(wk::Stream& data, uint16_t width, uint16_t height, PixelFormat format, bool has_data = true); 91 | void load_from_file(const SupercellSWF& swf, const fs::path& path); 92 | void load_from_file(const std::filesystem::path& path); 93 | void load_from_khronos_texture(wk::Stream& data); 94 | void load_from_compressed_khronos_texture(wk::Stream& data); 95 | void load_from_supercell_texture(const std::filesystem::path& path); 96 | 97 | protected: 98 | wk::Ref m_image = nullptr; 99 | bool m_linear = true; 100 | PixelFormat m_pixel_format = PixelFormat::RGBA8; 101 | TextureEncoding m_encoding = TextureEncoding::Raw; 102 | 103 | public: 104 | Filter filtering = Filter::LINEAR_NEAREST; 105 | bool downscaling = true; 106 | 107 | public: 108 | virtual void load(SupercellSWF& swf, uint8_t tag, bool use_external_texture); 109 | virtual void save(SupercellSWF& swf, bool has_data, bool is_lowres) const; 110 | virtual void save_buffer(wk::Stream& stream, bool is_lowres) const; 111 | virtual uint8_t tag(SupercellSWF& swf, bool has_data = false) const; 112 | 113 | std::filesystem::path save_to_external_file(const SupercellSWF& swf, uint32_t index, bool is_lowres) const; 114 | void save_to_external_file(const SupercellSWF& swf, const std::filesystem::path& path, bool is_lowres) const; 115 | std::filesystem::path get_external_filename(const SupercellSWF& swf, uint32_t index, bool is_lowres) const; 116 | 117 | public: 118 | static void load_sc2(SupercellSWF&, const SC2::DataStorage*, const uint8_t*); 119 | }; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/objects/SupercellSWF.cpp: -------------------------------------------------------------------------------- 1 | #include "SupercellSWF.h" 2 | #include "SupercellSWF2.h" 3 | 4 | #include "flash/flash_tags.h" 5 | #include 6 | 7 | #include "flash/SC2/FileDescriptor_generated.h" 8 | 9 | namespace fs = std::filesystem; 10 | 11 | namespace sc 12 | { 13 | namespace flash { 14 | #pragma region Loading 15 | void SupercellSWF::load(const fs::path& filepath) 16 | { 17 | current_file = filepath; 18 | use_external_texture = load_internal(filepath, false); 19 | 20 | if (use_external_texture) load_external_texture(); 21 | 22 | stream.clear(); 23 | } 24 | 25 | bool SupercellSWF::load_internal(const std::filesystem::path& filepath, bool is_texture) 26 | { 27 | stream.clear(); 28 | 29 | wk::InputFileStream file(filepath); 30 | if (SupercellSWF::IsSC2(file)) 31 | { 32 | load_sc2(file); 33 | return false; 34 | } 35 | else 36 | { 37 | file.seek(0); 38 | Decompressor::decompress(file, stream); 39 | return load_sc1(is_texture); 40 | } 41 | } 42 | 43 | void SupercellSWF::load_external_texture() 44 | { 45 | fs::path basename = current_file.stem(); 46 | fs::path dirname = current_file.parent_path(); 47 | 48 | fs::path multi_resolution_path = dirname / fs::path(basename).concat(multi_resolution_suffix.string()).concat("_tex.sc"); 49 | fs::path low_resolution_path = dirname / fs::path(basename).concat(low_resolution_suffix.string()).concat("_tex.sc"); 50 | fs::path common_file_path = dirname / fs::path(basename).concat("_tex.sc"); 51 | 52 | if (low_memory_usage_mode && use_low_resolution && fs::exists(low_resolution_path)) 53 | { 54 | load_internal(low_resolution_path, true); 55 | } 56 | else if (use_multi_resolution && fs::exists(multi_resolution_path)) 57 | { 58 | load_internal(multi_resolution_path, true); 59 | } 60 | else if (fs::exists(common_file_path)) 61 | { 62 | load_internal(common_file_path, true); 63 | } 64 | else 65 | { 66 | throw wk::Exception("Failed to load external texture file"); 67 | } 68 | } 69 | 70 | void SupercellSWF::load_sc2(wk::Stream& input) 71 | { 72 | uint32_t header_offset = 0; 73 | uint32_t resources_offset = 0; 74 | 75 | // Descriptor 76 | //{ 77 | uint32_t descriptor_size = input.read_unsigned_int(); 78 | wk::MemoryStream descriptor_data(descriptor_size); 79 | input.read(descriptor_data.data(), descriptor_data.length()); 80 | 81 | const SC2::FileDescriptor* descriptor = SC2::GetFileDescriptor(descriptor_data.data()); 82 | 83 | //assert(descriptor->unk1() == 1 && descriptor->unk2() == 1); 84 | 85 | uint32_t shape_count = descriptor->shape_count(); 86 | shapes.resize(shape_count); 87 | 88 | uint32_t movies_count = descriptor->movie_clips_count(); 89 | movieclips.resize(movies_count); 90 | 91 | uint32_t texture_count = descriptor->texture_count(); 92 | textures.resize(texture_count); 93 | 94 | uint32_t textFields_count = descriptor->text_fields_count(); 95 | textfields.resize(textFields_count); 96 | 97 | header_offset = descriptor->header_offset(); 98 | resources_offset = descriptor->resources_offset(); 99 | 100 | auto export_names_hash = descriptor->exports(); 101 | if (export_names_hash) 102 | { 103 | exports.resize(export_names_hash->size()); 104 | 105 | for (uint32_t i = 0; export_names_hash->size() > i; i++) 106 | { 107 | auto export_name_data = export_names_hash->Get(i); 108 | ExportName& export_name = exports[i]; 109 | export_name.name = SWFString( 110 | export_name_data->name()->data(), 111 | export_name_data->name()->size() 112 | ); 113 | 114 | if (export_name_data->hash()) 115 | { 116 | export_name.hash.resize(export_name_data->hash()->size()); 117 | wk::Memory::copy(export_name_data->hash()->data(), export_name.hash.data(), export_name.hash.size()); 118 | } 119 | } 120 | } 121 | //} 122 | 123 | // Compressed buffer 124 | { 125 | ZstdDecompressor decompressor; 126 | decompressor.decompress(input, stream); 127 | } 128 | 129 | load_sc2_internal(descriptor); 130 | } 131 | 132 | bool SupercellSWF::load_sc1(bool is_texture) 133 | { 134 | stream.seek(0); 135 | 136 | // Reading .sc file 137 | if (!is_texture) 138 | { 139 | uint16_t shapes_count = stream.read_unsigned_short(); 140 | shapes.resize(shapes_count); 141 | 142 | uint16_t movie_clips_count = stream.read_unsigned_short(); 143 | movieclips.resize(movie_clips_count); 144 | 145 | uint16_t textures_count = stream.read_unsigned_short(); 146 | textures.resize(textures_count); 147 | 148 | uint16_t textfield_count = stream.read_unsigned_short(); 149 | textfields.resize(textfield_count); 150 | 151 | uint16_t matrices_count = stream.read_unsigned_short(); 152 | uint16_t colors_count = stream.read_unsigned_short(); 153 | matrixBanks.resize(1, MatrixBank(matrices_count, colors_count)); 154 | 155 | stream.seek(5, wk::Stream::SeekMode::Add); // unused 156 | 157 | uint16_t exports_count = stream.read_unsigned_short(); 158 | exports.reserve(exports_count); 159 | 160 | SWFVector export_ids; 161 | export_ids.resize(exports_count); 162 | 163 | for (uint16_t i = 0; exports_count > i; i++) 164 | { 165 | export_ids[i] = stream.read_unsigned_short(); 166 | } 167 | 168 | for (uint16_t i = 0; exports_count > i; i++) 169 | { 170 | SWFString name; 171 | stream.read_string(name); 172 | 173 | CreateExportName(name, export_ids[i]); 174 | } 175 | } 176 | 177 | return load_tags(); 178 | } 179 | 180 | void SupercellSWF::load_sc2_internal(const SC2::FileDescriptor* descriptor) 181 | { 182 | const SC2::DataStorage* storage = nullptr; 183 | { 184 | stream.seek(descriptor->header_offset()); 185 | uint32_t data_storage_size = stream.read_unsigned_int(); 186 | storage = SC2::GetDataStorage((char*)stream.data() + stream.position()); 187 | stream.seek(data_storage_size, wk::Stream::SeekMode::Add); 188 | MatrixBank::load(*this, storage, descriptor->scale_precision(), descriptor->translation_precision()); 189 | } 190 | 191 | { 192 | stream.seek(descriptor->resources_offset()); 193 | using Table = SupercellSWF2CompileTable; 194 | Table::load_chunk(*this, storage, ExportName::load_sc2); 195 | Table::load_chunk(*this, storage, TextField::load_sc2); 196 | Table::load_chunk(*this, storage, Shape::load_sc2); 197 | Table::load_chunk(*this, storage, MovieClip::load_sc2); 198 | Table::load_chunk(*this, storage, MovieClipModifier::load_sc2); 199 | Table::load_chunk(*this, storage, SWFTexture::load_sc2); 200 | } 201 | } 202 | 203 | bool SupercellSWF::load_tags() 204 | { 205 | bool has_external_texture = false; 206 | 207 | uint16_t shapes_loaded = 0; 208 | uint16_t movieclips_loaded = 0; 209 | uint16_t textures_loaded = 0; 210 | uint16_t textfields_loaded = 0; 211 | uint8_t banks_loaded = 0; 212 | uint16_t matrices_loaded = 0; 213 | uint16_t colors_loaded = 0; 214 | uint16_t modifiers_loaded = 0; 215 | 216 | while (true) 217 | { 218 | uint8_t tag = stream.read_unsigned_byte(); 219 | int32_t tag_length = stream.read_int(); 220 | 221 | if (tag == TAG_END) 222 | break; 223 | 224 | if (tag_length < 0) 225 | throw wk::Exception("Negative tag length"); 226 | 227 | switch (tag) 228 | { 229 | case TAG_USE_MULTI_RES_TEXTURE: 230 | use_multi_resolution = true; 231 | break; 232 | 233 | case TAG_USE_LOW_RES_TEXTURE: 234 | use_low_resolution = true; 235 | break; 236 | 237 | case TAG_USE_EXTERNAL_TEXTURE: 238 | has_external_texture = true; 239 | break; 240 | 241 | case TAG_TEXTURE_FILE_SUFFIXES: 242 | stream.read_string(multi_resolution_suffix); 243 | stream.read_string(low_resolution_suffix); 244 | break; 245 | 246 | case TAG_TEXTURE: 247 | case TAG_TEXTURE_2: 248 | case TAG_TEXTURE_3: 249 | case TAG_TEXTURE_4: 250 | case TAG_TEXTURE_5: 251 | case TAG_TEXTURE_6: 252 | case TAG_TEXTURE_7: 253 | case TAG_TEXTURE_8: 254 | case TAG_TEXTURE_9: 255 | case TAG_TEXTURE_10: 256 | if (textures.size() < textures_loaded) { 257 | throw wk::Exception("Trying to load too many textures"); 258 | } 259 | 260 | textures[textures_loaded].load(*this, tag, !has_external_texture); 261 | textures_loaded++; 262 | break; 263 | 264 | case TAG_MOVIE_CLIP_MODIFIERS_COUNT: { 265 | uint16_t modifiers_count = stream.read_unsigned_short(); 266 | movieclip_modifiers.resize(modifiers_count); 267 | break; 268 | } 269 | 270 | case TAG_MOVIE_CLIP_MODIFIER: 271 | case TAG_MOVIE_CLIP_MODIFIER_2: 272 | case TAG_MOVIE_CLIP_MODIFIER_3: 273 | movieclip_modifiers[modifiers_loaded].load(*this, tag); 274 | modifiers_loaded++; 275 | break; 276 | 277 | case TAG_SHAPE: 278 | case TAG_SHAPE_2: 279 | if (shapes.size() < shapes_loaded) { 280 | throw wk::Exception("Trying to load too many Shapes"); 281 | } 282 | 283 | shapes[shapes_loaded].load(*this, tag); 284 | shapes_loaded++; 285 | break; 286 | 287 | case TAG_TEXT_FIELD: 288 | case TAG_TEXT_FIELD_2: 289 | case TAG_TEXT_FIELD_3: 290 | case TAG_TEXT_FIELD_4: 291 | case TAG_TEXT_FIELD_5: 292 | case TAG_TEXT_FIELD_6: 293 | case TAG_TEXT_FIELD_7: 294 | case TAG_TEXT_FIELD_8: 295 | if (textfields.size() < textfields_loaded) { 296 | throw wk::Exception("Trying to load too many TextFields"); 297 | } 298 | 299 | textfields[textfields_loaded].load(*this, tag); 300 | textfields_loaded++; 301 | break; 302 | 303 | case TAG_MATRIX_BANK: 304 | matrices_loaded = 0; 305 | colors_loaded = 0; 306 | banks_loaded++; 307 | { 308 | uint16_t matrix_count = stream.read_unsigned_short(); 309 | uint16_t colors_count = stream.read_unsigned_short(); 310 | matrixBanks.emplace_back(matrix_count, colors_count); 311 | } 312 | break; 313 | 314 | case TAG_MATRIX_2x3: 315 | case TAG_MATRIX_2x3_2: 316 | matrixBanks[banks_loaded].matrices[matrices_loaded].load(*this, tag); 317 | matrices_loaded++; 318 | break; 319 | 320 | case TAG_COLOR_TRANSFORM: 321 | matrixBanks[banks_loaded].color_transforms[colors_loaded].load(*this); 322 | colors_loaded++; 323 | break; 324 | 325 | case TAG_MOVIE_CLIP: 326 | case TAG_MOVIE_CLIP_2: 327 | case TAG_MOVIE_CLIP_3: 328 | case TAG_MOVIE_CLIP_4: 329 | case TAG_MOVIE_CLIP_5: 330 | case TAG_MOVIE_CLIP_6: 331 | if (movieclips.size() < movieclips_loaded) { 332 | throw wk::Exception("Trying to load too many MovieClips"); 333 | } 334 | 335 | movieclips[movieclips_loaded].load(*this, tag); 336 | movieclips_loaded++; 337 | break; 338 | 339 | default: 340 | stream.seek(tag_length, wk::Stream::SeekMode::Add); 341 | break; 342 | } 343 | } 344 | 345 | return has_external_texture; 346 | } 347 | 348 | #pragma endregion 349 | 350 | #pragma region Saving 351 | void SupercellSWF::save(const fs::path& filepath, Signature signature, bool save_lowres) 352 | { 353 | current_file = filepath; 354 | 355 | save_internal(false, false); 356 | stream.save_file(filepath, signature); 357 | 358 | { 359 | fs::path basename = filepath.stem(); 360 | fs::path dirname = filepath.parent_path(); 361 | 362 | fs::path multi_resolution_path = dirname / fs::path(basename).concat(multi_resolution_suffix.string()).concat("_tex.sc"); 363 | fs::path low_resolution_path = dirname / fs::path(basename).concat(low_resolution_suffix.string()).concat("_tex.sc"); 364 | fs::path common_file_path = dirname / fs::path(basename).concat("_tex.sc"); 365 | 366 | if (use_external_texture) 367 | { 368 | save_internal(true, false); 369 | stream.save_file(use_multi_resolution ? multi_resolution_path : common_file_path, signature); 370 | } 371 | 372 | if ((use_low_resolution || use_multi_resolution) && save_lowres) 373 | { 374 | save_internal(true, true); 375 | stream.save_file(low_resolution_path, signature); 376 | } 377 | } 378 | 379 | } 380 | 381 | void SupercellSWF::save_internal(bool is_texture, bool is_lowres) 382 | { 383 | if (!is_texture) 384 | { 385 | // Export names 386 | if (exports.size() >= std::numeric_limits().max()) 387 | { 388 | throw wk::Exception("Too many export names in use!"); 389 | } 390 | 391 | // Matrix banks 392 | if (matrixBanks.size() > std::numeric_limits().max()) 393 | { 394 | throw wk::Exception("Too many matrix banks in use!"); 395 | } 396 | 397 | // Textures 398 | if (textures.size() > std::numeric_limits().max()) 399 | { 400 | throw wk::Exception("Too many textures in use!"); 401 | } 402 | 403 | // Shapes 404 | if (shapes.size() >= std::numeric_limits().max()) 405 | { 406 | throw wk::Exception("Too many shapes in use!"); 407 | } 408 | 409 | // Movieclips 410 | if (movieclips.size() >= std::numeric_limits().max()) 411 | { 412 | throw wk::Exception("Too many movieclips in use!"); 413 | } 414 | 415 | // Textfields 416 | if (textfields.size() >= std::numeric_limits().max()) 417 | { 418 | throw wk::Exception("Too many textfields in use!"); 419 | } 420 | 421 | stream.write_unsigned_short((uint16_t)shapes.size()); 422 | stream.write_unsigned_short((uint16_t)movieclips.size()); 423 | stream.write_unsigned_short((uint16_t)textures.size()); 424 | stream.write_unsigned_short((uint16_t)textfields.size()); 425 | 426 | uint16_t matrices_count = 0; 427 | uint16_t colors_count = 0; 428 | 429 | if (!matrixBanks.empty()) 430 | { 431 | matrices_count = matrixBanks[0].matrices.size(); 432 | colors_count = matrixBanks[0].color_transforms.size(); 433 | } 434 | 435 | stream.write_unsigned_short(matrices_count); 436 | stream.write_unsigned_short(colors_count); 437 | 438 | // unused 5 bytes 439 | stream.write_unsigned_byte(0); 440 | stream.write_int(0); 441 | 442 | stream.write_unsigned_short((uint16_t)exports.size()); 443 | 444 | for (const ExportName& export_name : exports) 445 | { 446 | stream.write_unsigned_short(export_name.id); 447 | } 448 | 449 | for (const ExportName& export_name : exports) 450 | { 451 | stream.write_string(export_name.name); 452 | } 453 | 454 | save_tags(); 455 | } 456 | else 457 | { 458 | save_textures_sc1(is_texture, is_lowres); 459 | } 460 | 461 | stream.write_tag_flag(TAG_END); 462 | } 463 | 464 | void SupercellSWF::save_tags() 465 | { 466 | if (use_low_resolution) 467 | stream.write_tag_flag(TAG_USE_LOW_RES_TEXTURE); 468 | 469 | if (use_multi_resolution) 470 | stream.write_tag_flag(TAG_USE_MULTI_RES_TEXTURE); 471 | 472 | if (use_external_texture) 473 | stream.write_tag_flag(TAG_USE_EXTERNAL_TEXTURE); 474 | 475 | if ( 476 | multi_resolution_suffix.compare(MULTIRES_DEFAULT_SUFFIX) != 0 || 477 | low_resolution_suffix.compare(LOWRES_DEFAULT_SUFFIX) != 0) 478 | { 479 | size_t position = stream.write_tag_header(TAG_TEXTURE_FILE_SUFFIXES); 480 | stream.write_string(multi_resolution_suffix); 481 | stream.write_string(low_resolution_suffix); 482 | stream.write_tag_final(position); 483 | } 484 | 485 | save_textures_sc1(!use_external_texture, false); 486 | 487 | if (movieclip_modifiers.size() > 0) { 488 | stream.write_unsigned_byte(TAG_MOVIE_CLIP_MODIFIERS_COUNT); // Tag 489 | stream.write_int(sizeof(uint16_t)); // Tag Size 490 | stream.write_unsigned_short(movieclip_modifiers.size()); 491 | 492 | for (const MovieClipModifier& modifier : movieclip_modifiers) 493 | { 494 | size_t position = stream.write_tag_header(modifier.tag(*this)); 495 | modifier.save(*this); 496 | stream.write_tag_final(position); 497 | } 498 | } 499 | 500 | for (const Shape& shape : shapes) 501 | { 502 | size_t position = stream.write_tag_header(shape.tag(*this)); 503 | shape.save(*this); 504 | stream.write_tag_final(position); 505 | } 506 | 507 | for (const TextField& textField : textfields) 508 | { 509 | size_t position = stream.write_tag_header(textField.tag(*this)); 510 | textField.save(*this); 511 | stream.write_tag_final(position); 512 | } 513 | 514 | for (uint8_t i = 0; matrixBanks.size() > i; i++) 515 | { 516 | const MatrixBank& bank = matrixBanks[i]; 517 | 518 | if (i != 0) 519 | { 520 | stream.write_unsigned_byte(bank.tag(*this)); // Tag 521 | stream.write_int(sizeof(uint16_t) * 2); // Tag Size 522 | stream.write_unsigned_short(bank.matrices.size()); 523 | stream.write_unsigned_short(bank.color_transforms.size()); 524 | } 525 | 526 | for (const Matrix2D& matrix : bank.matrices) 527 | { 528 | size_t position = stream.write_tag_header(matrix.tag(*this)); 529 | matrix.save(*this); 530 | stream.write_tag_final(position); 531 | } 532 | 533 | for (const ColorTransform& color : bank.color_transforms) 534 | { 535 | size_t position = stream.write_tag_header(color.tag(*this)); 536 | color.save(*this); 537 | stream.write_tag_final(position); 538 | } 539 | } 540 | 541 | for (const MovieClip& movieclip : movieclips) 542 | { 543 | size_t position = stream.write_tag_header(movieclip.tag(*this)); 544 | movieclip.save(*this); 545 | stream.write_tag_final(position); 546 | } 547 | } 548 | 549 | void SupercellSWF::save_textures_sc1(bool has_data, bool is_lowres) 550 | { 551 | for (uint16_t i = 0; textures.size() > i; i++) 552 | { 553 | SWFTexture& texture = textures[i]; 554 | 555 | size_t position = stream.write_tag_header(texture.tag(*this, has_data)); 556 | if (use_external_textures && has_data) 557 | { 558 | texture.encoding(SWFTexture::TextureEncoding::KhronosTexture); 559 | 560 | // Path String In Tag 561 | fs::path filename = texture.save_to_external_file(*this, i, is_lowres); 562 | 563 | { 564 | std::string texture_filename = filename.string(); 565 | SWFString texture_path(texture_filename); 566 | stream.write_string(texture_path); 567 | } 568 | } 569 | texture.save(*this, has_data, is_lowres); 570 | stream.write_tag_final(position); 571 | } 572 | } 573 | 574 | void SupercellSWF::save_sc2(const fs::path& filepath) const 575 | { 576 | current_file = filepath; 577 | SupercellSWF2CompileTable table(*this); 578 | stream.clear(); 579 | 580 | // Saving all file content to this->stream 581 | table.save_buffer(); 582 | stream.seek(0); 583 | 584 | // Output stream 585 | wk::OutputFileStream file(filepath); 586 | file.write_unsigned_short(SC_MAGIC); // Magic 587 | file.write_unsigned_int(5); // Version 588 | table.save_descriptor(file); // Descriptor 589 | Compressor::compress(stream, file, Signature::Zstandard); // File Content 590 | 591 | } 592 | #pragma endregion 593 | 594 | ExportName* SupercellSWF::GetExportName(const SWFString& name) 595 | { 596 | auto it = std::find_if(std::execution::par_unseq, exports.begin(), exports.end(), [&name](const ExportName& other) 597 | { 598 | return other.name == name; 599 | }); 600 | 601 | if (it != exports.end()) 602 | { 603 | return &(*it); 604 | } 605 | 606 | return nullptr; 607 | } 608 | 609 | uint16_t SupercellSWF::GetDisplayObjectID(const SWFString& name) 610 | { 611 | auto export_name = GetExportName(name); 612 | if (export_name) 613 | { 614 | return export_name->id; 615 | } 616 | 617 | throw new wk::Exception("Failed to get export name \"%s\"", name.data()); 618 | } 619 | 620 | DisplayObject& SupercellSWF::GetDisplayObjectByID(uint16_t id) 621 | { 622 | for (Shape& shape : shapes) 623 | { 624 | if (shape.id == id) 625 | { 626 | return shape; 627 | } 628 | } 629 | 630 | for (TextField& textfield : textfields) 631 | { 632 | if (textfield.id == id) 633 | { 634 | return textfield; 635 | } 636 | } 637 | 638 | for (MovieClipModifier& modifier : movieclip_modifiers) 639 | { 640 | if (modifier.id == id) 641 | { 642 | return modifier; 643 | } 644 | } 645 | 646 | for (MovieClip& movie : movieclips) 647 | { 648 | if (movie.id == id) 649 | { 650 | return movie; 651 | } 652 | } 653 | 654 | throw new wk::Exception("Failed to get Display Object"); 655 | } 656 | 657 | MovieClip& SupercellSWF::GetDisplayObjectByName(const SWFString& name) 658 | { 659 | uint16_t id = GetDisplayObjectID(name); 660 | for (MovieClip& movie : movieclips) 661 | { 662 | if (movie.id == id) 663 | { 664 | return movie; 665 | } 666 | } 667 | 668 | throw new wk::Exception("Failed to get Display Object"); 669 | } 670 | 671 | void SupercellSWF::CreateExportName(const SWFString& name, uint16_t id) 672 | { 673 | auto possible_name = GetExportName(name); 674 | if (possible_name) 675 | { 676 | possible_name->id = id; 677 | } 678 | else 679 | { 680 | ExportName& export_name = exports.emplace_back(); 681 | export_name.name = name; 682 | export_name.id = id; 683 | } 684 | } 685 | 686 | bool SupercellSWF::IsSC2(wk::Stream& stream) 687 | { 688 | return stream.read_short() == SC_MAGIC && stream.read_unsigned_int() == 5; 689 | } 690 | } 691 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/objects/SupercellSWF.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "flash/flash_tags.h" 7 | #include "flash/types/SWFStream.hpp" 8 | 9 | // SWF Objects 10 | #include "SWFTexture.h" 11 | #include "ExportName.h" 12 | 13 | #include "flash/transform/MatrixBank.h" 14 | 15 | #include "flash/display_object/Shape.h" 16 | #include "flash/display_object/MovieClip.h" 17 | #include "flash/display_object/TextField.h" 18 | #include "flash/display_object/MovieClipModifier.h" 19 | 20 | namespace sc 21 | { 22 | namespace flash { 23 | constexpr auto MULTIRES_DEFAULT_SUFFIX = "_highres"; 24 | constexpr auto LOWRES_DEFAULT_SUFFIX = "_lowres"; 25 | 26 | namespace SC2 27 | { 28 | class DataStorage; 29 | } 30 | 31 | struct Sc2CompileSettings 32 | { 33 | // Save matrices with half precision 34 | bool use_half_precision_matrices = false; 35 | 36 | // Use short frames to optimize compiled file size 37 | bool use_short_frames = false; 38 | }; 39 | 40 | class SupercellSWF 41 | { 42 | // Type traits 43 | public: 44 | using ExportsArray = SWFVector; 45 | using MatrixBankArray = SWFVector; 46 | using TextureArray = SWFVector; 47 | using ShapeArray = SWFVector; 48 | using MovieClipArray = SWFVector; 49 | using TextFieldArray = SWFVector; 50 | using ModifiersArray = SWFVector; 51 | 52 | public: 53 | SupercellSWF() {}; 54 | virtual ~SupercellSWF() = default; 55 | SupercellSWF(const SupercellSWF&) = default; 56 | SupercellSWF(SupercellSWF&&) = default; 57 | SupercellSWF& operator=(const SupercellSWF&) = default; 58 | SupercellSWF& operator=(SupercellSWF&&) = default; 59 | 60 | public: 61 | mutable fs::path current_file; 62 | 63 | ExportsArray exports; 64 | MatrixBankArray matrixBanks; 65 | 66 | TextureArray textures; 67 | ShapeArray shapes; 68 | MovieClipArray movieclips; 69 | TextFieldArray textfields; 70 | ModifiersArray movieclip_modifiers; 71 | 72 | public: 73 | virtual void load(const std::filesystem::path& filePath); 74 | bool load_internal(const std::filesystem::path& filepath, bool is_texture); 75 | void load_external_texture(); 76 | 77 | bool load_sc1(bool is_texture); 78 | void load_sc2(wk::Stream& stream); 79 | void load_sc2_internal(const SC2::FileDescriptor* descriptor); 80 | 81 | virtual void save(const fs::path& filepath, Signature signature, bool save_lowres = false); 82 | void save_internal(bool is_texture, bool is_lowres); 83 | 84 | virtual void save_sc2(const fs::path& filepath) const; 85 | 86 | mutable SWFStream stream; 87 | 88 | protected: 89 | bool load_tags(); 90 | 91 | void save_tags(); 92 | void save_textures_sc1(bool has_data, bool is_lowres); 93 | 94 | public: 95 | ExportName* GetExportName(const SWFString& name); 96 | uint16_t GetDisplayObjectID(const SWFString& name); 97 | DisplayObject& GetDisplayObjectByID(uint16_t id); 98 | MovieClip& GetDisplayObjectByName(const SWFString& name); 99 | void CreateExportName(const SWFString& name, uint16_t id); 100 | 101 | public: 102 | // Saves all textures to _tex.sc if true 103 | bool use_external_texture = false; 104 | bool use_multi_resolution = false; 105 | 106 | // Use low-resolution texture 107 | bool use_low_resolution = false; 108 | 109 | // Write matrices with greater precision 110 | bool use_precision_matrix = false; 111 | 112 | // Compresses ktx/sctx texture files 113 | bool compress_external_textures = true; 114 | 115 | // Saves textures to ktx/sctx 116 | bool use_external_textures = false; 117 | 118 | // Saves custom properties in MovieClips 119 | bool save_custom_property = true; 120 | 121 | // Load only lowres files if available 122 | bool low_memory_usage_mode = false; 123 | 124 | Sc2CompileSettings sc2_compile_settings; 125 | 126 | SWFString multi_resolution_suffix = MULTIRES_DEFAULT_SUFFIX; 127 | SWFString low_resolution_suffix = LOWRES_DEFAULT_SUFFIX; 128 | 129 | public: 130 | static bool IsSC2(wk::Stream& stream); 131 | }; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/objects/SupercellSWF2.cpp: -------------------------------------------------------------------------------- 1 | #include "SupercellSWF2.h" 2 | 3 | #include "flash/objects/SupercellSWF.h" 4 | 5 | #include 6 | 7 | using namespace flatbuffers; 8 | 9 | namespace sc::flash 10 | { 11 | // SupercellSWF2CompileTable 12 | 13 | SupercellSWF2CompileTable::SupercellSWF2CompileTable(const SupercellSWF& _swf) : swf(_swf) 14 | { 15 | builder = FlatBufferBuilder(1024 * 1024, nullptr, false); 16 | 17 | if (swf.sc2_compile_settings.use_half_precision_matrices) 18 | { 19 | scale_presicion = SC2::Precision::Optimized; 20 | translation_precision = SC2::Precision::Twip; 21 | } 22 | } 23 | 24 | void SupercellSWF2CompileTable::load_chunk(SupercellSWF& swf, const SC2::DataStorage* storage, const std::function& reader) 25 | { 26 | uint32_t data_size = swf.stream.read_unsigned_int(); 27 | reader(swf, storage, (uint8_t*)swf.stream.data() + swf.stream.position()); 28 | swf.stream.seek(data_size, wk::Stream::SeekMode::Add); 29 | } 30 | 31 | const float SupercellSWF2CompileTable::get_precision_multiplier(SC2::Precision precision) 32 | { 33 | switch (precision) 34 | { 35 | case sc::flash::SC2::Precision::Twip: 36 | return 20.f; 37 | case sc::flash::SC2::Precision::Optimized: 38 | return 1024.f; 39 | default: 40 | return 1.f; 41 | } 42 | } 43 | 44 | uint32_t SupercellSWF2CompileTable::get_string_ref(const SWFString& target) 45 | { 46 | auto it = std::find(std::execution::par_unseq, strings.begin(), strings.end(), target); 47 | if (it != strings.end()) 48 | { 49 | return (uint32_t)std::distance(strings.begin(), it); 50 | } 51 | else 52 | { 53 | uint32_t result = strings.size(); 54 | strings.push_back(target); 55 | return result; 56 | } 57 | } 58 | 59 | uint32_t SupercellSWF2CompileTable::get_rect_ref(const wk::RectF& rect) 60 | { 61 | auto it = std::find_if(std::execution::par_unseq, rectangles.begin(), rectangles.end(), [&rect](const SC2::Typing::Rect& other) 62 | { 63 | return rect.left == other.left() && rect.right == other.right() && rect.top == other.top() && rect.bottom == other.bottom(); 64 | } 65 | ); 66 | 67 | if (it != rectangles.end()) 68 | { 69 | return (uint32_t)std::distance(rectangles.begin(), it); 70 | } 71 | else 72 | { 73 | uint32_t result = rectangles.size(); 74 | rectangles.emplace_back(rect.left, rect.top, rect.right, rect.bottom); 75 | return result; 76 | } 77 | } 78 | 79 | void SupercellSWF2CompileTable::gather_resources() 80 | { 81 | // Buffer preparing 82 | { 83 | size_t frame_elements_count = 0; 84 | for (const MovieClip& movie : swf.movieclips) 85 | { 86 | frame_elements_count += movie.frame_elements.size(); 87 | } 88 | 89 | size_t shape_commands_size = 0; 90 | for (const Shape& shape : swf.shapes) 91 | { 92 | for (const ShapeDrawBitmapCommand& command : shape.commands) 93 | { 94 | shape_commands_size += command.vertices.size() * ShapeDrawBitmapCommandVertex::Size; 95 | } 96 | } 97 | 98 | frame_elements_indices.reserve(frame_elements_count); 99 | bitmaps_buffer.reserve(shape_commands_size); 100 | 101 | exports_ref_indices.reserve(swf.exports.size()); 102 | textfields_ref_indices.reserve(swf.textfields.size()); 103 | movieclips_ref_indices.reserve(swf.movieclips.size()); 104 | } 105 | 106 | // Exports 107 | { 108 | for (const ExportName& export_name : swf.exports) 109 | { 110 | exports_ref_indices.push_back( 111 | get_string_ref(export_name.name) 112 | ); 113 | } 114 | } 115 | 116 | // TextFields 117 | { 118 | for (const TextField& textField : swf.textfields) 119 | { 120 | RefT font_name = get_string_ref(textField.font_name); 121 | RefT text = get_string_ref(textField.text); 122 | RefT style_path = get_string_ref(textField.typography_file); 123 | 124 | textfields_ref_indices.emplace_back(font_name, text, style_path); 125 | } 126 | } 127 | 128 | // Shapes 129 | { 130 | uint32_t bitmap_points_counter = 0; 131 | for (const Shape& shape : swf.shapes) 132 | { 133 | for (const ShapeDrawBitmapCommand& command : shape.commands) 134 | { 135 | bitmaps_offsets.push_back(bitmap_points_counter); 136 | command.write_buffer(bitmaps_buffer, false, true); 137 | 138 | bitmap_points_counter += command.vertices.size(); 139 | } 140 | } 141 | } 142 | 143 | // MovieClips 144 | { 145 | RefT frame_elements_counter = 0; 146 | for (const MovieClip& movieclip : swf.movieclips) 147 | { 148 | std::optional> children_names; 149 | 150 | bool has_children_names = std::any_of(movieclip.childrens.begin(), movieclip.childrens.end(), 151 | [](const DisplayObjectInstance& obj) { 152 | return !obj.name.empty(); 153 | } 154 | ); 155 | if (has_children_names) 156 | { 157 | children_names = RefArray(); 158 | children_names->reserve(movieclip.childrens.size()); 159 | for (const DisplayObjectInstance& instance : movieclip.childrens) 160 | { 161 | children_names->push_back( 162 | get_string_ref(instance.name) 163 | ); 164 | } 165 | } 166 | 167 | RefT frame_elements_offset = frame_elements_counter; 168 | for (const MovieClipFrameElement& element : movieclip.frame_elements) 169 | { 170 | frame_elements_indices.push_back(element.instance_index); 171 | frame_elements_indices.push_back(element.matrix_index); 172 | frame_elements_indices.push_back(element.colorTransform_index); 173 | } 174 | frame_elements_counter += movieclip.frame_elements.size() * 3; 175 | 176 | RefArray frame_labels; 177 | frame_labels.reserve(movieclip.frames.size()); 178 | 179 | for (const MovieClipFrame& frame : movieclip.frames) 180 | { 181 | frame_labels.push_back( 182 | get_string_ref(frame.label) 183 | ); 184 | } 185 | 186 | std::optional scaling_grid = std::nullopt; 187 | if (movieclip.scaling_grid.has_value()) 188 | { 189 | scaling_grid = get_rect_ref( 190 | movieclip.scaling_grid.value() 191 | ); 192 | } 193 | 194 | movieclips_ref_indices.emplace_back(children_names, frame_elements_offset, frame_labels, scaling_grid); 195 | } 196 | } 197 | 198 | // Textures 199 | { } 200 | } 201 | 202 | void SupercellSWF2CompileTable::save_header() 203 | { 204 | Offset>> strings_off = 0; 205 | Offset> rects_off = 0; 206 | Offset> movieclip_frame_elements_off = 0; 207 | Offset> shape_bitmaps_off = 0; 208 | Offset>> banks_off = 0; 209 | 210 | // Strings 211 | if (!strings.empty()) 212 | { 213 | RefArray> raw_strings_off; 214 | raw_strings_off.reserve(strings.size()); 215 | 216 | for (const auto& string : strings) 217 | { 218 | Offset string_off = builder.CreateString(string.data(), string.length()); 219 | raw_strings_off.push_back(string_off); 220 | 221 | } 222 | strings_off = builder.CreateVector(raw_strings_off); 223 | } 224 | 225 | // Rectangles 226 | if (!rectangles.empty()) 227 | { 228 | rects_off = builder.CreateVectorOfStructs(rectangles); 229 | } 230 | 231 | // MovieClip frame elements 232 | { 233 | movieclip_frame_elements_off = builder.CreateVector(frame_elements_indices); 234 | } 235 | 236 | // Shape bitmaps vertices 237 | { 238 | shape_bitmaps_off = builder.CreateVector((uint8_t*)bitmaps_buffer.data(), bitmaps_buffer.length()); 239 | } 240 | 241 | // Matrix Banks 242 | { 243 | RefArray> raw_banks_off; 244 | raw_banks_off.reserve(swf.matrixBanks.size()); 245 | 246 | for (const MatrixBank& bank : swf.matrixBanks) 247 | { 248 | flatbuffers::Offset bank_off; 249 | 250 | std::vector colors; 251 | for (const flash::ColorTransform& color : bank.color_transforms) 252 | { 253 | colors.emplace_back( 254 | color.multiply.r, color.multiply.g, color.multiply.b, 255 | color.alpha, 256 | color.add.r, color.add.g, color.add.b 257 | ); 258 | } 259 | 260 | 261 | if (swf.sc2_compile_settings.use_half_precision_matrices) 262 | { 263 | // Scale multiplier 264 | float sm = SupercellSWF2CompileTable::get_precision_multiplier(scale_presicion); 265 | // Translation multiplier 266 | float tm = SupercellSWF2CompileTable::get_precision_multiplier(translation_precision); 267 | 268 | std::vector matrices; 269 | matrices.reserve(bank.matrices.size()); 270 | 271 | for (const flash::Matrix2D& matrix : bank.matrices) 272 | { 273 | matrices.emplace_back( 274 | (int16_t)(matrix.a * sm), 275 | (int16_t)(matrix.b * sm), 276 | (int16_t)(matrix.c * sm), 277 | (int16_t)(matrix.d * sm), 278 | (int16_t)(matrix.tx * tm), 279 | (int16_t)(matrix.ty * tm) 280 | ); 281 | } 282 | 283 | bank_off = SC2::CreateMatrixBankDirect(builder, nullptr, &colors, &matrices); 284 | } 285 | else 286 | { 287 | std::vector matrices; 288 | matrices.reserve(bank.matrices.size()); 289 | 290 | for (const flash::Matrix2D& matrix : bank.matrices) 291 | { 292 | matrices.emplace_back(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); 293 | } 294 | 295 | bank_off = SC2::CreateMatrixBankDirect(builder, &matrices, &colors); 296 | } 297 | 298 | raw_banks_off.push_back(bank_off); 299 | } 300 | 301 | banks_off = builder.CreateVector(raw_banks_off); 302 | } 303 | 304 | Offset root_off = SC2::CreateDataStorage( 305 | builder, strings_off, 0, 0, rects_off, 306 | movieclip_frame_elements_off, shape_bitmaps_off, banks_off 307 | ); 308 | 309 | flush_builder(root_off); 310 | } 311 | 312 | void SupercellSWF2CompileTable::save_exports() 313 | { 314 | std::vector exports_ids; 315 | exports_ids.reserve(swf.exports.size()); 316 | for (const ExportName& name : swf.exports) 317 | { 318 | exports_ids.push_back(name.id); 319 | } 320 | 321 | Offset root_off = SC2::CreateExportNamesDirect( 322 | builder, &exports_ids, &exports_ref_indices 323 | ); 324 | 325 | flush_builder(root_off); 326 | } 327 | 328 | void SupercellSWF2CompileTable::save_textFields() 329 | { 330 | Offset> textfields_offs; 331 | 332 | { 333 | std::vector textfields_data; 334 | for (uint32_t i = 0; swf.textfields.size() > i; i++) 335 | { 336 | const TextField& textfield = swf.textfields[i]; 337 | auto& [font_name, text, style_path] = textfields_ref_indices[i]; 338 | 339 | SC2::TextField textfield_data( 340 | textfield.id, 0, font_name, 341 | textfield.left, textfield.top, textfield.right, textfield.bottom, 342 | textfield.font_color.as_value(), textfield.outline_color, text, style_path, 343 | textfield.get_style_flags(), textfield.get_align_flags(), 344 | textfield.font_size, 0, textfield.unknown_short 345 | ); 346 | 347 | textfields_data.push_back(textfield_data); 348 | } 349 | 350 | if (!swf.textfields.empty()) 351 | { 352 | textfields_offs = builder.CreateVectorOfStructs(textfields_data); 353 | } 354 | } 355 | 356 | Offset root_off = SC2::CreateTextFields( 357 | builder, textfields_offs 358 | ); 359 | 360 | flush_builder(root_off); 361 | } 362 | 363 | void SupercellSWF2CompileTable::save_shapes() 364 | { 365 | size_t command_counter = 0; 366 | Offset>> shapes_off; 367 | { 368 | std::vector> raw_shapes_off; 369 | 370 | for (const Shape& shape : swf.shapes) 371 | { 372 | std::vector commands; 373 | for (const ShapeDrawBitmapCommand& command : shape.commands) 374 | { 375 | uint32_t bitmap_data_offset = bitmaps_offsets[command_counter++]; 376 | 377 | commands.emplace_back( 378 | 0, command.texture_index, (uint32_t)command.vertices.size(), bitmap_data_offset 379 | ); 380 | } 381 | 382 | Offset shape_off; 383 | shape_off = SC2::CreateShapeDirect(builder, 384 | shape.id, &commands 385 | ); 386 | raw_shapes_off.push_back(shape_off); 387 | } 388 | 389 | if (!swf.shapes.empty()) 390 | { 391 | shapes_off = builder.CreateVector(raw_shapes_off); 392 | } 393 | } 394 | 395 | Offset root_off = SC2::CreateShapes( 396 | builder, shapes_off 397 | ); 398 | 399 | flush_builder(root_off); 400 | } 401 | 402 | void SupercellSWF2CompileTable::save_movieClips() 403 | { 404 | std::unordered_map export_names; 405 | for (uint32_t i = 0; swf.exports.size() > i; i++) 406 | { 407 | const ExportName& export_name = swf.exports[i]; 408 | export_names[export_name.id] = exports_ref_indices[i]; 409 | } 410 | 411 | Offset>> movieclips_off; 412 | { 413 | std::vector> raw_movieclips_off; 414 | 415 | for (uint32_t i = 0; swf.movieclips.size() > i; i++) 416 | { 417 | auto& [children_names_data, frame_elements_offset, frame_labels, scaling_grid_index] = movieclips_ref_indices[i]; 418 | const MovieClip& movieclip = swf.movieclips[i]; 419 | 420 | std::vector children_ids; 421 | std::vector children_blending; 422 | children_ids.reserve(movieclip.childrens.size()); 423 | children_blending.reserve(movieclip.childrens.size()); 424 | 425 | std::transform(movieclip.childrens.begin(), movieclip.childrens.end(), std::back_inserter(children_ids), 426 | [](const auto& obj) { return obj.id; }); 427 | 428 | std::transform(movieclip.childrens.begin(), movieclip.childrens.end(), std::back_inserter(children_blending), 429 | [](const auto& obj) { return (uint8_t)obj.blend_mode; }); 430 | 431 | Optional export_name_ref = nullopt; 432 | if (export_names.count(movieclip.id)) 433 | { 434 | export_name_ref = export_names[movieclip.id]; 435 | } 436 | 437 | std::vector* childrens_names = nullptr; 438 | if (children_names_data.has_value()) 439 | { 440 | childrens_names = &children_names_data.value(); 441 | } 442 | 443 | std::vector short_frames; 444 | std::vector frames; 445 | bool has_frame_lables = std::any_of(movieclip.frames.begin(), movieclip.frames.end(), 446 | [](const MovieClipFrame& frame) { 447 | return !frame.label.empty(); 448 | } 449 | ); 450 | 451 | bool elements_in_short_range = std::any_of(movieclip.frames.begin(), movieclip.frames.end(), 452 | [frame_elements_offset](const MovieClipFrame& frame) { 453 | return std::numeric_limits::max() > frame.elements_count; 454 | } 455 | ); 456 | 457 | 458 | if (swf.sc2_compile_settings.use_short_frames && !has_frame_lables && elements_in_short_range) 459 | { 460 | short_frames.reserve(movieclip.frames.size()); 461 | for (uint16_t t = 0; movieclip.frames.size() > t; t++) 462 | { 463 | const MovieClipFrame& frame = movieclip.frames[t]; 464 | short_frames.emplace_back((uint16_t)frame.elements_count); 465 | } 466 | } 467 | else 468 | { 469 | frames.reserve(movieclip.frames.size()); 470 | for (uint16_t t = 0; movieclip.frames.size() > t; t++) 471 | { 472 | const MovieClipFrame& frame = movieclip.frames[t]; 473 | frames.emplace_back(frame.elements_count, frame_labels[t]); 474 | } 475 | } 476 | 477 | Optional scaling_grid = nullopt; 478 | if (scaling_grid_index.has_value()) 479 | { 480 | scaling_grid = scaling_grid_index.value(); 481 | } 482 | 483 | Offset movieclip_off; 484 | movieclip_off = SC2::CreateMovieClipDirect( 485 | builder, movieclip.id, 486 | export_name_ref, 487 | movieclip.frame_rate, movieclip.frames.size(), movieclip.unknown_flag, 488 | children_ids.empty() ? nullptr : &children_ids, 489 | childrens_names, 490 | children_blending.empty() ? nullptr : &children_blending, 491 | frames.empty() ? nullptr : &frames, 492 | frame_elements_offset, movieclip.bank_index, scaling_grid, 493 | short_frames.empty() ? nullptr : &short_frames 494 | ); 495 | raw_movieclips_off.push_back(movieclip_off); 496 | } 497 | 498 | if (!swf.movieclips.empty()) 499 | { 500 | movieclips_off = builder.CreateVector(raw_movieclips_off); 501 | } 502 | } 503 | 504 | Offset root_off = SC2::CreateMovieClips( 505 | builder, movieclips_off 506 | ); 507 | 508 | flush_builder(root_off); 509 | } 510 | 511 | void SupercellSWF2CompileTable::save_modifiers() 512 | { 513 | Offset> modifiers_offs; 514 | { 515 | std::vector modifiers_data; 516 | for (const MovieClipModifier& modifier : swf.movieclip_modifiers) 517 | { 518 | modifiers_data.emplace_back(modifier.id, (uint8_t)modifier.type); 519 | } 520 | 521 | if (!swf.movieclip_modifiers.empty()) 522 | { 523 | modifiers_offs = builder.CreateVectorOfStructs(modifiers_data); 524 | } 525 | } 526 | 527 | Offset root_off = SC2::CreateMovieClipModifiers( 528 | builder, modifiers_offs 529 | ); 530 | 531 | flush_builder(root_off); 532 | } 533 | 534 | Offset SupercellSWF2CompileTable::create_texture(const SWFTexture& texture, uint32_t index, bool is_lowres) 535 | { 536 | Offset> texture_data_off; 537 | Offset external_path_off; 538 | Offset result; 539 | wk::BufferStream texture_buffer; 540 | 541 | if (swf.use_external_textures) 542 | { 543 | fs::path filename = texture.save_to_external_file(swf, index, is_lowres); 544 | external_path_off = builder.CreateString(filename.string()); 545 | } 546 | else 547 | { 548 | // Temporarly solution 549 | SWFTexture texture_copy = texture; 550 | texture_copy.encoding(SWFTexture::TextureEncoding::KhronosTexture); 551 | texture_copy.save_buffer(texture_buffer, is_lowres); 552 | 553 | texture_data_off = builder.CreateVector((uint8_t*)texture_buffer.data(), texture_buffer.length()); 554 | } 555 | 556 | result = SC2::CreateTextureData( 557 | builder, 8, 0, 558 | texture.image()->width(), 559 | texture.image()->height(), 560 | texture_data_off, 561 | external_path_off 562 | ); 563 | 564 | return result; 565 | } 566 | 567 | uint32_t SupercellSWF2CompileTable::save_textures() 568 | { 569 | Offset>> textures_offs; 570 | { 571 | std::vector> raw_textures_offs; 572 | 573 | for (uint32_t i = 0; swf.textures.size() > i; i++) 574 | { 575 | const SWFTexture& texture = swf.textures[i]; 576 | 577 | Offset texture_set_off; 578 | Offset highres_texuture_off; 579 | Offset lowres_texuture_off; 580 | 581 | if (swf.use_low_resolution) 582 | { 583 | lowres_texuture_off = create_texture(texture, i, true); 584 | } 585 | 586 | highres_texuture_off = create_texture(texture, i, false); 587 | 588 | texture_set_off = SC2::CreateTextureSet( 589 | builder, lowres_texuture_off, highres_texuture_off 590 | ); 591 | raw_textures_offs.push_back(texture_set_off); 592 | } 593 | 594 | if (!swf.textures.empty()) 595 | { 596 | textures_offs = builder.CreateVector(raw_textures_offs); 597 | } 598 | } 599 | 600 | Offset root_off = SC2::CreateTextures( 601 | builder, textures_offs 602 | ); 603 | 604 | return (uint32_t)flush_builder(root_off); 605 | } 606 | 607 | void SupercellSWF2CompileTable::save_buffer() 608 | { 609 | // just a mini optimization 610 | get_string_ref(""); 611 | 612 | gather_resources(); 613 | 614 | header_offset = (uint32_t)swf.stream.position(); 615 | save_header(); 616 | 617 | data_offset = (uint32_t)swf.stream.position(); 618 | save_exports(); 619 | save_textFields(); 620 | save_shapes(); 621 | save_movieClips(); 622 | save_modifiers(); 623 | 624 | textures_length = save_textures(); 625 | } 626 | 627 | void SupercellSWF2CompileTable::save_descriptor(wk::Stream& stream) 628 | { 629 | Offset>> exports_hash_off = 0; 630 | { 631 | std::vector> exports_hashes_offs; 632 | for (const ExportName& export_name : swf.exports) 633 | { 634 | if (export_name.name.empty()) continue; 635 | 636 | Offset> export_name_hash_off = 0; 637 | Offset export_name_off = 0; 638 | 639 | if (!export_name.hash.empty()) 640 | { 641 | export_name_hash_off = builder.CreateVector(export_name.hash.data(), export_name.hash.size()); 642 | } 643 | 644 | export_name_off = builder.CreateString(export_name.name.data(), export_name.name.length()); 645 | 646 | Offset name_off = SC2::CreateExportNameHash( 647 | builder, export_name_off, export_name_hash_off 648 | ); 649 | 650 | exports_hashes_offs.push_back(name_off); 651 | } 652 | 653 | exports_hash_off = builder.CreateVector(exports_hashes_offs); 654 | } 655 | 656 | Offset root_off = SC2::CreateFileDescriptor( 657 | builder, translation_precision, scale_presicion, 658 | swf.shapes.size(), swf.movieclips.size(), swf.textures.size(), swf.textfields.size(), 0, 659 | header_offset, data_offset, textures_length, exports_hash_off 660 | ); 661 | 662 | builder.FinishSizePrefixed(root_off); 663 | auto buffer = builder.GetBufferSpan(); 664 | stream.write(buffer.data(), buffer.size_bytes()); 665 | 666 | builder.Clear(); 667 | } 668 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/objects/SupercellSWF2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "flash/types/SWFContainer.hpp" 4 | #include "flash/types/SWFString.hpp" 5 | #include "flash/objects/SWFTexture.h" 6 | #include "core/io/buffer_stream.h" 7 | #include "core/math/rect.h" 8 | 9 | #include "flash/SC2/DataStorage_generated.h" 10 | #include "flash/SC2/FileDescriptor_generated.h" 11 | 12 | #include 13 | 14 | namespace sc::flash 15 | { 16 | class SupercellSWF; 17 | 18 | class SupercellSWF2CompileTable 19 | { 20 | public: 21 | using RefT = uint32_t; 22 | 23 | template 24 | using RefArray = std::vector; 25 | 26 | // font_name, text, style_path 27 | using TextfieldRef = std::tuple; 28 | 29 | // children_names, frame_elements_offset, frame_labels, scaling_grid 30 | using MovieClipRef = std::tuple>, RefT, RefArray, std::optional>; 31 | 32 | public: 33 | SupercellSWF2CompileTable(const SupercellSWF& swf); 34 | 35 | public: 36 | static void load_chunk(SupercellSWF& swf, const SC2::DataStorage* storage, const std::function& reader); 37 | static const float get_precision_multiplier(SC2::Precision); 38 | 39 | public: 40 | // Matrix precision 41 | SC2::Precision scale_presicion = SC2::Precision::Default; 42 | SC2::Precision translation_precision = SC2::Precision::Default; 43 | 44 | // Resource palette 45 | SWFVector strings; 46 | SWFVector rectangles; 47 | 48 | // Exports 49 | RefArray exports_ref_indices; 50 | 51 | // Movieclips 52 | std::vector frame_elements_indices; 53 | RefArray movieclips_ref_indices; 54 | 55 | // Shapes 56 | wk::BufferStream bitmaps_buffer; 57 | RefArray bitmaps_offsets; 58 | 59 | // Textfiels 60 | RefArray textfields_ref_indices; 61 | 62 | // Root 63 | flatbuffers::FlatBufferBuilder builder; 64 | 65 | public: 66 | uint32_t get_string_ref(const SWFString& string); 67 | uint32_t get_rect_ref(const wk::RectF& rectangle); 68 | 69 | private: 70 | template 71 | size_t flush_builder(flatbuffers::Offset root_off) 72 | { 73 | builder.FinishSizePrefixed(root_off); 74 | auto buffer = builder.GetBufferSpan(); 75 | size_t buffer_size = buffer.size_bytes(); 76 | swf.stream.write(buffer.data(), buffer_size); 77 | 78 | builder.Clear(); 79 | 80 | return buffer_size; 81 | } 82 | 83 | void save_header(); 84 | void save_exports(); 85 | void save_textFields(); 86 | void save_shapes(); 87 | void save_movieClips(); 88 | void save_modifiers(); 89 | uint32_t save_textures(); 90 | 91 | flatbuffers::Offset create_texture(const SWFTexture& texture, uint32_t index, bool is_lowres); 92 | 93 | public: 94 | void gather_resources(); 95 | void save_buffer(); 96 | void save_descriptor(wk::Stream& stream); 97 | 98 | private: 99 | const SupercellSWF& swf; 100 | 101 | uint32_t header_offset = 0; 102 | uint32_t data_offset = 0; 103 | uint32_t textures_length = 0; 104 | }; 105 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/transform/ColorTransform.cpp: -------------------------------------------------------------------------------- 1 | #include "ColorTransform.h" 2 | 3 | #include "flash/objects/SupercellSWF.h" 4 | 5 | namespace sc 6 | { 7 | namespace flash { 8 | void ColorTransform::load(SupercellSWF& swf) 9 | { 10 | add.r = swf.stream.read_unsigned_byte(); 11 | add.g = swf.stream.read_unsigned_byte(); 12 | add.b = swf.stream.read_unsigned_byte(); 13 | 14 | alpha = swf.stream.read_unsigned_byte(); 15 | 16 | multiply.r = swf.stream.read_unsigned_byte(); 17 | multiply.g = swf.stream.read_unsigned_byte(); 18 | multiply.b = swf.stream.read_unsigned_byte(); 19 | } 20 | 21 | void ColorTransform::save(SupercellSWF& swf) const 22 | { 23 | swf.stream.write_unsigned_byte(add.r); 24 | swf.stream.write_unsigned_byte(add.g); 25 | swf.stream.write_unsigned_byte(add.b); 26 | 27 | swf.stream.write_unsigned_byte(alpha); 28 | 29 | swf.stream.write_unsigned_byte(multiply.r); 30 | swf.stream.write_unsigned_byte(multiply.g); 31 | swf.stream.write_unsigned_byte(multiply.b); 32 | } 33 | 34 | bool ColorTransform::operator==(const ColorTransform& color) const 35 | { 36 | if (color.alpha == alpha && 37 | color.add.r == add.r && 38 | color.add.g == add.g && 39 | color.add.b == add.b && 40 | color.multiply.r == multiply.r && 41 | color.multiply.g == multiply.g && 42 | color.multiply.b == multiply.b) { 43 | return true; 44 | } 45 | 46 | return false; 47 | } 48 | 49 | uint8_t ColorTransform::tag(SupercellSWF&) const 50 | { 51 | return TAG_COLOR_TRANSFORM; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/transform/ColorTransform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace sc 6 | { 7 | namespace flash { 8 | class SupercellSWF; 9 | 10 | struct ColorTransform 11 | { 12 | public: 13 | ColorTransform() {}; 14 | 15 | public: 16 | uint8_t alpha = 255; 17 | 18 | wk::ColorRGB add{ 0, 0, 0 }; 19 | wk::ColorRGB multiply{ 255, 255, 255 }; 20 | 21 | public: 22 | void load(SupercellSWF& swf); 23 | void save(SupercellSWF& swf) const; 24 | 25 | uint8_t tag(SupercellSWF& swf) const; 26 | 27 | public: 28 | bool operator==(const ColorTransform& color) const; 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/transform/Matrix2D.cpp: -------------------------------------------------------------------------------- 1 | #include "Matrix2D.h" 2 | 3 | #include "flash/objects/SupercellSWF.h" 4 | 5 | #define floatEqual(a,b) (fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * 0.001)) 6 | 7 | namespace sc { 8 | namespace flash { 9 | void Matrix2D::load(SupercellSWF& swf, uint8_t tag) 10 | { 11 | float divider = tag == TAG_MATRIX_2x3 ? 1024.0f : 65535.0f; 12 | 13 | a = float(swf.stream.read_int() / divider); 14 | b = float(swf.stream.read_int() / divider); 15 | c = float(swf.stream.read_int() / divider); 16 | d = float(swf.stream.read_int() / divider); 17 | 18 | tx = swf.stream.read_twip(); 19 | ty = swf.stream.read_twip(); 20 | } 21 | 22 | void Matrix2D::save(SupercellSWF& swf) const 23 | { 24 | float multiplier = swf.use_precision_matrix ? 65535.0f : 1024.0f; 25 | 26 | swf.stream.write_int((int)(a * multiplier)); 27 | swf.stream.write_int((int)(b * multiplier)); 28 | swf.stream.write_int((int)(c * multiplier)); 29 | swf.stream.write_int((int)(d * multiplier)); 30 | 31 | swf.stream.write_twip(tx); 32 | swf.stream.write_twip(ty); 33 | } 34 | 35 | bool Matrix2D::operator==(const Matrix2D& matrix) const 36 | { 37 | if (floatEqual(a, matrix.a) && 38 | floatEqual(b, matrix.b) && 39 | floatEqual(c, matrix.c) && 40 | floatEqual(d, matrix.d) && 41 | floatEqual(tx, matrix.tx) && 42 | floatEqual(ty, matrix.ty)) { 43 | return true; 44 | } 45 | 46 | return false; 47 | } 48 | 49 | uint8_t Matrix2D::tag(SupercellSWF& swf) const 50 | { 51 | return swf.use_precision_matrix ? TAG_MATRIX_2x3_2 : TAG_MATRIX_2x3; 52 | }; 53 | } 54 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/transform/Matrix2D.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace sc 6 | { 7 | namespace flash { 8 | class SupercellSWF; 9 | 10 | struct Matrix2D : public wk::Matrix2D 11 | { 12 | public: 13 | Matrix2D() {}; 14 | 15 | public: 16 | void load(SupercellSWF& swf, uint8_t tag); 17 | void save(SupercellSWF& swf) const; 18 | 19 | uint8_t tag(SupercellSWF& swf) const; 20 | 21 | public: 22 | bool operator==(const Matrix2D& matrix) const; 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/transform/MatrixBank.cpp: -------------------------------------------------------------------------------- 1 | #include "MatrixBank.h" 2 | #include "flash/objects/SupercellSWF.h" 3 | #include "flash/objects/SupercellSWF2.h" 4 | 5 | #include 6 | 7 | #define floatEqual(a,b) (fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * 0.001)) 8 | 9 | namespace sc { 10 | namespace flash { 11 | MatrixBank::MatrixBank(uint16_t matrix_count, uint16_t color_transforms_count) 12 | { 13 | matrices.resize(matrix_count); 14 | color_transforms.resize(color_transforms_count); 15 | } 16 | 17 | bool MatrixBank::get_matrix_index(const Matrix2D& matrix, uint16_t& index) const 18 | { 19 | if (floatEqual(matrix.a, 1.0f) && 20 | floatEqual(matrix.b, 0) && 21 | floatEqual(matrix.c, 0) && 22 | floatEqual(matrix.d, 1.0f) && 23 | floatEqual(matrix.tx, 0) && 24 | floatEqual(matrix.ty, 0)) { 25 | index = 0xFFFF; 26 | return true; 27 | } 28 | 29 | auto result = std::find(std::execution::par_unseq, matrices.begin(), matrices.end(), matrix); 30 | if (result == matrices.end()) 31 | { 32 | return false; 33 | } 34 | else 35 | { 36 | index = (uint16_t)std::distance(matrices.begin(), result); 37 | return true; 38 | } 39 | }; 40 | 41 | bool MatrixBank::get_colorTransform_index(const ColorTransform& color, uint16_t& index) const 42 | { 43 | if (color.alpha == 0xFF && 44 | color.add.r == 0 && 45 | color.add.g == 0 && 46 | color.add.b == 0 && 47 | color.multiply.r == 0xFF && 48 | color.multiply.g == 0xFF && 49 | color.multiply.b == 0xFF) { 50 | index = 0xFFFF; 51 | return true; 52 | } 53 | 54 | auto result = std::find(std::execution::par_unseq, color_transforms.begin(), color_transforms.end(), color); 55 | if (result == color_transforms.end()) 56 | { 57 | return false; 58 | } 59 | else 60 | { 61 | index = (uint16_t)std::distance(color_transforms.begin(), result); 62 | return true; 63 | } 64 | } 65 | 66 | uint8_t MatrixBank::tag(SupercellSWF&) const 67 | { 68 | return TAG_MATRIX_BANK; 69 | }; 70 | 71 | void MatrixBank::load(SupercellSWF& swf, const SC2::DataStorage* storage, SC2::Precision scale_presicion, SC2::Precision translation_presicion) 72 | { 73 | auto matrix_banks_vector = storage->matrix_banks(); 74 | // Return if empty 75 | if (!matrix_banks_vector) return; 76 | 77 | float scale_multiplier = SupercellSWF2CompileTable::get_precision_multiplier(scale_presicion); 78 | float translation_multiplier = SupercellSWF2CompileTable::get_precision_multiplier(translation_presicion); 79 | 80 | uint32_t matrix_bank_count = matrix_banks_vector->size(); 81 | swf.matrixBanks.reserve((uint16_t)matrix_bank_count); 82 | 83 | for (auto data : *matrix_banks_vector) 84 | { 85 | MatrixBank& bank = swf.matrixBanks.emplace_back(); 86 | 87 | auto matrices_vector = data->matrices(); 88 | auto colors_vector = data->colors(); 89 | auto half_matrices_vector = data->half_matrices(); 90 | 91 | if (matrices_vector) 92 | { 93 | bank.matrices.reserve((uint16_t)matrices_vector->size()); 94 | 95 | for (auto mdata : *matrices_vector) 96 | { 97 | Matrix2D& matrix = bank.matrices.emplace_back(); 98 | matrix.a = mdata->a(); matrix.b = mdata->b(); matrix.c = mdata->c(); matrix.d = mdata->d(); 99 | matrix.tx = mdata->tx(); matrix.ty = mdata->ty(); 100 | } 101 | 102 | } 103 | else if (half_matrices_vector) 104 | { 105 | swf.sc2_compile_settings.use_half_precision_matrices = true; 106 | bank.matrices.reserve((uint16_t)half_matrices_vector->size()); 107 | 108 | for (auto mdata : *half_matrices_vector) 109 | { 110 | Matrix2D& matrix = bank.matrices.emplace_back(); 111 | matrix.a = (float)mdata->a() / scale_multiplier; 112 | matrix.b = (float)mdata->b() / scale_multiplier; 113 | matrix.c = (float)mdata->c() / scale_multiplier; 114 | matrix.d = (float)mdata->d() / scale_multiplier; 115 | matrix.tx = (float)mdata->tx() / translation_multiplier; 116 | matrix.ty = (float)mdata->ty() / translation_multiplier; 117 | } 118 | } 119 | 120 | if (colors_vector) 121 | { 122 | bank.color_transforms.reserve((uint16_t)colors_vector->size()); 123 | 124 | for (auto cdata : *colors_vector) 125 | { 126 | ColorTransform& color = bank.color_transforms.emplace_back(); 127 | color.add.r = cdata->r_add(); color.add.g = cdata->g_add(); color.add.b = cdata->b_add(); 128 | color.multiply.r = cdata->r_mul(); color.multiply.g = cdata->g_mul(); color.multiply.b = cdata->b_mul(); 129 | color.alpha = cdata->alpha(); 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/transform/MatrixBank.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "flash/flash_tags.h" 4 | #include "Matrix2D.h" 5 | #include "ColorTransform.h" 6 | #include "flash/types/SWFContainer.hpp" 7 | 8 | #include "flash/SC2/DataStorage_generated.h" 9 | #include "flash/SC2/FileDescriptor_generated.h" 10 | 11 | namespace sc 12 | { 13 | namespace flash { 14 | class SupercellSWF; 15 | 16 | struct MatrixBank 17 | { 18 | public: 19 | MatrixBank(uint16_t matrix_count = 0, uint16_t color_transforms_count = 0); 20 | virtual ~MatrixBank() = default; 21 | MatrixBank(const MatrixBank&) = default; 22 | MatrixBank(MatrixBank&&) = default; 23 | MatrixBank& operator=(const MatrixBank&) = default; 24 | MatrixBank& operator=(MatrixBank&&) = default; 25 | 26 | public: 27 | SWFVector matrices; 28 | SWFVector color_transforms; 29 | 30 | public: 31 | bool get_matrix_index(const Matrix2D& matrix, uint16_t& index) const; 32 | bool get_colorTransform_index(const ColorTransform& color, uint16_t& index) const; 33 | 34 | virtual uint8_t tag(SupercellSWF& swf) const; 35 | 36 | public: 37 | static void load(SupercellSWF&, const SC2::DataStorage*, SC2::Precision scale_presicion, SC2::Precision translation_presicion); 38 | }; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /supercell-flash/source/flash/types/SWFContainer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace sc 7 | { 8 | namespace flash 9 | { 10 | template 11 | using SWFVector = std::vector >; 12 | } 13 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/types/SWFStream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "SWFString.hpp" 8 | 9 | namespace sc 10 | { 11 | namespace flash { 12 | using namespace sc::compression::flash; 13 | class SWFStream : public wk::BufferStream 14 | { 15 | public: 16 | SWFStream() {}; 17 | SWFStream(const SWFStream&) {}; 18 | SWFStream& operator=(const SWFStream&) { return *this; }; 19 | 20 | public: 21 | #pragma region File IO 22 | public: 23 | void open_file(const std::filesystem::path& path) 24 | { 25 | clear(); 26 | 27 | wk::InputFileStream file(path); 28 | Decompressor::decompress(file, *this); 29 | 30 | seek(0); 31 | } 32 | 33 | void save_file(const std::filesystem::path& path, Signature signature) 34 | { 35 | wk::OutputFileStream file(path); 36 | 37 | Compressor::Context context; 38 | context.signature = signature; 39 | 40 | seek(0); 41 | Compressor::compress(*this, file, context); 42 | clear(); 43 | } 44 | 45 | void save_sc2_file(const std::filesystem::path& path) 46 | { 47 | wk::OutputFileStream file(path); 48 | 49 | file.write_unsigned_int(5); 50 | file.write_unsigned_int(0); 51 | 52 | seek(0); 53 | 54 | ZstdCompressor::Props props; 55 | props.compression_level = 20; 56 | props.checksum_flag = false; 57 | props.content_size_flag = true; 58 | 59 | ZstdCompressor compressor(props); 60 | compressor.compress(*this, file); 61 | clear(); 62 | } 63 | #pragma endregion 64 | 65 | #pragma region Writing Functions 66 | public: 67 | void inline write_string(const SWFString& string) 68 | { 69 | uint8_t string_size = string.length(); 70 | if (string_size) 71 | { 72 | write_unsigned_byte(string_size); 73 | write(string.data(), string_size); 74 | } 75 | else 76 | { 77 | write_unsigned_byte(0xFF); 78 | } 79 | } 80 | 81 | void inline write_twip(float twip) { 82 | write_int((int)(twip / 0.05f)); 83 | } 84 | 85 | size_t inline write_tag_header(uint8_t tag) 86 | { 87 | write_unsigned_byte(tag); 88 | write_int(-1); 89 | return position(); 90 | } 91 | 92 | void inline write_tag_final(size_t tag_start) 93 | { 94 | int* tag_length = (int*)((uint8_t*)data() + (tag_start - 4)); 95 | *tag_length = static_cast(position() - tag_start); 96 | } 97 | 98 | void inline write_tag_flag(uint8_t tag) 99 | { 100 | write_unsigned_byte(tag); 101 | write_int(0); 102 | } 103 | 104 | #pragma endregion 105 | 106 | #pragma region Reading Functions 107 | public: 108 | void inline read_string(SWFString& string) 109 | { 110 | uint8_t string_size = read_unsigned_byte(); 111 | 112 | if (string_size != 0xFF) 113 | { 114 | string.resize(string_size); 115 | read(string.data(), string_size); 116 | return; 117 | } 118 | 119 | string.resize(0); 120 | } 121 | 122 | float inline read_twip() 123 | { 124 | return (float)read_int() * 0.05f; 125 | } 126 | #pragma endregion 127 | }; 128 | } 129 | } -------------------------------------------------------------------------------- /supercell-flash/source/flash/types/SWFString.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "core/memory/memory.h" 7 | #include "core/memory/ref.h" 8 | #include "core/io/stream.h" 9 | #include "core/hashing/hash_stream.h" 10 | 11 | namespace sc 12 | { 13 | namespace flash 14 | { 15 | class SWFString 16 | { 17 | public: 18 | SWFString() {}; 19 | 20 | SWFString(const char* data, std::optional length = std::nullopt) 21 | { 22 | size_t string_size = 0; 23 | if (length.has_value()) 24 | { 25 | string_size = length.value(); 26 | } 27 | else 28 | { 29 | string_size = strlen(data); 30 | } 31 | 32 | m_length = string_size >= 0xFE ? 0xFE : static_cast(string_size); 33 | if (!m_length) return; 34 | 35 | char* data_ptr = wk::Memory::allocate(m_length + 1); 36 | m_data = wk::Ref(data_ptr); 37 | wk::Memory::copy(data, data_ptr, m_length); 38 | *(data_ptr + m_length) = '\0'; 39 | } 40 | 41 | SWFString(const std::string& string_data) 42 | { 43 | std::string string = string_data; 44 | string.erase( 45 | std::remove_if(string.begin(), string.end(), 46 | [](char value) { 47 | return !SWFString::IsValid(value); 48 | } 49 | ), 50 | string.end() 51 | ); 52 | 53 | m_length = string.length() >= 0xFE ? 0xFE : static_cast(string.length()); 54 | if (!m_length) return; 55 | 56 | char* data_ptr = wk::Memory::allocate(m_length + 1); 57 | m_data = wk::Ref(data_ptr); 58 | 59 | wk::Memory::copy(string.c_str(), data_ptr, m_length + 1); 60 | } 61 | SWFString& operator=(const SWFString&) = default; 62 | 63 | SWFString(const SWFString& string) 64 | { 65 | m_length = string.length(); 66 | if (!m_length) return; 67 | 68 | char* data_ptr = wk::Memory::allocate(m_length + 1); 69 | m_data = wk::Ref(data_ptr); 70 | 71 | wk::Memory::copy(string.data(), data_ptr, m_length + 1); 72 | } 73 | 74 | ~SWFString() 75 | { 76 | clear(); 77 | } 78 | 79 | public: 80 | bool empty() const 81 | { 82 | return m_data == nullptr || m_length == 0; 83 | } 84 | 85 | uint8_t length() const 86 | { 87 | return m_length; 88 | } 89 | 90 | char* data() const 91 | { 92 | return m_data.get(); 93 | } 94 | 95 | std::string string() const 96 | { 97 | if (empty()) return std::string(); 98 | return std::string((const char*)m_data.get(), (const char*)m_data.get() + m_length); 99 | } 100 | 101 | void clear() 102 | { 103 | if (m_data) 104 | { 105 | m_data.reset(); 106 | } 107 | m_length = 0; 108 | } 109 | 110 | int compare(const char* string, size_t len = 0) const 111 | { 112 | uint8_t string_size = static_cast(len); 113 | if (string && string_size == 0) 114 | { 115 | string_size = static_cast(strlen(string)); 116 | } 117 | 118 | if (m_length != string_size) 119 | { 120 | if (m_length > string_size) 121 | { 122 | return 1; 123 | } 124 | else 125 | { 126 | return -1; 127 | } 128 | } 129 | 130 | for (uint16_t i = 0; m_length > i; i++) 131 | { 132 | const char* lsymbol = string + i; 133 | const char* rsymbol = m_data.get() + i; 134 | 135 | if (*lsymbol != *rsymbol) 136 | { 137 | return -1; 138 | } 139 | } 140 | 141 | return 0; 142 | } 143 | 144 | int compare(const std::string& string) const 145 | { 146 | return compare(string.c_str(), string.size()); 147 | } 148 | 149 | void resize(uint8_t new_length, char fill = '\0') 150 | { 151 | if (m_length == new_length) 152 | { 153 | return; 154 | } 155 | 156 | if (new_length >= 0xFF) 157 | { 158 | new_length = 0xFE; 159 | } 160 | 161 | if (new_length == 0) 162 | { 163 | clear(); 164 | return; 165 | } 166 | 167 | char* new_data = wk::Memory::allocate(new_length + 1); 168 | 169 | for (uint8_t i = 0; new_length > i; i++) 170 | { 171 | *(new_data + i) = i >= m_length ? fill : *(m_data.get() + i); 172 | } 173 | *(new_data + new_length) = '\0'; 174 | 175 | clear(); 176 | m_data = wk::Ref(new_data); 177 | m_length = new_length; 178 | } 179 | 180 | bool operator==(const SWFString& other) const 181 | { 182 | return compare(other.data(), other.length()) == 0; 183 | } 184 | 185 | bool operator==(const char* other) const 186 | { 187 | return compare(other, strlen(other)) == 0; 188 | } 189 | 190 | public: 191 | // Function for character filtering 192 | // Since strings in sc in ASCII and are quite sensitive, we need to carefully select only required character range 193 | static bool IsValid(char ch) { 194 | return (ch >= 32 && ch <= 126) || ch == '\n' || ch == '\t'; 195 | } 196 | 197 | private: 198 | wk::Ref m_data = nullptr; 199 | uint8_t m_length = 0; 200 | }; 201 | } 202 | } 203 | 204 | namespace wk::hash 205 | { 206 | template<> 207 | struct Hash_t 208 | { 209 | template 210 | static void update(HashStream& stream, const sc::flash::SWFString& string) 211 | { 212 | if (string.empty()) return; 213 | 214 | stream.update((const uint8_t*)string.data(), string.length()); 215 | } 216 | }; 217 | } -------------------------------------------------------------------------------- /tools/test-tool/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project: supercell-flash test-tool 2 | 3 | # C++ headers and source files 4 | set(SOURCES source/main.cpp) 5 | 6 | set(CLI_TARGET supercell-flash-test-tool) 7 | 8 | add_executable(${CLI_TARGET} ${SOURCES}) 9 | 10 | set_target_properties(${CLI_TARGET} PROPERTIES 11 | FOLDER SupercellSDK/tools 12 | ) 13 | 14 | target_link_libraries(${CLI_TARGET} PRIVATE supercell::flash) 15 | wk_project_setup(${CLI_TARGET}) -------------------------------------------------------------------------------- /tools/test-tool/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "core/console/console.h" 9 | 10 | using namespace std; 11 | using namespace std::chrono; 12 | using namespace sc::flash; 13 | using namespace wk; 14 | 15 | namespace fs = std::filesystem; 16 | 17 | int main(int argc, char* argv[]) 18 | { 19 | wk::ArgumentParser program; 20 | 21 | program 22 | .add_argument("input") 23 | .required(); 24 | 25 | program 26 | .add_argument("version") 27 | .default_value(1) 28 | .scan<'i', int>(); 29 | 30 | program 31 | .add_argument("--half-presicion") 32 | .flag(); 33 | 34 | program 35 | .add_argument("--use-short-frames") 36 | .flag(); 37 | 38 | try 39 | { 40 | program.parse_args(argc, argv); 41 | } 42 | catch (const std::exception& e) 43 | { 44 | std::cout << "Error! " << e.what() << std::endl; 45 | std::cout << program << std::endl; 46 | } 47 | 48 | fs::path filepath = program.get("input"); 49 | if (!fs::exists(filepath)) { 50 | cout << "File not found"; 51 | return 1; 52 | } 53 | 54 | int version = program.get("version"); 55 | 56 | /* Loading test */ 57 | time_point loading_start = high_resolution_clock::now(); 58 | Timer loading; 59 | SupercellSWF swf; 60 | swf.load(filepath); 61 | 62 | cout << "Loading took: "; 63 | cout << loading.elapsed() << "ms" << endl << endl; 64 | loading.reset(); 65 | 66 | /* Save test */ 67 | fs::path folder = filepath.parent_path(); 68 | try { 69 | fs::path dest = folder / filepath.stem().concat("_new").concat(filepath.extension().string()); 70 | switch (version) 71 | { 72 | case 1: 73 | swf.save(dest, Signature::Zstandard); 74 | break; 75 | case 2: 76 | swf.sc2_compile_settings.use_half_precision_matrices = program.get("half-presicion"); 77 | swf.sc2_compile_settings.use_short_frames = program.get("use-short-frames"); 78 | swf.save_sc2(dest); 79 | break; 80 | default: 81 | break; 82 | } 83 | 84 | } 85 | catch (const wk::Exception& err) { 86 | cout << "Error. " << endl << "Message: " << err.what() << endl; 87 | } 88 | 89 | cout << "Saving took: "; 90 | cout << loading.elapsed() << "ms" << endl; 91 | 92 | return 0; 93 | } -------------------------------------------------------------------------------- /tools/texture-tool/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project: supercell-flash texture-tool 2 | 3 | # C++ headers and source files 4 | file(GLOB_RECURSE HEADERS source/*.h) 5 | file(GLOB_RECURSE SOURCES source/*.cpp source/*.hpp) 6 | 7 | set(CLI_TARGET supercell-flash-texture-tool) 8 | 9 | add_executable(${CLI_TARGET} ${HEADERS} ${SOURCES}) 10 | wk_project_setup(${CLI_TARGET}) 11 | wk_include_json() 12 | wk_include_argparse() 13 | 14 | set_target_properties(${CLI_TARGET} PROPERTIES 15 | FOLDER SupercellSDK/tools 16 | ) 17 | 18 | target_include_directories(${CLI_TARGET} PUBLIC source) 19 | 20 | target_link_libraries(${CLI_TARGET} PRIVATE supercell::flash) 21 | 22 | set_target_properties(${CLI_TARGET} PROPERTIES 23 | FOLDER Supercell/CLI 24 | OUTPUT_NAME "SCTex" 25 | ) 26 | -------------------------------------------------------------------------------- /tools/texture-tool/source/SWFFile.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "SpriteData.hpp" 11 | 12 | using namespace sc::flash; 13 | using json = nlohmann::ordered_json; 14 | 15 | namespace sc 16 | { 17 | namespace sctex 18 | { 19 | bool CompareUV(float x, float y, float epsilon = 0.001f) { 20 | if (fabs(x - y) < epsilon) 21 | return true; 22 | return false; 23 | } 24 | 25 | bool CommandEqual(const ShapeDrawBitmapCommand& a, const ShapeDrawBitmapCommand& b) 26 | { 27 | if (a.texture_index != b.texture_index) return false; 28 | if (a.vertices.size() != b.vertices.size()) return false; 29 | 30 | for (uint16_t i = 0; a.vertices.size() > i; i++) 31 | { 32 | const ShapeDrawBitmapCommandVertex& vertex_a = a.vertices[i]; 33 | const ShapeDrawBitmapCommandVertex& vertex_b = b.vertices[i]; 34 | 35 | if (!CompareUV(vertex_a.u, vertex_b.u)) return false; 36 | if (!CompareUV(vertex_a.v, vertex_b.v)) return false; 37 | } 38 | 39 | return true; 40 | } 41 | 42 | void DecodePixel(uint8_t* pixel_data, const wk::Image::PixelDepthInfo& pixel_info, uint8_t& r_channel, uint8_t& g_channel, uint8_t& b_channel, uint8_t& a_channel) 43 | { 44 | uint32_t pixel_buffer = 0; 45 | 46 | wk::Memory::copy( 47 | pixel_data, 48 | (uint8_t*)(&pixel_buffer), 49 | pixel_info.byte_count 50 | ); 51 | 52 | { 53 | uint8_t bit_index = 0; 54 | 55 | if (pixel_info.r_bits != 0xFF) 56 | { 57 | uint64_t r_bits_mask = (uint64_t)pow(2, pixel_info.r_bits) - 1; 58 | r_channel = static_cast(r_bits_mask & pixel_buffer); 59 | 60 | bit_index += pixel_info.r_bits; 61 | } 62 | 63 | if (pixel_info.g_bits != 0xFF) 64 | { 65 | uint64_t g_bits_mask = (uint64_t)pow(2, pixel_info.g_bits) - 1; 66 | g_channel = static_cast(((g_bits_mask << bit_index) & pixel_buffer) >> bit_index); 67 | 68 | bit_index += pixel_info.g_bits; 69 | } 70 | else 71 | { 72 | g_channel = r_channel; 73 | } 74 | 75 | if (pixel_info.b_bits != 0xFF) 76 | { 77 | uint64_t b_bits_mask = (uint64_t)pow(2, pixel_info.b_bits) - 1; 78 | b_channel = static_cast(((b_bits_mask << bit_index) & pixel_buffer) >> bit_index); 79 | 80 | bit_index += pixel_info.b_bits; 81 | } 82 | else 83 | { 84 | b_channel = g_channel; 85 | } 86 | 87 | if (pixel_info.a_bits != 0xFF) 88 | { 89 | uint64_t a_bits_mask = (uint64_t)pow(2, pixel_info.a_bits) - 1; 90 | a_channel = static_cast(((a_bits_mask << bit_index) & pixel_buffer) >> bit_index); 91 | } 92 | else 93 | { 94 | a_channel = 0xFF; 95 | } 96 | } 97 | } 98 | 99 | void EncodePixel(uint8_t* pixel_data, const wk::Image::PixelDepthInfo& pixel_info, const uint8_t r_channel, const uint8_t g_channel, const uint8_t b_channel, const uint8_t a_channel) 100 | { 101 | uint32_t pixel_buffer = 0; 102 | 103 | { 104 | uint8_t bit_index = pixel_info.byte_count * 8; 105 | 106 | if (pixel_info.a_bits != 0xFF) 107 | { 108 | bit_index -= pixel_info.a_bits; 109 | uint64_t bits_mask = (uint64_t)((pow(2, pixel_info.a_bits) - 1)); 110 | pixel_buffer |= ((a_channel & bits_mask)) << bit_index; 111 | } 112 | 113 | if (pixel_info.b_bits != 0xFF) 114 | { 115 | bit_index -= pixel_info.b_bits; 116 | uint64_t bits_mask = (uint64_t)((pow(2, pixel_info.b_bits) - 1)); 117 | pixel_buffer |= ((b_channel & bits_mask)) << bit_index; 118 | } 119 | 120 | if (pixel_info.g_bits != 0xFF) 121 | { 122 | bit_index -= pixel_info.g_bits; 123 | uint64_t bits_mask = (uint64_t)((pow(2, pixel_info.g_bits) - 1)); 124 | pixel_buffer |= ((g_channel & bits_mask)) << bit_index; 125 | } 126 | 127 | if (pixel_info.r_bits != 0xFF) 128 | { 129 | bit_index -= pixel_info.r_bits; 130 | uint64_t bits_mask = (uint64_t)((pow(2, pixel_info.r_bits) - 1)); 131 | pixel_buffer |= ((r_channel & bits_mask)) << bit_index; 132 | } 133 | } 134 | 135 | wk::Memory::copy( 136 | (uint8_t*)&pixel_buffer, 137 | pixel_data, 138 | pixel_info.byte_count 139 | ); 140 | } 141 | 142 | void PremultiplyToStraight(wk::RawImage& image) 143 | { 144 | uint64_t pixel_count = image.width() * image.height(); 145 | 146 | const wk::Image::PixelDepthInfo& pixel_info = wk::Image::PixelDepthTable[(uint16_t)image.depth()]; 147 | 148 | for (uint64_t pixel_index = 0; pixel_count > pixel_index; pixel_index++) 149 | { 150 | uint8_t* pixel_data = image.data() + (pixel_index * pixel_info.byte_count); 151 | 152 | uint8_t r_channel = 0; 153 | uint8_t g_channel = 0; 154 | uint8_t b_channel = 0; 155 | uint8_t a_channel = 0; 156 | 157 | DecodePixel(pixel_data, pixel_info, r_channel, g_channel, b_channel, a_channel); 158 | 159 | if (a_channel == 0) continue; 160 | 161 | // PreAlpha to Straight 162 | float factor = ((float)a_channel / 255.f); 163 | r_channel = (uint8_t)(std::clamp(r_channel / factor, 0.f, 255.f)); 164 | g_channel = (uint8_t)(std::clamp(g_channel / factor, 0.f, 255.f)); 165 | b_channel = (uint8_t)(std::clamp(b_channel / factor, 0.f, 255.f)); 166 | 167 | EncodePixel(pixel_data, pixel_info, r_channel, g_channel, b_channel, a_channel); 168 | } 169 | } 170 | 171 | void StraightToPremultiply(wk::RawImage& image) 172 | { 173 | uint64_t pixel_count = image.width() * image.height(); 174 | 175 | const wk::Image::PixelDepthInfo& pixel_info = wk::Image::PixelDepthTable[(uint16_t)image.depth()]; 176 | 177 | for (uint64_t pixel_index = 0; pixel_count > pixel_index; pixel_index++) 178 | { 179 | uint8_t* pixel_data = image.data() + (pixel_index * pixel_info.byte_count); 180 | 181 | uint8_t r_channel = 0; 182 | uint8_t g_channel = 0; 183 | uint8_t b_channel = 0; 184 | uint8_t a_channel = 0; 185 | 186 | DecodePixel(pixel_data, pixel_info, r_channel, g_channel, b_channel, a_channel); 187 | 188 | if (a_channel == 0) 189 | { 190 | r_channel = 0; 191 | g_channel = 0; 192 | b_channel = 0; 193 | } 194 | else 195 | { 196 | // Straight to PreAlpha 197 | float factor = ((float)a_channel / 255.f); 198 | r_channel = (uint8_t)((float)r_channel * factor); 199 | g_channel = (uint8_t)((float)g_channel * factor); 200 | b_channel = (uint8_t)((float)b_channel * factor); 201 | } 202 | 203 | EncodePixel(pixel_data, pixel_info, r_channel, g_channel, b_channel, a_channel); 204 | } 205 | } 206 | } 207 | } 208 | 209 | namespace sc 210 | { 211 | namespace sctex 212 | { 213 | class SWFFile : public SupercellSWF 214 | { 215 | public: 216 | SWFFile() {}; 217 | 218 | SWFFile(std::filesystem::path path, bool load_all = false) 219 | { 220 | current_file = path; 221 | 222 | wk::InputFileStream file(path); 223 | if (SupercellSWF::IsSC2(file)) 224 | { 225 | std::cout << "File is loaded as a SC2" << std::endl; 226 | load_sc2(file); 227 | } 228 | else 229 | { 230 | load_sc1(path, load_all); 231 | } 232 | } 233 | 234 | public: 235 | // If the file is a real texture, only the texture tags are loaded, otherwise the entire file is loaded. 236 | void load_sc1(std::filesystem::path path, bool load_all = false) 237 | { 238 | stream.open_file(path); 239 | 240 | // Path Check 241 | if (path.string().find("_tex") != std::string::npos) 242 | { 243 | load_texures_from_binary(); 244 | 245 | return; 246 | } 247 | 248 | // First tag check 249 | { 250 | uint8_t tag = stream.read_unsigned_byte(); 251 | int32_t tag_length = stream.read_int(); 252 | 253 | switch (tag) 254 | { 255 | case TAG_TEXTURE: 256 | case TAG_TEXTURE_2: 257 | case TAG_TEXTURE_3: 258 | case TAG_TEXTURE_4: 259 | case TAG_TEXTURE_5: 260 | case TAG_TEXTURE_6: 261 | case TAG_TEXTURE_7: 262 | case TAG_TEXTURE_8: 263 | case TAG_TEXTURE_9: 264 | if (tag_length <= 0) break; 265 | 266 | std::cout << "File is loaded as a texture file because the first tag is a texture" << std::endl; 267 | 268 | stream.seek(0); 269 | load_texures_from_binary(); 270 | 271 | return; 272 | default: 273 | stream.seek(0); 274 | break; 275 | } 276 | } 277 | 278 | // Whole file loading 279 | { 280 | std::cout << "File is loaded as a default asset file because it did not pass all texture checks" << std::endl; 281 | 282 | if (load_all) 283 | { 284 | stream.clear(); 285 | load(path); 286 | } 287 | else 288 | { 289 | // Skip of all count fields 290 | stream.seek(17); 291 | 292 | // export names skip 293 | uint16_t exports_count = stream.read_unsigned_short(); 294 | 295 | for (uint16_t i = 0; exports_count > i; i++) 296 | { 297 | stream.read_unsigned_short(); 298 | } 299 | 300 | for (uint16_t i = 0; exports_count > i; i++) 301 | { 302 | uint8_t length = stream.read_unsigned_byte(); 303 | stream.seek(length, wk::Stream::SeekMode::Add); 304 | } 305 | 306 | load_texures_from_binary(); 307 | } 308 | } 309 | } 310 | 311 | void load_texures_from_binary() 312 | { 313 | while (true) 314 | { 315 | uint8_t tag = stream.read_unsigned_byte(); 316 | int32_t tag_length = stream.read_int(); 317 | 318 | if (tag == TAG_END) 319 | break; 320 | 321 | if (tag_length < 0) 322 | throw wk::Exception("Negative tag length"); 323 | 324 | switch (tag) 325 | { 326 | case TAG_TEXTURE: 327 | case TAG_TEXTURE_2: 328 | case TAG_TEXTURE_3: 329 | case TAG_TEXTURE_4: 330 | case TAG_TEXTURE_5: 331 | case TAG_TEXTURE_6: 332 | case TAG_TEXTURE_7: 333 | case TAG_TEXTURE_8: 334 | case TAG_TEXTURE_9: 335 | case TAG_TEXTURE_10: 336 | textures.emplace_back().load(*this, tag, true); 337 | break; 338 | 339 | default: 340 | stream.seek(tag_length, wk::Stream::SeekMode::Add); 341 | break; 342 | } 343 | } 344 | } 345 | 346 | void save_sprites_to_folder(std::filesystem::path output_path) 347 | { 348 | SpriteData data; 349 | std::vector commands; 350 | 351 | for (SWFTexture& texture : textures) 352 | { 353 | data.textures.emplace_back(texture.image()->width(), texture.image()->height()); 354 | } 355 | 356 | for (Shape& shape : shapes) 357 | { 358 | for (ShapeDrawBitmapCommand& command : shape.commands) 359 | { 360 | auto commnand_it = std::find_if(commands.begin(), commands.end(), [&command](const ShapeDrawBitmapCommand& current) {return CommandEqual(command, current); }); 361 | if (commands.size() == 0 || commnand_it == commands.end()) 362 | { 363 | auto& sprite = data.sprites.emplace_back(); 364 | sprite.name = std::string("sprite_") + std::to_string(commands.size()); 365 | sprite.texture_index = command.texture_index; 366 | auto image = textures[command.texture_index].image(); 367 | for (auto& vertex : command.vertices) 368 | { 369 | sprite.vertices.emplace_back((uint16_t)(vertex.u * image->width()), (uint16_t)(vertex.v * image->height())); 370 | } 371 | 372 | commands.push_back(command); 373 | } 374 | } 375 | } 376 | 377 | std::filesystem::path basename = output_path.stem(); 378 | std::filesystem::path data_file = output_path / fs::path(basename).concat(".sctex"); 379 | 380 | data.Save(data_file); 381 | 382 | return; 383 | } 384 | 385 | void save_textures_to_folder(std::filesystem::path output_path) 386 | { 387 | json texture_infos = json::array(); 388 | 389 | for (uint16_t i = 0; textures.size() > i; i++) 390 | { 391 | SWFTexture& texture = textures[i]; 392 | 393 | // Texture Info 394 | { 395 | std::string encoding; 396 | { 397 | switch (texture.encoding()) 398 | { 399 | case SWFTexture::TextureEncoding::KhronosTexture: 400 | encoding = "khronos"; 401 | break; 402 | case SWFTexture::TextureEncoding::Raw: 403 | encoding = "raw"; 404 | break; 405 | default: 406 | break; 407 | } 408 | } 409 | 410 | std::string pixel_type = "RGBA8"; 411 | 412 | switch (texture.pixel_format()) 413 | { 414 | case SWFTexture::PixelFormat::RGBA4: 415 | pixel_type = "RGBA4"; 416 | break; 417 | case SWFTexture::PixelFormat::RGB5_A1: 418 | pixel_type = "RGB5_A1"; 419 | break; 420 | case SWFTexture::PixelFormat::RGB565: 421 | pixel_type = "RGB565"; 422 | break; 423 | case SWFTexture::PixelFormat::LUMINANCE8_ALPHA8: 424 | pixel_type = "LUMINANCE8_ALPHA8"; 425 | break; 426 | case SWFTexture::PixelFormat::LUMINANCE8: 427 | pixel_type = "LUMINANCE8"; 428 | break; 429 | 430 | case SWFTexture::PixelFormat::RGBA8: 431 | default: 432 | break; 433 | } 434 | 435 | std::string filtering = "LINEAR_NEAREST"; 436 | 437 | switch (texture.filtering) 438 | { 439 | case SWFTexture::Filter::LINEAR_MIPMAP_NEAREST: 440 | filtering = "LINEAR_MIPMAP_NEAREST"; 441 | break; 442 | case SWFTexture::Filter::NEAREST_NEAREST: 443 | filtering = "NEAREST_NEAREST"; 444 | break; 445 | case SWFTexture::Filter::LINEAR_NEAREST: 446 | default: 447 | break; 448 | } 449 | 450 | json texture_info = { 451 | {"Encoding", encoding}, 452 | {"PixelFormat", pixel_type}, 453 | {"Filtering", filtering}, 454 | {"Linear", texture.linear()}, 455 | }; 456 | 457 | texture_infos.push_back(texture_info); 458 | } 459 | 460 | // Texture Image 461 | std::filesystem::path basename = output_path.stem(); 462 | std::filesystem::path output_image_path = output_path / basename.concat("_").concat(std::to_string(i)).concat(".png"); 463 | wk::OutputFileStream output_image(output_image_path); 464 | 465 | wk::stb::ImageFormat format = wk::stb::ImageFormat::PNG; 466 | 467 | switch (texture.encoding()) 468 | { 469 | case SWFTexture::TextureEncoding::KhronosTexture: 470 | { 471 | wk::RawImage image( 472 | texture.image()->width(), texture.image()->height(), 473 | texture.image()->depth() 474 | ); 475 | 476 | wk::SharedMemoryStream image_data(image.data(), image.data_length()); 477 | ((sc::texture::KhronosTexture*)(texture.image().get()))->decompress_data(image_data); 478 | 479 | PremultiplyToStraight(image); 480 | wk::stb::write_image(image, format, output_image); 481 | } 482 | break; 483 | 484 | case SWFTexture::TextureEncoding::Raw: 485 | { 486 | texture.linear(true); 487 | 488 | wk::RawImage& image = *(wk::RawImage*)(texture.image().get()); 489 | PremultiplyToStraight(image); 490 | wk::stb::write_image( 491 | image, 492 | format, 493 | output_image 494 | ); 495 | } 496 | break; 497 | 498 | default: 499 | break; 500 | } 501 | 502 | std::cout << "Decoded texture: " << output_image_path << std::endl; 503 | } 504 | 505 | std::string serialized_data = texture_infos.dump(4); 506 | 507 | wk::OutputFileStream file_info(output_path / output_path.stem().concat(".json")); 508 | file_info.write(serialized_data.data(), serialized_data.size()); 509 | } 510 | 511 | void load_textures_from_folder(std::filesystem::path input) 512 | { 513 | current_file = input; 514 | std::filesystem::path basename = input.stem(); 515 | std::filesystem::path texture_info_path = std::filesystem::path(input / basename.concat(".json")); 516 | 517 | // Texture Info Parsing 518 | json texture_infos = json::array({}); 519 | 520 | if (!std::filesystem::exists(texture_info_path)) 521 | { 522 | std::cout << "Texture info file does not exist. Default settings will be used instead" << std::endl; 523 | } 524 | else 525 | { 526 | std::ifstream file(texture_info_path); 527 | texture_infos = json::parse(file); 528 | } 529 | 530 | // Texture Images path gather 531 | SWFVector texture_images_paths; 532 | 533 | for (auto const& file_descriptor : std::filesystem::directory_iterator(input)) 534 | { 535 | std::filesystem::path filepath = file_descriptor.path(); 536 | std::filesystem::path file_extension = filepath.extension(); 537 | 538 | if (file_extension == ".png") 539 | { 540 | texture_images_paths.push_back(filepath); 541 | } 542 | } 543 | 544 | //Texture Converting 545 | for (uint16_t i = 0; texture_images_paths.size() > i; i++) 546 | { 547 | // Image Loading 548 | wk::Ref image; 549 | wk::InputFileStream image_file(texture_images_paths[i]); 550 | wk::stb::load_image(image_file, image); 551 | StraightToPremultiply(*image); 552 | 553 | wk::SharedMemoryStream image_data(image->data(), image->data_length()); 554 | 555 | // Image Converting 556 | SWFTexture texture; 557 | texture.load_from_image(*image); 558 | 559 | if (texture_infos.size() > i) 560 | { 561 | json texture_info = texture_infos[i]; 562 | 563 | SWFTexture::PixelFormat texture_type = SWFTexture::PixelFormat::RGBA8; 564 | 565 | if (texture_info["PixelFormat"] == "RGBA4") 566 | { 567 | texture_type = SWFTexture::PixelFormat::RGBA4; 568 | } 569 | else if (texture_info["PixelFormat"] == "RGB5_A1") 570 | { 571 | texture_type = SWFTexture::PixelFormat::RGB5_A1; 572 | } 573 | else if (texture_info["PixelFormat"] == "RGB565") 574 | { 575 | texture_type = SWFTexture::PixelFormat::RGB565; 576 | } 577 | else if (texture_info["PixelFormat"] == "LUMINANCE8_ALPHA8") 578 | { 579 | texture_type = SWFTexture::PixelFormat::LUMINANCE8_ALPHA8; 580 | } 581 | else if (texture_info["PixelFormat"] == "LUMINANCE8") 582 | { 583 | texture_type = SWFTexture::PixelFormat::LUMINANCE8; 584 | } 585 | 586 | if (texture_info["Filtering"] == "LINEAR_NEAREST") 587 | { 588 | texture.filtering = SWFTexture::Filter::LINEAR_NEAREST; 589 | } 590 | else if (texture_info["Filtering"] == "LINEAR_MIPMAP_NEAREST") 591 | { 592 | texture.filtering = SWFTexture::Filter::LINEAR_MIPMAP_NEAREST; 593 | } 594 | else if (texture_info["Filtering"] == "NEAREST_NEAREST") 595 | { 596 | texture.filtering = SWFTexture::Filter::NEAREST_NEAREST; 597 | } 598 | 599 | if (texture_info["Encoding"] == "khronos") 600 | { 601 | texture.encoding(SWFTexture::TextureEncoding::KhronosTexture); 602 | } 603 | else if (texture_info["Encoding"] == "raw") 604 | { 605 | texture.encoding(SWFTexture::TextureEncoding::Raw); 606 | texture.pixel_format(texture_type); 607 | } 608 | } 609 | 610 | // Texture Insert 611 | if (textures.size() <= i) 612 | { 613 | textures.push_back(texture); 614 | } 615 | else 616 | { 617 | textures[i] = texture; 618 | } 619 | 620 | std::cout << "Processed texture: " << texture_images_paths[i] << std::endl; 621 | } 622 | } 623 | }; 624 | } 625 | } -------------------------------------------------------------------------------- /tools/texture-tool/source/SpriteData.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace sc::flash; 7 | 8 | namespace sc 9 | { 10 | namespace sctex 11 | { 12 | class SpriteData 13 | { 14 | public: 15 | class TextureInstance 16 | { 17 | public: 18 | uint16_t width; 19 | uint16_t height; 20 | public: 21 | TextureInstance(uint16_t width, uint16_t height) : width(width), height(height) {} 22 | }; 23 | 24 | class SpriteInstance 25 | { 26 | public: 27 | SWFString name; 28 | uint8_t texture_index; 29 | SWFVector> vertices; 30 | }; 31 | 32 | private: 33 | SWFStream m_stream; 34 | 35 | public: 36 | SWFVector textures; 37 | SWFVector sprites; 38 | 39 | public: 40 | void Save(std::filesystem::path path) 41 | { 42 | m_stream.clear(); 43 | 44 | WriteV1(); 45 | m_stream.save_file(path, sc::flash::Signature::Zstandard); 46 | 47 | m_stream.clear(); 48 | } 49 | 50 | void Load(std::filesystem::path path) 51 | { 52 | m_stream.clear(); 53 | m_stream.open_file(path); 54 | 55 | uint8_t version = m_stream.read_unsigned_byte(); 56 | switch (version) 57 | { 58 | case 1: 59 | ReadV1(); 60 | break; 61 | default: 62 | break; 63 | } 64 | 65 | m_stream.clear(); 66 | } 67 | private: 68 | void WriteV1() 69 | { 70 | // Version 71 | m_stream.write_byte(1); 72 | 73 | // Count 74 | m_stream.write_unsigned_short(textures.size()); 75 | m_stream.write_unsigned_short(sprites.size()); 76 | 77 | // Data 78 | for (TextureInstance& texture : textures) 79 | { 80 | m_stream.write_unsigned_short(texture.width); 81 | m_stream.write_unsigned_short(texture.height); 82 | } 83 | 84 | for (SpriteInstance& sprite : sprites) 85 | { 86 | m_stream.write_string(sprite.name); 87 | m_stream.write_unsigned_byte(sprite.texture_index); 88 | m_stream.write_unsigned_short(sprite.vertices.size()); 89 | 90 | for (auto& vertex : sprite.vertices) 91 | { 92 | m_stream.write_unsigned_short(vertex.u); 93 | m_stream.write_unsigned_short(vertex.v); 94 | } 95 | } 96 | } 97 | 98 | void ReadV1() 99 | { 100 | uint16_t texture_count = m_stream.read_unsigned_short(); 101 | uint32_t commands_count = m_stream.read_unsigned_short(); 102 | 103 | for (uint16_t i = 0; texture_count > i; i++) 104 | { 105 | textures.emplace_back(m_stream.read_unsigned_short(), m_stream.read_unsigned_short()); 106 | } 107 | 108 | for (uint32_t i = 0; commands_count > i; i++) 109 | { 110 | SpriteInstance& instance = sprites.emplace_back(); 111 | 112 | m_stream.read_string(instance.name); 113 | instance.texture_index = m_stream.read_unsigned_byte(); 114 | uint16_t vertices_count = m_stream.read_unsigned_byte(); 115 | instance.vertices.reserve(vertices_count); 116 | 117 | for (uint16_t v = 0; vertices_count > v; v++) 118 | { 119 | auto& vertex = instance.vertices.emplace_back(); 120 | 121 | vertex.u = m_stream.read_unsigned_short(); 122 | vertex.v = m_stream.read_unsigned_short(); 123 | } 124 | } 125 | } 126 | }; 127 | } 128 | } -------------------------------------------------------------------------------- /tools/texture-tool/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include "core/time/timer.h" 2 | #include "SWFFile.hpp" 3 | #include 4 | #include 5 | #include 6 | #include "core/console/console.h" 7 | 8 | using namespace std; 9 | using namespace std::chrono; 10 | using namespace sc::sctex; 11 | using namespace wk; 12 | 13 | namespace fs = std::filesystem; 14 | 15 | #define print(text) std::cout << text << std::endl; 16 | 17 | int main(int argc, char* argv[]) 18 | { 19 | wk::ArgumentParser parser("SC Flash Texture Tool"); 20 | 21 | parser 22 | .add_argument("mode") 23 | .choices("decode", "encode") 24 | .required(); 25 | 26 | parser 27 | .add_argument("input") 28 | .help("input _tex.sc file or folder") 29 | .required(); 30 | 31 | parser 32 | .add_argument("external") 33 | .default_value("") 34 | .help("_dl.sc or sc2 file for encode"); 35 | 36 | parser 37 | .add_argument("-t", "--type") 38 | .choices("sc1", "sc2") 39 | .default_value("sc1") 40 | .help("_dl.sc or sc2 file for encode"); 41 | 42 | try 43 | { 44 | parser.parse_args(argc, argv); 45 | } 46 | catch (const std::exception& e) 47 | { 48 | std::cout << parser << std::endl; 49 | } 50 | 51 | fs::path input_path = fs::path(parser.get("input")); 52 | if (!fs::exists(input_path)) { 53 | cout << "Path is inncorrect or does not exist"; 54 | return 1; 55 | } 56 | 57 | bool use_external_files = false; 58 | bool use_sprites = false; 59 | bool save_to_sc1 = parser.get("type") == "sc1"; 60 | std::string operation = parser.get("mode"); 61 | fs::path basename = input_path.stem(); 62 | 63 | Timer operation_timer; 64 | try 65 | { 66 | if (operation == "decode") 67 | { 68 | if (fs::is_directory(input_path)) 69 | { 70 | print("Input path is a directory! Failed to load file"); 71 | return 1; 72 | } 73 | 74 | SWFFile file(input_path, use_sprites); 75 | 76 | fs::path output_directory = input_path.replace_extension(); 77 | if (!fs::is_directory(output_directory)) 78 | { 79 | fs::create_directory(output_directory); 80 | } 81 | 82 | if (use_sprites) 83 | { 84 | file.save_sprites_to_folder(output_directory); 85 | } 86 | else 87 | { 88 | file.save_textures_to_folder(output_directory); 89 | } 90 | } 91 | else if (operation == "encode") 92 | { 93 | fs::path dl_file_path; 94 | if (argc >= 4) 95 | { 96 | dl_file_path = argv[3]; 97 | } 98 | 99 | if (!fs::is_directory(input_path)) 100 | { 101 | print("Input path is not a directory! Failed to load file content"); 102 | return 1; 103 | } 104 | 105 | SWFFile file; 106 | file.compress_external_textures = use_external_files; 107 | bool is_dl_file = !dl_file_path.empty() && fs::exists(dl_file_path); 108 | if (is_dl_file) 109 | { 110 | file.load(dl_file_path); 111 | } 112 | 113 | file.load_textures_from_folder(input_path); 114 | fs::path output_path = fs::path(input_path.parent_path() / fs::path(basename.concat(".sc"))); 115 | if (is_dl_file) 116 | { 117 | if (save_to_sc1) 118 | { 119 | file.save(output_path, Signature::Zstandard); 120 | } 121 | else 122 | { 123 | file.save_sc2(output_path); 124 | } 125 | 126 | } 127 | else 128 | { 129 | file.save_internal(true, false); 130 | file.stream.save_file(output_path, Signature::Zstandard); 131 | file.stream.clear(); 132 | } 133 | } 134 | 135 | cout << "Operation took: " << operation_timer.elapsed() / 60000 << " seconds"; 136 | } 137 | catch (wk::Exception& error) 138 | { 139 | std::cout << error.what() << std::endl; 140 | return 1; 141 | } 142 | 143 | return 0; 144 | } --------------------------------------------------------------------------------