1 /// Franks PC Shapes (Textures) 2 /// 3 /// See_Also: $(UL 4 /// $(LI <a href="https://www.wiki.sc4devotion.com/index.php?title=FSH">Franks PC Shapes</a> (SC4D Encyclopedia)) 5 /// $(LI <a href="https://www.wiki.sc4devotion.com/index.php?title=FSH_Format">FSH Format</a> (SC4D Encyclopedia)) 6 /// ) 7 /// 8 /// Authors: Chance Snow 9 /// Copyright: Copyright © 2024 Chance Snow. All rights reserved. 10 /// License: MIT License 11 module dbpf.files.fsh; 12 13 import std.conv : to; 14 import std.exception : enforce; 15 import std.string : representation; 16 17 /// 18 enum DirectoryId { 19 /// Building textures 20 building = "G354".representation, 21 /// Network textures, Sim textures, Sim heads, Sim animations, Trees, props, Base textures, Misc. colors 22 generic = "G264".representation, 23 /// 3D Animation textures (e.g. the green rotating diamond in `loteditor.dat`) 24 _3dAnimation = "G266".representation, 25 /// Dispatch marker textures 26 dispatch = "G290".representation, 27 /// Small Sim texture, Network Transport Model textures (trains, etc.) 28 simThumbOrNetworkModel = "G315".representation, 29 /// UI Editor textures 30 ui = "GIMX".representation, 31 /// BAT generator texture maps 32 bat = "G344".representation, 33 } 34 35 /// 36 struct Header { 37 /// Always `SHPI`. 38 static const identifier = "SHPI".representation; 39 align(1): 40 /// Always `SHPI`. 41 ubyte[4] magic = identifier.to!(ubyte[4]); 42 /// 43 uint size; 44 /// 45 uint entryCount; 46 /// See_Also: `DirectoryId` 47 uint directoryId; 48 } 49 50 static assert(Header.alignof == 1); 51 static assert(Header.sizeof == 16); 52 53 /// 54 enum EntryName { 55 /// Global palette for 8-bit Indexed Bitmaps. 56 palette = "!pal".representation, 57 /// Buildings, props, network intersections, and terrain textures. 58 zero = "0000".representation, 59 /// Always used for a rail texture, whereas for street/road intersections it's always by instance. 60 rail = "rail".representation, 61 /// First sprite animation entry in a directory. 62 tb2 = "TB2".representation, 63 /// Any sprite animation entries in a directory after TB2. 64 tb3 = "TB3".representation, 65 } 66 67 /// 68 struct Directory { 69 import dbpf.types : str; 70 align(1): 71 /// Remarks: 72 /// When searching for a global palette for 8-bit bitmaps, the directory entry name for the global palette will 73 /// always '!pal'. Once the '!pal' directory entry has been found, the global palette can be extracted and used for 74 /// any bitmaps that use 8-bit indexed color. If no global palette is found, FSH decoders should look for a local 75 /// palette directly following the indexed bitmap. If no palette is found, then no palette will be created or 76 /// associated with the bitmap. 77 /// 78 /// Most tools, like FSHTool, simply ignore missing palettes and save the bitmap with an empty palette with all 79 /// indices set to black. 80 /// See_Also: `EntryName` 81 str!4 entryName; 82 /// Offset of the entry in the FSH file, in bytes. 83 uint offset; 84 } 85 86 static assert(Directory.alignof == 1); 87 static assert(Directory.sizeof == 8); 88 89 /// See_Also: <a href="https://www.wiki.sc4devotion.com/index.php?title=FSH_Format#FSH_Entry_Header">FSH Entry Header</a> (SC4D Encyclopedia) 90 struct EntryHeader { 91 import dbpf.types : int24; 92 93 align(1): 94 /// Record ID 95 /// 96 /// Logically `AND`ed by `0x7f` for bitmap code or `0x80` to check if the entry is QFS compressed (unused by SC4). 97 ubyte recordId; 98 /// Size of an entry including this header. 99 /// 100 /// Only used if the file contains an attachment or embedded mipmaps. It is zero otherwise. 101 /// Remarks: 102 /// $(P For single images this is usually: `width x height + 0x10h`.) 103 /// $(P 104 /// For images with embedded mipmaps, this is the total size of the original image, plus all mipmaps, plus the 105 /// header. 106 /// ) 107 /// $(P In either case, it may include additional data as a binary attachment with unknown format.) 108 int24 size; 109 /// 110 ushort width; 111 /// 112 ushort height; 113 /// 114 ushort centerX; 115 /// 116 ushort centerY; 117 /// 118 ushort positionX; 119 /// 120 ushort positionY; 121 } 122 123 static assert(EntryHeader.alignof == 1); 124 static assert(EntryHeader.sizeof == 16); 125 126 import std.typecons : Tuple; 127 /// A tuple of an entry's header and its data. 128 /// `data` is either palette or bitmap data. 129 /// Remarks: 130 /// After an entry's `header` is its bitmap, palette, or pixel color data. 131 ///Authors: 132 /// Palettes are generally arrays of 256 colors, each 1 byte. Bitmaps may store their pixel data in one of many ways, 133 /// either raw bitmap pixel data, or they can make use of Microsoft DXTC compressed formats. 134 /// See_Also: $(UL 135 /// $(LI `EntryHeader`) 136 /// $(LI <a href="https://www.wiki.sc4devotion.com/index.php?title=FSH_Format#Bitmap_or_Palette_Data">Bitmap data</a> (SC4D Encyclopedia)) 137 /// $(LI <a href="https://www.wiki.sc4devotion.com/index.php?title=FSH_Format#FSH_Entry_Header">FSH Entry Header</a> (SC4D Encyclopedia)) 138 /// ) 139 alias Entry = Tuple!(EntryHeader, "header", ubyte[], "data"); 140 141 /// FSH images can store their pixel data raw, or they can make use of Microsoft DXTC compressed formats. 142 /// See_Also: <a href="https://www.wiki.sc4devotion.com/index.php?title=FSH_Format#Bitmap_or_Palette_Data">Bitmap data</a> (SC4D Encyclopedia) 143 enum BitmapType : ushort { 144 /// 8-bit indexed 145 /// 146 /// Directly follows bitmap or uses global palette. 147 indexed = 0x7B, 148 /// 32-bit A8R8G8B8 149 a8r8g8b8 = 0x7D, 150 /// 24-bit A0R8G8B8 151 a0r8g8b8 = 0x7F, 152 /// 16-bit A1R5G5B5 153 a1r5g5b5 = 0x7E, 154 /// 16-bit A0R5G6B5 155 a0r5g6b5 = 0x78, 156 /// 16-bit A4R4G4B4 157 a4r4g4b4 = 0x6D, 158 /// DXT3 4x4 packed, 4-bit alpha 159 /// 160 /// 4x4 grid compressed, half-byte per pixel 161 dxt3 = 0x61, 162 /// DXT1 4x4 packed, 1-bit alpha 163 /// 164 /// 4x4 grid compressed, half-byte per pixel 165 dxt1 = 0x60 166 } 167 168 /// See_Also: <a href="https://www.wiki.sc4devotion.com/index.php?title=FSH_Format#Bitmap_or_Palette_Data">Palette codes</a> (SC4D Encyclopedia) 169 enum Palette : ushort { 170 /// 24-bit DOS 171 dos = 0x22, 172 /// 24-bit 173 _24bit = 0x24, 174 /// 16-bit NFS5 175 nfs5 = 0x29, 176 /// 32-bit 177 _32bit = 0x2A, 178 /// 16-bit 179 _16bit = 0x2D, 180 } 181 182 /// See_Also: <a href="https://www.wiki.sc4devotion.com/index.php?title=FSH_Format#Bitmap_or_Palette_Data">Text codes</a> (SC4D Encyclopedia) 183 enum Text : ushort { 184 /// Standard Text file 185 text = 0x6F, 186 /// ETXT of arbitrary length with full entry header 187 etxt = 0x69, 188 /// ETXT of 16 bytes or less including the header 189 etxt16 = 0x70, 190 /// Defined Pixel region hot-spot data for image 191 hotspot = 0x7C, 192 } 193 194 /// A FSH document. 195 /// See_Also: $(UL 196 /// $(LI `Header`) 197 /// $(LI `Directory`) 198 /// $(LI `Entry`) 199 /// $(LI <a href="https://www.wiki.sc4devotion.com/index.php?title=FSH">Franks PC Shapes</a> (SC4D Encyclopedia)) 200 /// $(LI <a href="https://www.wiki.sc4devotion.com/index.php?title=FSH_Format">FSH Format</a> (SC4D Encyclopedia)) 201 /// ) 202 alias Fsh = Tuple!(Header, "header", Directory[], "directories", Entry[], "entries"); 203 204 /// 205 Fsh read(ubyte[] file) { 206 import dbpf.files : read; 207 import std.algorithm : startsWith; 208 import std.conv : castFrom, to; 209 import std.typecons : tuple; 210 211 enforce(file.startsWith(Header.identifier), "Input is not a FSH document."); 212 auto header = file.read!Header(); 213 // TODO: Read bitmap entries 214 215 Fsh result = tuple( 216 header, 217 castFrom!(void[]).to!(Directory[])([]), 218 castFrom!(void[]).to!(Entry[])([]), 219 ); 220 return result; 221 } 222 223 /// 224 ubyte[] write(Fsh document) { 225 import dbpf.files : toBytes; 226 import std.algorithm : copy, map, sum; 227 228 auto buffer = new ubyte[ 229 Header.sizeof + 230 (Directory.sizeof * document.directories.length) + 231 (EntryHeader.sizeof * document.entries.length) + 232 document.entries.map!(entry => EntryHeader.sizeof + entry.header.size.value).sum 233 ]; 234 235 document.header.toBytes.copy(buffer[0..Header.sizeof]); 236 // TODO: Write bitmap entries to the buffer 237 238 return buffer; 239 }