├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── BIS.ALB ├── ALB1.cs ├── BIS.ALB.csproj ├── MapArea.cs └── ObjectTree.cs ├── BIS.Core.Test ├── ArmaTextDeserializerTest.cs ├── ArmaTextSerializerTest.cs └── BIS.Core.Test.csproj ├── BIS.Core ├── BIS.Core.csproj ├── Color.cs ├── Compression │ ├── LZO.cs │ ├── LZSS.cs │ ├── LzssCompression.cs │ └── MiniLzo.cs ├── Config │ ├── ConfigFiles.cs │ └── Params.cs ├── Math │ ├── Matrix4P.cs │ ├── QuaternionP.cs │ ├── ShortFloat.cs │ ├── TransformP.cs │ ├── Vector3P.cs │ └── Vector3PCompressed.cs ├── Methods.cs ├── QuadTree.cs ├── Serialization │ ├── ArmaTextDeserializer.cs │ └── ArmaTextSerializer.cs └── Stream │ ├── BinaryReaderEx.cs │ ├── BinaryWriterEx.cs │ ├── IReadObject.cs │ ├── IReadWriteObject.cs │ └── StreamHelper.cs ├── BIS.P3D ├── BIS.P3D.csproj ├── MLOD │ ├── Face.cs │ ├── LOD.cs │ ├── MLOD.cs │ ├── Point.cs │ └── Taggs.cs ├── P3D.cs └── Resolutions.cs ├── BIS.PAA.Encoder ├── BIS.PAA.Encoder.csproj ├── MipmapEncoder.cs ├── PAAFlags.cs └── PaaEncoder.cs ├── BIS.PAA ├── BIS.PAA.csproj ├── ChannelSwizzling.cs ├── Mipmap.cs ├── PAA.cs ├── Palette.cs └── PixelFormatConversion.cs ├── BIS.PBO ├── BIS.PBO.csproj ├── FileEntry.cs └── PBO.cs ├── BIS.RTM ├── AnimKeyStone.cs ├── BIS.RTM.csproj ├── RTM.cs └── RTMB.cs ├── BIS.WRP ├── AnyWrp.cs ├── BIS.WRP.csproj ├── EditableWrp.cs ├── EditableWrpObject.cs ├── GeographyInfo.cs ├── IWrp.cs ├── OPRW.cs ├── Object.cs ├── ObjectId.cs ├── RoadLink.cs └── StaticEntityInfo.cs ├── Extras ├── Extras.sln └── PaaPdnPlugin │ ├── GlobalSuppressions.cs │ ├── PaaFileType.cs │ ├── PaaFileTypeFactory.cs │ └── PaaPdnPlugin.csproj ├── LICENSE ├── README.md ├── Utils ├── Image2PAA │ ├── Image2PAA.csproj │ └── Program.cs ├── PAA2PNG │ ├── PAA2PNG.csproj │ └── Program.cs ├── Utils.sln └── WrpUtil │ ├── ModInfo.cs │ ├── PboInfo.cs │ ├── Program.cs │ ├── README.md │ └── WrpUtil.csproj └── bis-file-formats.sln /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: dotnet 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | dotnet: 9 | strategy: 10 | matrix: 11 | os: [macos-latest, ubuntu-latest, windows-latest] 12 | runs-on: ${{ matrix.os }} 13 | name: Build (${{ matrix.os }}) 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup dotnet 19 | uses: actions/setup-dotnet@v1 20 | with: 21 | dotnet-version: 5.0.x 22 | 23 | - name: Build 24 | run: dotnet build 25 | 26 | - name: Test 27 | run: dotnet test 28 | 29 | - name: Build Utils 30 | run: dotnet build 31 | working-directory: Utils 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /BIS.ALB/ALB1.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Streams; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace BIS.ALB 9 | { 10 | public enum ALB_Datatype: byte 11 | { 12 | Character=1, 13 | Integer=5, 14 | Integer2=6, 15 | Integer3=7, 16 | Integer4=8, 17 | Boolean=9, 18 | Float=10, 19 | String=11, 20 | List=12, 21 | Object=13, 22 | Unknown=15, 23 | Unknown2=19, 24 | Double=20, 25 | DoubleArray=21 26 | } 27 | 28 | public class ALB1 29 | { 30 | private Dictionary tags = new Dictionary(); 31 | private Dictionary classes = new Dictionary(); 32 | 33 | private LinkedList entries = new LinkedList(); 34 | 35 | public class ALB_Entry 36 | { 37 | public int TagID { get; } 38 | public ALB_Value Value { get; } 39 | 40 | public ALB_Entry(BinaryReaderEx input, int? layerVersion = null) 41 | { 42 | TagID = input.ReadInt16(); 43 | var datatype = (ALB_Datatype)input.ReadByte(); 44 | Value = ALB_Value.ReadALBValue(datatype, input, layerVersion); 45 | } 46 | } 47 | 48 | #region ValueTypes 49 | public abstract class ALB_Value 50 | { 51 | public static ALB_Value ReadALBValue(ALB_Datatype dataType, BinaryReaderEx input, int? layerVersion = null) 52 | { 53 | switch (dataType) 54 | { 55 | case ALB_Datatype.Boolean: 56 | return new ALB_SimpleValue(input.ReadBoolean()); 57 | case ALB_Datatype.Character: 58 | return new ALB_SimpleValue(input.ReadChar()); 59 | case ALB_Datatype.Float: 60 | return new ALB_SimpleValue(input.ReadSingle()); 61 | case ALB_Datatype.DoubleArray: 62 | return new ALB_DoubleArray(input); 63 | case ALB_Datatype.Integer: 64 | return new ALB_SimpleValue(input.ReadInt32()); 65 | case ALB_Datatype.Integer2: //mnPriority 66 | return new ALB_SimpleValue(input.ReadInt32()); 67 | case ALB_Datatype.Integer3: //objectCount, Hash (uint?) 68 | return new ALB_SimpleValue(input.ReadInt32()); 69 | case ALB_Datatype.Integer4: 70 | return new ALB_SimpleValue(input.ReadInt32()); 71 | case ALB_Datatype.List: 72 | return new ALB_List(input, layerVersion); 73 | case ALB_Datatype.Object: 74 | return new ALB_Object(input); 75 | case ALB_Datatype.String: 76 | return new ALB_SimpleValue(input.ReadAscii()); 77 | case ALB_Datatype.Unknown: //KeyValue? 78 | return new ALB_Unknown(input); 79 | case ALB_Datatype.Unknown2: 80 | return new ALB_Unknown2(input); 81 | case ALB_Datatype.Double: 82 | return new ALB_SimpleValue(input.ReadDouble()); 83 | 84 | default: 85 | throw new FormatException(); 86 | } 87 | } 88 | 89 | public abstract string ToString(ALB1 alb, int indLvl = 0); 90 | } 91 | 92 | public class ALB_SimpleValue : ALB_Value 93 | { 94 | public T Value { get; } 95 | public ALB_SimpleValue(T value) 96 | { 97 | this.Value = value; 98 | } 99 | 100 | public override string ToString(ALB1 alb, int indLvl = 0) 101 | { 102 | if (Value is string) return $"\"{Value}\""; 103 | return Value.ToString(); 104 | } 105 | } 106 | 107 | public class ALB_List : ALB_Value 108 | { 109 | int size; 110 | ALB_Entry[] entries; 111 | public ObjectTreeNode treeRoot; 112 | 113 | public ALB_List(BinaryReaderEx input, int? layerVersion = null) 114 | { 115 | size = input.ReadInt32(); 116 | var nEntries = input.ReadInt32(); 117 | 118 | if (nEntries > 0 && (size - 4 == nEntries)) 119 | { 120 | if (!layerVersion.HasValue) 121 | throw new FormatException("No layerVersion specified before reading ObjectTree"); 122 | treeRoot = new ObjectTreeNode(input, layerVersion.Value); 123 | } 124 | else 125 | { 126 | entries = Enumerable.Range(0, nEntries).Select(_ => new ALB_Entry(input)).ToArray(); 127 | } 128 | } 129 | 130 | public override string ToString(ALB1 alb, int indLvl = 0) 131 | { 132 | if (entries == null || entries.Length == 0) return "Empty List"; 133 | 134 | return $"\r\n{alb.EntriesToString(entries, indLvl + 1)}"; 135 | } 136 | } 137 | 138 | public class ALB_Object : ALB_Value 139 | { 140 | int size; 141 | public int classID; 142 | int objectID; 143 | LinkedList entries = new LinkedList(); 144 | 145 | public ALB_Object(BinaryReaderEx input) 146 | { 147 | size = input.ReadInt32(); 148 | classID = input.ReadInt16(); 149 | objectID = input.ReadInt32(); 150 | 151 | var bytesRead = 6; 152 | while (bytesRead < size) 153 | { 154 | var pos = input.Position; 155 | entries.AddLast(new ALB_Entry(input)); 156 | bytesRead += (int)(input.Position - pos); 157 | } 158 | } 159 | public override string ToString(ALB1 alb, int indLvl = 0) 160 | { 161 | return $"\r\n{alb.EntriesToString(entries, indLvl + 1)}"; 162 | } 163 | 164 | } 165 | 166 | public class ALB_Unknown : ALB_Value 167 | { 168 | ALB_Entry entry1; 169 | ALB_Entry entry2; 170 | 171 | public ALB_Unknown(BinaryReaderEx input) 172 | { 173 | entry1 = new ALB_Entry(input); 174 | entry2 = new ALB_Entry(input); 175 | } 176 | 177 | public override string ToString(ALB1 alb, int indLvl = 0) 178 | { 179 | return $"\r\n{alb.EntryToString(entry1, indLvl + 1)}\r\n{alb.EntryToString(entry2, indLvl + 1)}"; 180 | } 181 | } 182 | 183 | public class ALB_Unknown2 : ALB_Value 184 | { 185 | byte[] data; 186 | 187 | public ALB_Unknown2(BinaryReaderEx input) 188 | { 189 | data = input.ReadBytes(21); 190 | } 191 | 192 | public override string ToString(ALB1 alb, int indLvl = 0) 193 | { 194 | return string.Join(",", data); 195 | } 196 | } 197 | 198 | public class ALB_DoubleArray : ALB_Value 199 | { 200 | double[] values; 201 | 202 | public ALB_DoubleArray(BinaryReaderEx input) 203 | { 204 | var n = input.ReadByte(); 205 | values = Enumerable.Range(0, n).Select(_ => input.ReadDouble()).ToArray(); 206 | } 207 | 208 | public override string ToString(ALB1 alb, int indLvl = 0) 209 | { 210 | return string.Join(", ", values); 211 | } 212 | } 213 | #endregion 214 | 215 | public ALB1(BinaryReaderEx input) 216 | { 217 | var sig = input.ReadAscii(4); 218 | if (sig != "ALB1") 219 | throw new FormatException("ALB1 signature missing"); 220 | 221 | //unknown data 222 | input.ReadBytes(15); 223 | 224 | var nTags = input.ReadInt32(); 225 | 226 | for(int i=0;i).Value; 256 | 257 | entries.AddLast(e); 258 | } 259 | } 260 | 261 | private string EntryToString(ALB_Entry e, int indLvl = 0) 262 | { 263 | var tag = tags[e.TagID]; 264 | 265 | var cls = (e.Value is ALB_Object obj) ? $"({classes[obj.classID]})" : ""; 266 | var ind = new string(' ', 4 * indLvl); 267 | return $"{ind}{tag}{cls}={e.Value.ToString(this, indLvl)}"; 268 | } 269 | 270 | private string EntriesToString(IEnumerable entries, int indLvl = 0) 271 | { 272 | var res = new StringBuilder(); 273 | foreach (var e in entries) 274 | { 275 | res.AppendLine(EntryToString(e, indLvl)); 276 | } 277 | 278 | return res.ToString(); 279 | } 280 | 281 | public string ExtractObjectData() 282 | { 283 | var treeEntry = entries.FirstOrDefault(e => tags[e.TagID].Equals("tree")); 284 | var sb = new StringBuilder(); 285 | if(treeEntry != null) 286 | { 287 | var listValue = treeEntry.Value as ALB_List; 288 | if(listValue.treeRoot != null) 289 | { 290 | var objData = new LinkedList(); 291 | ExtractObjectData(listValue.treeRoot, objData); 292 | 293 | foreach (var objNode in objData) 294 | { 295 | sb.AppendLine(objNode.ToString()); 296 | } 297 | } 298 | } 299 | 300 | return sb.ToString(); 301 | } 302 | 303 | public void ExtractObjectData(ObjectTreeNode node, LinkedList list) 304 | { 305 | if (node.NodeType == 16) 306 | { 307 | for (int i = 0; i < 4; i++) 308 | { 309 | if (node.Objects[i] != null) 310 | list.AddLast(node.Objects[i]); 311 | } 312 | } 313 | else 314 | { 315 | for (int i = 0; i < 4; i++) 316 | { 317 | if (node.Childs[i] != null) 318 | ExtractObjectData(node.Childs[i], list); 319 | } 320 | } 321 | } 322 | 323 | public override string ToString() 324 | { 325 | return EntriesToString(entries); 326 | } 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /BIS.ALB/BIS.ALB.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | Braini01 7 | LICENSE 8 | https://github.com/Braini01/bis-file-formats 9 | 10 | 11 | 12 | 13 | True 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BIS.ALB/MapArea.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace BIS.ALB 7 | { 8 | public class MapArea 9 | { 10 | public double X1; 11 | public double Y1; 12 | public double X2; 13 | public double Y2; 14 | 15 | public double Width => X2 - X1; 16 | public double Height => Y2 - Y1; 17 | 18 | public MapArea(BinaryReader input, bool readDouble = true) 19 | { 20 | if (readDouble) 21 | { 22 | X1 = input.ReadDouble(); 23 | Y1 = input.ReadDouble(); 24 | X2 = input.ReadDouble(); 25 | Y2 = input.ReadDouble(); 26 | } 27 | else 28 | { 29 | X1 = input.ReadSingle(); 30 | Y1 = input.ReadSingle(); 31 | X2 = input.ReadSingle(); 32 | Y2 = input.ReadSingle(); 33 | } 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return $"{X1:0.###};{Y1:0.###};{X2:0.###};{Y2:0.###}"; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /BIS.ALB/ObjectTree.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Streams; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace BIS.ALB 10 | { 11 | public class ObjectTreeNode 12 | { 13 | public sbyte NodeType { get; } 14 | public MapArea Area { get; } 15 | public int Level { get; } 16 | public byte[] Color { get; } 17 | public byte flags; 18 | 19 | public ObjectTreeNode[] Childs; 20 | 21 | public ObjectTreeLeaf[] Objects; 22 | 23 | public ObjectTreeNode(BinaryReaderEx input, int layerVersion) 24 | { 25 | NodeType = input.ReadSByte(); 26 | 27 | Area = new MapArea(input, layerVersion >= 4); 28 | 29 | Level = input.ReadInt32(); 30 | Color = Enumerable.Range(0, 4).Select(_ => input.ReadByte()).ToArray(); 31 | flags = input.ReadByte(); 32 | 33 | if (NodeType == 16) 34 | { 35 | Objects = new ObjectTreeLeaf[4]; 36 | var isChild = flags; 37 | for (int i = 0; i < 4; i++) 38 | { 39 | if ((isChild & 1) == 1) Objects[i] = new ObjectTreeLeaf(input, layerVersion); 40 | isChild >>= 1; 41 | } 42 | } 43 | else 44 | { 45 | Childs = new ObjectTreeNode[4]; 46 | var isChild = flags; 47 | for (int i = 0; i < 4; i++) 48 | { 49 | if ((isChild & 1) == 1) Childs[i] = new ObjectTreeNode(input, layerVersion); 50 | isChild >>= 1; 51 | } 52 | } 53 | } 54 | } 55 | 56 | public class ObjectTreeLeaf 57 | { 58 | public MapArea Area { get; } 59 | public byte[] Color { get; } 60 | 61 | //it's currently not clear what object hash is stored here; maybe the one covering the most area 62 | public int HashValue { get; } 63 | public int ObjectTypeCount { get; } 64 | public int[] ObjectTypeHashes { get; } 65 | public ObjectInfo[][] ObjectInfos { get; } 66 | 67 | public ObjectTreeLeaf(BinaryReaderEx input, int layerVersion) 68 | { 69 | Area = new MapArea(input, layerVersion >= 4); 70 | Color = input.ReadBytes(4); 71 | HashValue = input.ReadInt32(); 72 | ObjectTypeCount = input.ReadInt32(); 73 | 74 | ObjectTypeHashes = new int[ObjectTypeCount]; 75 | ObjectInfos = new ObjectInfo[ObjectTypeCount][]; 76 | for(int curObjType = 0; curObjType < ObjectTypeCount; curObjType++) 77 | { 78 | var nObjects = input.ReadInt32(); 79 | ObjectTypeHashes[curObjType] = input.ReadInt32(); 80 | ObjectInfos[curObjType] = new ObjectInfo[nObjects]; 81 | for (int obj = 0; obj < nObjects; obj++) 82 | { 83 | ObjectInfos[curObjType][obj] = new ObjectInfo(input); 84 | } 85 | } 86 | } 87 | 88 | public override string ToString() 89 | { 90 | var node = $"{Area};{HashValue}:"; 91 | var sb = new StringBuilder(node); 92 | sb.AppendLine(); 93 | for(int i=0;i < ObjectTypeCount; i++) 94 | { 95 | var objType = ObjectTypeHashes[i]; 96 | foreach(var objinfo in ObjectInfos[i]) 97 | { 98 | sb.AppendLine($" {objType};{objinfo}"); 99 | } 100 | } 101 | 102 | return sb.ToString(); 103 | } 104 | } 105 | 106 | public class ObjectInfo 107 | { 108 | public double X { get; } 109 | public double Y { get; } 110 | public float Yaw { get; } 111 | public float Pitch { get; } 112 | public float Roll { get; } 113 | public float Scale { get; } 114 | public float RelativeElevation { get; } 115 | public int ID { get; } 116 | 117 | public ObjectInfo(BinaryReaderEx input) 118 | { 119 | X = input.ReadDouble(); 120 | Y = input.ReadDouble(); 121 | Yaw = input.ReadSingle(); 122 | Pitch = input.ReadSingle(); 123 | Roll = input.ReadSingle(); 124 | Scale = input.ReadSingle(); 125 | RelativeElevation = input.ReadSingle(); 126 | ID = input.ReadInt32(); 127 | } 128 | 129 | public override string ToString() 130 | { 131 | return $"{X:0.###};{Y:0.###};{Yaw:0.###};{Pitch:0.###};{Roll:0.###};{Scale:0.###};{RelativeElevation:0.###};{ID}"; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /BIS.Core.Test/ArmaTextDeserializerTest.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Serialization; 2 | using Xunit; 3 | 4 | namespace BIS.Core.Test.Serialization 5 | { 6 | public class ArmaTextDeserializerTest 7 | { 8 | [Fact] 9 | public void ParseSimpleArray() 10 | { 11 | var value = ArmaTextDeserializer.ParseSimpleArray("[\"Hello \"\"world\"\" !\",123.456,null,-789.123,\"Hello \"\"world\"\" !\", true, false]"); 12 | Assert.Equal(new object[] { "Hello \"world\" !", 123.456d, null, -789.123d, "Hello \"world\" !", true, false}, value); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /BIS.Core.Test/ArmaTextSerializerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BIS.Core.Serialization; 3 | using Xunit; 4 | 5 | namespace BIS.Core.Test.Serialization 6 | { 7 | public class ArmaTextSerializerTest 8 | { 9 | [Fact] 10 | public void ToSimpleArrayString() 11 | { 12 | var value = ArmaTextSerializer.ToSimpleArrayString(new object[] { "Hello \"world\" !", 123.456d, null, -789.123d, "Hello \"world\" !", true, false }); 13 | Assert.Equal("[\"Hello \"\"world\"\" !\",123.456,null,-789.123,\"Hello \"\"world\"\" !\",true,false]", value); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BIS.Core.Test/BIS.Core.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /BIS.Core/BIS.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | Braini01, GrueArbre 7 | LICENSE 8 | https://github.com/Braini01/bis-file-formats 9 | True 10 | 11 | 12 | 13 | 14 | True 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /BIS.Core/Color.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Diagnostics; 3 | 4 | using BIS.Core.Streams; 5 | 6 | namespace BIS.Core 7 | { 8 | public struct ColorP 9 | { 10 | public float Red { get; private set; } 11 | public float Green { get; private set; } 12 | public float Blue { get; private set; } 13 | public float Alpha { get; private set; } 14 | 15 | public ColorP(float r, float g, float b, float a) 16 | { 17 | Red = r; 18 | Green = g; 19 | Blue = b; 20 | Alpha = a; 21 | } 22 | public ColorP(BinaryReaderEx input) 23 | { 24 | Red = input.ReadSingle(); 25 | Green = input.ReadSingle(); 26 | Blue = input.ReadSingle(); 27 | Alpha = input.ReadSingle(); 28 | } 29 | 30 | public void Read(BinaryReaderEx input) 31 | { 32 | Red = input.ReadSingle(); 33 | Green = input.ReadSingle(); 34 | Blue = input.ReadSingle(); 35 | Alpha = input.ReadSingle(); 36 | } 37 | 38 | public void Write(BinaryWriterEx output) 39 | { 40 | output.Write(Red); 41 | output.Write(Green); 42 | output.Write(Blue); 43 | output.Write(Alpha); 44 | } 45 | 46 | public override string ToString() 47 | { 48 | CultureInfo cultureInfo = new CultureInfo("en-GB"); 49 | return "{" + Red.ToString(cultureInfo.NumberFormat) + "," + Green.ToString(cultureInfo.NumberFormat) + "," + this.Blue.ToString(cultureInfo.NumberFormat) + "," + this.Alpha.ToString(cultureInfo.NumberFormat) + "}"; 50 | } 51 | } 52 | 53 | public struct PackedColor 54 | { 55 | private uint value; 56 | 57 | public byte A8 => (byte)((value >> 24) & 0xff); 58 | public byte R8 => (byte)((value >> 16) & 0xff); 59 | public byte G8 => (byte)((value >> 8) & 0xff); 60 | public byte B8 => (byte)((value ) & 0xff); 61 | 62 | public PackedColor(uint value) 63 | { 64 | this.value = value; 65 | } 66 | 67 | public PackedColor(byte r, byte g, byte b, byte a=255) 68 | { 69 | value = PackColor(r, g, b, a); 70 | } 71 | 72 | public PackedColor(float r, float g, float b, float a) 73 | { 74 | Debug.Assert(r <= 1.0f && r >= 0 && !float.IsNaN(r)); 75 | Debug.Assert(g <= 1.0f && g >= 0 && !float.IsNaN(g)); 76 | Debug.Assert(b <= 1.0f && b >= 0 && !float.IsNaN(b)); 77 | Debug.Assert(a <= 1.0f && a >= 0 && !float.IsNaN(a)); 78 | 79 | byte r8 = (byte)(r * 255); 80 | byte g8 = (byte)(g * 255); 81 | byte b8 = (byte)(b * 255); 82 | byte a8 = (byte)(a * 255); 83 | 84 | value = PackColor(r8, g8, b8, a8); 85 | } 86 | 87 | internal static uint PackColor(byte r, byte g, byte b, byte a) 88 | { 89 | return (uint)(a << 24 | r << 16 | g << 8) | b; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /BIS.Core/Compression/LZSS.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BIS.Core.Compression 4 | { 5 | public static class LZSS 6 | { 7 | public static uint ReadLZSS(System.IO.Stream input, out byte[] dst, uint expectedSize, bool useSignedChecksum) 8 | { 9 | const int N = 4096; 10 | const int F = 18; 11 | const int THRESHOLD = 2; 12 | char[] text_buf = new char[N+F-1]; 13 | dst = new byte[expectedSize]; 14 | 15 | if( expectedSize<=0 ) return 0; 16 | 17 | var startPos = input.Position; 18 | var bytesLeft = expectedSize; 19 | int iDst = 0; 20 | 21 | int i,j,r,c,csum=0; 22 | int flags; 23 | for( i=0; i0 ) 26 | { 27 | if( ((flags>>= 1)&256)==0 ) 28 | { 29 | c=input.ReadByte(); 30 | flags=c|0xff00; 31 | } 32 | if( (flags&1) != 0) 33 | { 34 | c=input.ReadByte(); 35 | if (useSignedChecksum) 36 | csum += (sbyte)c; 37 | else 38 | csum += (byte)c; 39 | 40 | // save byte 41 | dst[iDst++]=(byte)c; 42 | bytesLeft--; 43 | // continue decompression 44 | text_buf[r]=(char)c; 45 | r++;r&=(N-1); 46 | } 47 | else 48 | { 49 | i=input.ReadByte(); 50 | j=input.ReadByte(); 51 | i|=(j&0xf0)<<4; j&=0x0f; j+=THRESHOLD; 52 | 53 | int ii = r-i; 54 | int jj = j+ii; 55 | 56 | if (j+1>bytesLeft) 57 | { 58 | throw new ArgumentException("LZSS overflow"); 59 | } 60 | 61 | for(; ii<=jj; ii++ ) 62 | { 63 | c=(byte)text_buf[ii&(N-1)]; 64 | if (useSignedChecksum) 65 | csum += (sbyte)c; 66 | else 67 | csum += (byte)c; 68 | 69 | // save byte 70 | dst[iDst++]=(byte)c; 71 | bytesLeft--; 72 | // continue decompression 73 | text_buf[r]=(char)c; 74 | r++;r&=(N-1); 75 | } 76 | } 77 | } 78 | 79 | var csData = new byte[4]; 80 | input.Read(csData,0,4); 81 | int csr = BitConverter.ToInt32(csData, 0); 82 | 83 | if( csr!=csum ) 84 | { 85 | throw new ArgumentException("Checksum mismatch"); 86 | } 87 | 88 | return (uint)(input.Position - startPos); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /BIS.Core/Config/ConfigFiles.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Streams; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace BIS.Core.Config 8 | { 9 | public class ParamFile : IReadObject 10 | { 11 | public ParamClass Root { get; private set; } 12 | public List> EnumValues { get; private set; } 13 | 14 | public ParamFile() 15 | { 16 | EnumValues = new List>(10); 17 | } 18 | 19 | public ParamFile(System.IO.Stream stream) 20 | { 21 | Read(new BinaryReaderEx(stream)); 22 | } 23 | 24 | public void Read(BinaryReaderEx input) 25 | { 26 | var sig = new char[] { '\0', 'r', 'a', 'P' }; 27 | if (!input.ReadBytes(4).SequenceEqual(sig.Select(c => (byte)c))) 28 | throw new ArgumentException(); 29 | 30 | var ofpVersion = input.ReadInt32(); 31 | var version = input.ReadInt32(); 32 | var offsetToEnums = input.ReadInt32(); 33 | 34 | Root = new ParamClass(input, "rootClass"); 35 | 36 | input.Position = offsetToEnums; 37 | var nEnumValues = input.ReadInt32(); 38 | EnumValues = Enumerable.Range(0, nEnumValues).Select(_ => new KeyValuePair(input.ReadAsciiz(), input.ReadInt32())).ToList(); 39 | } 40 | 41 | public override string ToString() 42 | { 43 | return Root.ToString(0, true); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /BIS.Core/Math/Matrix4P.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BIS.Core.Streams; 3 | 4 | namespace BIS.Core.Math 5 | { 6 | /// 7 | /// Layout: 8 | /// [m11, m12, m13, 0] 9 | /// [m21, m22, m23, 0] 10 | /// [m31, m32, m33, 0] 11 | /// [m41, m42, m43, 1] 12 | /// 13 | public class Matrix4P 14 | { 15 | private System.Numerics.Matrix4x4 matrix; 16 | 17 | public Matrix4P(BinaryReaderEx input) : this( 18 | new System.Numerics.Matrix4x4( 19 | input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), 0f, 20 | input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), 0f, 21 | input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), 0f, 22 | input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), 1f) 23 | ) { 24 | } 25 | 26 | public Matrix4P(System.Numerics.Matrix4x4 matrix) 27 | { 28 | this.matrix = matrix; 29 | } 30 | 31 | public static Matrix4P operator *(Matrix4P a, Matrix4P b) 32 | { 33 | return new Matrix4P(a.matrix * b.matrix); 34 | } 35 | 36 | public void Write(BinaryWriterEx output) 37 | { 38 | if (matrix.M14 != 0f || matrix.M24 != 0f || matrix.M34 != 0f || matrix.M44 != 1f) 39 | { 40 | throw new InvalidOperationException(); 41 | } 42 | output.Write(matrix.M11); 43 | output.Write(matrix.M12); 44 | output.Write(matrix.M13); 45 | output.Write(matrix.M21); 46 | output.Write(matrix.M22); 47 | output.Write(matrix.M23); 48 | output.Write(matrix.M31); 49 | output.Write(matrix.M32); 50 | output.Write(matrix.M33); 51 | output.Write(matrix.M41); 52 | output.Write(matrix.M42); 53 | output.Write(matrix.M43); 54 | } 55 | 56 | public override string ToString() 57 | { 58 | return matrix.ToString(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /BIS.Core/Math/QuaternionP.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Numerics; 4 | 5 | namespace BIS.Core.Math 6 | { 7 | public class QuaternionP 8 | { 9 | private System.Numerics.Quaternion quaternion; 10 | 11 | public float X => quaternion.X; 12 | public float Y => quaternion.Y; 13 | public float Z => quaternion.Z; 14 | public float W => quaternion.W; 15 | 16 | public static QuaternionP ReadCompressed(BinaryReader input) 17 | { 18 | var x = (float)(input.ReadInt16() / 16384d); 19 | var y = (float)(input.ReadInt16() / 16384d); 20 | var z = (float)(input.ReadInt16() / 16384d); 21 | var w = (float)(input.ReadInt16() / 16384d); 22 | 23 | return new QuaternionP(x, y, z, w); 24 | } 25 | 26 | public QuaternionP() 27 | : this(System.Numerics.Quaternion.Identity) 28 | { 29 | } 30 | 31 | public QuaternionP(float x, float y, float z, float w) 32 | : this(new System.Numerics.Quaternion(x, y, z, w)) 33 | { 34 | 35 | } 36 | 37 | public QuaternionP(System.Numerics.Quaternion quaternion) 38 | { 39 | this.quaternion = quaternion; 40 | } 41 | 42 | public static QuaternionP operator *(QuaternionP a, QuaternionP b) 43 | { 44 | return new QuaternionP(a.quaternion * b.quaternion); 45 | } 46 | 47 | public QuaternionP Inverse 48 | { 49 | get 50 | { 51 | Normalize(); 52 | return Conjugate; 53 | } 54 | } 55 | 56 | public QuaternionP Conjugate => new QuaternionP(System.Numerics.Quaternion.Conjugate(quaternion)); 57 | 58 | public void Normalize() 59 | { 60 | quaternion = System.Numerics.Quaternion.Normalize(quaternion); 61 | } 62 | 63 | public Vector3P Transform(Vector3P xyz) 64 | { 65 | return new Vector3P(System.Numerics.Vector3.Transform(xyz.Vector3, quaternion)); 66 | } 67 | 68 | /// 69 | /// for unit quaternions only? 70 | /// 71 | /// 72 | public Matrix4P ToRotationMatrix() 73 | { 74 | return new Matrix4P(Matrix4x4.CreateFromQuaternion(quaternion)); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /BIS.Core/Math/ShortFloat.cs: -------------------------------------------------------------------------------- 1 | namespace BIS.Core.Math 2 | { 3 | public struct ShortFloat 4 | { 5 | private const int MANTISSA_SIZE = 10; 6 | private const int SIGN_BIT = 0x8000; 7 | private const int M_MASK = 1 << MANTISSA_SIZE; 8 | private const int EXPONENT_SIZE = ((16 - 1 - 1) - MANTISSA_SIZE); 9 | private const int E_MASK = (1 << EXPONENT_SIZE); 10 | 11 | private ushort value; 12 | 13 | public ShortFloat(ushort v) 14 | { 15 | value = v; 16 | } 17 | 18 | public static implicit operator float(ShortFloat d) 19 | { 20 | return (float)d.DoubleValue; 21 | } 22 | 23 | 24 | public double DoubleValue 25 | { 26 | get 27 | { 28 | double sign = ((value & SIGN_BIT) != 0) ? -1 : 1; 29 | int exponent = (value & (SIGN_BIT - 1)) >> MANTISSA_SIZE; 30 | double significandbits = ((double)(value & (M_MASK - 1)) / M_MASK); 31 | 32 | if (exponent == 0) return ((sign / 0x4000) * (0 + significandbits)); 33 | return (sign * System.Math.Pow(2, exponent - (E_MASK - 1)) * (1 + significandbits)); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /BIS.Core/Math/TransformP.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using BIS.Core.Streams; 5 | 6 | namespace BIS.Core.Math 7 | { 8 | public class TransformP 9 | { 10 | private readonly QuaternionP quaternion; 11 | private readonly Vector3P vector; 12 | 13 | public TransformP(QuaternionP quaternion, Vector3P vector) 14 | { 15 | this.quaternion = quaternion; 16 | this.vector = vector; 17 | } 18 | 19 | public static TransformP Read(BinaryReaderEx input) 20 | { 21 | var quaternion = QuaternionP.ReadCompressed(input); 22 | var x = new ShortFloat(input.ReadUInt16()); 23 | var y = new ShortFloat(input.ReadUInt16()); 24 | var z = new ShortFloat(input.ReadUInt16()); 25 | var vector = new Vector3P((float)x.DoubleValue, (float)y.DoubleValue, (float)z.DoubleValue); 26 | 27 | return new TransformP(quaternion, vector); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BIS.Core/Math/Vector3P.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Numerics; 4 | using BIS.Core.Streams; 5 | 6 | namespace BIS.Core.Math 7 | { 8 | public class Vector3P 9 | { 10 | private Vector3 xyz; 11 | 12 | public float X 13 | { 14 | get { return xyz.X; } 15 | set { xyz.X = value; } 16 | } 17 | 18 | public float Y 19 | { 20 | get { return xyz.Y; } 21 | set { xyz.Y = value; } 22 | } 23 | 24 | public float Z 25 | { 26 | get { return xyz.Z; } 27 | set { xyz.Z = value; } 28 | } 29 | 30 | public Vector3P() : this(0f) { } 31 | 32 | public Vector3P(float val) : this(val, val, val) { } 33 | 34 | public Vector3P(BinaryReaderEx input) : this(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()) { } 35 | 36 | public Vector3P(int compressed) : this() 37 | { 38 | const double scaleFactor = -1.0 / 511; 39 | int x = compressed & 0x3FF; 40 | int y = (compressed >> 10) & 0x3FF; 41 | int z = (compressed >> 20) & 0x3FF; 42 | if (x > 511) x -= 1024; 43 | if (y > 511) y -= 1024; 44 | if (z > 511) z -= 1024; 45 | X = (float)(x * scaleFactor); 46 | Y = (float)(y * scaleFactor); 47 | Z = (float)(z * scaleFactor); 48 | } 49 | 50 | public Vector3P(float x, float y, float z) 51 | { 52 | xyz = new Vector3( x, y, z ); 53 | } 54 | 55 | public Vector3P(Vector3 xyz) 56 | { 57 | this.xyz = xyz; 58 | } 59 | 60 | public float Length => xyz.Length(); 61 | 62 | public Vector3 Vector3 => xyz; 63 | 64 | public float this[int i] 65 | { 66 | get 67 | { 68 | switch(i) 69 | { 70 | case 0: return X; 71 | case 1: return Y; 72 | case 2: return Z; 73 | default: 74 | throw new ArgumentOutOfRangeException(nameof(i)); 75 | } 76 | } 77 | 78 | set 79 | { 80 | switch (i) 81 | { 82 | case 0: X = value; break; 83 | case 1: Y = value; break; 84 | case 2: Z = value; break; 85 | default: 86 | throw new ArgumentOutOfRangeException(nameof(i)); 87 | } 88 | } 89 | } 90 | 91 | public static Vector3P operator -(Vector3P a) 92 | { 93 | return new Vector3P(-a.xyz); 94 | } 95 | 96 | public void Write(BinaryWriterEx output) 97 | { 98 | output.Write(X); 99 | output.Write(Y); 100 | output.Write(Z); 101 | } 102 | 103 | //Scalarmultiplication 104 | public static Vector3P operator *(Vector3P a, float b) 105 | { 106 | return new Vector3P(a.xyz * b); 107 | } 108 | 109 | //Scalarproduct 110 | public static float operator *(Vector3P a, Vector3P b) 111 | { 112 | return Vector3.Dot(a.xyz, b.xyz); 113 | } 114 | 115 | public static Vector3P operator +(Vector3P a, Vector3P b) 116 | { 117 | return new Vector3P(a.xyz + b.xyz); 118 | } 119 | 120 | public static Vector3P operator -(Vector3P a, Vector3P b) 121 | { 122 | return new Vector3P(a.xyz - b.xyz); 123 | } 124 | 125 | public override bool Equals(object obj) 126 | { 127 | Vector3P p = obj as Vector3P; 128 | if (p == null) 129 | { 130 | return false; 131 | } 132 | 133 | return base.Equals(obj) && Equals(p); 134 | } 135 | 136 | //ToDo: 137 | public override int GetHashCode() 138 | { 139 | return xyz.GetHashCode(); 140 | } 141 | 142 | public bool Equals(Vector3P other) 143 | { 144 | Func nearlyEqual = (f1, f2) => System.Math.Abs(f1 - f2) < 0.05; 145 | 146 | return ( nearlyEqual(X, other.X) && nearlyEqual(Y, other.Y) && nearlyEqual(Z, other.Z)); 147 | } 148 | 149 | public override string ToString() 150 | { 151 | return "{" + X.ToString(CultureInfo.InvariantCulture) + "," + Y.ToString(CultureInfo.InvariantCulture) + "," + Z.ToString(CultureInfo.InvariantCulture) + "}"; 152 | } 153 | 154 | public float Distance(Vector3P v) 155 | { 156 | return Vector3.Distance(xyz, v.xyz); 157 | } 158 | 159 | public void Normalize() 160 | { 161 | xyz = Vector3.Normalize(xyz); 162 | } 163 | 164 | 165 | public static Vector3P CrossProduct(Vector3P a, Vector3P b) 166 | { 167 | return new Vector3P(Vector3.Cross(a.xyz, b.xyz)); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /BIS.Core/Math/Vector3PCompressed.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Streams; 2 | 3 | namespace BIS.Core.Math 4 | { 5 | 6 | public class Vector3PCompressed 7 | { 8 | private int value; 9 | private const float scaleFactor = -1.0f / 511.0f; 10 | 11 | public float X 12 | { 13 | get 14 | { 15 | int x = value & 0x3FF; 16 | if (x > 511) x -= 1024; 17 | return x * scaleFactor; 18 | } 19 | } 20 | 21 | public float Y 22 | { 23 | get 24 | { 25 | int y = (value >> 10) & 0x3FF; 26 | if (y > 511) y -= 1024; 27 | return y * scaleFactor; 28 | } 29 | } 30 | 31 | public float Z 32 | { 33 | get 34 | { 35 | int z = (value >> 20) & 0x3FF; 36 | if (z > 511) z -= 1024; 37 | return z * scaleFactor; 38 | } 39 | } 40 | 41 | public static implicit operator Vector3P(Vector3PCompressed src) 42 | { 43 | int x = src.value & 0x3FF; 44 | int y = (src.value >> 10) & 0x3FF; 45 | int z = (src.value >> 20) & 0x3FF; 46 | if (x > 511) x -= 1024; 47 | if (y > 511) y -= 1024; 48 | if (z > 511) z -= 1024; 49 | 50 | return new Vector3P(x * scaleFactor, y * scaleFactor, z * scaleFactor); 51 | } 52 | 53 | public static implicit operator int(Vector3PCompressed src) 54 | { 55 | return src.value; 56 | } 57 | 58 | public static implicit operator Vector3PCompressed(int src) 59 | { 60 | return new Vector3PCompressed(src); 61 | } 62 | 63 | public Vector3PCompressed(int value) 64 | { 65 | this.value = value; 66 | } 67 | public Vector3PCompressed(BinaryReaderEx input) 68 | { 69 | value = input.ReadInt32(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /BIS.Core/Methods.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | using static System.Math; 5 | 6 | namespace BIS.Core 7 | { 8 | public static class Methods 9 | { 10 | public static void Swap(ref T v1, ref T v2) 11 | { 12 | var tmp = v1; 13 | v1 = v2; 14 | v2 = tmp; 15 | } 16 | 17 | public static bool EqualsFloat(float f1, float f2, float tolerance = 0.0001f) 18 | { 19 | var dif = Abs(f1 - f2); 20 | if (dif <= tolerance) return true; 21 | return false; 22 | } 23 | 24 | public static IEnumerable Yield(this T src) 25 | { 26 | yield return src; 27 | } 28 | 29 | public static IEnumerable Yield(params T[] elems) 30 | { 31 | return elems; 32 | } 33 | 34 | public static string CharsToString(this IEnumerable chars) 35 | { 36 | return new string(chars.ToArray()); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /BIS.Core/QuadTree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace BIS.Core 9 | { 10 | public class QuadTree : IEnumerable, IReadOnlyList 11 | { 12 | /// 13 | /// how many elements exist in X-dimension 14 | /// 15 | private int sizeX; 16 | /// 17 | /// how many elements exist in Y-dimension 18 | /// 19 | private int sizeY; 20 | 21 | /// 22 | /// how many virtual elements exist in X-dimension 23 | /// 24 | private int sizeTotalX; 25 | /// 26 | /// how many virtual elements exist in Y-dimension 27 | /// 28 | private int sizeTotalY; 29 | 30 | private const int logSizeX = 2; 31 | private const int logSizeY = 2; 32 | 33 | private int logSizeTotalX; 34 | private int logSizeTotalY; 35 | 36 | //static because it only depends on TElement 37 | private static int leafLogSizeX; 38 | private static int leafLogSizeY; 39 | 40 | /// 41 | /// root of the quadTree that represents the whole area 42 | /// 43 | private IQuadTreeNode root; 44 | 45 | /// 46 | /// tells if the root is a leafNode or a tree 47 | /// 48 | private bool flag; 49 | 50 | private IEnumerable allElementsEnumeration; 51 | 52 | private static Func readElement; 53 | private static int elementSize; 54 | 55 | public int SizeX => sizeX; 56 | public int SizeY => sizeY; 57 | 58 | public int Count => sizeX * sizeY; 59 | 60 | public TElement this[int index] => Get(index % SizeX, index / sizeX); 61 | 62 | public QuadTree(int sizeX, int sizeY, BinaryReader input, Func readElement, int elementSize) 63 | { 64 | QuadTree.readElement = readElement; 65 | QuadTree.elementSize = elementSize; 66 | 67 | CalculateDimensions(sizeX, sizeY); 68 | allElementsEnumeration = from y in Enumerable.Range(0, SizeY) 69 | from x in Enumerable.Range(0, SizeX) 70 | select Get(x, y); 71 | 72 | flag = input.ReadBoolean(); 73 | 74 | if (flag) 75 | { 76 | root = new QuadTreeNode(input); 77 | } 78 | else 79 | { 80 | root = new QuadTreeLeaf(input); 81 | } 82 | } 83 | 84 | public TElement Get(int x, int y) 85 | { 86 | if (x < 0 || x >= sizeX) 87 | throw new ArgumentOutOfRangeException("x"); 88 | if (y < 0 || y >= sizeY) 89 | throw new ArgumentOutOfRangeException("y"); 90 | 91 | uint shiftedX = (uint)(x << (8 * sizeof(int) - logSizeTotalX)); // make highest bits accessible on left side 92 | uint shiftedY = (uint)(y << (8 * sizeof(int) - logSizeTotalY)); // make highest bits accessible on left side 93 | if (flag) 94 | return ((QuadTreeNode)root).Get(x, y, shiftedX, shiftedY); 95 | else 96 | return ((QuadTreeLeaf)root).Get(x, y); 97 | } 98 | 99 | private void CalculateDimensions(int x, int y) 100 | { 101 | sizeX = x; 102 | sizeY = y; 103 | 104 | x--; 105 | logSizeTotalX = 0; 106 | while (x != 0) 107 | { 108 | logSizeTotalX++; 109 | x >>= 1; 110 | } 111 | 112 | y--; 113 | logSizeTotalY = 0; 114 | while (y != 0) 115 | { 116 | logSizeTotalY++; 117 | y >>= 1; 118 | } 119 | 120 | switch(elementSize) 121 | { 122 | case 1: 123 | leafLogSizeX = 1; 124 | leafLogSizeY = 1; 125 | break; 126 | case 2: 127 | leafLogSizeX = 1; 128 | leafLogSizeY = 0; 129 | break; 130 | case 4: 131 | leafLogSizeX = 0; 132 | leafLogSizeY = 0; 133 | break; 134 | 135 | default: throw new ArgumentException("Element size needs to be 1, 2 or 4"); 136 | } 137 | 138 | // optimize _logSizeTotalX, _logSizeTotalY 139 | int numLevelsX = (logSizeTotalX - leafLogSizeX + logSizeX - 1) / logSizeX; 140 | int numLevelsY = (logSizeTotalY - leafLogSizeY + logSizeY - 1) / logSizeY; 141 | 142 | int numLevels = numLevelsX > numLevelsY ? numLevelsX : numLevelsY; 143 | 144 | logSizeTotalX = numLevels * logSizeX + leafLogSizeX; 145 | logSizeTotalY = numLevels * logSizeY + leafLogSizeY; 146 | sizeTotalX = 1 << logSizeTotalX; 147 | sizeTotalY = 1 << logSizeTotalY; 148 | 149 | // check if quad tree is well-formed 150 | Debug.Assert(sizeTotalX >= sizeX); 151 | Debug.Assert(sizeTotalY >= sizeY); 152 | } 153 | 154 | public IEnumerator GetEnumerator() 155 | { 156 | return allElementsEnumeration.GetEnumerator(); 157 | } 158 | 159 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 160 | { 161 | return allElementsEnumeration.GetEnumerator(); 162 | } 163 | 164 | 165 | private interface IQuadTreeNode { } 166 | 167 | private class QuadTreeNode : IQuadTreeNode 168 | { 169 | private const int logSizeX = 2; 170 | private const int logSizeY = 2; 171 | 172 | /// 173 | /// bits determine if subTree is a leaf or not 174 | /// 175 | private short flag; 176 | private IQuadTreeNode[] subTrees = new IQuadTreeNode[16]; 177 | 178 | public QuadTreeNode(BinaryReader input) 179 | { 180 | Read(input); 181 | } 182 | 183 | private void Read(BinaryReader input) 184 | { 185 | flag = input.ReadInt16(); 186 | var bitMask = flag; 187 | for (int i = 0; i < 16; i++) 188 | { 189 | if ((bitMask & 1) == 1) 190 | { 191 | subTrees[i] = new QuadTreeNode(input); 192 | } 193 | else 194 | { 195 | subTrees[i] = new QuadTreeLeaf(input); 196 | } 197 | bitMask >>= 1; 198 | } 199 | } 200 | 201 | public TElement Get(int x, int y, uint shiftedX, uint shiftedY) 202 | { 203 | // use (logSize) highest bits of shiftedX, shiftedX as indices 204 | uint indexX = shiftedX >> (8 * sizeof(int) - logSizeX); 205 | uint indexY = shiftedY >> (8 * sizeof(int) - logSizeY); 206 | // 2D to 1D array conversion 207 | int index = (int)((indexY << logSizeX) + indexX); 208 | if ((flag & (1 << index)) != 0) 209 | { 210 | // move shiftedX, shiftedY to make next bits available 211 | return ((QuadTreeNode)subTrees[index]).Get(x, y, shiftedX << logSizeX, shiftedY << logSizeY); 212 | } 213 | else 214 | { 215 | // mask only lowest bits from the original x, y 216 | int maskX = (1 << leafLogSizeX) - 1; 217 | int maskY = (1 << leafLogSizeY) - 1; 218 | return ((QuadTreeLeaf)subTrees[index]).Get(x & maskX, y & maskY); 219 | } 220 | } 221 | } 222 | 223 | private class QuadTreeLeaf : IQuadTreeNode 224 | { 225 | private byte[] value; 226 | 227 | private static Func getFunc; 228 | 229 | public QuadTreeLeaf(BinaryReader input) 230 | { 231 | if(getFunc == null) 232 | { 233 | switch(elementSize) 234 | { 235 | case 1: getFunc = (src, x, y) => readElement(src, 0); break; 236 | case 2: getFunc = (src, x, y) => readElement(src, x*2); break; 237 | case 4: getFunc = (src, x, y) => readElement(src, (y<<1) + x); break; 238 | } 239 | } 240 | 241 | value = input.ReadBytes(4); 242 | } 243 | 244 | public TElement Get(int x, int y) 245 | { 246 | return getFunc(value, x, y); 247 | } 248 | } 249 | 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /BIS.Core/Serialization/ArmaTextDeserializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace BIS.Core.Serialization 9 | { 10 | /// 11 | /// Utilities to parse text representation of engine simple types 12 | /// 13 | public class ArmaTextDeserializer 14 | { 15 | /// 16 | /// Converts given, formatted as simple array, String into a valid Array 17 | /// https://community.bistudio.com/wiki/parseSimpleArray 18 | /// 19 | /// 20 | /// 21 | public static object[] ParseSimpleArray(string str) 22 | { 23 | return ReadArray(new StringReader(str)); 24 | } 25 | 26 | public static T[] ParseNumberArray(string str) 27 | where T : unmanaged 28 | { 29 | return ParseSimpleArray(str).Cast().Select(n => (T)Convert.ChangeType(n, typeof(T))).ToArray(); 30 | } 31 | 32 | public static double? ParseDouble(string str) 33 | { 34 | if (string.IsNullOrEmpty(str) || IsNullToken(str)) 35 | { 36 | return null; 37 | } 38 | return double.Parse(str.Trim(), CultureInfo.InvariantCulture); 39 | } 40 | 41 | public static string ParseString(string str) 42 | { 43 | if (IsNullToken(str)) 44 | { 45 | return null; 46 | } 47 | return ReadString(new StringReader(str)); 48 | } 49 | 50 | private static bool IsNullToken(string token) 51 | { 52 | return token == "null" || token == "" || token == "nil" || token == "any"; 53 | } 54 | 55 | private static string ReadString(StringReader str) 56 | { 57 | if (str.Peek() == '"') 58 | { 59 | var sb = new StringBuilder(); 60 | str.Read(); // Consume '"' 61 | while (str.Peek() != -1) 62 | { 63 | char c = (char)str.Read(); 64 | if (c == '"') 65 | { 66 | if (str.Peek() == '"') 67 | { 68 | str.Read(); // Consume second '"' 69 | sb.Append(c); 70 | } 71 | else 72 | { 73 | return sb.ToString(); 74 | } 75 | } 76 | else 77 | { 78 | sb.Append(c); 79 | } 80 | } 81 | return sb.ToString(); 82 | } 83 | return null; 84 | } 85 | 86 | private static double? ReadNumber(StringReader str) 87 | { 88 | var sb = new StringBuilder(); 89 | int i; 90 | while ((i = str.Peek()) != -1) 91 | { 92 | char c = (char)i; 93 | if (char.IsDigit(c) || c == '.' || c == '-') 94 | { 95 | str.Read(); 96 | sb.Append(c); 97 | } 98 | else 99 | { 100 | return ParseDouble(sb.ToString()); 101 | } 102 | } 103 | return ParseDouble(sb.ToString()); 104 | } 105 | 106 | private static object[] ReadArray(StringReader str) 107 | { 108 | if (str.Peek() == '[') 109 | { 110 | var data = new List(); 111 | var expectItem = true; 112 | str.Read(); // Consume '[' 113 | 114 | int i; 115 | while ((i = str.Peek()) != -1) 116 | { 117 | char c = (char)i; 118 | if (c == ']') 119 | { 120 | str.Read(); 121 | return data.ToArray(); 122 | } 123 | if (c == ',') 124 | { 125 | str.Read(); 126 | expectItem = true; 127 | } 128 | else if (c != ' ' && expectItem) 129 | { 130 | if (c == '"') 131 | { 132 | data.Add(ReadString(str)); 133 | } 134 | else if (c == '[') 135 | { 136 | data.Add(ReadArray(str)); 137 | } 138 | else if (char.IsDigit(c) || c == '-') 139 | { 140 | data.Add(ReadNumber(str)); 141 | } 142 | else if (c == 'n' || c == '<' || c == 'a') // null, , nil or any 143 | { 144 | str.Read(); 145 | data.Add(null); 146 | } 147 | else if (c == 't') // true 148 | { 149 | str.Read(); 150 | data.Add(true); 151 | } 152 | else if (c == 'f') // false 153 | { 154 | str.Read(); 155 | data.Add(false); 156 | } 157 | expectItem = false; 158 | } 159 | else 160 | { 161 | str.Read(); 162 | } 163 | } 164 | return data.ToArray(); 165 | } 166 | return null; 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /BIS.Core/Serialization/ArmaTextSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Globalization; 4 | using System.Text; 5 | 6 | namespace BIS.Core.Serialization 7 | { 8 | /// 9 | /// Utilities to serialize to text representation of engine simple types 10 | /// 11 | public static class ArmaTextSerializer 12 | { 13 | private static string ToArmaString(object obj) 14 | { 15 | if (obj == null) 16 | { 17 | return "null"; 18 | } 19 | if (obj is string str) 20 | { 21 | return ToArmaString(str); 22 | } 23 | if (obj is double dnum) 24 | { 25 | return ToArmaString(dnum); 26 | } 27 | if (obj is float fnum) 28 | { 29 | return ToArmaString(fnum); 30 | } 31 | if (obj is int inum) 32 | { 33 | return ToArmaString(inum); 34 | } 35 | if (obj is long lnum) 36 | { 37 | return ToArmaString(lnum); 38 | } 39 | if (obj is bool boolean) 40 | { 41 | return ToArmaString(boolean); 42 | } 43 | if (obj is IEnumerable list) 44 | { 45 | return ToSimpleArrayString(list); 46 | } 47 | throw new ArgumentException($"Sorry, type '{obj.GetType().FullName}' is not supported"); 48 | } 49 | 50 | private static string Escape(string str) 51 | { 52 | return str.Replace("\"", "\"\""); 53 | } 54 | 55 | private static string ToArmaString(string str) 56 | { 57 | return $"\"{Escape(str)}\""; 58 | } 59 | 60 | private static string ToArmaString(double num) 61 | { 62 | return num.ToString(CultureInfo.InvariantCulture); 63 | } 64 | 65 | private static string ToArmaString(bool boolean) 66 | { 67 | return boolean ? "true" : "false"; 68 | } 69 | 70 | private static string ToArmaString(int num) 71 | { 72 | return num.ToString(CultureInfo.InvariantCulture); 73 | } 74 | 75 | /// 76 | /// Serialize an array for parseSimpleArray 77 | /// 78 | /// https://community.bistudio.com/wiki/parseSimpleArray 79 | /// 80 | /// 81 | /// 82 | public static string ToSimpleArrayString(IEnumerable list) 83 | { 84 | var sb = new StringBuilder("["); 85 | foreach(var item in list) 86 | { 87 | if (sb.Length > 1) 88 | { 89 | sb.Append(","); 90 | } 91 | sb.Append(ToArmaString(item)); 92 | } 93 | sb.Append("]"); 94 | return sb.ToString(); 95 | } 96 | 97 | 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /BIS.Core/Stream/BinaryReaderEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Linq; 6 | using System.Text; 7 | using BIS.Core.Compression; 8 | 9 | namespace BIS.Core.Streams 10 | { 11 | public class BinaryReaderEx : BinaryReader 12 | { 13 | public bool UseCompressionFlag { get; set; } 14 | public bool UseLZOCompression { get; set; } 15 | 16 | //used to store file format versions (e.g. ODOL v60) 17 | public int Version { get; set; } 18 | 19 | public long Position 20 | { 21 | get 22 | { 23 | return BaseStream.Position; 24 | } 25 | set 26 | { 27 | BaseStream.Position = value; 28 | } 29 | } 30 | 31 | public bool HasReachedEnd => BaseStream.Position == BaseStream.Length; 32 | 33 | public BinaryReaderEx(Stream stream): base(stream) 34 | { 35 | UseCompressionFlag = false; 36 | } 37 | 38 | 39 | public uint ReadUInt24() 40 | { 41 | return (uint)(ReadByte() + (ReadByte() << 8) + (ReadByte() << 16)); 42 | } 43 | 44 | public string ReadAscii(int count) 45 | { 46 | var str = new StringBuilder(); 47 | for (int index = 0; index < count; ++index) 48 | str.Append((char)ReadByte()); 49 | return str.ToString(); 50 | } 51 | 52 | public string ReadAscii() 53 | { 54 | var n = ReadUInt16(); 55 | return ReadAscii(n); 56 | } 57 | 58 | public string ReadAscii32() 59 | { 60 | var n = ReadUInt32(); 61 | return ReadAscii((int)n); 62 | } 63 | 64 | public string ReadAsciiz() 65 | { 66 | var str = new StringBuilder(); 67 | char ch; 68 | while ((ch = (char)ReadByte()) != 0) 69 | str.Append(ch); 70 | return str.ToString(); 71 | } 72 | 73 | #region SimpleArray 74 | public T[] ReadArrayBase(Func readElement, int size) 75 | { 76 | var array = new T[size]; 77 | for (int i = 0; i < size; i++) 78 | array[i] = readElement(this); 79 | 80 | return array; 81 | } 82 | 83 | public T[] ReadArray(Func readElement) => ReadArrayBase(readElement, ReadInt32()); 84 | public float[] ReadFloatArray() => ReadArray(i => i.ReadSingle()); 85 | public int[] ReadIntArray() => ReadArray(i => i.ReadInt32()); 86 | public string[] ReadStringArray() => ReadArray(i => i.ReadAsciiz()); 87 | 88 | #endregion 89 | 90 | #region CompressedArray 91 | public T[] ReadCompressedArray(Func readElement, int elemSize) 92 | { 93 | int nElements = ReadInt32(); 94 | return ReadCompressed(readElement, nElements, elemSize); 95 | } 96 | 97 | public short[] ReadCompressedShortArray() => ReadCompressedArray(i => i.ReadInt16(), 2); 98 | public int[] ReadCompressedIntArray() => ReadCompressedArray(i => i.ReadInt32(), 4); 99 | public float[] ReadCompressedFloatArray() => ReadCompressedArray(i => i.ReadSingle(), 4); 100 | public byte[] ReadCompressedByteArray() => ReadCompressedArray(i => i.ReadByte(), 1); 101 | 102 | #endregion 103 | 104 | #region CondensedArray 105 | 106 | public T[] ReadCondensedArray(Func readElement, int sizeOfT) 107 | { 108 | int size = ReadInt32(); 109 | T[] result = new T[size]; 110 | bool defaultFill = ReadBoolean(); 111 | if (defaultFill) 112 | { 113 | var defaultValue = readElement(this); 114 | for (int i = 0; i < size; i++) 115 | result[i] = defaultValue; 116 | 117 | return result; 118 | } 119 | 120 | var expectedDataSize = (uint)(size * sizeOfT); 121 | using (var stream = new BinaryReaderEx(new MemoryStream(ReadCompressed(expectedDataSize)))) 122 | { 123 | result = stream.ReadArrayBase(readElement, size); 124 | } 125 | 126 | return result; 127 | } 128 | 129 | public int[] ReadCondensedIntArray() => ReadCondensedArray(i => i.ReadInt32(), 4); 130 | #endregion 131 | 132 | public int ReadCompactInteger() 133 | { 134 | int result = 0; 135 | int i = 0; 136 | bool end; 137 | do 138 | { 139 | int b = ReadByte(); 140 | result |= (b & 0x7f) << (i * 7); 141 | end = b < 0x80; 142 | i++; 143 | } while (!end); 144 | return result; 145 | } 146 | 147 | public byte[] ReadCompressed(uint expectedSize, bool forceCompressed = false) 148 | { 149 | if (expectedSize == 0) 150 | { 151 | return new byte[0]; 152 | } 153 | 154 | if (UseLZOCompression) return ReadLZO(expectedSize, forceCompressed); 155 | 156 | return ReadLZSS(expectedSize); 157 | } 158 | 159 | public byte[] ReadLZO(uint expectedSize, bool forceCompressed = false) 160 | { 161 | bool isCompressed = (expectedSize >= 1024) || forceCompressed; 162 | if (UseCompressionFlag) 163 | { 164 | isCompressed = ReadBoolean(); 165 | } 166 | 167 | if (!isCompressed) 168 | { 169 | return ReadBytes((int)expectedSize); 170 | } 171 | 172 | return LZO.ReadLZO(BaseStream, expectedSize); 173 | } 174 | 175 | public byte[] ReadLZSS(uint expectedSize, bool inPAA = false) 176 | { 177 | if (expectedSize < 1024 && !inPAA) //data is always compressed in PAAs 178 | { 179 | return ReadBytes((int)expectedSize); 180 | } 181 | else 182 | { 183 | // XXX: Needs testing 184 | //var buffer = new byte[expectedSize]; 185 | //using (var lzss = new LzssStream(BaseStream, CompressionMode.Decompress, true)) 186 | //{ 187 | // lzss.Read(buffer, 0, (int)expectedSize); 188 | //} 189 | //Chesksum(inPAA, buffer); //PAAs calculate checksums with signed byte values 190 | byte[] buffer; 191 | LZSS.ReadLZSS(BaseStream, out buffer, expectedSize, inPAA); 192 | return buffer; 193 | } 194 | } 195 | 196 | // XXX: Needs testing 197 | //private void Chesksum(bool useSignedChecksum, byte[] buffer) 198 | //{ 199 | // var csum = useSignedChecksum ? buffer.Sum(e => (int)(sbyte)e) : buffer.Sum(e => (int)(byte)e); 200 | // var csData = new byte[4]; 201 | // BaseStream.Read(csData, 0, 4); 202 | // int csr = BitConverter.ToInt32(csData, 0); 203 | // if (csr != csum) 204 | // { 205 | // throw new ArgumentException("Checksum mismatch"); 206 | // } 207 | //} 208 | 209 | public byte[] ReadCompressedIndices(int bytesToRead, uint expectedSize) 210 | { 211 | var result = new byte[expectedSize]; 212 | int outputI = 0; 213 | for(int i=0;i r.ReadSingle(), nElements, 4); 238 | } 239 | 240 | public float[] ReadFloats(int nElements) 241 | { 242 | return ReadArrayBase(r => r.ReadSingle(), nElements); 243 | } 244 | 245 | public ushort[] ReadUshorts(int nElements) 246 | { 247 | return ReadArrayBase(r => r.ReadUInt16(), nElements); 248 | } 249 | 250 | public T[] ReadCompressed(Func readElement, int nElements, int elemSize) 251 | { 252 | var expectedDataSize = (uint)(nElements * elemSize); 253 | var stream = new BinaryReaderEx(new MemoryStream(ReadCompressed(expectedDataSize))); 254 | return stream.ReadArrayBase(readElement, nElements); 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /BIS.Core/Stream/BinaryWriterEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace BIS.Core.Streams 8 | { 9 | public class BinaryWriterEx : BinaryWriter 10 | { 11 | public bool UseCompressionFlag { get; set; } 12 | public bool UseLZOCompression { get; set; } 13 | 14 | public long Position 15 | { 16 | get 17 | { 18 | return BaseStream.Position; 19 | } 20 | set 21 | { 22 | BaseStream.Position = value; 23 | } 24 | } 25 | public BinaryWriterEx(Stream dstStream) : base(dstStream, Encoding.ASCII) { } 26 | 27 | public BinaryWriterEx(Stream dstStream, bool leaveOpen): base(dstStream, Encoding.ASCII, leaveOpen) {} 28 | 29 | public void WriteAscii(string text, uint len) 30 | { 31 | Write(text.ToCharArray()); 32 | uint num = (uint)(len - text.Length); 33 | for (int index = 0; index < num; ++index) 34 | Write(char.MinValue); //ToDo: check encoding, should always write one byte and never two or more 35 | } 36 | 37 | public void WriteAscii32(string text) 38 | { 39 | Write(text.Length); 40 | Write(text.ToCharArray()); 41 | } 42 | 43 | public void WriteAsciiz(string text) 44 | { 45 | Write(text.ToCharArray()); 46 | Write(char.MinValue); 47 | } 48 | 49 | 50 | public void WriteArray(T[] array, Action write) 51 | { 52 | Write(array.Length); 53 | WriteArrayBase(array, write); 54 | } 55 | 56 | private void WriteArrayBase(T[] array, Action write) 57 | { 58 | foreach (var item in array) 59 | { 60 | write(this, item); 61 | } 62 | } 63 | 64 | public void WriteCompressedFloatArray(float[] array) 65 | { 66 | WriteCompressedArray(array, (w, v) => w.Write(v), 4); 67 | } 68 | 69 | public void WriteCompressedArray(T[] array, Action write, int size, bool forceCompressed = false) 70 | { 71 | var mem = new MemoryStream(); 72 | using (var writer = new BinaryWriterEx(mem)) 73 | { 74 | foreach (var item in array) 75 | { 76 | write(writer, item); 77 | } 78 | } 79 | Write(array.Length); 80 | var bytes = mem.ToArray(); 81 | if (array.Length * size != bytes.Length) 82 | { 83 | throw new InvalidOperationException(); 84 | } 85 | WriteCompressed(bytes, forceCompressed); 86 | } 87 | 88 | private void WriteCompressed(byte[] bytes, bool forceCompressed = false) 89 | { 90 | if (UseLZOCompression) 91 | { 92 | WriteLZO(bytes, forceCompressed); 93 | } 94 | else 95 | { 96 | WriteLZSS(bytes); 97 | } 98 | } 99 | 100 | 101 | public void WriteLZO(byte[] bytes, bool forceCompressed = false) 102 | { 103 | if (bytes.Length < 1024 && !forceCompressed) 104 | { 105 | if (UseCompressionFlag) 106 | { 107 | Write((byte)2); 108 | } 109 | Write(bytes); 110 | } 111 | else 112 | { 113 | if (UseCompressionFlag) 114 | { 115 | Write((byte)2); 116 | } 117 | Write(MiniLZO.MiniLZO.Compress(bytes)); 118 | } 119 | } 120 | 121 | public void WriteLZSS(byte[] bytes, bool inPAA = false) 122 | { 123 | if (bytes.Length < 1024 && !inPAA) //data is always compressed in PAAs 124 | { 125 | Write(bytes); 126 | } 127 | else 128 | { 129 | var csum = inPAA ? bytes.Sum(e => (int)(sbyte)e) : bytes.Sum(e => (int)(byte)e); 130 | using (var lzss = new LzssStream(BaseStream, System.IO.Compression.CompressionMode.Compress, true)) 131 | { 132 | lzss.Write(bytes, 0, bytes.Length); 133 | } 134 | Write(BitConverter.GetBytes(csum)); 135 | } 136 | 137 | } 138 | 139 | public void WriteUInt24(uint length) 140 | { 141 | Write((byte)(length & 0xFF)); 142 | Write((byte)((length >> 8) & 0xFF)); 143 | Write((byte)((length >> 16) & 0xFF)); 144 | } 145 | 146 | public void WriteFloats(float[] elements) 147 | { 148 | WriteArrayBase(elements, (r, e) => r.Write(e)); 149 | } 150 | 151 | public void WriteUshorts(ushort[] elements) 152 | { 153 | WriteArrayBase(elements, (r,e) => r.Write(e)); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /BIS.Core/Stream/IReadObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BIS.Core.Streams 6 | { 7 | public interface IReadObject 8 | { 9 | void Read(BinaryReaderEx input); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BIS.Core/Stream/IReadWriteObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BIS.Core.Streams 6 | { 7 | public interface IReadWriteObject : IReadObject 8 | { 9 | void Write(BinaryWriterEx output); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BIS.Core/Stream/StreamHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace BIS.Core.Streams 4 | { 5 | public static class StreamHelper 6 | { 7 | private static MemoryStream MakeBuffer(Stream stream) 8 | { 9 | var ms = new MemoryStream((int)stream.Length); 10 | stream.CopyTo(ms); 11 | ms.Position = 0; 12 | return ms; 13 | } 14 | 15 | public static T Read (Stream input) where T : IReadObject, new() 16 | { 17 | var o = new T(); 18 | o.Read(new BinaryReaderEx(MakeBuffer(input))); 19 | return o; 20 | } 21 | 22 | public static T Read(string filename) where T : IReadObject, new() 23 | { 24 | using(var input = File.OpenRead(filename)) 25 | { 26 | return Read(input); 27 | } 28 | } 29 | 30 | public static T ReadNoBuffer(Stream input) where T : IReadObject, new() 31 | { 32 | var o = new T(); 33 | o.Read(new BinaryReaderEx(MakeBuffer(input))); 34 | return o; 35 | } 36 | 37 | public static T ReadNoBuffer(string filename) where T : IReadObject, new() 38 | { 39 | using (var input = File.OpenRead(filename)) 40 | { 41 | return Read(input); 42 | } 43 | } 44 | 45 | public static void Write(this T value, string filename) where T : IReadWriteObject 46 | { 47 | using (var output = new FileStream(filename, FileMode.Create, FileAccess.Write)) 48 | { 49 | Write(value, output); 50 | } 51 | } 52 | 53 | public static void Write(this T value, Stream stream) where T : IReadWriteObject 54 | { 55 | value.Write(new BinaryWriterEx(stream)); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /BIS.P3D/BIS.P3D.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | Braini01 7 | LICENSE 8 | https://github.com/Braini01/bis-file-formats 9 | 10 | 11 | 12 | 13 | True 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BIS.P3D/MLOD/Face.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Streams; 2 | using System; 3 | 4 | namespace BIS.P3D.MLOD 5 | { 6 | [Flags] 7 | public enum FaceFlags 8 | { 9 | DEFAULT = 0, 10 | NOLIGHT = 0x1, 11 | AMBIENT = 0x2, 12 | FULLLIGHT = 0x4, 13 | BOTHSIDESLIGHT = 0x20, 14 | SKYLIGHT = 0x80, 15 | REVERSELIGHT = 0x100000, 16 | FLATLIGHT = 0x200000, 17 | LIGHT_MASK = 0x3000a7 18 | } 19 | 20 | public class Face 21 | { 22 | public int VertexCount { get; private set; } 23 | public Vertex[] Vertices { get; private set; } 24 | public FaceFlags Flags { get; private set; } 25 | public string Texture { get; private set; } 26 | public string Material { get; private set; } 27 | 28 | public Face(int nVerts, Vertex[] verts, FaceFlags flags, string texture, string material) 29 | { 30 | VertexCount = nVerts; 31 | Vertices = verts; 32 | Flags = flags; 33 | Texture = texture; 34 | Material = material; 35 | } 36 | 37 | public Face(BinaryReaderEx input) 38 | { 39 | Read(input); 40 | } 41 | 42 | public void Read(BinaryReaderEx input) 43 | { 44 | VertexCount = input.ReadInt32(); 45 | Vertices = new Vertex[4]; 46 | for (int i = 0; i < 4; ++i) 47 | { 48 | Vertices[i] = new Vertex(input); 49 | } 50 | Flags = (FaceFlags)input.ReadInt32(); 51 | Texture = input.ReadAsciiz(); 52 | Material = input.ReadAsciiz(); 53 | } 54 | 55 | public void Write(BinaryWriterEx output) 56 | { 57 | output.Write(VertexCount); 58 | for (int i = 0; i < 4; ++i) 59 | if (i < Vertices.Length && Vertices[i] != null) 60 | Vertices[i].Write(output); 61 | else 62 | { 63 | output.Write(0); 64 | output.Write(0); 65 | output.Write(0); 66 | output.Write(0); 67 | } 68 | 69 | output.Write((int)Flags); 70 | output.WriteAsciiz(Texture); 71 | output.WriteAsciiz(Material); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /BIS.P3D/MLOD/LOD.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Math; 2 | using BIS.Core.Streams; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BIS.P3D.MLOD 8 | { 9 | public class P3DM_LOD 10 | { 11 | private int Flags { get; set; } 12 | public int Version { get; private set; } 13 | public Point[] Points { get; private set; } 14 | public Vector3P[] Normals { get; private set; } 15 | public Face[] Faces { get; private set; } 16 | public LinkedList Taggs { get; private set; } 17 | public float Resolution { get; private set; } 18 | 19 | public P3DM_LOD(BinaryReaderEx input) 20 | { 21 | Read(input); 22 | } 23 | 24 | public P3DM_LOD(float resolution, Point[] points, Vector3P[] normals, Face[] faces, IEnumerable taggs) 25 | { 26 | Version = 0x100; 27 | Points = points; 28 | Normals = normals; 29 | Faces = faces; 30 | Taggs = new LinkedList(taggs); 31 | Resolution = resolution; 32 | } 33 | 34 | public void Read(BinaryReaderEx input) 35 | { 36 | if (input.ReadAscii(4) != "P3DM") 37 | throw new ArgumentException("Only P3DM LODs are supported"); 38 | 39 | var headerSize = input.ReadInt32(); 40 | Version = input.ReadInt32(); 41 | 42 | if (headerSize != 28 || Version != 0x100) 43 | throw new ArgumentOutOfRangeException("Unknown P3DM version"); 44 | var nPoints = input.ReadInt32(); 45 | var nNormals = input.ReadInt32(); 46 | var nFaces = input.ReadInt32(); 47 | 48 | Flags = input.ReadInt32(); 49 | Points = new Point[nPoints]; 50 | Normals = new Vector3P[nNormals]; 51 | Faces = new Face[nFaces]; 52 | for (int i = 0; i < nPoints; ++i) 53 | { 54 | Points[i] = new Point(input); 55 | } 56 | for (int i = 0; i < nNormals; ++i) 57 | { 58 | Normals[i] = new Vector3P(input); 59 | } 60 | for (int i = 0; i < nFaces; ++i) 61 | { 62 | Faces[i] = new Face(input); 63 | } 64 | 65 | if (input.ReadAscii(4) != "TAGG") 66 | throw new FormatException("TAGG expected"); 67 | 68 | Taggs = new LinkedList(); 69 | Tagg mlodTagg; 70 | do 71 | { 72 | mlodTagg = Tagg.ReadTagg(input, nPoints, Faces); 73 | Taggs.AddLast(mlodTagg); 74 | } 75 | while (!(mlodTagg is EOFTagg)); 76 | 77 | Resolution = input.ReadSingle(); 78 | } 79 | 80 | public void Write(BinaryWriterEx output) 81 | { 82 | var nPoints = Points.Length; 83 | var nNormals = Normals.Length; 84 | var nFaces = Faces.Length; 85 | 86 | output.WriteAscii("P3DM", 4); 87 | output.Write(28); //headerSize 88 | output.Write(Version); 89 | output.Write(nPoints); 90 | output.Write(nNormals); 91 | output.Write(nFaces); 92 | output.Write(Flags); 93 | for (int i = 0; i < nPoints; ++i) 94 | Points[i].Write(output); 95 | for (int index = 0; index < nNormals; ++index) 96 | Normals[index].Write(output); 97 | for (int index = 0; index < nFaces; ++index) 98 | Faces[index].Write(output); 99 | output.WriteAscii("TAGG", 4); 100 | foreach (Tagg tagg in Taggs) 101 | tagg.Write(output); 102 | output.Write(Resolution); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /BIS.P3D/MLOD/MLOD.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Streams; 2 | using System; 3 | using System.IO; 4 | 5 | namespace BIS.P3D.MLOD 6 | { 7 | public class MLOD 8 | { 9 | public int Version { get; private set; } 10 | public P3DM_LOD[] Lods { get; private set; } 11 | 12 | public MLOD(string fileName) : this(File.OpenRead(fileName)) {} 13 | 14 | public MLOD(Stream stream) 15 | { 16 | Read(new BinaryReaderEx(stream)); 17 | } 18 | 19 | public MLOD(P3DM_LOD[] lods) 20 | { 21 | Version = 257; 22 | Lods = lods; 23 | } 24 | 25 | private void Read(BinaryReaderEx input) 26 | { 27 | if (input.ReadAscii(4) != "MLOD") 28 | throw new FormatException("MLOD signature expected"); 29 | 30 | Version = input.ReadInt32(); 31 | if (Version != 257) 32 | throw new ArgumentException("Unknown MLOD version"); 33 | 34 | Lods = input.ReadArray(inp => new P3DM_LOD(inp)); 35 | } 36 | 37 | private void Write(BinaryWriterEx output) 38 | { 39 | output.WriteAscii("MLOD", 4); 40 | output.Write(Version); 41 | output.Write(Lods.Length); 42 | for (int index = 0; index < Lods.Length; ++index) 43 | Lods[index].Write(output); 44 | } 45 | 46 | public void WriteToFile(string file, bool allowOverwriting=false) 47 | { 48 | var mode = (allowOverwriting) ? FileMode.Create : FileMode.CreateNew; 49 | 50 | var fs = new FileStream(file, mode); 51 | using (var output = new BinaryWriterEx(fs)) 52 | { 53 | Write(output); 54 | } 55 | } 56 | 57 | public MemoryStream WriteToMemory() 58 | { 59 | var memStream = new MemoryStream(100000); 60 | var outStream = new BinaryWriterEx(memStream); 61 | Write(outStream); 62 | outStream.Position = 0; 63 | return memStream; 64 | } 65 | 66 | public void WriteToStream(Stream stream) 67 | { 68 | var output = new BinaryWriterEx(stream); 69 | Write(output); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /BIS.P3D/MLOD/Point.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Math; 2 | using BIS.Core.Streams; 3 | using System; 4 | using System.IO; 5 | 6 | namespace BIS.P3D.MLOD 7 | { 8 | [Flags] 9 | public enum PointFlags 10 | { 11 | NONE = 0, 12 | 13 | ONLAND = 0x1, 14 | UNDERLAND = 0x2, 15 | ABOVELAND = 0x4, 16 | KEEPLAND = 0x8, 17 | LAND_MASK = 0xf, 18 | 19 | DECAL = 0x100, 20 | VDECAL = 0x200, 21 | DECAL_MASK = 0x300, 22 | 23 | NOLIGHT = 0x10, 24 | AMBIENT = 0x20, 25 | FULLLIGHT = 0x40, 26 | HALFLIGHT = 0x80, 27 | LIGHT_MASK = 0xf0, 28 | 29 | NOFOG = 0x1000, 30 | SKYFOG = 0x2000, 31 | FOG_MASK = 0x3000, 32 | 33 | USER_MASK = 0xff0000, 34 | USER_STEP = 0x010000, 35 | 36 | SPECIAL_MASK = 0xf000000, 37 | SPECIAL_HIDDEN = 0x1000000, 38 | 39 | ALL_FLAGS = LAND_MASK | DECAL_MASK | LIGHT_MASK | FOG_MASK | USER_MASK | SPECIAL_MASK 40 | } 41 | 42 | public class Point : Vector3P 43 | { 44 | public PointFlags PointFlags { get; private set; } 45 | 46 | public Point(Vector3P pos, PointFlags flags) : base(pos.X, pos.Y, pos.Z) 47 | { 48 | PointFlags = flags; 49 | } 50 | 51 | public Point(BinaryReaderEx input) : base(input) 52 | { 53 | PointFlags = (PointFlags)input.ReadInt32(); 54 | } 55 | 56 | public new void Write(BinaryWriterEx output) 57 | { 58 | base.Write(output); 59 | output.Write((int)PointFlags); 60 | } 61 | } 62 | 63 | public class Vertex 64 | { 65 | public int PointIndex { get; private set; } 66 | public int NormalIndex { get; private set; } 67 | public float U { get; private set; } 68 | public float V { get; private set; } 69 | 70 | public Vertex(BinaryReaderEx input) 71 | { 72 | Read(input); 73 | } 74 | 75 | public Vertex(int point, int normal, float u, float v) 76 | { 77 | PointIndex = point; 78 | NormalIndex = normal; 79 | U = u; 80 | V = v; 81 | } 82 | 83 | public void Read(BinaryReaderEx input) 84 | { 85 | PointIndex = input.ReadInt32(); 86 | NormalIndex = input.ReadInt32(); 87 | U = input.ReadSingle(); 88 | V = input.ReadSingle(); 89 | } 90 | 91 | public void Write(BinaryWriter output) 92 | { 93 | output.Write(PointIndex); 94 | output.Write(NormalIndex); 95 | output.Write(U); 96 | output.Write(V); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /BIS.P3D/P3D.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Math; 2 | using BIS.Core.Streams; 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace BIS.P3D 8 | { 9 | //public abstract class P3D_LOD 10 | //{ 11 | // public float Resolution { get; protected set; } 12 | 13 | // public string Name 14 | // { 15 | // get { return Resolution.GetLODName(); } 16 | // } 17 | 18 | // public abstract Vector3P[] Points 19 | // { 20 | // get; 21 | // } 22 | 23 | // public abstract Vector3P[] NormalVectors 24 | // { 25 | // get; 26 | // } 27 | 28 | // public abstract string[] Textures 29 | // { 30 | // get; 31 | // } 32 | 33 | // public abstract string[] MaterialNames 34 | // { 35 | // get; 36 | // } 37 | //} 38 | 39 | public static class P3D 40 | { 41 | //public int Version { get; protected set; } 42 | 43 | //public static P3D GetInstance(string fileName) 44 | //{ 45 | // return GetInstance(File.OpenRead(fileName)); 46 | //} 47 | 48 | //public static P3D GetInstance(Stream stream) 49 | //{ 50 | // var binaryReader = new BinaryReaderEx(stream); 51 | // var sig = binaryReader.ReadAscii(4); 52 | // stream.Position -= 4; 53 | // if (sig == "ODOL") 54 | // return new ODOL.ODOL(stream); 55 | // if (sig == "MLOD") 56 | // return new MLOD.MLOD(stream); 57 | // else 58 | // throw new FormatException("Neither MLOD nor ODOL signature detected"); 59 | //} 60 | 61 | public static bool IsODOL(string filePath) 62 | { 63 | return IsODOL(File.OpenRead(filePath)); 64 | } 65 | 66 | public static bool IsODOL(Stream stream) 67 | { 68 | bool result = false; 69 | if (stream.ReadByte() == 'O' 70 | && stream.ReadByte() == 'D' 71 | && stream.ReadByte() == 'O' 72 | && stream.ReadByte() == 'L') 73 | result = true; ; 74 | 75 | stream.Position = 0; 76 | 77 | return result; 78 | } 79 | public static bool IsMLOD(string filePath) 80 | { 81 | return IsMLOD(File.OpenRead(filePath)); 82 | } 83 | 84 | public static bool IsMLOD(Stream stream) 85 | { 86 | bool result = false; 87 | if (stream.ReadByte() == 'M' 88 | && stream.ReadByte() == 'L' 89 | && stream.ReadByte() == 'O' 90 | && stream.ReadByte() == 'D') 91 | result = true; ; 92 | 93 | stream.Position = 0; 94 | 95 | return result; 96 | } 97 | 98 | //public abstract P3D_LOD[] LODs { get; } 99 | 100 | //public virtual P3D_LOD GetLOD(float resolution) 101 | //{ 102 | // return LODs.FirstOrDefault(lod => lod.Resolution == resolution); 103 | //} 104 | 105 | //public abstract float Mass 106 | //{ 107 | // get; 108 | //} 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /BIS.P3D/Resolutions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BIS.P3D 6 | { 7 | public enum LodName 8 | { 9 | ViewGunner, 10 | ViewPilot, 11 | ViewCargo, 12 | Geometry, 13 | Memory, 14 | LandContact, 15 | Roadway, 16 | Paths, 17 | HitPoints, 18 | ViewGeometry, 19 | FireGeometry, 20 | ViewCargoGeometry, 21 | ViewCargoFireGeometry, 22 | ViewCommander, 23 | ViewCommanderGeometry, 24 | ViewCommanderFireGeometry, 25 | ViewPilotGeometry, 26 | ViewPilotFireGeometry, 27 | ViewGunnerGeometry, 28 | ViewGunnerFireGeometry, 29 | SubParts, 30 | ShadowVolumeViewCargo, 31 | ShadowVolumeViewPilot, 32 | ShadowVolumeViewGunner, 33 | Wreck, 34 | PhysX, 35 | ShadowVolume, 36 | Resolution, 37 | Undefined 38 | } 39 | 40 | public static class Resolution 41 | { 42 | private const float specialLod = 1e15f; 43 | 44 | public const float GEOMETRY = 1e13f; 45 | public const float BUOYANCY = 2e13f; 46 | public const float PHYSXOLD = 3e13f; 47 | public const float PHYSX = 4e13f; 48 | 49 | public const float MEMORY = 1e15f; 50 | public const float LANDCONTACT = 2e15f; 51 | public const float ROADWAY = 3e15f; 52 | public const float PATHS = 4e15f; 53 | public const float HITPOINTS = 5e15f; 54 | 55 | public const float VIEW_GEOMETRY = 6e15f; 56 | public const float FIRE_GEOMETRY = 7e15f; 57 | 58 | public const float VIEW_GEOMETRY_CARGO = 8e15f; 59 | public const float VIEW_GEOMETRY_PILOT = 13e15f; 60 | public const float VIEW_GEOMETRY_GUNNER = 15e15f; 61 | public const float FIRE_GEOMETRY_GUNNER = 16e15f; 62 | 63 | public const float SUBPARTS = 17e15f; 64 | 65 | public const float SHADOWVOLUME_CARGO = 18e15f; 66 | public const float SHADOWVOLUME_PILOT = 19e15f; 67 | public const float SHADOWVOLUME_GUNNER = 20e15f; 68 | 69 | public const float WRECK = 21e15f; 70 | 71 | public const float VIEW_COMMANDER = 10e15f; 72 | public const float VIEW_GUNNER = 1000f; 73 | public const float VIEW_PILOT = 1100f; 74 | public const float VIEW_CARGO = 1200f; 75 | 76 | public const float SHADOWVOLUME = 10000.0f; 77 | public const float SHADOWBUFFER = 11000.0f; 78 | 79 | public const float SHADOW_MIN = 10000.0f; 80 | public const float SHADOW_MAX = 20000.0f; 81 | 82 | /// 83 | /// Tells us if the current LOD with given resolution has normal NamedSelections (returns true) or empty ones (return false) 84 | /// 85 | /// 86 | /// 87 | public static bool KeepsNamedSelections(float r) 88 | { 89 | return r == MEMORY || r == FIRE_GEOMETRY || r == GEOMETRY 90 | || r == VIEW_GEOMETRY || r == VIEW_GEOMETRY_PILOT || r == VIEW_GEOMETRY_GUNNER 91 | || r == VIEW_GEOMETRY_CARGO || r == PATHS || r == HITPOINTS 92 | || r == PHYSX || r == BUOYANCY; 93 | } 94 | 95 | public static LodName GetLODType(this float res) 96 | { 97 | if (res == specialLod) return LodName.Memory; 98 | if (res == 2 * specialLod) return LodName.LandContact; 99 | if (res == 3 * specialLod) return LodName.Roadway; 100 | if (res == 4 * specialLod) return LodName.Paths; 101 | 102 | if (res == 5 * specialLod) return LodName.HitPoints; 103 | if (res == 6 * specialLod) return LodName.ViewGeometry; 104 | if (res == 7 * specialLod) return LodName.FireGeometry; 105 | if (res == 8 * specialLod) return LodName.ViewCargoGeometry; 106 | if (res == 9 * specialLod) return LodName.ViewCargoFireGeometry; 107 | if (res == 10 * specialLod) return LodName.ViewCommander; 108 | if (res == 11 * specialLod) return LodName.ViewCommanderGeometry; 109 | if (res == 12 * specialLod) return LodName.ViewCommanderFireGeometry; 110 | if (res == 13 * specialLod) return LodName.ViewPilotGeometry; 111 | if (res == 14 * specialLod) return LodName.ViewPilotFireGeometry; 112 | if (res == 15 * specialLod) return LodName.ViewGunnerGeometry; 113 | if (res == 16 * specialLod) return LodName.ViewGunnerFireGeometry; 114 | if (res == 17 * specialLod) return LodName.SubParts; 115 | if (res == 18 * specialLod) return LodName.ShadowVolumeViewCargo; 116 | if (res == 19 * specialLod) return LodName.ShadowVolumeViewPilot; 117 | if (res == 20 * specialLod) return LodName.ShadowVolumeViewGunner; 118 | if (res == 21 * specialLod) return LodName.Wreck; 119 | 120 | if (res == 1000.0f) return LodName.ViewGunner; 121 | if (res == 1100.0f) return LodName.ViewPilot; 122 | if (res == 1200.0f) return LodName.ViewCargo; 123 | 124 | if (res == 1e13f) return LodName.Geometry; 125 | if (res == 4e13f) return LodName.PhysX; 126 | 127 | if (res >= 10000.0 && res <= 20000.0) return LodName.ShadowVolume; 128 | 129 | return LodName.Resolution; 130 | } 131 | 132 | public static string GetLODName(this float res) 133 | { 134 | var lodType = res.GetLODType(); 135 | 136 | if (lodType == LodName.Resolution) 137 | return res.ToString("0.000"); 138 | if (lodType == LodName.ShadowVolume) 139 | return "ShadowVolume" + (res - 10000f).ToString("0.000"); 140 | else 141 | return Enum.GetName(typeof(LodName), lodType); 142 | } 143 | 144 | public static bool IsResolution(float r) 145 | { 146 | return r < SHADOW_MIN; 147 | } 148 | 149 | public static bool IsShadow(float r) 150 | { 151 | return (r >= SHADOW_MIN && r < SHADOW_MAX) || 152 | r == SHADOWVOLUME_GUNNER || 153 | r == SHADOWVOLUME_PILOT || 154 | r == SHADOWVOLUME_CARGO; 155 | } 156 | 157 | public static bool IsVisual(float r) 158 | { 159 | return IsResolution(r) || 160 | r == VIEW_CARGO || 161 | r == VIEW_GUNNER || 162 | r == VIEW_PILOT || 163 | r == VIEW_COMMANDER; 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /BIS.PAA.Encoder/BIS.PAA.Encoder.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /BIS.PAA.Encoder/MipmapEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BIS.PAA.Encoder 6 | { 7 | internal class MipmapEncoder 8 | { 9 | public MipmapEncoder(byte[] mipmap, int width, int height, int offset) 10 | { 11 | PaaData = mipmap; 12 | Width = (ushort)width; 13 | WidthEncoded = Width; 14 | Height = (ushort)height; 15 | Offset = offset; 16 | 17 | if (Width >= 256 || Height >= 256) 18 | { 19 | PaaData = MiniLZO.MiniLZO.Compress(PaaData); // Less efficient than BI's ImageToPAA LZO lib, but works 20 | WidthEncoded = (ushort)(Width | 0x8000); 21 | } 22 | } 23 | 24 | public ushort Width { get; } 25 | public ushort WidthEncoded { get; } 26 | public ushort Height { get; } 27 | public int Offset { get; } 28 | public byte[] PaaData { get; } 29 | public int MipmapEntrySize => PaaData.Length + 7; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BIS.PAA.Encoder/PAAFlags.cs: -------------------------------------------------------------------------------- 1 | namespace BIS.PAA.Encoder 2 | { 3 | public enum PAAFlags 4 | { 5 | /// 6 | /// 7 | /// 8 | None = 0, 9 | 10 | /// 11 | /// Interpolated alpha channel (default behaviour) 12 | /// 13 | InterpolatedAlpha = 1, 14 | 15 | /// 16 | /// Alpha channel interpolation disabled 17 | /// 18 | KeepAlphaAsIs = 2 19 | } 20 | } -------------------------------------------------------------------------------- /BIS.PAA.Encoder/PaaEncoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using BCnEncoder.Shared; 5 | using BIS.Core.Streams; 6 | using Microsoft.Toolkit.HighPerformance; 7 | 8 | namespace BIS.PAA.Encoder 9 | { 10 | public static class PaaEncoder 11 | { 12 | public static void WritePAA(string file, ColorRgba32[,] image, PAAType type = PAAType.UNDEFINED, PAAFlags flags = PAAFlags.InterpolatedAlpha) 13 | { 14 | using (var writer = new BinaryWriterEx(File.Create(file))) 15 | { 16 | WritePAA(writer, image, type, flags); 17 | } 18 | } 19 | 20 | public static void WritePAA(BinaryWriterEx writer, ColorRgba32[,] image, PAAType type = PAAType.UNDEFINED, PAAFlags flags = PAAFlags.InterpolatedAlpha) 21 | { 22 | var max = new ColorRgba32(0, 0, 0, 0); 23 | ulong sumR = 0; 24 | ulong sumG = 0; 25 | ulong sumB = 0; 26 | ulong sumA = 0; 27 | byte minA = 255; 28 | ulong totalPixels = 0; 29 | for (int y = 0; y < image.GetLength(0); ++y) 30 | { 31 | for (int x = 0; x < image.GetLength(1); ++x) 32 | { 33 | var color = image[y,x]; 34 | max.r = Math.Max(color.r, max.r); 35 | max.g = Math.Max(color.g, max.g); 36 | max.b = Math.Max(color.b, max.b); 37 | max.a = Math.Max(color.a, max.a); 38 | minA = Math.Min(color.a, minA); 39 | if (color.a > 128) 40 | { 41 | sumR += color.r; 42 | sumG += color.g; 43 | sumB += color.b; 44 | sumA += color.a; 45 | totalPixels++; 46 | } 47 | } 48 | } 49 | var avg = new ColorRgba32((byte)(sumR / totalPixels),(byte)(sumG / totalPixels),(byte)(sumB / totalPixels),(byte)(sumA / totalPixels)); 50 | 51 | if (type == PAAType.UNDEFINED) 52 | { 53 | if (minA == 255) 54 | { 55 | type = PAAType.DXT1; 56 | } 57 | else 58 | { 59 | type = PAAType.DXT5; 60 | } 61 | } 62 | 63 | WritePAA(writer, new ReadOnlyMemory2D(image), max, avg, type, flags); 64 | } 65 | 66 | public static void WritePAA(BinaryWriterEx writer, ReadOnlyMemory2D image, ColorRgba32 max, ColorRgba32 avg, PAAType type = PAAType.DXT5, PAAFlags flags = PAAFlags.InterpolatedAlpha) 67 | { 68 | if (type != PAAType.DXT5 && type != PAAType.DXT1) 69 | { 70 | throw new NotSupportedException(); 71 | } 72 | 73 | var enc = new BCnEncoder.Encoder.BcEncoder(type == PAAType.DXT5 ? CompressionFormat.Bc3 : CompressionFormat.Bc1); 74 | 75 | var mipmaps = new List(); 76 | 77 | var width = image.Width; 78 | var height = image.Width; 79 | var offset = type == PAAType.DXT5 ? 128 : 112; 80 | foreach (var mipmap in enc.EncodeToRawBytes(image)) 81 | { 82 | if (width > 2 || height > 2) 83 | { 84 | var menc = new MipmapEncoder(mipmap, width, height, offset); 85 | offset += menc.MipmapEntrySize; 86 | mipmaps.Add(menc); 87 | width = width / 2; 88 | height = height / 2; 89 | } 90 | } 91 | 92 | writer.Write(type == PAAType.DXT5 ? (ushort)0xff05 : (ushort)0xff01); 93 | 94 | writer.WriteAscii("GGATCGVA", 8); 95 | writer.Write((uint)4); 96 | writer.Write(avg.r); 97 | writer.Write(avg.g); 98 | writer.Write(avg.b); 99 | writer.Write(avg.a); 100 | 101 | writer.WriteAscii("GGATCXAM", 8); 102 | writer.Write((uint)4); 103 | writer.Write(max.r); 104 | writer.Write(max.g); 105 | writer.Write(max.b); 106 | writer.Write(max.a); 107 | 108 | if (type == PAAType.DXT5) 109 | { 110 | writer.WriteAscii("GGATGALF", 8); 111 | writer.Write((uint)4); 112 | writer.Write((uint)flags); 113 | } 114 | 115 | writer.WriteAscii("GGATSFFO", 8); 116 | writer.Write(16 * 4); 117 | for (int i = 0; i < 16; ++i ) 118 | { 119 | if (i < mipmaps.Count) 120 | { 121 | writer.Write((uint)mipmaps[i].Offset); 122 | } 123 | else 124 | { 125 | writer.Write((uint)0); 126 | } 127 | } 128 | 129 | writer.Write((ushort)0x0000); 130 | 131 | int index = 0; 132 | foreach (var mipmap in mipmaps) 133 | { 134 | if (writer.Position != mipmap.Offset) 135 | { 136 | throw new Exception($"Wrong offset @{mipmap.Width} : {writer.Position} != { mipmap.Offset}"); 137 | } 138 | writer.Write(mipmap.WidthEncoded); 139 | writer.Write(mipmap.Height); 140 | writer.WriteUInt24((uint)mipmap.PaaData.Length); 141 | writer.Write(mipmap.PaaData); 142 | index++; 143 | width = width / 2; 144 | height = height / 2; 145 | } 146 | writer.Write((uint)0x0000); 147 | writer.Write((ushort)0x0000); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /BIS.PAA/BIS.PAA.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | Braini01 7 | LICENSE 8 | https://github.com/Braini01/bis-file-formats 9 | 10 | 11 | 12 | 13 | True 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BIS.PAA/ChannelSwizzling.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace BIS.PAA 7 | { 8 | internal enum TexSwizzle: byte 9 | { 10 | TSAlpha, 11 | TSRed, 12 | TSGreen, 13 | TSBlue, 14 | TSInvAlpha, 15 | TSInvRed, 16 | TSInvGreen, 17 | TSInvBlue, 18 | TSOne 19 | } 20 | 21 | internal struct ARGBSwizzle 22 | { 23 | internal TexSwizzle SwizB; 24 | internal TexSwizzle SwizG; 25 | internal TexSwizzle SwizR; 26 | internal TexSwizzle SwizA; 27 | 28 | internal TexSwizzle this[int ch] 29 | { 30 | get 31 | { 32 | switch(ch) 33 | { 34 | case 0: return SwizA; 35 | case 1: return SwizR; 36 | case 2: return SwizG; 37 | case 3: return SwizB; 38 | default: throw new ArgumentOutOfRangeException(); 39 | } 40 | } 41 | 42 | set 43 | { 44 | switch (ch) 45 | { 46 | case 0: SwizA = value; break; 47 | case 1: SwizR = value; break; 48 | case 2: SwizG = value; break; 49 | case 3: SwizB = value; break; 50 | default: throw new ArgumentOutOfRangeException(); 51 | } 52 | } 53 | } 54 | 55 | 56 | static ARGBSwizzle() 57 | { 58 | Default.SwizA = TexSwizzle.TSAlpha; 59 | Default.SwizR = TexSwizzle.TSRed; 60 | Default.SwizG = TexSwizzle.TSGreen; 61 | Default.SwizB = TexSwizzle.TSBlue; 62 | } 63 | 64 | internal static ARGBSwizzle Default; 65 | } 66 | 67 | internal static class ChannelSwizzling 68 | { 69 | internal static void Apply(byte[] argbPixels, ARGBSwizzle swizzle) 70 | { 71 | ARGBSwizzle invSwizzle = ARGBSwizzle.Default; 72 | InvertSwizzle(ref invSwizzle, in swizzle, 0); 73 | InvertSwizzle(ref invSwizzle, in swizzle, 1); 74 | InvertSwizzle(ref invSwizzle, in swizzle, 2); 75 | InvertSwizzle(ref invSwizzle, in swizzle, 3); 76 | ChannelSwizzle(in invSwizzle, argbPixels); 77 | } 78 | 79 | private static void InvertSwizzle(ref ARGBSwizzle invSwizzle, in ARGBSwizzle swizzle, byte ch) 80 | { 81 | TexSwizzle swiz = TexSwizzle.TSAlpha + ch; 82 | if (swizzle[ch] >= TexSwizzle.TSInvAlpha && swizzle[ch] <= TexSwizzle.TSInvBlue) 83 | { 84 | invSwizzle[swizzle[ch] - TexSwizzle.TSInvAlpha] = TexSwizzle.TSInvAlpha - TexSwizzle.TSAlpha + swiz; 85 | } 86 | else if (swizzle[ch] <= TexSwizzle.TSBlue) 87 | { 88 | invSwizzle[(int)swizzle[ch]] = swiz; 89 | } 90 | } 91 | 92 | private static (int offset, int mul, int add) CheckInvSwizzle(TexSwizzle swiz) 93 | { 94 | if (swiz == TexSwizzle.TSOne) 95 | { 96 | // one - ignore input (mul by 0) and set it to one (add 255) 97 | return (0, 0, 255); 98 | } 99 | int mul = 1; 100 | int add = 0; 101 | switch (swiz) 102 | { 103 | case TexSwizzle.TSInvAlpha: swiz = TexSwizzle.TSAlpha; mul = -1; add = 255; break; 104 | case TexSwizzle.TSInvRed: swiz = TexSwizzle.TSRed; mul = -1; add = 255; break; 105 | case TexSwizzle.TSInvGreen: swiz = TexSwizzle.TSGreen; mul = -1; add = 255; break; 106 | case TexSwizzle.TSInvBlue: swiz = TexSwizzle.TSBlue; mul = -1; add = 255; break; 107 | } 108 | int offset = swiz < TexSwizzle.TSOne ? 24 - (int)swiz * 8 : 0; 109 | 110 | return (offset, mul, add); 111 | } 112 | 113 | private static void ChannelSwizzle(in ARGBSwizzle channelSwizzle, byte[] argbPixels) 114 | { 115 | if (channelSwizzle[0] == TexSwizzle.TSAlpha && channelSwizzle[1] == TexSwizzle.TSRed && 116 | channelSwizzle[2] == TexSwizzle.TSGreen && channelSwizzle[3] == TexSwizzle.TSBlue) 117 | { 118 | return; 119 | } 120 | 121 | (int aOffset, int mulA, int addA) = CheckInvSwizzle(channelSwizzle[0]); 122 | (int rOffset, int mulR, int addR) = CheckInvSwizzle(channelSwizzle[1]); 123 | (int gOffset, int mulG, int addG) = CheckInvSwizzle(channelSwizzle[2]); 124 | (int bOffset, int mulB, int addB) = CheckInvSwizzle(channelSwizzle[3]); 125 | 126 | int nPixel = argbPixels.Length / 4; 127 | while (--nPixel >= 0) 128 | { 129 | int pixOffset = nPixel * 4; 130 | int p = BitConverter.ToInt32(argbPixels, pixOffset); 131 | int a = (p >> aOffset) & 0xff; 132 | int r = (p >> rOffset) & 0xff; 133 | int g = (p >> gOffset) & 0xff; 134 | int b = (p >> bOffset) & 0xff; 135 | 136 | argbPixels[pixOffset] = (byte)(b * mulB + addB); 137 | argbPixels[pixOffset + 1] = (byte)(g * mulG + addG); 138 | argbPixels[pixOffset + 2] = (byte)(r * mulR + addR); 139 | argbPixels[pixOffset + 3] = (byte)(a * mulA + addA); 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /BIS.PAA/Mipmap.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Streams; 2 | using System; 3 | 4 | namespace BIS.PAA 5 | { 6 | public class Mipmap 7 | { 8 | private const ushort MAGIC_LZW_W = 1234; 9 | private const ushort MAGIC_LZW_H = 8765; 10 | 11 | private bool hasMagicLZW; 12 | 13 | public int Offset { get; } 14 | public int DataOffset { get; } 15 | public bool IsLZOCompressed { get; } 16 | public ushort Width { get; } 17 | public ushort Height { get; } 18 | public uint DataSize { get; } 19 | 20 | public Mipmap(BinaryReaderEx input, int offset) 21 | { 22 | Offset = offset; 23 | 24 | IsLZOCompressed = false; 25 | hasMagicLZW = false; 26 | Width = input.ReadUInt16(); 27 | Height = input.ReadUInt16(); 28 | 29 | if (Width == MAGIC_LZW_W && Height == MAGIC_LZW_H) 30 | { 31 | hasMagicLZW = true; 32 | Width = input.ReadUInt16(); 33 | Height = input.ReadUInt16(); 34 | } 35 | 36 | if ((Width & 0x8000) != 0) 37 | { 38 | Width &= 0x7FFF; 39 | IsLZOCompressed = true; 40 | } 41 | 42 | DataSize = input.ReadUInt24(); 43 | DataOffset = (int)input.Position; 44 | input.Position += DataSize; //skip data 45 | } 46 | 47 | public byte[] GetRawPixelData(BinaryReaderEx input, PAAType type) 48 | { 49 | input.Position = DataOffset; 50 | 51 | uint expectedSize = (uint)(Width * Height); 52 | 53 | switch (type) 54 | { 55 | case PAAType.AI88: 56 | case PAAType.RGBA_5551: 57 | case PAAType.RGBA_4444: 58 | expectedSize *= 2; 59 | return input.ReadLZSS(expectedSize, true); 60 | 61 | case PAAType.P8: 62 | return !hasMagicLZW ? 63 | input.ReadCompressedIndices((int)DataSize, expectedSize) : 64 | input.ReadLZSS(expectedSize, true); 65 | 66 | case PAAType.RGBA_8888: 67 | expectedSize *= 4; 68 | return input.ReadLZSS(expectedSize, true); 69 | 70 | case PAAType.DXT1: 71 | expectedSize /= 2; 72 | goto case PAAType.DXT2; 73 | case PAAType.DXT2: 74 | case PAAType.DXT3: 75 | case PAAType.DXT4: 76 | case PAAType.DXT5: 77 | return !IsLZOCompressed ? 78 | input.ReadBytes((int)DataSize) : 79 | input.ReadLZO(expectedSize); 80 | 81 | default: throw new ArgumentException("Unexpected PAA type", nameof(type)); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /BIS.PAA/PAA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | 6 | using BIS.Core.Streams; 7 | 8 | namespace BIS.PAA 9 | { 10 | 11 | 12 | public enum PAAType 13 | { 14 | DXT1 = 1, 15 | DXT2 = 2, 16 | DXT3 = 3, 17 | DXT4 = 4, 18 | DXT5 = 5, 19 | RGBA_5551, 20 | RGBA_4444, 21 | AI88, 22 | RGBA_8888, 23 | P8, //8bit index to palette 24 | 25 | UNDEFINED = -1 26 | } 27 | 28 | public class PAA 29 | { 30 | private List mipmaps; 31 | private int[] mipmapOffsets = new int[16]; 32 | 33 | public PAAType Type { get; private set; } = PAAType.UNDEFINED; 34 | public Palette Palette { get; private set; } 35 | 36 | public int Width => mipmaps[0].Width; 37 | public int Height => mipmaps[0].Height; 38 | 39 | 40 | public PAA(string file) : this(File.OpenRead(file), !file.EndsWith(".pac")) { } 41 | public PAA(Stream stream, bool isPac = false) : this(new BinaryReaderEx(stream), isPac) { } 42 | 43 | public PAA(BinaryReaderEx stream, bool isPac = false) 44 | { 45 | Read(stream, isPac); 46 | } 47 | 48 | public IEnumerable Mipmaps => mipmaps; 49 | 50 | public Mipmap this[int i] => mipmaps[i]; 51 | 52 | private static PAAType MagicNumberToType(ushort magic) 53 | { 54 | switch (magic) 55 | { 56 | case 0x4444: return PAAType.RGBA_4444; 57 | case 0x8080: return PAAType.AI88; 58 | case 0x1555: return PAAType.RGBA_5551; 59 | case 0xff01: return PAAType.DXT1; 60 | case 0xff02: return PAAType.DXT2; 61 | case 0xff03: return PAAType.DXT3; 62 | case 0xff04: return PAAType.DXT4; 63 | case 0xff05: return PAAType.DXT5; 64 | } 65 | 66 | return PAAType.UNDEFINED; 67 | } 68 | 69 | private void Read(BinaryReaderEx input, bool isPac = false) 70 | { 71 | var magic = input.ReadUInt16(); 72 | var type = MagicNumberToType(magic); 73 | if (type == PAAType.UNDEFINED) 74 | { 75 | type = (!isPac) ? PAAType.RGBA_4444 : PAAType.P8; 76 | input.Position -= 2; 77 | } 78 | Type = type; 79 | 80 | Palette = new Palette(type); 81 | Palette.Read(input, mipmapOffsets); 82 | 83 | mipmaps = new List(16); 84 | int i = 0; 85 | while (input.ReadUInt32() != 0) 86 | { 87 | input.Position -= 4; 88 | 89 | Debug.Assert(input.Position == mipmapOffsets[i]); 90 | var mipmap = new Mipmap(input, mipmapOffsets[i++]); 91 | mipmaps.Add(mipmap); 92 | } 93 | if (input.ReadUInt16() != 0) 94 | throw new FormatException("Expected two more zero's at end of file."); 95 | } 96 | 97 | public static byte[] GetARGB32PixelData(Stream paaStream, bool isPac = false, int mipmapIndex = 0) 98 | { 99 | var paa = new PAA(paaStream, isPac); 100 | return GetARGB32PixelData(paa, paaStream, mipmapIndex); 101 | } 102 | 103 | public static byte[] GetARGB32PixelData(PAA paa, Stream paaStream, int mipmapIndex = 0) 104 | { 105 | Mipmap mipmap = paa[mipmapIndex]; 106 | 107 | return GetARGB32PixelData(paa, paaStream, mipmap); 108 | } 109 | 110 | public static byte[] GetARGB32PixelData(PAA paa, Stream paaStream, Mipmap mipmap) 111 | { 112 | var input = new BinaryReaderEx(paaStream); 113 | var rawData = mipmap.GetRawPixelData(input, paa.Type); 114 | 115 | byte[] argbPixels; 116 | switch (paa.Type) 117 | { 118 | case PAAType.RGBA_8888: 119 | case PAAType.P8: 120 | return PixelFormatConversion.P8ToARGB32(rawData, paa.Palette); //never uses swizzling 121 | case PAAType.DXT1: 122 | case PAAType.DXT2: 123 | case PAAType.DXT3: 124 | case PAAType.DXT4: 125 | case PAAType.DXT5: 126 | argbPixels = PixelFormatConversion.DXTToARGB32(rawData, mipmap.Width, mipmap.Height, (int)paa.Type); break; 127 | case PAAType.RGBA_4444: 128 | argbPixels = PixelFormatConversion.ARGB16ToARGB32(rawData); break; 129 | case PAAType.RGBA_5551: 130 | argbPixels = PixelFormatConversion.ARGB1555ToARGB32(rawData); break; 131 | case PAAType.AI88: 132 | argbPixels = PixelFormatConversion.AI88ToARGB32(rawData); break; 133 | default: 134 | throw new Exception($"Cannot retrieve pixel data from this PaaType: {paa.Type}"); 135 | } 136 | 137 | ChannelSwizzling.Apply(argbPixels, paa.Palette.ChannelSwizzle); 138 | return argbPixels; 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /BIS.PAA/Palette.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core; 2 | using BIS.Core.Streams; 3 | using System.Diagnostics; 4 | 5 | namespace BIS.PAA 6 | { 7 | public class Palette 8 | { 9 | public const int PicFlagAlpha = 1; 10 | public const int PicFlagTransparent = 2; 11 | 12 | public PackedColor[] Colors { get; private set; } 13 | 14 | public PackedColor AverageColor { get; private set; } 15 | //! color used to maximize dynamic range 16 | public PackedColor MaxColor { get; private set; } 17 | 18 | internal ARGBSwizzle ChannelSwizzle { get; private set; } = ARGBSwizzle.Default; 19 | 20 | public bool IsAlpha { get; private set; } 21 | public bool IsTransparent { get; private set; } 22 | 23 | 24 | public Palette(PAAType format) 25 | { 26 | MaxColor = new PackedColor(0xffffffff); 27 | switch (format) 28 | { 29 | case PAAType.RGBA_4444: 30 | case PAAType.RGBA_8888: 31 | case PAAType.AI88: 32 | AverageColor = new PackedColor(0x80c02020); 33 | break; 34 | default: 35 | AverageColor = new PackedColor(0xff802020); 36 | break; 37 | } 38 | } 39 | 40 | public void Read(BinaryReaderEx input, int[] startOffsets) 41 | { 42 | //read Taggs 43 | while (input.ReadAscii(4) == "GGAT") 44 | { 45 | var taggName = input.ReadAscii(4); 46 | int taggSize = input.ReadInt32(); 47 | 48 | switch (taggName) 49 | { 50 | case "CXAM": //MAXC 51 | Debug.Assert(taggSize == 4); 52 | MaxColor = new PackedColor(input.ReadUInt32()); 53 | break; 54 | case "CGVA": //AVGC 55 | Debug.Assert(taggSize == 4); 56 | AverageColor = new PackedColor(input.ReadUInt32()); 57 | break; 58 | case "GALF": //FLAG 59 | Debug.Assert(taggSize == 4); 60 | 61 | int flags = input.ReadInt32(); 62 | if ((flags & PicFlagAlpha) != 0) 63 | IsAlpha = true; 64 | if ((flags & PicFlagTransparent) != 0) 65 | IsTransparent = true; 66 | break; 67 | 68 | case "SFFO": //OFFS 69 | int nOffs = taggSize / sizeof(int); 70 | for (int i = 0; i < nOffs; i++) 71 | { 72 | startOffsets[i] = input.ReadInt32(); 73 | } 74 | break; 75 | case "ZIWS": //SWIZ 76 | Debug.Assert(taggSize == 4); 77 | ARGBSwizzle newSwizzle; 78 | newSwizzle.SwizA = (TexSwizzle)input.ReadByte(); 79 | newSwizzle.SwizR = (TexSwizzle)input.ReadByte(); 80 | newSwizzle.SwizG = (TexSwizzle)input.ReadByte(); 81 | newSwizzle.SwizB = (TexSwizzle)input.ReadByte(); 82 | ChannelSwizzle = newSwizzle; 83 | break; 84 | 85 | default: 86 | //just skip the data 87 | Debug.Fail("What is that unknown PAA tagg?"); 88 | input.Position += taggSize; 89 | break; 90 | } 91 | } 92 | input.Position -= 4; 93 | 94 | //read palette colors 95 | var nPaletteColors = input.ReadUInt16(); 96 | Colors = new PackedColor[nPaletteColors]; 97 | for (int index = 0; index < nPaletteColors; ++index) 98 | { 99 | var b = input.ReadByte(); 100 | var g = input.ReadByte(); 101 | var r = input.ReadByte(); 102 | Colors[index] = new PackedColor(r, g, b); 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /BIS.PBO/BIS.PBO.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | Braini01, GrueArbre 7 | LICENSE 8 | https://github.com/Braini01/bis-file-formats 9 | 10 | 11 | 12 | 13 | True 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BIS.PBO/FileEntry.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Streams; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace BIS.PBO 7 | { 8 | public class FileEntry 9 | { 10 | public string FileName { get; set; } 11 | public int CompressedMagic { get; set; } 12 | public int UncompressedSize { get; set; } 13 | public int StartOffset { get; set; } 14 | public int TimeStamp { get; set; } 15 | public int DataSize { get; set; } 16 | 17 | public static int VersionMagic = BitConverter.ToInt32(Encoding.ASCII.GetBytes("sreV"), 0); //Vers 18 | public static int CompressionMagic = BitConverter.ToInt32(Encoding.ASCII.GetBytes("srpC"), 0); //Cprs 19 | public static int EncryptionMagic = BitConverter.ToInt32(Encoding.ASCII.GetBytes("rcnE"), 0); //Encr 20 | 21 | public FileEntry() 22 | { 23 | FileName = ""; 24 | CompressedMagic = 0; 25 | UncompressedSize = 0; 26 | StartOffset = 0; 27 | TimeStamp = 0; 28 | DataSize = 0; 29 | } 30 | public FileEntry(BinaryReaderEx input) 31 | { 32 | Read(input); 33 | } 34 | 35 | public void Read(BinaryReaderEx input) 36 | { 37 | FileName = input.ReadAsciiz(); 38 | CompressedMagic = input.ReadInt32(); 39 | UncompressedSize = input.ReadInt32(); 40 | StartOffset = input.ReadInt32(); 41 | TimeStamp = input.ReadInt32(); 42 | DataSize = input.ReadInt32(); 43 | } 44 | 45 | public void Write(BinaryWriterEx output) 46 | { 47 | output.WriteAsciiz(FileName); 48 | output.Write(CompressedMagic); 49 | output.Write(UncompressedSize); 50 | output.Write(StartOffset); 51 | output.Write(TimeStamp); 52 | output.Write(DataSize); 53 | } 54 | 55 | public bool IsVersion => CompressedMagic == VersionMagic && TimeStamp == 0 && DataSize == 0; 56 | public bool IsCompressed => CompressedMagic == CompressionMagic; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /BIS.PBO/PBO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using BIS.Core; 6 | using BIS.Core.Streams; 7 | 8 | namespace BIS.PBO 9 | { 10 | public class PBO 11 | { 12 | private static FileEntry VersionEntry; 13 | private static FileEntry EmptyEntry; 14 | 15 | private FileStream pboFileStream; 16 | 17 | public string PBOFilePath { get; private set; } 18 | 19 | public FileStream PBOFileStream 20 | { 21 | get 22 | { 23 | pboFileStream = pboFileStream ?? File.OpenRead(PBOFilePath); 24 | return pboFileStream; 25 | } 26 | } 27 | public LinkedList FileEntries { get; } = new LinkedList(); 28 | public LinkedList Properties { get; } = new LinkedList(); 29 | public int DataOffset { get; private set; } 30 | public string Prefix { get; private set; } 31 | public string FileName => Path.GetFileName(PBOFilePath); 32 | 33 | static PBO() 34 | { 35 | VersionEntry = new FileEntry 36 | { 37 | CompressedMagic = FileEntry.VersionMagic, 38 | FileName = "" 39 | }; 40 | 41 | EmptyEntry = new FileEntry(); 42 | } 43 | 44 | public PBO(string fileName, bool keepStreamOpen = false) 45 | { 46 | PBOFilePath = fileName; 47 | var input = new BinaryReaderEx(PBOFileStream); 48 | ReadHeader(input); 49 | if (!keepStreamOpen) 50 | { 51 | pboFileStream.Close(); 52 | pboFileStream = null; 53 | } 54 | } 55 | 56 | private void ReadHeader(BinaryReaderEx input) 57 | { 58 | int curOffset = 0; 59 | FileEntry pboEntry; 60 | do 61 | { 62 | pboEntry = new FileEntry(input) 63 | { 64 | StartOffset = curOffset 65 | }; 66 | 67 | curOffset += pboEntry.DataSize; 68 | 69 | if (pboEntry.IsVersion) 70 | { 71 | string name; 72 | string value; 73 | do 74 | { 75 | name = input.ReadAsciiz(); 76 | if (name == "") break; 77 | Properties.AddLast(name); 78 | 79 | value = input.ReadAsciiz(); 80 | Properties.AddLast(value); 81 | 82 | if (name == "prefix") 83 | Prefix = value; 84 | } 85 | while (name != ""); 86 | 87 | if (Properties.Count % 2 != 0) 88 | throw new Exception("metaData count is not even."); 89 | } 90 | else if (pboEntry.FileName != "") 91 | FileEntries.AddLast(pboEntry); 92 | } 93 | while (pboEntry.FileName != "" || FileEntries.Count == 0); 94 | 95 | DataOffset = (int)input.Position; 96 | 97 | if (Prefix == null) 98 | { 99 | Prefix = Path.GetFileNameWithoutExtension(PBOFilePath); 100 | } 101 | } 102 | 103 | private byte[] GetFileData(FileEntry entry) 104 | { 105 | PBOFileStream.Position = DataOffset + entry.StartOffset; 106 | byte[] bytes; 107 | if (entry.CompressedMagic == 0) 108 | { 109 | bytes = new byte[entry.DataSize]; 110 | PBOFileStream.Read(bytes, 0, entry.DataSize); 111 | } 112 | else 113 | { 114 | if (!entry.IsCompressed) 115 | throw new Exception("Unexpected packingMethod"); 116 | 117 | var br = new BinaryReaderEx(PBOFileStream); 118 | bytes = br.ReadLZSS((uint)entry.UncompressedSize); 119 | } 120 | 121 | return bytes; 122 | } 123 | 124 | public void ExtractFile(FileEntry entry, string dst) 125 | { 126 | ExtractFiles(Methods.Yield(entry), dst); 127 | } 128 | 129 | public void ExtractFiles(IEnumerable entries, string dst, bool keepStreamOpen = false) 130 | { 131 | foreach (var entry in entries.OrderBy(e => e.StartOffset)) 132 | { 133 | if (entry.DataSize <= 0) continue; 134 | string path = Path.Combine(dst, entry.FileName); 135 | Directory.CreateDirectory(Path.GetDirectoryName(path)); 136 | File.WriteAllBytes(path, GetFileData(entry)); 137 | } 138 | 139 | if (!keepStreamOpen) 140 | { 141 | pboFileStream.Close(); 142 | pboFileStream = null; 143 | } 144 | } 145 | 146 | public void ExtractAllFiles(string directory) 147 | { 148 | var dstPath = Path.Combine(directory, Prefix); 149 | ExtractFiles(FileEntries, dstPath); 150 | } 151 | 152 | public MemoryStream GetFileEntryStream(FileEntry entry) 153 | { 154 | return GetFileEntryStreams(Methods.Yield(entry)).First(); 155 | } 156 | 157 | public IEnumerable GetFileEntryStreams(IEnumerable entries, bool keepStreamOpen = false) 158 | { 159 | foreach (var entry in entries) 160 | { 161 | if (entry.DataSize <= 0) continue; 162 | yield return new MemoryStream(GetFileData(entry), false); 163 | } 164 | 165 | if (!keepStreamOpen) 166 | { 167 | pboFileStream.Close(); 168 | pboFileStream = null; 169 | } 170 | } 171 | 172 | private void WriteBasicHeader(BinaryWriterEx output) 173 | { 174 | WriteBasicHeader(output, FileEntries); 175 | } 176 | 177 | private static void WriteBasicHeader(BinaryWriterEx output, IEnumerable fileEntries) 178 | { 179 | foreach (var entry in fileEntries) 180 | { 181 | entry.Write(output); 182 | } 183 | 184 | EmptyEntry.Write(output); 185 | } 186 | 187 | private void WriteProperties(BinaryWriterEx output) 188 | { 189 | WriteProperties(output, Properties); 190 | } 191 | 192 | private static void WriteProperties(BinaryWriterEx output, IEnumerable properties) 193 | { 194 | //create starting entry 195 | VersionEntry.Write(output); 196 | 197 | foreach (var e in properties) 198 | { 199 | output.WriteAsciiz(e); 200 | } 201 | output.Write((byte)0); //empty string 202 | } 203 | 204 | private void WriteHeader(BinaryWriterEx output) 205 | { 206 | WriteProperties(output); 207 | WriteBasicHeader(output); 208 | } 209 | 210 | public static IEnumerable> GetAllNonEmptyFileEntries(string path) 211 | { 212 | var allPBOs = Directory.GetFiles(path, "*.pbo", SearchOption.AllDirectories); 213 | 214 | foreach (var pboPath in allPBOs) 215 | { 216 | var pbo = new PBO(pboPath); 217 | foreach (var entry in pbo.FileEntries) 218 | { 219 | if (entry.DataSize > 0) 220 | yield return new KeyValuePair(entry, pbo); 221 | } 222 | } 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /BIS.RTM/AnimKeyStone.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Streams; 2 | 3 | namespace BIS.RTM 4 | { 5 | public enum AnimKeystoneTypeID 6 | { 7 | AKSStepSound, 8 | NAnimKeystoneTypeID, 9 | AKSUninitialized = -1 10 | } 11 | 12 | public enum AnimMetaDataID 13 | { 14 | AMDWalkCycles, 15 | AMDAnimLength, 16 | NAnimMetaDataID, 17 | AMDUninitialized = -1 18 | } 19 | 20 | public class AnimKeyStone 21 | { 22 | public AnimKeystoneTypeID ID { get; private set; } 23 | public string StringID { get; private set; } 24 | public float Time { get; private set; } 25 | public string Value { get; private set; } 26 | 27 | public AnimKeyStone(BinaryReaderEx input) 28 | { 29 | ID = (AnimKeystoneTypeID)input.ReadInt32(); 30 | StringID = input.ReadAsciiz(); 31 | Time = input.ReadSingle(); 32 | Value = input.ReadAsciiz(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BIS.RTM/BIS.RTM.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | Braini01, GrueArbre 7 | LICENSE 8 | https://github.com/Braini01/bis-file-formats 9 | 10 | 11 | 12 | 13 | True 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BIS.RTM/RTM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | using BIS.Core.Streams; 7 | using BIS.Core.Math; 8 | 9 | namespace BIS.RTM 10 | { 11 | public class RTM 12 | { 13 | public Vector3P Displacement { get; private set; } 14 | public string[] BoneNames { get; private set; } 15 | public float[] FrameTimes { get; private set; } 16 | public Matrix4P[,] FrameTransforms { get; private set; } 17 | 18 | public RTM(string fileName) : this(File.OpenRead(fileName)) { } 19 | 20 | public RTM(Stream stream) 21 | { 22 | var input = new BinaryReaderEx(stream); 23 | Read(input); 24 | input.Close(); 25 | } 26 | 27 | private void Read(BinaryReaderEx input) 28 | { 29 | if ("RTM_0101" == input.ReadAscii(8)) 30 | { 31 | ReadRTM(input); 32 | return; 33 | } 34 | throw new FormatException("No RTM signature found"); 35 | } 36 | 37 | private void Write(BinaryWriterEx output) 38 | { 39 | output.WriteAscii("RTM_0101", 8); 40 | Displacement.Write(output); 41 | 42 | var nFrames = FrameTimes.Length; 43 | var nBones = BoneNames.Length; 44 | 45 | output.Write(nFrames); 46 | output.Write(nBones); 47 | 48 | for (int i = 0; i < nBones; i++) 49 | output.WriteAscii(BoneNames[i], 32); 50 | 51 | for (int frame = 0; frame < nFrames; frame++) 52 | { 53 | output.Write(FrameTimes[frame]); 54 | for (int b = 0; b < nBones; b++) 55 | { 56 | output.WriteAscii(BoneNames[b], 32); 57 | FrameTransforms[frame, b].Write(output); 58 | } 59 | } 60 | } 61 | 62 | private void ReadRTM(BinaryReaderEx input) 63 | { 64 | Displacement = new Vector3P(input); 65 | var nFrames = input.ReadInt32(); 66 | 67 | BoneNames = input.ReadArray( inp => inp.ReadAscii(32) ); 68 | 69 | var nBones = BoneNames.Length; 70 | 71 | FrameTimes = new float[nFrames]; 72 | FrameTransforms = new Matrix4P[nFrames, nBones]; 73 | for (int frame = 0; frame < nFrames; frame++) 74 | { 75 | FrameTimes[frame] = input.ReadSingle(); 76 | for (int b = 0; b < nBones; b++) 77 | { 78 | input.ReadAscii(32); //redundand boneName 79 | FrameTransforms[frame, b] = new Matrix4P(input); 80 | } 81 | } 82 | } 83 | 84 | public void WriteToFile(string file) 85 | { 86 | var output = new BinaryWriterEx(File.OpenWrite(file)); 87 | Write(output); 88 | output.Close(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /BIS.RTM/RTMB.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Math; 2 | using BIS.Core.Streams; 3 | using System; 4 | using System.Linq; 5 | 6 | namespace BIS.RTM 7 | { 8 | public class RTMB 9 | { 10 | // version 3 - LZO compression used for compressed arrays 11 | // version 4 - animation metadata 12 | 13 | public int Version { get; private set; } 14 | public bool Reversed { get; private set; } 15 | public Vector3P Step { get; private set; } 16 | public int PreloadCount { get; private set; } 17 | public string[] BoneNames { get; private set; } 18 | public string[] MetaDataValues { get; private set; } 19 | public AnimKeyStone[] AnimKeyStones { get; private set; } 20 | public float[] PhaseTimes { get; private set; } 21 | public TransformP[][] Phases { get; private set; } 22 | 23 | private void Read(BinaryReaderEx input) 24 | { 25 | if ("BMTR" != input.ReadAscii(4)) 26 | throw new FormatException(); 27 | 28 | Version = input.ReadInt32(); 29 | input.Version = Version; 30 | if (Version >= 3) input.UseLZOCompression = true; 31 | if (Version >= 5) input.UseCompressionFlag = true; 32 | 33 | Reversed = input.ReadBoolean(); 34 | Step = new Vector3P(input); 35 | var nPhases = input.ReadInt32(); 36 | PreloadCount = input.ReadInt32(); 37 | var nAnimatedBones = input.ReadInt32(); 38 | BoneNames = input.ReadStringArray(); 39 | 40 | //metadata 41 | if (Version >= 4) 42 | { 43 | MetaDataValues = input.ReadStringArray(); 44 | AnimKeyStones = input.ReadArray(inp => new AnimKeyStone(inp)); 45 | } 46 | 47 | PhaseTimes = input.ReadCompressedFloatArray(); 48 | Phases = Enumerable.Range(0, nPhases).Select(_ => input.ReadCompressedArray(inp => TransformP.Read(inp), 14)).ToArray(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /BIS.WRP/AnyWrp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using BIS.Core.Streams; 4 | 5 | namespace BIS.WRP 6 | { 7 | /// 8 | /// Abstraction of a wrp file, binarised or editable 9 | /// 10 | public class AnyWrp : IReadObject, IWrp 11 | { 12 | private OPRW binarized; 13 | private EditableWrp editable; 14 | private IWrp wrp; 15 | 16 | public int LandRangeX => wrp.LandRangeX; 17 | 18 | public int LandRangeY => wrp.LandRangeY; 19 | 20 | public int TerrainRangeX => wrp.TerrainRangeX; 21 | 22 | public int TerrainRangeY => wrp.TerrainRangeY; 23 | 24 | public float CellSize => wrp.CellSize; 25 | 26 | public float[] Elevation => wrp.Elevation; 27 | 28 | public string[] MatNames => wrp.MatNames; 29 | 30 | public IReadOnlyList MaterialIndex => wrp.MaterialIndex; 31 | 32 | public void Read(BinaryReaderEx input) 33 | { 34 | var signature = input.ReadAscii(4); 35 | switch (signature) 36 | { 37 | case "OPRW": 38 | binarized = new OPRW(); 39 | binarized.ReadContent(input); 40 | wrp = binarized; 41 | editable = null; 42 | break; 43 | case "8WVR": 44 | editable = new EditableWrp(); 45 | editable.ReadContent(input); 46 | wrp = editable; 47 | binarized = null; 48 | break; 49 | default: 50 | throw new InvalidOperationException($"Unknown WRP format '{signature}'"); 51 | } 52 | } 53 | 54 | public EditableWrp GetEditableWrp() 55 | { 56 | if (editable == null) 57 | { 58 | if (binarized != null) 59 | { 60 | editable = binarized.ToEditableWrp(); 61 | } 62 | } 63 | return editable; 64 | } 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /BIS.WRP/BIS.WRP.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | Braini01, GrueArbre 7 | LICENSE 8 | https://github.com/Braini01/bis-file-formats 9 | 10 | 11 | 12 | 13 | True 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BIS.WRP/EditableWrp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using BIS.Core.Streams; 7 | 8 | namespace BIS.WRP 9 | { 10 | public class EditableWrp : IReadWriteObject, IWrp// aka 8WVR 11 | { 12 | public EditableWrp() 13 | { 14 | 15 | } 16 | 17 | public EditableWrp(Stream s) 18 | : this(new BinaryReaderEx(s)) 19 | { 20 | } 21 | 22 | public EditableWrp(BinaryReaderEx input) 23 | { 24 | Read(input); 25 | } 26 | 27 | public void Read(BinaryReaderEx input) 28 | { 29 | if (input.ReadAscii(4) != "8WVR") 30 | { 31 | throw new FormatException("8WVR file does not start with correct file signature"); 32 | } 33 | 34 | ReadContent(input); 35 | } 36 | 37 | internal void ReadContent(BinaryReaderEx input) 38 | { 39 | LandRangeX = input.ReadInt32(); 40 | LandRangeY = input.ReadInt32(); 41 | TerrainRangeX = input.ReadInt32(); 42 | TerrainRangeY = input.ReadInt32(); 43 | CellSize = input.ReadSingle(); 44 | Elevation = input.ReadFloats(TerrainRangeX * TerrainRangeY); 45 | MaterialIndex = input.ReadUshorts(LandRangeX * LandRangeY); 46 | 47 | var nMaterials = input.ReadInt32(); 48 | MatNames = new string[nMaterials]; 49 | for (int i = 0; i < nMaterials; i++) 50 | { 51 | int len; 52 | do 53 | { 54 | len = input.ReadInt32(); 55 | if (len != 0) 56 | { 57 | MatNames[i] = input.ReadAscii(len); 58 | } 59 | } while (len != 0); 60 | } 61 | 62 | while (!input.HasReachedEnd) 63 | { 64 | Objects.Add(new EditableWrpObject(input)); 65 | } 66 | } 67 | 68 | public void Write(BinaryWriterEx output) 69 | { 70 | output.WriteAscii("8WVR", 4); 71 | output.Write(LandRangeX); 72 | output.Write(LandRangeY); 73 | output.Write(TerrainRangeX ); 74 | output.Write(TerrainRangeY); 75 | output.Write(CellSize); 76 | output.WriteFloats(Elevation); 77 | output.WriteUshorts(MaterialIndex); 78 | output.Write(MatNames.Length); 79 | foreach (var mat in MatNames) 80 | { 81 | if (!string.IsNullOrEmpty(mat)) 82 | { 83 | output.WriteAscii32(mat); 84 | } 85 | output.WriteAscii32(""); 86 | } 87 | foreach(var obj in Objects) 88 | { 89 | obj.Write(output); 90 | } 91 | } 92 | 93 | 94 | public int LandRangeX { get; set; } 95 | public int LandRangeY { get; set; } 96 | public int TerrainRangeX { get; set; } 97 | public int TerrainRangeY { get; set; } 98 | public float CellSize { get; set; } 99 | public float[] Elevation { get; set; } 100 | public ushort[] MaterialIndex { get; set; } 101 | public string[] MatNames { get; set; } 102 | public List Objects { get; set; } = new List(); 103 | 104 | IReadOnlyList IWrp.MaterialIndex => MaterialIndex; 105 | 106 | public IEnumerable GetNonDummyObjects() => Objects.TakeWhile(o => !string.IsNullOrEmpty(o.Model)); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /BIS.WRP/EditableWrpObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Numerics; 4 | using System.Text; 5 | using BIS.Core.Math; 6 | using BIS.Core.Streams; 7 | 8 | namespace BIS.WRP 9 | { 10 | public class EditableWrpObject 11 | { 12 | public static EditableWrpObject Dummy = new EditableWrpObject() 13 | { 14 | Model = "", 15 | ObjectID = int.MaxValue, 16 | Transform = new Matrix4P(new Matrix4x4( 17 | float.NaN, float.NaN, float.NaN, 0f, 18 | float.NaN, float.NaN, float.NaN, 0f, 19 | float.NaN, float.NaN, float.NaN, 0f, 20 | float.NaN, float.NaN, float.NaN, 1f)) 21 | }; 22 | 23 | public EditableWrpObject() 24 | { 25 | 26 | } 27 | 28 | public EditableWrpObject(BinaryReaderEx input) 29 | { 30 | Transform = new Matrix4P(input); 31 | ObjectID = input.ReadInt32(); 32 | Model = input.ReadAscii32(); 33 | } 34 | 35 | public Matrix4P Transform { get; set; } 36 | public int ObjectID { get; set; } 37 | public string Model { get; set; } 38 | 39 | internal void Write(BinaryWriterEx output) 40 | { 41 | Transform.Write(output); 42 | output.Write(ObjectID); 43 | output.WriteAscii32(Model); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /BIS.WRP/GeographyInfo.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Math; 2 | using BIS.Core.Streams; 3 | 4 | namespace BIS.WRP 5 | { 6 | public struct GeographyInfo 7 | { 8 | private short info; 9 | 10 | public byte MinWaterDepth => (byte)(info & 0b11); 11 | public bool Full => ((info >> 2) & 0b1) > 0; 12 | public bool Forest => ((info >> 3) & 0b1) > 0; 13 | public bool Road => ((info >> 4) & 0b1) > 0; 14 | public byte MaxWaterDepth => (byte)((info >> 5) & 0b11); 15 | public byte HowManyObjects => (byte)((info >> 7) & 0b11); 16 | public byte HowManyHardObjects => (byte)((info >> 9) & 0b11); 17 | public byte Gradient => (byte)((info >> 11) & 0b111); 18 | public bool SomeRoadway => ((info >> 14) & 0b1) > 0; 19 | public bool SomeObjects => ((info >> 15) & 0b1) > 0; 20 | 21 | 22 | public static implicit operator short(GeographyInfo d) 23 | { 24 | return d.info; 25 | } 26 | 27 | public static implicit operator GeographyInfo(short d) 28 | { 29 | var g = new GeographyInfo 30 | { 31 | info = d 32 | }; 33 | return g; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /BIS.WRP/IWrp.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BIS.WRP 4 | { 5 | internal interface IWrp 6 | { 7 | int LandRangeX { get; } 8 | int LandRangeY { get; } 9 | int TerrainRangeX { get; } 10 | int TerrainRangeY { get; } 11 | float CellSize { get; } 12 | float[] Elevation { get; } 13 | string[] MatNames { get; } 14 | IReadOnlyList MaterialIndex { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /BIS.WRP/OPRW.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Diagnostics; 4 | 5 | using BIS.Core; 6 | using BIS.Core.Math; 7 | using BIS.Core.Streams; 8 | using System.Linq; 9 | using System.Collections.Generic; 10 | using System.Numerics; 11 | 12 | namespace BIS.WRP 13 | { 14 | public class OPRW : IReadObject, IWrp 15 | { 16 | public int Version { get; private set; } 17 | public int AppID { get; private set; } 18 | public int LandRangeX { get; private set; } 19 | public int LandRangeY { get; private set; } 20 | public int TerrainRangeX { get; private set; } 21 | public int TerrainRangeY { get; private set; } 22 | public float CellSize { get; private set; } 23 | public QuadTree Geography { get; private set; } 24 | public QuadTree SoundMap { get; private set; } 25 | public Vector3P[] Mountains { get; private set; } //map peaks 26 | public QuadTree Materials { get; private set; } 27 | public byte[] Random { get; private set; } //short values 28 | public byte[] GrassApprox { get; private set; } 29 | public byte[] PrimTexIndex { get; private set; } //coord to primary texture mapping 30 | public float[] Elevation { get; private set; } 31 | public string[] MatNames { get; private set; } 32 | public string[] Models { get; private set; } 33 | public StaticEntityInfo[] EntityInfos { get; private set; } 34 | public QuadTree ObjectOffsets { get; private set; } 35 | public QuadTree MapObjectOffsets { get; private set; } 36 | public byte[] Persistent { get; private set; } 37 | public int MaxObjectId { get; private set; } 38 | public RoadLink[][] Roadnet { get; private set; } 39 | public Object[] Objects { get; private set; } 40 | public byte[] MapInfos { get; private set; } 41 | 42 | IReadOnlyList IWrp.MaterialIndex => Materials; 43 | public OPRW() 44 | { 45 | 46 | } 47 | 48 | public OPRW(Stream s) 49 | { 50 | var input = new BinaryReaderEx(s); 51 | Read(input); 52 | 53 | // version 3 - OFP Retail landscape (no streaming, no map) 54 | // version 5 - OFP XBox landscape beta (streaming, no map) 55 | // version 6 - landscape (streaming and map) 56 | // version 7 - landscape, including roads (streaming and map) 57 | // version 10 - landscape, quad trees 58 | // version 11 - landscape, changed geography 59 | // version 12 - OFP Xbox/FP2 landscape, different grid for textures and terrain 60 | // version 13 - landscape, subdivision hints included 61 | // version 14 - landscape, skew object flag added 62 | // version 15 - landscape, entity list added 63 | // version 16 - ArmA landscape, roads transform + LODShape added 64 | // version 17 - major texture pass added 65 | // version 18 - grass map added, float used as raw data 66 | // version 19 - water depth geography info change 67 | // version 20 - grass map contains flat areas around roads 68 | // version 21 - randomization array removed 69 | // version 22 - primary texture info added 70 | // version 23 - LZO compression used for compressed arrays 71 | // version 24 - extended info for roads (connection types) 72 | // version 25 - appID of the app or DLC the map belongs 73 | // version 26 - offset table at the beginning (_VBS3_WRP_OFFSET_TABLE), heightmap compression (_VBS3_HEIGHTMAP_COMPRESSION) 74 | // version 27 - storing of large static objects R-tree in wrp <-- NOTE: technology not used. Implemented without need of WRP changes! 75 | } 76 | 77 | //minimal version 10 78 | public void Read(BinaryReaderEx input) 79 | { 80 | var fileSig = input.ReadAscii(4); 81 | if (fileSig != "OPRW") 82 | { 83 | throw new FormatException("OPRW file does not start with correct file signature"); 84 | } 85 | 86 | ReadContent(input); 87 | } 88 | 89 | internal void ReadContent(BinaryReaderEx input) 90 | { 91 | Version = input.ReadInt32(); 92 | input.Version = Version; 93 | if (Version < 10) throw new NotSupportedException("OPRW file versions below 10 are not supported"); 94 | 95 | if (Version >= 23) input.UseLZOCompression = true; 96 | //if (version >= 25) input.UseCompressionFlag = true; 97 | 98 | if (Version >= 25) 99 | AppID = input.ReadInt32(); 100 | 101 | if (Version >= 12) 102 | { 103 | LandRangeX = input.ReadInt32(); 104 | LandRangeY = input.ReadInt32(); //same as x? 105 | TerrainRangeX = input.ReadInt32(); 106 | TerrainRangeY = input.ReadInt32(); //same as x? 107 | CellSize = input.ReadSingle(); 108 | Debug.Assert(LandRangeX == LandRangeY && TerrainRangeX == TerrainRangeY); 109 | } 110 | 111 | Geography = new QuadTree(LandRangeX, LandRangeY, input, (src, off) => BitConverter.ToInt16(src, off), 2); 112 | //if(version<19) transformOldWaterInformation 113 | 114 | var soundMapCoef = 1; //ToDo: this is read from config 115 | SoundMap = new QuadTree(LandRangeX * soundMapCoef, LandRangeX * soundMapCoef, input, (src, off) => src[off], 1); //both landRangeX are correct. no mistake 116 | 117 | Mountains = input.ReadArray(inp => new Vector3P(inp)); 118 | 119 | Materials = new QuadTree(LandRangeX, LandRangeY, input, (src, off) => BitConverter.ToUInt16(src, off), 2); 120 | 121 | if (Version < 21) 122 | Random = input.ReadCompressed((uint)(LandRangeX * LandRangeY * 2)); //short values 123 | 124 | if (Version >= 18) 125 | GrassApprox = input.ReadCompressed((uint)(TerrainRangeX * TerrainRangeY)); //byte values 126 | 127 | if (Version >= 22) 128 | PrimTexIndex = input.ReadCompressed((uint)(TerrainRangeX * TerrainRangeY)); //signed byte values? 129 | 130 | Elevation = input.ReadCompressedFloats(TerrainRangeX * TerrainRangeY); 131 | 132 | var nMaterials = input.ReadInt32(); 133 | MatNames = new string[nMaterials]; 134 | var major = new byte[nMaterials]; 135 | for (int i = 0; i < nMaterials; i++) 136 | { 137 | MatNames[i] = input.ReadAsciiz(); 138 | major[i] = input.ReadByte(); 139 | } 140 | 141 | Models = input.ReadStringArray(); 142 | 143 | if (Version >= 15) 144 | { 145 | EntityInfos = input.ReadArray(inp => new StaticEntityInfo(inp)); 146 | } 147 | 148 | ObjectOffsets = new QuadTree(LandRangeX, LandRangeY, input, (src, off) => BitConverter.ToInt32(src, off), 4); 149 | var sizeOfObjects = input.ReadInt32(); 150 | MapObjectOffsets = new QuadTree(LandRangeX, LandRangeY, input, (src, off) => BitConverter.ToInt32(src, off), 4); 151 | var sizeOfMapinfo = input.ReadInt32(); 152 | 153 | Persistent = input.ReadCompressed((uint)(LandRangeX * LandRangeY)); 154 | var subDivHints = input.ReadCompressed((uint)(TerrainRangeX * TerrainRangeY)); 155 | 156 | MaxObjectId = input.ReadInt32(); 157 | var roadnetSize = input.ReadInt32(); 158 | 159 | Roadnet = new RoadLink[LandRangeX * LandRangeY][]; 160 | var pos = input.Position; 161 | for (int i = 0; i < LandRangeX * LandRangeY; i++) 162 | { 163 | Roadnet[i] = input.ReadArray(inp => new RoadLink(inp)); 164 | } 165 | var read = input.Position - pos; 166 | 167 | var nObjects = sizeOfObjects / 60; 168 | Objects = new Object[nObjects]; 169 | 170 | for (int i = 0; i < nObjects; i++) 171 | { 172 | Objects[i] = new Object(input); 173 | } 174 | 175 | MapInfos = input.ReadBytes((int)(input.BaseStream.Length - input.BaseStream.Position)); 176 | } 177 | 178 | public EditableWrp ToEditableWrp() 179 | { 180 | return new EditableWrp() 181 | { 182 | CellSize = CellSize, 183 | Elevation = Elevation, 184 | LandRangeX = LandRangeX, 185 | LandRangeY = LandRangeY, 186 | MatNames = MatNames, 187 | TerrainRangeX = TerrainRangeX, 188 | TerrainRangeY = TerrainRangeY, 189 | Objects = Objects.OrderBy(o => o.ObjectID).Select(o => new EditableWrpObject() 190 | { 191 | Model = Models[o.ModelIndex], 192 | ObjectID = o.ObjectID, 193 | Transform = o.Transform 194 | }).Concat(new[] { EditableWrpObject.Dummy }).ToList(), 195 | MaterialIndex = Materials.ToArray() 196 | }; 197 | } 198 | } 199 | } -------------------------------------------------------------------------------- /BIS.WRP/Object.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Math; 2 | using BIS.Core.Streams; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace BIS.WRP 8 | { 9 | public class Object 10 | { 11 | public int ObjectID { get; } 12 | public int ModelIndex { get; } // into the [[#Models|models path name list]] (1 based) 13 | public Matrix4P Transform { get; } 14 | public int ShapeParam { get; } 15 | 16 | public Object(BinaryReaderEx input) 17 | { 18 | ObjectID = input.ReadInt32(); 19 | ModelIndex = input.ReadInt32(); 20 | Transform = new Matrix4P(input); 21 | if (input.Version >= 14) 22 | ShapeParam = input.ReadInt32(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BIS.WRP/ObjectId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BIS.WRP 6 | { 7 | public struct ObjectId 8 | { 9 | private int id; 10 | 11 | public bool IsObject => ((id >> 31) & 1) > 0; 12 | public short ObjId => (short)(id & 0b111_1111_1111); 13 | public short ObjX => (short)((id >> 11) & 0b11_1111_1111); 14 | public short ObjZ => (short)((id >> 21) & 0b11_1111_1111); 15 | 16 | public int Id => id; 17 | 18 | public static implicit operator int(ObjectId d) 19 | { 20 | return d.id; 21 | } 22 | 23 | public static implicit operator ObjectId(int d) 24 | { 25 | var o = new ObjectId 26 | { 27 | id = d 28 | }; 29 | return o; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BIS.WRP/RoadLink.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Math; 2 | using BIS.Core.Streams; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace BIS.WRP 8 | { 9 | //public class RoadList 10 | //{ 11 | // private int nRoadLinks; 12 | // private RoadLink[] roadLinks; 13 | 14 | // public void Read(BinaryReaderEx input, int version) 15 | // { 16 | // nRoadLinks = input.ReadInt32(); 17 | // roadLinks = new RoadLink[nRoadLinks]; 18 | // for (int i = 0; i < nRoadLinks; i++) 19 | // { 20 | // roadLinks[i] = new RoadLink(input); 21 | // } 22 | // } 23 | //} 24 | 25 | public class RoadLink 26 | { 27 | public short ConnectionCount { get; } 28 | public Vector3P[] Positions { get; } 29 | public byte[] ConnectionTypes { get; } 30 | public int ObjectID { get; } 31 | public string P3dPath { get; } 32 | public Matrix4P ToWorld { get; } 33 | 34 | public RoadLink(BinaryReaderEx input) 35 | { 36 | ConnectionCount = input.ReadInt16(); 37 | Positions = new Vector3P[ConnectionCount]; 38 | for (int i = 0; i < ConnectionCount; i++) 39 | Positions[i] = new Vector3P(input); 40 | 41 | if (input.Version >= 24) 42 | { 43 | ConnectionTypes = new byte[ConnectionCount]; 44 | for (int i = 0; i < ConnectionCount; i++) 45 | ConnectionTypes[i] = input.ReadByte(); 46 | } 47 | 48 | ObjectID = input.ReadInt32(); 49 | 50 | if (input.Version >= 16) 51 | { 52 | P3dPath = input.ReadAsciiz(); 53 | ToWorld = new Matrix4P(input); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /BIS.WRP/StaticEntityInfo.cs: -------------------------------------------------------------------------------- 1 | using BIS.Core.Math; 2 | using BIS.Core.Streams; 3 | 4 | namespace BIS.WRP 5 | { 6 | public class StaticEntityInfo 7 | { 8 | public string ClassName { get; } 9 | public string ShapeName { get; } 10 | public Vector3P Position { get; } 11 | public ObjectId ObjectId { get; } 12 | 13 | public StaticEntityInfo(BinaryReaderEx input) 14 | { 15 | ClassName = input.ReadAsciiz(); 16 | ShapeName = input.ReadAsciiz(); 17 | Position = new Vector3P(input); 18 | ObjectId = input.ReadInt32(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Extras/Extras.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31321.278 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.Core", "..\BIS.Core\BIS.Core.csproj", "{B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.PAA", "..\BIS.PAA\BIS.PAA.csproj", "{9399460C-6D54-4C03-A4DE-1E398A9A007E}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.PAA.Encoder", "..\BIS.PAA.Encoder\BIS.PAA.Encoder.csproj", "{E65A45D8-02A3-4186-9D0F-8806C5C2E1A6}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PaaPdnPlugin", "PaaPdnPlugin\PaaPdnPlugin.csproj", "{D44A8ED5-F1E6-46D9-960D-7BB2DD6688A5}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {E65A45D8-02A3-4186-9D0F-8806C5C2E1A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {E65A45D8-02A3-4186-9D0F-8806C5C2E1A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {E65A45D8-02A3-4186-9D0F-8806C5C2E1A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {E65A45D8-02A3-4186-9D0F-8806C5C2E1A6}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {D44A8ED5-F1E6-46D9-960D-7BB2DD6688A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {D44A8ED5-F1E6-46D9-960D-7BB2DD6688A5}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {D44A8ED5-F1E6-46D9-960D-7BB2DD6688A5}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {D44A8ED5-F1E6-46D9-960D-7BB2DD6688A5}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {CF9BC82F-3E0E-4D8D-96DC-11EFC3AF4035} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /Extras/PaaPdnPlugin/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Interoperability", "CA1416:Valider la compatibilité de la plateforme")] 9 | 10 | -------------------------------------------------------------------------------- /Extras/PaaPdnPlugin/PaaFileType.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using BCnEncoder.Shared; 3 | using BIS.Core.Streams; 4 | using BIS.PAA; 5 | using BIS.PAA.Encoder; 6 | using PaintDotNet; 7 | 8 | namespace PaaPdnPlugin 9 | { 10 | public class PaaFileType : FileType 11 | { 12 | private readonly IFileTypeHost host; 13 | 14 | public PaaFileType(IFileTypeHost host) 15 | : base("PAA texture", new FileTypeOptions() 16 | { 17 | LoadExtensions = new[] { ".paa" }, 18 | SaveExtensions = new[] { ".paa" }, 19 | SupportsLayers = false 20 | }) 21 | { 22 | this.host = host; 23 | } 24 | 25 | protected override Document OnLoad(Stream input) 26 | { 27 | var paa = new PAA(input); 28 | var pixels = PAA.GetARGB32PixelData(paa, input); 29 | 30 | var doc = new Document(paa.Width, paa.Height); 31 | var bitmap = Layer.CreateBackgroundLayer(doc.Width, doc.Height); 32 | unsafe 33 | { 34 | fixed (byte* pixelsB = pixels) 35 | { 36 | var src = pixelsB; 37 | for (int y = 0; y < paa.Height; ++y) 38 | { 39 | var dst = bitmap.Surface.GetRowPointer(y); 40 | for (int x = 0; x < paa.Width; ++x) 41 | { 42 | *dst = ColorBgra.FromBgra(src[0], src[1], src[2], src[3]); 43 | dst++; 44 | src += 4; 45 | } 46 | } 47 | } 48 | } 49 | doc.Layers.Add(bitmap); 50 | return doc; 51 | } 52 | 53 | protected override void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback) 54 | { 55 | input.Flatten(scratchSurface); 56 | 57 | var pixels = new ColorRgba32[scratchSurface.Width, scratchSurface.Height]; 58 | 59 | unsafe 60 | { 61 | 62 | for (int y = 0; y < scratchSurface.Height; ++y) 63 | { 64 | var src = scratchSurface.GetRowPointer(y); 65 | for (int x = 0; x < scratchSurface.Width; ++x) 66 | { 67 | pixels[y,x] = new ColorRgba32((*src).R, (*src).G, (*src).B, (*src).A); 68 | src++; 69 | } 70 | } 71 | } 72 | 73 | using (var writer = new BinaryWriterEx(output, true)) 74 | { 75 | PaaEncoder.WritePAA(writer, pixels); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Extras/PaaPdnPlugin/PaaFileTypeFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using PaintDotNet; 7 | 8 | namespace PaaPdnPlugin 9 | { 10 | public class PaaFileTypeFactory : IFileTypeFactory2 11 | { 12 | public FileType[] GetFileTypeInstances(IFileTypeHost host) 13 | { 14 | return new[] { new PaaFileType(host) }; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Extras/PaaPdnPlugin/PaaPdnPlugin.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | true 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ..\..\..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.Base.dll 27 | 28 | 29 | ..\..\..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.Core.dll 30 | 31 | 32 | ..\..\..\..\..\..\..\..\Program Files\paint.net\PaintDotNet.Data.dll 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Braini01 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 | # ArmA File Format Library 2 | This project provides developers with libraries that enable them to read common files that are mainly used by [Bohemia Interactive][1] for their games of the ArmA series, like rapified configs (config.bin), textures (\*.paa,\*.pac), skeletal animations (\*.rtm), game file container (\*.pbo) etc. 3 | 4 | ## The Idea, Goal and Vision 5 | The basic idea is to create a central and public code base for those ArmA file formats, that is easy to use and integrate into a project. Ideally, this project would become the one stop shop for every developer working with those file formats. Such efforts have not had much success in the past and with this project some of the common reasons for this are tried to be avoided (see Features). 6 | 7 | ## Features 8 | 9 | ### Modularity 10 | By providing small packages you can keep your project slick and dont need to include huge libraries with stuff you do not care about. 11 | 12 | ### Portability 13 | The libraries are created using the [.NET Standard][2]. This makes it possible to use the libraries in most .NET applications and or libraries regardless of it being a .NET Core, .NET Framework, Mono, UWP or Xamarin application or library. With .NET Core being available on most platforms a good degree of platform-independence is achieved. You also can choose from a [lot of programming languages][3] to write a .NET application, like C#, F#, C++/CLI, VB.NET and many more. 14 | 15 | ### Nuget (Not implemented yet) 16 | By providing all libs as NuGet packages, the integration of a library into your project becomes a piece of cake. 17 | 18 | ## Current Project State 19 | The code you can find here is basically a little reorganized dump of some of the code that I created over the years researching those file formats. It currently basically enables you to read most files. It probably is often missing important accessors or functions that would be useful for a public API and I hope by putting this as an open source project that a lot of people will contribute to make this code base useful. So I highly encourage everyone to post some PRs to make this a great API. 20 | 21 | 22 | 23 | [1]: https://www.bistudio.com/ 24 | [2]: https://github.com/dotnet/standard 25 | [3]: https://en.wikipedia.org/wiki/List_of_CLI_languages 26 | -------------------------------------------------------------------------------- /Utils/Image2PAA/Image2PAA.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Utils/Image2PAA/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using BCnEncoder.Shared; 5 | using BIS.PAA.Encoder; 6 | using CommandLine; 7 | using SixLabors.ImageSharp; 8 | using SixLabors.ImageSharp.PixelFormats; 9 | 10 | namespace Image2PAA 11 | { 12 | class Program 13 | { 14 | public class Options 15 | { 16 | [Value(0, MetaName = "source", HelpText = "Source file.", Required = true)] 17 | public string Source { get; set; } 18 | 19 | [Value(1, MetaName = "target", HelpText = "Target file.", Required = false)] 20 | public string Target { get; set; } 21 | } 22 | 23 | static int Main(string[] args) 24 | { 25 | return Parser.Default.ParseArguments(args) 26 | .MapResult(o => 27 | { 28 | if (Path.GetFileNameWithoutExtension(o.Source).Contains("*")) 29 | { 30 | var files = Directory.GetFiles(Path.GetDirectoryName(o.Source), Path.GetFileName(o.Source)); 31 | 32 | Parallel.ForEach(files, file => 33 | { 34 | var target = string.IsNullOrEmpty(o.Target) ? 35 | Path.ChangeExtension(file, ".paa") : 36 | Path.Combine(o.Target, Path.ChangeExtension(Path.GetFileName(file), ".paa")); 37 | Convert(file, target); 38 | }); 39 | } 40 | else 41 | { 42 | if (!File.Exists(o.Source)) 43 | { 44 | Console.Error.WriteLine($"File '{o.Source}' does not exists."); 45 | return 1; 46 | } 47 | var target = string.IsNullOrEmpty(o.Target) ? 48 | Path.ChangeExtension(o.Source, ".paa") : 49 | o.Target; 50 | Convert(o.Source, target); 51 | } 52 | return 0; 53 | }, 54 | e => 3); 55 | } 56 | 57 | private static void Convert(string source, string target) 58 | { 59 | Console.WriteLine($"{source} -> {target}"); 60 | using (var paaStream = File.OpenRead(source)) 61 | { 62 | using (var img = Image.Load(source)) 63 | { 64 | var targetPixels = new ColorRgba32[img.Height, img.Width]; 65 | for (int y = 0; y < img.Height; ++y) 66 | { 67 | for (int x = 0; x < img.Width; ++x) 68 | { 69 | var srcPixel = img[x, y]; 70 | targetPixels[y, x] = new ColorRgba32(srcPixel.R, srcPixel.G, srcPixel.B, srcPixel.A); 71 | } 72 | } 73 | PaaEncoder.WritePAA(target, targetPixels); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Utils/PAA2PNG/PAA2PNG.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Utils/PAA2PNG/Program.cs: -------------------------------------------------------------------------------- 1 | using BIS.PAA; 2 | using CommandLine; 3 | using SixLabors.ImageSharp; 4 | using SixLabors.ImageSharp.PixelFormats; 5 | using System; 6 | using System.IO; 7 | using System.Linq; 8 | 9 | namespace PAA2PNG 10 | { 11 | class Program 12 | { 13 | public class Options 14 | { 15 | [Value(0, MetaName = "source", HelpText = "Source file.", Required = true)] 16 | public string Source { get; set; } 17 | 18 | [Value(1, MetaName = "target", HelpText = "Target file.", Required = false)] 19 | public string Target { get; set; } 20 | } 21 | 22 | static int Main(string[] args) 23 | { 24 | return Parser.Default.ParseArguments(args) 25 | .MapResult(o => 26 | { 27 | var ext = Path.GetExtension(o.Source); 28 | var isPAA = ext.Equals(".paa", System.StringComparison.OrdinalIgnoreCase); 29 | var isPAC = ext.Equals(".pac", System.StringComparison.OrdinalIgnoreCase); 30 | if (!isPAA && !isPAC) 31 | { 32 | Console.Error.WriteLine($"File '{o.Source}' is not a PAA or a PAC."); 33 | return 2; 34 | } 35 | 36 | if (Path.GetFileNameWithoutExtension(o.Source).Contains("*")) 37 | { 38 | var files = Directory.GetFiles(Path.GetDirectoryName(o.Source), Path.GetFileName(o.Source)); 39 | 40 | foreach (var file in files) 41 | { 42 | var target = string.IsNullOrEmpty(o.Target) ? 43 | Path.ChangeExtension(file, ".png") : 44 | Path.Combine(o.Target, Path.ChangeExtension(Path.GetFileName(file), ".png")); 45 | 46 | Convert(isPAC, file, target); 47 | } 48 | } 49 | else 50 | { 51 | if (!File.Exists(o.Source)) 52 | { 53 | Console.Error.WriteLine($"File '{o.Source}' does not exists."); 54 | return 1; 55 | } 56 | var target = string.IsNullOrEmpty(o.Target) ? 57 | Path.ChangeExtension(o.Source, ".png") : 58 | o.Target; 59 | Convert(isPAC, o.Source, target); 60 | } 61 | return 0; 62 | }, 63 | e => 3); 64 | } 65 | 66 | private static void Convert(bool isPAC, string source, string target) 67 | { 68 | Console.WriteLine($"{source} -> {target}"); 69 | using (var paaStream = File.OpenRead(source)) 70 | { 71 | var paa = new PAA(paaStream, isPAC); 72 | var pixels = PAA.GetARGB32PixelData(paa, paaStream); 73 | using (var image = Image.LoadPixelData(pixels, paa.Width, paa.Height)) 74 | { 75 | image.SaveAsPng(target); 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Utils/Utils.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31321.278 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WrpUtil", "WrpUtil\WrpUtil.csproj", "{184D5A1E-8A5A-4E15-AF39-19EBB72C80BC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PAA2PNG", "PAA2PNG\PAA2PNG.csproj", "{AA61F860-E10E-4B5C-9AC0-EA92E58F2864}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.ALB", "..\BIS.ALB\BIS.ALB.csproj", "{8D1B5008-1084-43F8-9E45-AF7894ECA64C}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.Core", "..\BIS.Core\BIS.Core.csproj", "{B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.P3D", "..\BIS.P3D\BIS.P3D.csproj", "{842AB5B1-28EF-476C-88FC-DD26EF6DBD4F}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.PAA", "..\BIS.PAA\BIS.PAA.csproj", "{9399460C-6D54-4C03-A4DE-1E398A9A007E}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.PBO", "..\BIS.PBO\BIS.PBO.csproj", "{500A441F-1736-4FE1-AAE0-0DE1F52A30FB}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.RTM", "..\BIS.RTM\BIS.RTM.csproj", "{B8B2BF3E-CCB4-441C-B4A1-2036090AFF9E}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.WRP", "..\BIS.WRP\BIS.WRP.csproj", "{2B43DF8A-8D46-45FE-AD10-63AF88E0B570}" 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Image2PAA", "Image2PAA\Image2PAA.csproj", "{C6BF36AE-E9D3-46F5-ACF0-71FB93317D5F}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.PAA.Encoder", "..\BIS.PAA.Encoder\BIS.PAA.Encoder.csproj", "{E65A45D8-02A3-4186-9D0F-8806C5C2E1A6}" 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Release|Any CPU = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {184D5A1E-8A5A-4E15-AF39-19EBB72C80BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {184D5A1E-8A5A-4E15-AF39-19EBB72C80BC}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {184D5A1E-8A5A-4E15-AF39-19EBB72C80BC}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {184D5A1E-8A5A-4E15-AF39-19EBB72C80BC}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {AA61F860-E10E-4B5C-9AC0-EA92E58F2864}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {AA61F860-E10E-4B5C-9AC0-EA92E58F2864}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {AA61F860-E10E-4B5C-9AC0-EA92E58F2864}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {AA61F860-E10E-4B5C-9AC0-EA92E58F2864}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {8D1B5008-1084-43F8-9E45-AF7894ECA64C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {8D1B5008-1084-43F8-9E45-AF7894ECA64C}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {8D1B5008-1084-43F8-9E45-AF7894ECA64C}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {8D1B5008-1084-43F8-9E45-AF7894ECA64C}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {842AB5B1-28EF-476C-88FC-DD26EF6DBD4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {842AB5B1-28EF-476C-88FC-DD26EF6DBD4F}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {842AB5B1-28EF-476C-88FC-DD26EF6DBD4F}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {842AB5B1-28EF-476C-88FC-DD26EF6DBD4F}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {500A441F-1736-4FE1-AAE0-0DE1F52A30FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {500A441F-1736-4FE1-AAE0-0DE1F52A30FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {500A441F-1736-4FE1-AAE0-0DE1F52A30FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {500A441F-1736-4FE1-AAE0-0DE1F52A30FB}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {B8B2BF3E-CCB4-441C-B4A1-2036090AFF9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {B8B2BF3E-CCB4-441C-B4A1-2036090AFF9E}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {B8B2BF3E-CCB4-441C-B4A1-2036090AFF9E}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {B8B2BF3E-CCB4-441C-B4A1-2036090AFF9E}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {2B43DF8A-8D46-45FE-AD10-63AF88E0B570}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {2B43DF8A-8D46-45FE-AD10-63AF88E0B570}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {2B43DF8A-8D46-45FE-AD10-63AF88E0B570}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {2B43DF8A-8D46-45FE-AD10-63AF88E0B570}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {C6BF36AE-E9D3-46F5-ACF0-71FB93317D5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {C6BF36AE-E9D3-46F5-ACF0-71FB93317D5F}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {C6BF36AE-E9D3-46F5-ACF0-71FB93317D5F}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {C6BF36AE-E9D3-46F5-ACF0-71FB93317D5F}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {E65A45D8-02A3-4186-9D0F-8806C5C2E1A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {E65A45D8-02A3-4186-9D0F-8806C5C2E1A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {E65A45D8-02A3-4186-9D0F-8806C5C2E1A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 77 | {E65A45D8-02A3-4186-9D0F-8806C5C2E1A6}.Release|Any CPU.Build.0 = Release|Any CPU 78 | EndGlobalSection 79 | GlobalSection(SolutionProperties) = preSolution 80 | HideSolutionNode = FALSE 81 | EndGlobalSection 82 | GlobalSection(ExtensibilityGlobals) = postSolution 83 | SolutionGuid = {CF9BC82F-3E0E-4D8D-96DC-11EFC3AF4035} 84 | EndGlobalSection 85 | EndGlobal 86 | -------------------------------------------------------------------------------- /Utils/WrpUtil/ModInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WrpUtil 4 | { 5 | internal class ModInfo 6 | { 7 | public string Path { get; internal set; } 8 | public List Pbos { get; internal set; } 9 | public string WorkshopId { get; internal set; } 10 | } 11 | } -------------------------------------------------------------------------------- /Utils/WrpUtil/PboInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WrpUtil 4 | { 5 | internal class PboInfo 6 | { 7 | public string Path { get; internal set; } 8 | public HashSet Files { get; internal set; } 9 | public ModInfo Mod { get; internal set; } 10 | } 11 | } -------------------------------------------------------------------------------- /Utils/WrpUtil/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using BIS.Core.Streams; 7 | using BIS.PBO; 8 | using BIS.WRP; 9 | using CommandLine; 10 | 11 | namespace WrpUtil 12 | { 13 | 14 | class Program 15 | { 16 | [Verb("convert", HelpText = "Convert to editable WRP.")] 17 | class ConvertOptions 18 | { 19 | [Value(0, MetaName = "source", HelpText = "Source file.", Required = true)] 20 | public string Source { get; set; } 21 | 22 | [Value(1, MetaName = "target", HelpText = "Target file.", Required = true)] 23 | public string Target { get; set; } 24 | } 25 | 26 | [Verb("merge", HelpText = "Merge data from two editable WRP.")] 27 | class MergeOptions 28 | { 29 | [Value(0, MetaName = "master", HelpText = "Master source file, its terrain definition is kept.", Required = true)] 30 | public string Master { get; set; } 31 | 32 | [Value(1, MetaName = "objects", HelpText = "Objects source file, its objects are kept.", Required = true)] 33 | public string ToMerge { get; set; } 34 | 35 | [Value(2, MetaName = "target", HelpText = "Target file.", Required = true)] 36 | public string Target { get; set; } 37 | } 38 | 39 | [Verb("strip", HelpText = "Strip objects from a WRP, keep only terrain.")] 40 | class StripOptions 41 | { 42 | [Value(0, MetaName = "source", HelpText = "Source WRP file.", Required = true)] 43 | public string Source { get; set; } 44 | 45 | [Value(1, MetaName = "target", HelpText = "Target WRP file.", Required = true)] 46 | public string Target { get; set; } 47 | } 48 | 49 | [Verb("dependencies", HelpText = "Compute dependencies of a WRP.")] 50 | class DependenciesOptions 51 | { 52 | [Value(0, MetaName = "source", HelpText = "Source WRP file.", Required = true)] 53 | public string Source { get; set; } 54 | 55 | [Value(1, MetaName = "report", HelpText = "Report text file.", Required = false)] 56 | public string ReportFile { get; set; } 57 | 58 | [Option('m', "mods", Required = false, HelpText = "Base path of mods directory (by default !Workshop of Arma installation directory).")] 59 | public string ModsBasePath { get; set; } 60 | } 61 | 62 | public static int Main(string[] args) 63 | { 64 | return CommandLine.Parser.Default.ParseArguments(args) 65 | .MapResult( 66 | (ConvertOptions opts) => Convert(opts), 67 | (MergeOptions opts) => Merge(opts), 68 | (StripOptions opts) => Strip(opts), 69 | (DependenciesOptions opts) => Dependencies(opts), 70 | errs => 1); 71 | } 72 | 73 | 74 | private static int Convert(ConvertOptions opts) 75 | { 76 | Console.WriteLine($"Read WRP from '{opts.Source}'"); 77 | var source = StreamHelper.Read(opts.Source); 78 | 79 | Console.WriteLine("Convert"); 80 | var editable = source.GetEditableWrp(); 81 | 82 | Console.WriteLine($"Write to '{opts.Target}'"); 83 | editable.Write(opts.Target); 84 | 85 | Console.WriteLine("Done"); 86 | return 0; 87 | } 88 | 89 | private static int Merge(MergeOptions opts) 90 | { 91 | Console.WriteLine($"Read WRP from '{opts.Master}'"); 92 | var master = StreamHelper.Read(opts.Master).GetEditableWrp(); 93 | 94 | Console.WriteLine($"Read WRP from '{opts.ToMerge}'"); 95 | var tomerge = StreamHelper.Read(opts.ToMerge).GetEditableWrp().GetNonDummyObjects(); 96 | 97 | Console.WriteLine("Merge"); 98 | var objects = master.GetNonDummyObjects().ToList(); 99 | var idShift = objects.Count > 0 ? objects.Max(o => o.ObjectID) + 1 : 0; 100 | objects.AddRange(tomerge.Select(o => new EditableWrpObject() { Model = o.Model, ObjectID = o.ObjectID + idShift, Transform = o.Transform })); 101 | objects.Add(EditableWrpObject.Dummy); 102 | master.Objects = objects; 103 | 104 | Console.WriteLine($"Write to '{opts.Target}'"); 105 | master.Write(opts.Target); 106 | 107 | Console.WriteLine("Done"); 108 | return 0; 109 | } 110 | 111 | private static int Strip(StripOptions opts) 112 | { 113 | Console.WriteLine($"Read WRP from '{opts.Source}'"); 114 | var source = StreamHelper.Read(opts.Source); 115 | 116 | Console.WriteLine("Convert"); 117 | var editable = source.GetEditableWrp(); 118 | editable.Objects = new List() { EditableWrpObject.Dummy }; 119 | 120 | Console.WriteLine($"Write to '{opts.Target}'"); 121 | editable.Write(opts.Target); 122 | 123 | Console.WriteLine("Done"); 124 | return 0; 125 | } 126 | 127 | private static int Dependencies(DependenciesOptions opts) 128 | { 129 | if (string.IsNullOrEmpty(opts.ModsBasePath)) 130 | { 131 | opts.ModsBasePath = @"C:\Program Files (x86)\Steam\steamapps\common\Arma 3\!Workshop"; 132 | } 133 | Console.WriteLine($"Build index of mods pbo and files from '{opts.ModsBasePath}'"); 134 | var mods = Directory.GetDirectories(opts.ModsBasePath); 135 | var modsData = new List(); 136 | foreach (var mod in mods) 137 | { 138 | var path = Path.Combine(mod, "addons"); 139 | if (Directory.Exists(path)) 140 | { 141 | var infos = new ModInfo(); 142 | infos.Path = mod; 143 | infos.Pbos = new List(); 144 | var allPBOs = Directory.GetFiles(Path.Combine(mod, "addons"), "*.pbo"); 145 | foreach (var pboPath in allPBOs) 146 | { 147 | var pbo = new PBO(pboPath); 148 | var pboInfos = new PboInfo(); 149 | pboInfos.Mod = infos; 150 | pboInfos.Path = pboPath; 151 | pboInfos.Files = new HashSet(StringComparer.OrdinalIgnoreCase); 152 | foreach (var entry in pbo.FileEntries) 153 | { 154 | if (string.Equals(Path.GetExtension(entry.FileName), ".p3d", StringComparison.OrdinalIgnoreCase)) 155 | { 156 | pboInfos.Files.Add(Path.Combine(pbo.Prefix, entry.FileName)); 157 | } 158 | } 159 | if (pboInfos.Files.Count > 0) 160 | { 161 | infos.Pbos.Add(pboInfos); 162 | } 163 | } 164 | if (infos.Pbos.Count > 0) 165 | { 166 | infos.WorkshopId = GetWorkshopId(mod); 167 | modsData.Add(infos); 168 | } 169 | } 170 | } 171 | 172 | var allPbos = modsData.SelectMany(m => m.Pbos); 173 | 174 | Console.WriteLine($"Read WRP from '{opts.Source}'"); 175 | var source = StreamHelper.Read(opts.Source); 176 | 177 | Console.WriteLine("Compute model list"); 178 | var models = source.GetEditableWrp().GetNonDummyObjects().Select(e => e.Model).Distinct().ToHashSet(StringComparer.OrdinalIgnoreCase); 179 | 180 | var usedPbo = new HashSet(); 181 | foreach(var model in models) 182 | { 183 | if (!model.StartsWith("a3\\")) 184 | { 185 | var pbo = allPbos.FirstOrDefault(p => p.Files.Contains(model)); 186 | if (pbo != null) 187 | { 188 | usedPbo.Add(pbo); 189 | } 190 | else 191 | { 192 | Console.Error.WriteLine($"Model '{model}' was not found."); 193 | } 194 | } 195 | } 196 | 197 | var usedMods = usedPbo.GroupBy(p => p.Mod).Select(m => new ModInfo() 198 | { 199 | Path = m.Key.Path, 200 | WorkshopId = m.Key.WorkshopId, 201 | Pbos = m.Select(p => new PboInfo() 202 | { 203 | Path = p.Path, 204 | Files = p.Files.Where(f => models.Contains(f)).ToHashSet(StringComparer.OrdinalIgnoreCase) 205 | }).ToList() 206 | }).ToList(); 207 | 208 | if (string.IsNullOrEmpty(opts.ReportFile)) 209 | { 210 | opts.ReportFile = Path.ChangeExtension(opts.Source, ".txt"); 211 | } 212 | 213 | Console.WriteLine($"Write full report to '{opts.ReportFile}'"); 214 | using (var writer = new StreamWriter(opts.ReportFile, false)) 215 | { 216 | foreach (var mod in usedMods) 217 | { 218 | Console.WriteLine($" Depends on '{Path.GetFileName(mod.Path)}' (Workshop #{mod.WorkshopId})"); 219 | writer.WriteLine($"Depends on '{Path.GetFileName(mod.Path)}'"); 220 | writer.WriteLine($" Workshop #{mod.WorkshopId}"); 221 | writer.WriteLine($" '{mod.Path}')"); 222 | foreach (var pbo in mod.Pbos) 223 | { 224 | writer.WriteLine($" Content from '{Path.GetFileName(pbo.Path)}'"); 225 | foreach(var file in pbo.Files) 226 | { 227 | writer.WriteLine($" '{file}'"); 228 | } 229 | writer.WriteLine(); 230 | } 231 | writer.WriteLine(); 232 | writer.WriteLine(); 233 | } 234 | 235 | writer.WriteLine($"Project drive minimal setup (using bankrev)"); 236 | foreach (var mod in usedMods) 237 | { 238 | foreach (var pbo in mod.Pbos) 239 | { 240 | writer.WriteLine($@" bankrev -f ""P:"" -prefix ""{pbo.Path}"" "); 241 | } 242 | } 243 | } 244 | 245 | Console.WriteLine("Done"); 246 | return 0; 247 | } 248 | 249 | private static readonly Regex IdRegex = new Regex(@"publishedid\s*=\s*([0-9]+);", RegexOptions.Compiled | RegexOptions.IgnoreCase); 250 | 251 | private static string GetWorkshopId(string mod) 252 | { 253 | var infos = Path.Combine(mod, "meta.cpp"); 254 | if (File.Exists(infos)) 255 | { 256 | var match = IdRegex.Match(File.ReadAllText(infos)); 257 | if (match.Success) 258 | { 259 | return match.Groups[1].Value; 260 | } 261 | } 262 | return ""; 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /Utils/WrpUtil/README.md: -------------------------------------------------------------------------------- 1 | # Utility to manipulate WRP files. 2 | 3 | You need usually 2.5GB free memory for most operations (everything is agressively loaded into memory for performance). 4 | 5 | Remarks: If your are not the author of the source files, you have to ensure that files licence allows you to create a derivate work. 6 | You must contact author if your are not sure about this. 7 | Allowed by APL-SA, APL, CC BY-NC-SA. 8 | Forbidden by APL-ND, Bohemia Interactive EULA. 9 | 10 | ## wrputil convert 11 | 12 | Allows you to convert a binarised file to a file that can be imported into Terrain Builder (like the ConvertWrp utility but with fewer supported formats). 13 | 14 | Can save your life, if your Terrain Builder projet was corrupted or lost (I hope you still have the imagery, it can't be restored). 15 | 16 | It can also import maps from older Arma versions. 17 | 18 | Like stated earlier, you must ensure that you can create a derivate work, check twice the licence ! 19 | 20 | Can read any OPRW or 8WVR, can write only 8WVR. 21 | 22 | ## wrputil merge 23 | 24 | Allows you to work in Terrain Builder with multiple projets, with fewer objects, and merge back to a single WRP to binarize. It allows to work with multiple authors on the 25 | same map without sharing the Terrain Builder mess. 26 | 27 | If you split in more than two parts, you will have to chain the calls. 28 | 29 | ## wrputil strip 30 | 31 | Create a WRP without any object, an empty world. Can be usefull with `wrputil merge`. 32 | 33 | ## wrputil dependencies 34 | 35 | Compute real dependencies of map, and generates a report will all dependencies. 36 | -------------------------------------------------------------------------------- /Utils/WrpUtil/WrpUtil.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | GrueArbre 7 | 0.1.0 8 | Utility to manipulate WRP files. 9 | 10 | Remarks: If your are not the author of the source files, you have to ensure that files licence allows you to create a derivate work. 11 | You must contact author if your are not sure about this. 12 | Allowed by APL-SA, APL, CC BY-NC-SA. 13 | Forbidden by APL-ND, Bohemia Interactive EULA. 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /bis-file-formats.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31321.278 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.ALB", "BIS.ALB\BIS.ALB.csproj", "{8D1B5008-1084-43F8-9E45-AF7894ECA64C}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.Core", "BIS.Core\BIS.Core.csproj", "{B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.P3D", "BIS.P3D\BIS.P3D.csproj", "{842AB5B1-28EF-476C-88FC-DD26EF6DBD4F}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.PAA", "BIS.PAA\BIS.PAA.csproj", "{9399460C-6D54-4C03-A4DE-1E398A9A007E}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.PBO", "BIS.PBO\BIS.PBO.csproj", "{500A441F-1736-4FE1-AAE0-0DE1F52A30FB}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.RTM", "BIS.RTM\BIS.RTM.csproj", "{B8B2BF3E-CCB4-441C-B4A1-2036090AFF9E}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.WRP", "BIS.WRP\BIS.WRP.csproj", "{2B43DF8A-8D46-45FE-AD10-63AF88E0B570}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.Core.Test", "BIS.Core.Test\BIS.Core.Test.csproj", "{6A91F1B2-5C8A-495A-824B-1BC7825B1939}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {8D1B5008-1084-43F8-9E45-AF7894ECA64C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {8D1B5008-1084-43F8-9E45-AF7894ECA64C}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {8D1B5008-1084-43F8-9E45-AF7894ECA64C}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {8D1B5008-1084-43F8-9E45-AF7894ECA64C}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {B877F60B-0E26-4572-9E6A-6C9E67C5F8D7}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {842AB5B1-28EF-476C-88FC-DD26EF6DBD4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {842AB5B1-28EF-476C-88FC-DD26EF6DBD4F}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {842AB5B1-28EF-476C-88FC-DD26EF6DBD4F}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {842AB5B1-28EF-476C-88FC-DD26EF6DBD4F}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {9399460C-6D54-4C03-A4DE-1E398A9A007E}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {500A441F-1736-4FE1-AAE0-0DE1F52A30FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {500A441F-1736-4FE1-AAE0-0DE1F52A30FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {500A441F-1736-4FE1-AAE0-0DE1F52A30FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {500A441F-1736-4FE1-AAE0-0DE1F52A30FB}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {B8B2BF3E-CCB4-441C-B4A1-2036090AFF9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {B8B2BF3E-CCB4-441C-B4A1-2036090AFF9E}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {B8B2BF3E-CCB4-441C-B4A1-2036090AFF9E}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {B8B2BF3E-CCB4-441C-B4A1-2036090AFF9E}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {2B43DF8A-8D46-45FE-AD10-63AF88E0B570}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {2B43DF8A-8D46-45FE-AD10-63AF88E0B570}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {2B43DF8A-8D46-45FE-AD10-63AF88E0B570}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {2B43DF8A-8D46-45FE-AD10-63AF88E0B570}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {6A91F1B2-5C8A-495A-824B-1BC7825B1939}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {6A91F1B2-5C8A-495A-824B-1BC7825B1939}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {6A91F1B2-5C8A-495A-824B-1BC7825B1939}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {6A91F1B2-5C8A-495A-824B-1BC7825B1939}.Release|Any CPU.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(ExtensibilityGlobals) = postSolution 65 | SolutionGuid = {CF9BC82F-3E0E-4D8D-96DC-11EFC3AF4035} 66 | EndGlobalSection 67 | EndGlobal 68 | --------------------------------------------------------------------------------