├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE.txt ├── PSTParse ├── ListsTablesPropertiesLayer │ ├── BTH.cs │ ├── BTHDataEntry.cs │ ├── BTHDataNode.cs │ ├── BTHHEADER.cs │ ├── BTHIndexEntry.cs │ ├── BTHIndexNode.cs │ ├── EntryID.cs │ ├── ExchangeProperty.cs │ ├── HID.cs │ ├── HN.cs │ ├── HNBITMAPHDR.cs │ ├── HNBlock.cs │ ├── HNDataDTO.cs │ ├── HNHDR.cs │ ├── HNPAGEHDR.cs │ ├── HNPAGEMAP.cs │ ├── HeapNodeBO.cs │ ├── PropertyContext.cs │ ├── TCINFOHEADER.cs │ ├── TCOLDESC.cs │ ├── TCRowMatrix.cs │ ├── TCRowMatrixData.cs │ ├── TableContext.cs │ └── unused │ │ ├── BTHDataRecord.cs │ │ ├── BTHIndexAllocationRecords.cs │ │ ├── HNID.cs │ │ ├── MVPropVarBase.cs │ │ └── PCBTHRecord.cs ├── MessageLayer │ ├── Attachment.cs │ ├── IPMItem.cs │ ├── MailFolder.cs │ ├── MailStore.cs │ ├── Message.cs │ ├── MessageProperty.cs │ ├── Recipient.cs │ ├── Recipients.cs │ └── unused │ │ ├── MessagePropertyTypes.cs │ │ ├── NAMEID.cs │ │ ├── NamedProperty.cs │ │ ├── NamedToPropertyLookup.cs │ │ └── PropType.cs ├── NodeDatabaseLayer │ ├── BBTENTRY.cs │ ├── BREF.cs │ ├── BTENTRY.cs │ ├── BTPAGEENTRY.cs │ ├── BTPage.cs │ ├── BlockBO.cs │ ├── BlockDataDTO.cs │ ├── BlockTrailer.cs │ ├── DatatEncoder.cs │ ├── IBLOCK.cs │ ├── NBTENTRY.cs │ ├── NID.cs │ ├── NodeDataDTO.cs │ ├── PSTBTree.cs │ ├── PageTrailer.cs │ ├── SIBLOCK.cs │ ├── SIENTRY.cs │ ├── SLBLOCK.cs │ ├── SLENTRY.cs │ ├── SpecialNIDs.cs │ ├── XBLOCK.cs │ ├── XXBLOCK.cs │ └── unused │ │ ├── BID.cs │ │ ├── BlockFactory.cs │ │ ├── NodeBTree.cs │ │ ├── PSTBTreeNode.cs │ │ └── SubNodeDataDTO.cs ├── PSTEnums.cs ├── PSTFile.cs ├── PSTHeader.cs ├── PSTParse.csproj ├── PSTRoot.cs └── Utilities │ ├── ArrayUtilities.cs │ ├── CRC32.cs │ ├── RtfDecompressor.cs │ └── Utilities.cs ├── PSTParseApp.sln ├── PSTParseApp ├── PSTParseApp.csproj └── Program.cs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | #customs 2 | db.lock 3 | storage.ide 4 | storage.ide-shm 5 | storage.ide-wal 6 | .dtbcache 7 | *.nupkg 8 | *.pst 9 | 10 | #OS junk files 11 | [Tt]humbs.db 12 | *.DS_Store 13 | 14 | #Visual Studio files 15 | *.[Oo]bj 16 | *.user 17 | *.aps 18 | *.pch 19 | *.vspscc 20 | *.vssscc 21 | *_i.c 22 | *_p.c 23 | *.ncb 24 | *.suo 25 | *.tlb 26 | *.tlh 27 | *.bak 28 | *.[Cc]ache 29 | *.ilk 30 | *.log 31 | *.lib 32 | *.sbr 33 | *.sdf 34 | *.opensdf 35 | *.unsuccessfulbuild 36 | ipch/ 37 | obj/ 38 | [Bb]in 39 | [Dd]ebug*/ 40 | [Rr]elease*/ 41 | Ankh.NoLoad 42 | 43 | #MonoDevelop 44 | *.pidb 45 | *.userprefs 46 | 47 | #Tooling 48 | _ReSharper*/ 49 | *.resharper 50 | [Tt]est[Rr]esult* 51 | *.sass-cache 52 | 53 | #Project files 54 | [Bb]uild/ 55 | 56 | #Subversion files 57 | .svn 58 | 59 | # Office Temp Files 60 | ~$* 61 | 62 | #NuGet 63 | packages/ 64 | 65 | #ncrunch 66 | *ncrunch* 67 | *crunch*.local.xml 68 | 69 | # visual studio database projects 70 | *.dbmdl 71 | 72 | #Test files 73 | *.testsettings 74 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (console)", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build", 9 | "program": "${workspaceFolder}/PSTParseApp/bin/Debug/net60/PSTParseApp.dll", 10 | "args": [], 11 | "cwd": "${workspaceFolder}", 12 | "console": "internalConsole", 13 | "stopAtEntry": false 14 | }, 15 | { 16 | "name": ".NET Core Attach", 17 | "type": "coreclr", 18 | "request": "attach" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/PSTParseApp/PSTParseApp.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/PSTParseApp/PSTParseApp.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/PSTParseApp/PSTParseApp.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 sharpiro 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. -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/BTH.cs: -------------------------------------------------------------------------------- 1 | using PSTParse.MessageLayer; 2 | using PSTParse.Utilities; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace PSTParse.ListsTablesPropertiesLayer 7 | { 8 | /// 9 | /// Btree on Heap (BTH) 10 | /// 11 | public class BTH 12 | { 13 | public HN HeapNode { get; set; } 14 | public BTHHEADER Header { get; set; } 15 | public BTHIndexNode Root { get; set; } 16 | public int CurrentLevel { get; set; } 17 | public Dictionary Properties { get; set; } 18 | 19 | public BTH(HN heapNode, HID userRoot = null) 20 | { 21 | HeapNode = heapNode; 22 | 23 | var bthHeaderHID = userRoot ?? heapNode.HeapNodes[0].Header.UserRoot; 24 | Header = new BTHHEADER(HeapNodeBO.GetHNHIDBytes(heapNode, bthHeaderHID)); 25 | Root = new BTHIndexNode(Header.BTreeRoot, this, (int)Header.NumLevels); 26 | 27 | Properties = new Dictionary(new ArrayUtilities.ByteArrayComparer()); 28 | 29 | var stack = new Stack(); 30 | stack.Push(Root); 31 | while (stack.Count > 0) 32 | { 33 | var cur = stack.Pop(); 34 | 35 | try 36 | { 37 | if (cur.Data != null) 38 | foreach (var entry in cur.Data.DataEntries) 39 | Properties.Add(entry.Key, entry); 40 | } 41 | catch (Exception ex) 42 | { 43 | throw new Exception("Cannot display this view. Failed to create all properties in this location", ex); 44 | } 45 | 46 | 47 | if (cur.Children != null) 48 | foreach (var child in cur.Children) 49 | stack.Push(child); 50 | 51 | } 52 | } 53 | 54 | public HNDataDTO GetHIDBytes(HID hid) 55 | { 56 | return HeapNode.GetHIDBytes(hid); 57 | } 58 | 59 | // todo: this might be getting called twice 60 | public Dictionary GetExchangeProperties() 61 | { 62 | var ret = new Dictionary(); 63 | 64 | var stack = new Stack(); 65 | stack.Push(Root); 66 | while (stack.Count > 0) 67 | { 68 | var cur = stack.Pop(); 69 | 70 | if (cur.Data != null) 71 | foreach (var entry in cur.Data.DataEntries) 72 | { 73 | var curKey = BitConverter.ToUInt16(entry.Key, 0); 74 | int i = 0; 75 | if (curKey == 0x02) 76 | i++; 77 | ret.Add((MessageProperty)curKey, new ExchangeProperty(entry, this)); 78 | } 79 | 80 | if (cur.Children != null) 81 | foreach (var child in cur.Children) 82 | stack.Push(child); 83 | 84 | } 85 | 86 | return ret; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/BTHDataEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using PSTParse.NodeDatabaseLayer; 6 | using PSTParse.Utilities; 7 | 8 | namespace PSTParse.ListsTablesPropertiesLayer 9 | { 10 | public class BTHDataEntry 11 | { 12 | public byte[] Key; 13 | public byte[] Data; 14 | 15 | public ulong DataOffset; 16 | public ulong DataBlockOffset; 17 | public BTH ParentTree; 18 | 19 | public BTHDataEntry(HNDataDTO data, int offset, BTH tree) 20 | { 21 | this.Key = data.Data.RangeSubset(offset, (int) tree.Header.KeySize); 22 | //this.Key = bytes.Skip(offset).Take((int)tree.Header.KeySize).ToArray(); 23 | var temp = offset + (int) tree.Header.KeySize; 24 | this.Data = data.Data.RangeSubset(temp, (int)tree.Header.DataSize); 25 | this.DataOffset = (ulong) offset + tree.Header.KeySize; 26 | this.ParentTree = tree; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/BTHDataNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PSTParse.ListsTablesPropertiesLayer 4 | { 5 | public class BTHDataNode 6 | { 7 | public List DataEntries { get; } 8 | public HNDataDTO Data { get; } 9 | public BTH Tree { get; } 10 | 11 | public BTHDataNode(HID hid, BTH tree) 12 | { 13 | Tree = tree; 14 | 15 | var bytes = tree.GetHIDBytes(hid); 16 | Data = bytes; 17 | DataEntries = new List(); 18 | for (int i = 0; i < bytes.Data.Length; i += (int)(tree.Header.KeySize + tree.Header.DataSize)) 19 | DataEntries.Add(new BTHDataEntry(bytes, i, tree)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/BTHHEADER.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using PSTParse.NodeDatabaseLayer; 6 | using PSTParse.Utilities; 7 | 8 | namespace PSTParse.ListsTablesPropertiesLayer 9 | { 10 | public class BTHHEADER 11 | { 12 | public uint BType; 13 | //must be 2,4,8,16 14 | public uint KeySize; 15 | //must be >0 <=32 16 | public uint DataSize; 17 | public uint NumLevels; 18 | public HID BTreeRoot; 19 | 20 | public BTHHEADER(HNDataDTO block) 21 | { 22 | var bytes = block.Data; 23 | this.BType = bytes[0]; 24 | this.KeySize = bytes[1]; 25 | this.DataSize = bytes[2]; 26 | this.NumLevels = bytes[3]; 27 | this.BTreeRoot = new HID(bytes.RangeSubset(4, 4)); 28 | 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/BTHIndexEntry.cs: -------------------------------------------------------------------------------- 1 | using PSTParse.Utilities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace PSTParse.ListsTablesPropertiesLayer 8 | { 9 | public class BTHIndexEntry 10 | { 11 | public byte[] Key; 12 | public HID HID; 13 | 14 | public BTHIndexEntry(byte[] bytes, int offset, BTHHEADER header) 15 | { 16 | this.Key = bytes.RangeSubset(offset,(int)header.KeySize); 17 | var temp = offset + (int) header.KeySize; 18 | this.HID = new HID(bytes.RangeSubset(temp, 4)); 19 | 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/BTHIndexNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PSTParse.ListsTablesPropertiesLayer 4 | { 5 | public class BTHIndexNode 6 | { 7 | public HID HID; 8 | public int Level; 9 | 10 | public List Entries; 11 | public List Children; 12 | public BTHDataNode Data; 13 | 14 | public BTHIndexNode(HID hid, BTH tree, int level) 15 | { 16 | this.Level = level; 17 | this.HID = hid; 18 | if (hid.hidBlockIndex == 0 && hid.hidIndex == 0) 19 | return; 20 | 21 | this.Entries = new List(); 22 | 23 | if (level == 0) 24 | { 25 | this.Data = new BTHDataNode(hid, tree); 26 | /* 27 | for (int i = 0; i < bytes.Length; i += (int)tree.Header.KeySize + 4) 28 | this.Entries.Add(new BTHIndexEntry(bytes, i, tree.Header)); 29 | this.DataChildren = new List(); 30 | foreach(var entry in this.Entries) 31 | this.DataChildren.Add(new BTHDataNode(entry.HID, tree));*/ 32 | } 33 | else 34 | { 35 | var bytes = tree.GetHIDBytes(hid); 36 | for (int i = 0; i < bytes.Data.Length; i += (int)tree.Header.KeySize + 4) 37 | this.Entries.Add(new BTHIndexEntry(bytes.Data, i, tree.Header)); 38 | this.Children = new List(); 39 | foreach (var entry in this.Entries) 40 | this.Children.Add(new BTHIndexNode(entry.HID, tree, level - 1)); 41 | } 42 | 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/EntryID.cs: -------------------------------------------------------------------------------- 1 | using PSTParse.Utilities; 2 | using System; 3 | using System.IO; 4 | 5 | namespace PSTParse.ListsTablesPropertiesLayer 6 | { 7 | public class EntryID 8 | { 9 | public uint Flags { get; set; } 10 | public byte[] PSTUID { get; set; } 11 | public ulong NID { get; set; } 12 | 13 | public EntryID(byte[] bytes, int offset = 0) 14 | { 15 | if (bytes.Length == 0) 16 | { 17 | throw new InvalidDataException("The entry id was invalid, try running a PST repair"); 18 | } 19 | 20 | Flags = BitConverter.ToUInt32(bytes, offset); 21 | PSTUID = bytes.RangeSubset(4+offset, 16); 22 | NID = BitConverter.ToUInt32(bytes, offset + 20); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/ExchangeProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using PSTParse.MessageLayer; 4 | using PSTParse.NodeDatabaseLayer; 5 | using PSTParse.Utilities; 6 | 7 | namespace PSTParse.ListsTablesPropertiesLayer 8 | { 9 | public class ExchangeProperty 10 | { 11 | public enum PropType : uint 12 | { 13 | Unspecified = 0x0000, 14 | NullType = 0x0001, 15 | Integer16 = 0x0002, 16 | Integer32 = 0x0003, 17 | Floating32 = 0x0004, 18 | Floating64 = 0x0005, 19 | Currency = 0x0006, 20 | FloatingTime = 0x0007, 21 | ErrorCode = 0x000A, 22 | Boolean = 0x000B, 23 | ObjectType = 0x000D, 24 | Integer64 = 0x0014, 25 | String = 0x001F, 26 | String8 = 0x001E, 27 | Time = 0x0040, 28 | Guid = 0x0048, 29 | ServerId = 0x00FB, 30 | Restriction = 0x00FD, 31 | RuleAction = 0x00FE, 32 | Binary = 0x0102, 33 | MultipleInteger16 = 0x1002, 34 | MultipleInteger32 = 0x1003, 35 | MultipleFloating32 = 0x1004, 36 | MultipleFloating64 = 0x1005, 37 | MultipleCurrency = 0x1006, 38 | MultipleFloatingTime = 0x1007, 39 | MultipleInteger64 = 0x1014, 40 | MultipleString = 0x101F, 41 | MultipleString8 = 0x101E, 42 | MultipleTime = 0x1040, 43 | MultipleGuid = 0x1048, 44 | MultipleBinary = 0x1102, 45 | } 46 | 47 | public static Dictionary 48 | PropertyLookupByTypeID = new Dictionary 49 | 50 | { 51 | {(PropType)0x0002,new ExchangeProperty{ByteCount = 2,Type = (PropType)0x0002,MultiValue = false, Variable = false}}, 52 | {(PropType)0x0003,new ExchangeProperty{ByteCount = 4,Type = (PropType)0x0003,MultiValue = false, Variable = false}}, 53 | {(PropType)0x0004,new ExchangeProperty{ByteCount = 4,Type = (PropType)0x0004,MultiValue = false, Variable = false}}, 54 | {(PropType)0x0005,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x0005,MultiValue = false, Variable = false}}, 55 | {(PropType)0x0006,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x0006,MultiValue = false, Variable = false}}, 56 | {(PropType)0x0007,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x0007,MultiValue = false, Variable = false}}, 57 | {(PropType)0x000A,new ExchangeProperty{ByteCount = 4,Type = (PropType)0x000A,MultiValue = false, Variable = false}}, 58 | {(PropType)0x000B,new ExchangeProperty{ByteCount = 1,Type = (PropType)0x000B,MultiValue = false, Variable = false}}, 59 | {(PropType)0x0014,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x0014,MultiValue = false, Variable = false}}, 60 | {(PropType)0x001F,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x001F,MultiValue = true, Variable = true}}, 61 | {(PropType)0x001E,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x001E,MultiValue = true, Variable = true}}, 62 | {(PropType)0x0040,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x0040,MultiValue = false, Variable = false}}, 63 | {(PropType)0x0048,new ExchangeProperty{ByteCount = 16,Type = (PropType)0x0048,MultiValue = false, Variable = false}}, 64 | {(PropType)0x00FB,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x00FB,MultiValue = false, Variable = true}}, 65 | {(PropType)0x00FD,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x00FD,MultiValue = false, Variable = true}}, 66 | {(PropType)0x00FE,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x00FE,MultiValue = true, Variable = true}}, 67 | {(PropType)0x0102,new ExchangeProperty{ByteCount = 1,Type = (PropType)0x0102,MultiValue = true, Variable = false}}, 68 | {(PropType)0x1002,new ExchangeProperty{ByteCount = 2,Type = (PropType)0x1002,MultiValue = true, Variable = false}}, 69 | {(PropType)0x1003,new ExchangeProperty{ByteCount = 4,Type = (PropType)0x1003,MultiValue = true, Variable = false}}, 70 | {(PropType)0x1004,new ExchangeProperty{ByteCount = 4,Type = (PropType)0x1004,MultiValue = true, Variable = false}}, 71 | {(PropType)0x1005,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1005,MultiValue = true, Variable = false}}, 72 | {(PropType)0x1006,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1006,MultiValue = true, Variable = false}}, 73 | {(PropType)0x1007,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1007,MultiValue = true, Variable = false}}, 74 | {(PropType)0x1014,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1014,MultiValue = true, Variable = false}}, 75 | {(PropType)0x101F,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x101F,MultiValue = true, Variable = true}}, 76 | {(PropType)0x101E,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x101E,MultiValue = true, Variable = true}}, 77 | {(PropType)0x1040,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1040,MultiValue = true, Variable = false}}, 78 | {(PropType)0x1048,new ExchangeProperty{ByteCount = 8,Type = (PropType)0x1048,MultiValue = true, Variable = false}}, 79 | {(PropType)0x1102,new ExchangeProperty{ByteCount = 0,Type = (PropType)0x1102,MultiValue = true, Variable = true}}, 80 | //{0x1102,new ExchangeProperty{ByteCount = 0,Type = 0x1102,MultiValue = true, Variable = true}} 81 | }; 82 | 83 | public MessageProperty ID { get; set; } 84 | public PropType Type { get; set; } 85 | public bool MultiValue { get; set; } 86 | public bool Variable { get; set; } 87 | public uint ByteCount { get; set; } 88 | public byte[] Data { get; set; } 89 | //private BTHDataEntry entry; 90 | 91 | private readonly byte[] _key; 92 | 93 | public ExchangeProperty() { } 94 | 95 | public ExchangeProperty(UInt16 ID, UInt16 type, BTH heap, byte[] key) 96 | { 97 | this.ID = (MessageProperty)ID; 98 | this.Type = (PropType)type; 99 | /*var tempKey = new byte[key.Length + 2]; 100 | tempKey[0] = 0x00; 101 | tempKey[1] = 0x00; 102 | for (int i = 0; i < key.Length; i++) 103 | tempKey[i + 2] = key[i];*/ 104 | this._key = key; 105 | 106 | GetData(heap, true); 107 | } 108 | 109 | public ExchangeProperty(BTHDataEntry entry, BTH heap) 110 | { 111 | //this.entry = entry; 112 | 113 | _key = entry.Data.RangeSubset(2, entry.Data.Length - 2); 114 | ID = (MessageProperty)BitConverter.ToUInt16(entry.Key, 0); 115 | Type = (PropType)BitConverter.ToUInt16(entry.Data, 0); 116 | 117 | GetData(heap); 118 | } 119 | 120 | private void GetData(BTH heap, bool isTable = false) 121 | { 122 | if (PropertyLookupByTypeID.ContainsKey(Type)) 123 | { 124 | var prop = PropertyLookupByTypeID[this.Type]; 125 | MultiValue = prop.MultiValue; 126 | Variable = prop.Variable; 127 | ByteCount = prop.ByteCount; 128 | } 129 | //get data here 130 | 131 | if (!this.MultiValue && !this.Variable) 132 | { 133 | if (this.ByteCount <= 4 || (isTable && this.ByteCount <= 8)) 134 | { 135 | this.Data = this._key; 136 | } 137 | else 138 | { 139 | this.Data = heap.GetHIDBytes(new HID(this._key)).Data; 140 | } 141 | } 142 | else 143 | { 144 | //oh no, it's an HNID 145 | var curID = BitConverter.ToUInt32(this._key, 0); 146 | 147 | if (curID == 0) 148 | { 149 | 150 | } 151 | else if ((curID & 0x1F) == 0) //must be HID 152 | { 153 | this.Data = heap.GetHIDBytes(new HID(this._key)).Data; 154 | } 155 | else //let's assume NID 156 | { 157 | var totalSize = 0; 158 | var dataBlocks = new List(); 159 | if (heap.HeapNode.HeapSubNode.ContainsKey(curID)) 160 | dataBlocks = heap.HeapNode.HeapSubNode[curID].NodeData; 161 | else 162 | { 163 | var tempSubNodeXREF = new Dictionary(); 164 | foreach (var heapSubNode in heap.HeapNode.HeapSubNode) 165 | tempSubNodeXREF.Add(heapSubNode.Key & 0xFFFFFFFF, heapSubNode.Value); 166 | dataBlocks = tempSubNodeXREF[curID].NodeData; 167 | //dataBlocks = entry.ParentTree.HeapNode.HeapSubNode[curID].NodeData; 168 | } 169 | foreach (var dataBlock in dataBlocks) 170 | totalSize += dataBlock.Data.Length; 171 | var allData = new byte[totalSize]; 172 | var curPos = 0; 173 | foreach (var datablock in dataBlocks) 174 | { 175 | for (int i = 0; i < datablock.Data.Length; i++) 176 | { 177 | allData[i + curPos] = datablock.Data[i]; 178 | } 179 | curPos += datablock.Data.Length; 180 | } 181 | this.Data = allData; 182 | } 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/HID.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PSTParse.ListsTablesPropertiesLayer 7 | { 8 | public class HID 9 | { 10 | public ulong HID_Type; 11 | //the index in the allocations for the specific heap block. 12 | public ulong hidIndex; 13 | //the index in the block array for this heap 14 | public ulong hidBlockIndex; 15 | 16 | public HID(byte[] bytes, int offset = 0) 17 | { 18 | var temp = BitConverter.ToUInt32(bytes, offset); 19 | this.HID_Type = temp & 0x1F; 20 | this.hidIndex = (temp >> 5) & 0x7FF; 21 | this.hidBlockIndex = BitConverter.ToUInt16(bytes, offset + 2); 22 | } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/HN.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PSTParse.NodeDatabaseLayer; 3 | 4 | namespace PSTParse.ListsTablesPropertiesLayer 5 | { 6 | /// 7 | /// Heap on Node (HN) 8 | /// 9 | public class HN 10 | { 11 | // why unused 12 | // public NBTENTRY HNNode { get; set; } 13 | public List HeapNodes { get; set; } 14 | public Dictionary HeapSubNode { get; set; } 15 | 16 | public HN(NodeDataDTO nodeData) 17 | { 18 | HeapNodes = new List(); 19 | var numBlocks = nodeData.NodeData.Count; 20 | for (int i = 0; i < numBlocks; i++) 21 | { 22 | var curBlock = new HNBlock(i, nodeData.NodeData[i]); 23 | HeapNodes.Add(curBlock); 24 | } 25 | 26 | HeapSubNode = nodeData.SubNodeData; 27 | } 28 | 29 | public HNDataDTO GetHIDBytes(HID hid) 30 | { 31 | return HeapNodes[(int)hid.hidBlockIndex].GetAllocation(hid); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/HNBITMAPHDR.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PSTParse.ListsTablesPropertiesLayer 7 | { 8 | public class HNBITMAPHDR 9 | { 10 | public uint HNPageMapOffset; 11 | public byte[] FillLevel; 12 | 13 | public HNBITMAPHDR(ref byte[] bytes) 14 | { 15 | this.HNPageMapOffset = BitConverter.ToUInt16(bytes, 0); 16 | this.FillLevel = bytes.Skip(2).Take(64).ToArray(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/HNBlock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PSTParse.NodeDatabaseLayer; 3 | using PSTParse.Utilities; 4 | 5 | namespace PSTParse.ListsTablesPropertiesLayer 6 | { 7 | /// 8 | /// Heap Node Block (HNBlock) 9 | /// 10 | public class HNBlock 11 | { 12 | public HNHDR Header { get; } 13 | public HNPAGEHDR PageHeader { get; } 14 | public HNBITMAPHDR BitMapPageHeader { get; } 15 | public HNPAGEMAP PageMap { get; } 16 | public ushort PageMapOffset { get; } 17 | private BlockDataDTO _bytes { get; } 18 | 19 | public HNBlock(int blockIndex, BlockDataDTO bytes) 20 | { 21 | _bytes = bytes; 22 | var bytesData = _bytes.Data; 23 | 24 | PageMapOffset = BitConverter.ToUInt16(_bytes.Data, 0); 25 | PageMap = new HNPAGEMAP(_bytes.Data, PageMapOffset); 26 | if (blockIndex == 0) 27 | { 28 | Header = new HNHDR(_bytes.Data); 29 | } else if (blockIndex % 128 == 8) 30 | { 31 | BitMapPageHeader = new HNBITMAPHDR(ref bytesData); 32 | } else 33 | { 34 | PageHeader = new HNPAGEHDR(ref bytesData); 35 | } 36 | } 37 | 38 | public HNDataDTO GetAllocation(HID hid) 39 | { 40 | var begOffset = PageMap.AllocationTable[(int) hid.hidIndex - 1]; 41 | var endOffset = PageMap.AllocationTable[(int) hid.hidIndex]; 42 | return new HNDataDTO 43 | { 44 | Data = _bytes.Data.RangeSubset(begOffset, endOffset - begOffset), 45 | BlockOffset = begOffset, 46 | Parent = _bytes 47 | }; 48 | } 49 | 50 | public int GetOffset() 51 | { 52 | if (Header != null) 53 | return 12; 54 | if (PageHeader != null) 55 | return 2; 56 | return 66; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/HNDataDTO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using PSTParse.NodeDatabaseLayer; 6 | 7 | namespace PSTParse.ListsTablesPropertiesLayer 8 | { 9 | public class HNDataDTO 10 | { 11 | public BlockDataDTO Parent; 12 | public long BlockOffset; 13 | public byte[] Data; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/HNHDR.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PSTParse.ListsTablesPropertiesLayer 7 | { 8 | public class HNHDR 9 | { 10 | public enum ClientSig 11 | { 12 | TableContext = 0x7C, 13 | BTreeHeap = 0xB5, 14 | PropertyContext = 0xBC 15 | } 16 | public ulong OffsetHNPageMap; 17 | public ulong bSig; 18 | public ClientSig ClientSigType; 19 | public HID UserRoot; 20 | public ulong FillLevel_raw; 21 | 22 | public HNHDR(byte[] bytes) 23 | { 24 | this.ClientSigType = (ClientSig)bytes[3]; 25 | this.bSig = bytes[2]; 26 | this.OffsetHNPageMap = BitConverter.ToUInt16(bytes, 0); 27 | this.UserRoot = new HID(bytes.Skip(4).Take(4).ToArray()); 28 | this.FillLevel_raw = BitConverter.ToUInt32(bytes, 8); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/HNPAGEHDR.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PSTParse.ListsTablesPropertiesLayer 7 | { 8 | public class HNPAGEHDR 9 | { 10 | public UInt16 HNPageMapOffset; 11 | public HNPAGEHDR(ref byte[] bytes) 12 | { 13 | this.HNPageMapOffset = BitConverter.ToUInt16(bytes, 0); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/HNPAGEMAP.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PSTParse.ListsTablesPropertiesLayer 7 | { 8 | public class HNPAGEMAP 9 | { 10 | public uint AllocationsCount; 11 | public uint FreeItemsCount; 12 | public List AllocationTable; 13 | 14 | public HNPAGEMAP(byte[] bytes, int offset) 15 | { 16 | this.AllocationsCount = BitConverter.ToUInt16(bytes, offset); 17 | this.FreeItemsCount = BitConverter.ToUInt16(bytes, offset+2); 18 | this.AllocationTable = new List(); 19 | 20 | for(int i= 0;i < AllocationsCount+1;i++) 21 | this.AllocationTable.Add(BitConverter.ToUInt16(bytes,offset+4+i*2)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/HeapNodeBO.cs: -------------------------------------------------------------------------------- 1 | using PSTParse.NodeDatabaseLayer; 2 | 3 | namespace PSTParse.ListsTablesPropertiesLayer 4 | { 5 | public static class HeapNodeBO 6 | { 7 | public static HN GetHeapNode(ulong NID, PSTFile pst) 8 | { 9 | return new HN(BlockBO.GetNodeData(NID, pst)); 10 | } 11 | 12 | public static HNDataDTO GetHNHIDBytes(HN heapNode, HID hid) 13 | { 14 | var hnblock = heapNode.HeapNodes[(int)hid.hidBlockIndex]; 15 | return hnblock.GetAllocation(hid); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/PropertyContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PSTParse.MessageLayer; 3 | using PSTParse.NodeDatabaseLayer; 4 | using static PSTParse.Utilities.Utilities; 5 | using static System.Text.Encoding; 6 | 7 | namespace PSTParse.ListsTablesPropertiesLayer 8 | { 9 | public class PropertyContext 10 | { 11 | private string _messageClassProperty; 12 | 13 | public BTH BTH { get; } 14 | public Dictionary Properties { get; } 15 | public ulong NID { get; } 16 | public string MessageClassProperty => 17 | Lazy(ref _messageClassProperty, () => Unicode.GetString(Properties[MessageProperty.MessageClass].Data)); 18 | 19 | public PropertyContext(ulong nid, PSTFile pst) : this(BlockBO.GetNodeData(nid, pst)) => NID = nid; 20 | public PropertyContext(NodeDataDTO nodeData) 21 | { 22 | var HN = new HN(nodeData); 23 | BTH = new BTH(HN); 24 | Properties = BTH.GetExchangeProperties(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/TCINFOHEADER.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PSTParse.ListsTablesPropertiesLayer 5 | { 6 | public class TCINFOHEADER 7 | { 8 | public byte Type; 9 | public ushort ColumnCount; 10 | public ushort EndOffset48; 11 | public ushort EndOffset2; 12 | public ushort EndOffset1; 13 | public ushort EndOffsetCEB; 14 | public HID RowIndexLocation; 15 | public ulong RowMatrixLocation; 16 | 17 | public List ColumnsDescriptors; 18 | 19 | public TCINFOHEADER(byte[] bytes) 20 | { 21 | this.Type = bytes[0]; 22 | this.ColumnCount = bytes[1]; 23 | this.EndOffset48 = BitConverter.ToUInt16(bytes, 2); 24 | this.EndOffset2 = BitConverter.ToUInt16(bytes, 4); 25 | this.EndOffset1 = BitConverter.ToUInt16(bytes, 6); 26 | this.EndOffsetCEB = BitConverter.ToUInt16(bytes, 8); 27 | this.RowIndexLocation = new HID(bytes, 10); 28 | this.RowMatrixLocation = BitConverter.ToUInt32(bytes, 14); 29 | this.ColumnsDescriptors = new List(); 30 | for (var i = 0; i < this.ColumnCount; i++) 31 | { 32 | this.ColumnsDescriptors.Add(new TCOLDESC(bytes, 22 + i*8)); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/TCOLDESC.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PSTParse.ListsTablesPropertiesLayer 7 | { 8 | public class TCOLDESC 9 | { 10 | public uint Tag; 11 | public ushort DataOffset; 12 | public ushort DataSize; 13 | public ushort CEBIndex; 14 | 15 | public TCOLDESC(byte[] bytes, int offset = 0) 16 | { 17 | this.Tag = BitConverter.ToUInt32(bytes, offset); 18 | this.DataOffset = BitConverter.ToUInt16(bytes, offset + 4); 19 | this.DataSize = bytes[offset + 6]; 20 | this.CEBIndex = bytes[offset + 7]; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/TCRowMatrix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using PSTParse.NodeDatabaseLayer; 6 | 7 | namespace PSTParse.ListsTablesPropertiesLayer 8 | { 9 | public class TCRowMatrix 10 | { 11 | public TableContext TableContext; 12 | public List TCRMData; 13 | 14 | public List Rows; 15 | public Dictionary RowXREF; 16 | 17 | public TCRowMatrix(TableContext tableContext, BTH heap) 18 | { 19 | this.Rows = new List(); 20 | this.RowXREF = new Dictionary(); 21 | 22 | this.TableContext = tableContext; 23 | var rowMatrixHNID = this.TableContext.TCHeader.RowMatrixLocation; 24 | if (rowMatrixHNID == 0) 25 | return; 26 | 27 | if ((rowMatrixHNID & 0x1F) == 0)//HID 28 | { 29 | this.TCRMData = new List{ 30 | new BlockDataDTO 31 | { 32 | Data = this.TableContext.HeapNode.GetHIDBytes(new HID(BitConverter.GetBytes(rowMatrixHNID))).Data 33 | }}; 34 | } else 35 | { 36 | if (this.TableContext.HeapNode.HeapSubNode.ContainsKey(rowMatrixHNID)) 37 | this.TCRMData = this.TableContext.HeapNode.HeapSubNode[rowMatrixHNID].NodeData; 38 | else 39 | { 40 | var tempSubNodes = new Dictionary(); 41 | foreach(var nod in this.TableContext.HeapNode.HeapSubNode) 42 | tempSubNodes.Add(nod.Key & 0xffffffff, nod.Value); 43 | this.TCRMData = tempSubNodes[rowMatrixHNID].NodeData; 44 | } 45 | } 46 | //this.TCRMSubNodeData = this.TableContext.HeapNode.HeapSubNode[]; 47 | var rowSize = this.TableContext.TCHeader.EndOffsetCEB; 48 | //var rowPerBlock = (8192 - 16)/rowSize; 49 | 50 | foreach(var row in this.TableContext.RowIndexBTH.Properties) 51 | { 52 | var rowIndex = BitConverter.ToUInt32(row.Value.Data, 0); 53 | 54 | var blockTrailerSize = 16; 55 | var maxBlockSize = 8192 - blockTrailerSize; 56 | var recordsPerBlock = maxBlockSize/rowSize; 57 | 58 | var blockIndex = (int)rowIndex/recordsPerBlock; 59 | var indexInBlock = rowIndex%recordsPerBlock; 60 | var curRow = new TCRowMatrixData(this.TCRMData[blockIndex].Data, this.TableContext, heap, 61 | (int) indexInBlock*rowSize); 62 | this.RowXREF.Add(BitConverter.ToUInt32(row.Key, 0), curRow); 63 | this.Rows.Add(curRow); 64 | } 65 | /* 66 | uint curIndex = 0; 67 | foreach (var dataBlock in this.TCRMData) 68 | { 69 | for(int i = 0;i + rowSize < dataBlock.Data.Length; i += rowSize) 70 | { 71 | var curRow = new TCRowMatrixData(dataBlock.Data, this.TableContext, i); 72 | this.RowXREF.Add(this.TableContext.ReverseRowIndex[curIndex], curRow); 73 | this.Rows.Add(curRow); 74 | curIndex++; 75 | } 76 | }*/ 77 | 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/TCRowMatrixData.cs: -------------------------------------------------------------------------------- 1 | using PSTParse.MessageLayer; 2 | using PSTParse.Utilities; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace PSTParse.ListsTablesPropertiesLayer 8 | { 9 | public class TCRowMatrixData : IEnumerable 10 | { 11 | private readonly BTH _heap; 12 | 13 | public Dictionary ColumnXREF { get; set; } 14 | 15 | public TCRowMatrixData(byte[] bytes, TableContext context, BTH heap, int offset = 0) 16 | { 17 | ColumnXREF = new Dictionary(); 18 | _heap = heap; 19 | 20 | //todo: cell existence test 21 | //var rowSize = context.TCHeader.EndOffsetCEB; 22 | foreach (var col in context.TCHeader.ColumnsDescriptors) 23 | { 24 | ColumnXREF.Add((MessageProperty)col.Tag, bytes.RangeSubset(offset + col.DataOffset, col.DataSize)); 25 | } 26 | } 27 | 28 | public IEnumerator GetEnumerator() 29 | { 30 | foreach (var col in this.ColumnXREF) 31 | { 32 | var uIntKey = (UInt16)(((uint)col.Key) >> 16); 33 | var type = (UInt16)((uint)col.Key & 0xFFFF); 34 | yield return new ExchangeProperty(uIntKey, type, _heap, col.Value); 35 | } 36 | } 37 | 38 | IEnumerator IEnumerable.GetEnumerator() 39 | { 40 | return GetEnumerator(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/TableContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using PSTParse.NodeDatabaseLayer; 4 | 5 | namespace PSTParse.ListsTablesPropertiesLayer 6 | { 7 | public class TableContext 8 | { 9 | public TCINFOHEADER TCHeader { get; } 10 | public HN HeapNode { get; } 11 | public NodeDataDTO NodeData { get; } 12 | public BTH RowIndexBTH { get; } 13 | public Dictionary ReverseRowIndex { get; } 14 | public TCRowMatrix RowMatrix { get; } 15 | 16 | public TableContext(ulong nid, PSTFile pst) 17 | { 18 | NodeData = BlockBO.GetNodeData(nid, pst); 19 | 20 | HeapNode = new HN(NodeData); 21 | 22 | var tcinfoHID = HeapNode.HeapNodes[0].Header.UserRoot; 23 | var tcinfoHIDbytes = HeapNode.GetHIDBytes(tcinfoHID); 24 | TCHeader = new TCINFOHEADER(tcinfoHIDbytes.Data); 25 | 26 | RowIndexBTH = new BTH(HeapNode,TCHeader.RowIndexLocation); 27 | ReverseRowIndex = new Dictionary(); 28 | foreach(var prop in RowIndexBTH.Properties) 29 | { 30 | var temp = BitConverter.ToUInt32(prop.Value.Data, 0); 31 | ReverseRowIndex.Add(temp,BitConverter.ToUInt32(prop.Key, 0)); 32 | } 33 | RowMatrix = new TCRowMatrix(this, RowIndexBTH); 34 | } 35 | 36 | public TableContext(NodeDataDTO nodeData) 37 | { 38 | NodeData = nodeData; 39 | HeapNode = new HN(NodeData); 40 | 41 | var tcinfoHID = HeapNode.HeapNodes[0].Header.UserRoot; 42 | var tcinfoHIDbytes = HeapNode.GetHIDBytes(tcinfoHID); 43 | TCHeader = new TCINFOHEADER(tcinfoHIDbytes.Data); 44 | 45 | RowIndexBTH = new BTH(HeapNode, TCHeader.RowIndexLocation); 46 | ReverseRowIndex = new Dictionary(); 47 | foreach (var prop in RowIndexBTH.Properties) 48 | { 49 | var temp = BitConverter.ToUInt32(prop.Value.Data, 0); 50 | ReverseRowIndex.Add(temp, BitConverter.ToUInt32(prop.Key, 0)); 51 | } 52 | RowMatrix = new TCRowMatrix(this, RowIndexBTH); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/unused/BTHDataRecord.cs: -------------------------------------------------------------------------------- 1 | // using System; 2 | // using System.Collections.Generic; 3 | // using System.Linq; 4 | // using System.Text; 5 | 6 | // namespace PSTParse.ListsTablesPropertiesLayer 7 | // { 8 | // public class BTHDataRecord 9 | // { 10 | // public uint Key; 11 | // public uint Value; 12 | 13 | // public BTHDataRecord(byte[] bytes, BTHHEADER header) 14 | // { 15 | // var keySize = (int)header.KeySize; 16 | // var dataSize = (int) header.DataSize; 17 | 18 | // this.Key = BitConverter.ToUInt16(bytes.Take(keySize).ToArray(), 0); 19 | // this.Value = BitConverter.ToUInt32(bytes.Skip(keySize).Take(dataSize).ToArray(), 0); 20 | // } 21 | // } 22 | // } 23 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/unused/BTHIndexAllocationRecords.cs: -------------------------------------------------------------------------------- 1 | // using System; 2 | // using System.Collections.Generic; 3 | // using System.Linq; 4 | // using System.Text; 5 | 6 | // namespace PSTParse.ListsTablesPropertiesLayer 7 | // { 8 | // public class BTHIndexAllocationRecords 9 | // { 10 | // public List BTHIndexRecords; 11 | // public int CurrentLevel; 12 | // public BTHIndexAllocationRecords(byte[] bytes, int offset, BTHHEADER header, int level) 13 | // { 14 | // this.CurrentLevel = level; 15 | // this.BTHIndexRecords = new List(); 16 | 17 | // var keySize = (int)header.KeySize; 18 | // for (int i = offset; i < bytes.Length; i += 4 + keySize) 19 | // this.BTHIndexRecords.Add(new BTHIndexNode(bytes, header, i)); 20 | // } 21 | // } 22 | // } 23 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/unused/HNID.cs: -------------------------------------------------------------------------------- 1 | // using System; 2 | // using System.Collections.Generic; 3 | // using System.Linq; 4 | // using System.Text; 5 | 6 | // namespace PSTParse.ListsTablesPropertiesLayer 7 | // { 8 | // public class HNID 9 | // { 10 | // public ulong HNID_Type; 11 | // public ulong hnidIndex; 12 | // public ulong hnidBlockIndex; 13 | 14 | // public HNID(byte[] bytes) 15 | // { 16 | // var temp = BitConverter.ToUInt64(bytes, 0); 17 | // this.HNID_Type = temp & 0x1F; 18 | // this.hnidIndex = (temp >> 5) & 0x4FF; 19 | // this.hnidBlockIndex = temp >> 16; 20 | // } 21 | // } 22 | // } 23 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/unused/MVPropVarBase.cs: -------------------------------------------------------------------------------- 1 | // using System; 2 | // using System.Collections.Generic; 3 | // using System.Linq; 4 | // using System.Text; 5 | 6 | // namespace PSTParse.ListsTablesPropertiesLayer 7 | // { 8 | // public class MVPropVarBase 9 | // { 10 | // public UInt32 PropCount; 11 | // private List PropOffsets; 12 | // private List PropDataItems; 13 | // public MVPropVarBase(byte[] bytes) 14 | // { 15 | // this.PropCount = BitConverter.ToUInt32(bytes, 0); 16 | // this.PropOffsets = new List(); 17 | 18 | // for(int i= 0;i < this.PropCount; i++) 19 | // this.PropOffsets.Add(BitConverter.ToUInt64(bytes, 4 + i*8)); 20 | 21 | // this.PropDataItems = new List(); 22 | // for(int i = 0;i < this.PropCount; i++) 23 | // { 24 | // if (i < PropCount-1) 25 | // { 26 | // this.PropDataItems.Add( 27 | // bytes.Skip((int) this.PropOffsets[i]).Take((int) (this.PropOffsets[i + 1] - this.PropOffsets[i])) 28 | // .ToArray()); 29 | // } 30 | // } 31 | // } 32 | // } 33 | // } 34 | -------------------------------------------------------------------------------- /PSTParse/ListsTablesPropertiesLayer/unused/PCBTHRecord.cs: -------------------------------------------------------------------------------- 1 | // using PSTParse.Utilities; 2 | // using System; 3 | // using System.Linq; 4 | 5 | // namespace PSTParse.ListsTablesPropertiesLayer 6 | // { 7 | // public class PCBTHRecord 8 | // { 9 | // public UInt16 PropID; 10 | // public UInt16 PropType; 11 | // public ExchangeProperty PropertyValue; 12 | 13 | // public PCBTHRecord(byte[] bytes) 14 | // { 15 | // PropID = BitConverter.ToUInt16(bytes.Take(2).ToArray(), 0); 16 | // PropType = BitConverter.ToUInt16(bytes.Skip(2).Take(2).ToArray(), 0); 17 | // var prop= PropertyValue = ExchangeProperty.PropertyLookupByTypeID[(ExchangeProperty.PropType)PropType]; 18 | // if (!prop.MultiValue) 19 | // { 20 | // if (!prop.Variable) 21 | // { 22 | // if (prop.ByteCount <= 4 && prop.ByteCount != 0) 23 | // { 24 | // PropertyValue.Data = bytes.RangeSubset(4, (int) prop.ByteCount); 25 | // } 26 | // else 27 | // { 28 | 29 | // } 30 | // } 31 | // } 32 | // //HNID = new HNID(bytes.Skip(4).ToArray()); 33 | // } 34 | // } 35 | // } -------------------------------------------------------------------------------- /PSTParse/MessageLayer/Attachment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using PSTParse.ListsTablesPropertiesLayer; 6 | 7 | namespace PSTParse.MessageLayer 8 | { 9 | public enum NodeValue 10 | { 11 | AttachmentTable = 0x671 // 1649 12 | } 13 | 14 | public enum AttachmentMethod 15 | { 16 | NONE = 0x00, 17 | BY_VALUE = 0x01, 18 | BY_REFERENCE = 0X02, 19 | BY_REFERENCE_ONLY = 0X04, 20 | EMBEDDEED_MESSAGE = 0X05, 21 | STORAGE = 0X06 22 | } 23 | 24 | public class Attachment 25 | { 26 | public AttachmentMethod Method { get; set; } 27 | public uint Size { get; set; } 28 | public uint RenderingPosition { get; set; } 29 | public string Filename { get; set; } 30 | public string AttachmentLongFileName { get; set; } 31 | public string DisplayName { get; set; } 32 | public uint LTPRowID { get; set; } 33 | public uint LTPRowVer { get; set; } 34 | public bool InvisibleInHTML { get; set; } 35 | public bool InvisibleInRTF { get; set; } 36 | public bool RenderedInBody { get; set; } 37 | public byte[] Data { get; set; } 38 | 39 | public Attachment(PropertyContext propertyContext) : this(propertyContext?.Properties.Select(d => d.Value)) { } 40 | 41 | public Attachment(IEnumerable exchangeProperties) 42 | { 43 | exchangeProperties = exchangeProperties ?? Enumerable.Empty(); 44 | foreach (var property in exchangeProperties) 45 | { 46 | switch (property.ID) 47 | { 48 | case MessageProperty.AttachmentData: 49 | Data = property.Data; 50 | break; 51 | case MessageProperty.AttachmentSize: 52 | Size = BitConverter.ToUInt32(property.Data, 0); 53 | break; 54 | case MessageProperty.AttachmentFileName: 55 | if (property.Data != null) 56 | Filename = Encoding.Unicode.GetString(property.Data); 57 | //else 58 | // Filename = Guid.NewGuid().ToString(); 59 | break; 60 | case MessageProperty.DisplayName: 61 | if (property.Data != null) 62 | DisplayName = Encoding.Unicode.GetString(property.Data); 63 | else 64 | DisplayName = Guid.NewGuid().ToString(); 65 | break; 66 | case MessageProperty.AttachmentLongFileName: 67 | if (property.Data != null) 68 | AttachmentLongFileName = Encoding.Unicode.GetString(property.Data); 69 | //else 70 | // AttachmentLongFileName = Guid.NewGuid().ToString(); 71 | break; 72 | case MessageProperty.AttachmentMethod: 73 | Method = (AttachmentMethod)BitConverter.ToUInt32(property.Data, 0); 74 | break; 75 | case MessageProperty.AttachmentRenderPosition: 76 | RenderingPosition = BitConverter.ToUInt32(property.Data, 0); 77 | break; 78 | case MessageProperty.AttachmentFlags: 79 | var flags = BitConverter.ToUInt32(property.Data, 0); 80 | InvisibleInHTML = (flags & 0x1) != 0; 81 | InvisibleInRTF = (flags & 0x02) != 0; 82 | RenderedInBody = (flags & 0x04) != 0; 83 | break; 84 | case MessageProperty.AttachmentLTPRowID: 85 | LTPRowID = BitConverter.ToUInt32(property.Data, 0); 86 | break; 87 | case MessageProperty.AttachmentLTPRowVer: 88 | LTPRowVer = BitConverter.ToUInt32(property.Data, 0); 89 | break; 90 | default: 91 | break; 92 | } 93 | } 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /PSTParse/MessageLayer/IPMItem.cs: -------------------------------------------------------------------------------- 1 | using PSTParse.ListsTablesPropertiesLayer; 2 | 3 | namespace PSTParse.MessageLayer 4 | { 5 | public class IPMItem 6 | { 7 | public string MessageClass => PropertyContext.MessageClassProperty; 8 | protected PropertyContext PropertyContext { get; } 9 | 10 | //public IPMItem(PSTFile pst, ulong nid) 11 | //{ 12 | // PropertyContext = new PropertyContext(nid, pst); 13 | // MessageClass = Encoding.Unicode.GetString(PropertyContext.Properties[(MessageProperty)0x1a].Data); 14 | //} 15 | 16 | public IPMItem(PSTFile pst, PropertyContext propertyContext) 17 | { 18 | PropertyContext = propertyContext; 19 | //MessageClass = Encoding.Unicode.GetString(PropertyContext.Properties[(MessageProperty)0x1a].Data); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PSTParse/MessageLayer/MailFolder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using PSTParse.ListsTablesPropertiesLayer; 5 | using PSTParse.MessageLayer; 6 | 7 | namespace PSTParse 8 | { 9 | public class MailFolder 10 | { 11 | private readonly PSTFile _pst; 12 | private readonly PropertyContext PC; 13 | private readonly TableContext HeirachyTC; 14 | private readonly TableContext ContentsTC; 15 | private readonly TableContext FaiTC; 16 | 17 | public string DisplayName { get; } 18 | public string ContainerClass { get; } 19 | public List Path { get; } 20 | public List SubFolders { get; } 21 | public int Count => ContentsTC.RowIndexBTH.Properties.Count; 22 | 23 | public MailFolder(ulong nid, List path, PSTFile pst) 24 | { 25 | _pst = pst; 26 | 27 | Path = path; 28 | var pcNID = ((nid >> 5) << 5) | 0x02; 29 | PC = new PropertyContext(pcNID, pst); 30 | DisplayName = Encoding.Unicode.GetString(PC.Properties[MessageProperty.DisplayName].Data); 31 | 32 | 33 | PC.Properties.TryGetValue(MessageProperty.ContainerClass, out ExchangeProperty containerClassProperty); 34 | ContainerClass = containerClassProperty?.Data == null 35 | ? "" 36 | : Encoding.Unicode.GetString(containerClassProperty.Data); 37 | 38 | Path = new List(path) { DisplayName }; 39 | 40 | var heirachyNID = ((nid >> 5) << 5) | 0x0D; 41 | var contentsNID = ((nid >> 5) << 5) | 0x0E; 42 | var faiNID = ((nid >> 5) << 5) | 0x0F; 43 | 44 | HeirachyTC = new TableContext(heirachyNID, pst); 45 | 46 | SubFolders = new List(); 47 | foreach (var row in HeirachyTC.ReverseRowIndex) 48 | { 49 | SubFolders.Add(new MailFolder(row.Value, Path, pst)); 50 | //SubFolderEntryIDs.Add(row.); 51 | } 52 | 53 | ContentsTC = new TableContext(contentsNID, pst); 54 | 55 | 56 | FaiTC = new TableContext(faiNID, pst); 57 | } 58 | 59 | public IEnumerable GetIpmItems() 60 | { 61 | foreach (var row in ContentsTC.ReverseRowIndex) 62 | { 63 | var propertyContext = GetPropertyContext(row.Value); 64 | if (propertyContext.MessageClassProperty == "IPM.Note") 65 | { 66 | yield return new Message(_pst, propertyContext); 67 | } 68 | else 69 | { 70 | yield return new IPMItem(_pst, propertyContext); 71 | } 72 | } 73 | } 74 | 75 | public IEnumerable GetIpmNotes() 76 | { 77 | foreach (var row in ContentsTC.ReverseRowIndex) 78 | { 79 | var propertyContext = GetPropertyContext(row.Value); 80 | if (propertyContext.MessageClassProperty == "IPM.Note") 81 | { 82 | yield return new Message(_pst, propertyContext); 83 | } 84 | } 85 | } 86 | 87 | private PropertyContext GetPropertyContext(uint rowValue) 88 | { 89 | try 90 | { 91 | return new PropertyContext(rowValue, _pst); 92 | } 93 | catch (Exception ex) 94 | { 95 | throw new Exception($"Error: PST corruption detected while parsing {string.Join("/", Path)}", ex); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /PSTParse/MessageLayer/MailStore.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Text; 4 | using PSTParse.ListsTablesPropertiesLayer; 5 | using PSTParse.NodeDatabaseLayer; 6 | 7 | namespace PSTParse.MessageLayer 8 | { 9 | public class MailStore 10 | { 11 | private PropertyContext _pc; 12 | 13 | public EntryID RootFolder { get; } 14 | public string? DisplayName { get; } 15 | 16 | public MailStore(PSTFile pst) 17 | { 18 | _pc = new PropertyContext(SpecialNIDs.NID_MESSAGE_STORE, pst); 19 | RootFolder = new EntryID(_pc.Properties[MessageProperty.RootFolder].Data); 20 | 21 | _pc.Properties.TryGetValue(MessageProperty.DisplayName, out ExchangeProperty? displayProp); 22 | if (displayProp?.Data != null) 23 | { 24 | DisplayName = Encoding.Unicode.GetString(displayProp.Data); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /PSTParse/MessageLayer/Message.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Text; 5 | using PSTParse.ListsTablesPropertiesLayer; 6 | using PSTParse.NodeDatabaseLayer; 7 | using PSTParse.Utilities; 8 | using static PSTParse.Utilities.Utilities; 9 | 10 | namespace PSTParse.MessageLayer 11 | { 12 | public class Message : IPMItem 13 | { 14 | //private readonly NodeDataDTO _data; 15 | //private readonly TableContext _attachmentTable; 16 | //private readonly TableContext _recipientTable; 17 | //private readonly PropertyContext _attachmentPC; 18 | //private readonly PropertyContext _messagePC; 19 | private readonly PSTFile _pst; 20 | private readonly ulong _nid; 21 | private Dictionary _subNodeDataDtoLazy; 22 | private Dictionary _subNodeHeaderDataDtoLazy; 23 | private readonly Lazy _isRMSEncryptedLazy; 24 | private readonly Lazy _isRMSEncryptedHeadersLazy; 25 | private Recipients _recipientsLazy; 26 | private List _attachmentsLazy; 27 | private IEnumerable _attachmentHeadersLazy; 28 | 29 | private Dictionary SubNodeDataDto => Lazy(ref _subNodeDataDtoLazy, () => BlockBO.GetSubNodeData(_nid, _pst)); 30 | private Dictionary SubNodeHeaderDataDto => Lazy(ref _subNodeHeaderDataDtoLazy, () => BlockBO.GetSubNodeData(_nid, _pst, take: 1)); 31 | 32 | public string Subject { get; set; } 33 | public string SubjectPrefix { get; set; } 34 | public Importance Importance { get; set; } 35 | public Sensitivity Sensitivity { get; set; } 36 | public DateTime LastSaved { get; set; } 37 | public DateTime ClientSubmitTime { get; set; } 38 | public string SentRepresentingName { get; set; } 39 | public string ConversationTopic { get; set; } 40 | public string SenderName { get; set; } 41 | public string SenderAddress { get; set; } 42 | public string SenderAddressType { get; set; } 43 | public string SenderSMTPAddress { get; set; } 44 | public DateTime MessageDeliveryTime { get; set; } 45 | public bool Read { get; set; } 46 | public bool Unsent { get; set; } 47 | public bool Unmodified { get; set; } 48 | public bool HasAttachments { get; set; } 49 | public bool FromMe { get; set; } 50 | public bool IsFAI { get; set; } 51 | public bool NotifyReadRequested { get; set; } 52 | public bool NotifyUnreadRequested { get; set; } 53 | public bool EverRead { get; set; } 54 | public uint MessageSize { get; set; } 55 | public string Headers { get; set; } 56 | public string BodyPlainText { get; set; } 57 | public string BodyHtml { get; set; } 58 | public uint InternetArticleNumber { get; set; } 59 | public string BodyCompressedRTFString { get; set; } 60 | public string InternetMessageID { get; set; } 61 | public string UrlCompositeName { get; set; } 62 | public bool AttributeHidden { get; set; } 63 | public bool ReadOnly { get; set; } 64 | public DateTime CreationTime { get; set; } 65 | public DateTime LastModificationTime { get; set; } 66 | public uint CodePage { get; set; } 67 | public String CreatorName { get; set; } 68 | public uint NonUnicodeCodePage { get; set; } 69 | public uint MessageFlags { get; set; } 70 | public Recipients Recipients => Lazy(ref _recipientsLazy, GetRecipients); 71 | public List Attachments => Lazy(ref _attachmentsLazy, GetAttachments); 72 | public IEnumerable AttachmentHeaders => Lazy(ref _attachmentHeadersLazy, GetAttachmentHeaders); 73 | public bool IsRMSEncrypted => _isRMSEncryptedLazy.Value; 74 | public bool IsRMSEncryptedHeaders => _isRMSEncryptedHeadersLazy.Value; 75 | 76 | //public Message(PSTFile pst, ulong nid) : this(pst, new PropertyContext(nid, pst)) { } 77 | 78 | public Message(PSTFile pst, PropertyContext propertyContext) : base(pst, propertyContext) 79 | { 80 | _nid = propertyContext.NID; 81 | _pst = pst; 82 | 83 | //_subNodeDataDtoLazy = new Lazy>(() => BlockBO.GetSubNodeData(_nid, _pst)); 84 | _isRMSEncryptedLazy = new Lazy(GetIsRMSEncrypted); 85 | _isRMSEncryptedHeadersLazy = new Lazy(GetIsRMSEncryptedHeaders); 86 | 87 | foreach (var prop in PropertyContext.Properties) 88 | { 89 | if (prop.Value.Data == null) 90 | continue; 91 | switch (prop.Key) 92 | { 93 | case MessageProperty.Importance: 94 | Importance = (Importance)BitConverter.ToInt16(prop.Value.Data, 0); 95 | break; 96 | case MessageProperty.Sensitivity: 97 | Sensitivity = (Sensitivity)BitConverter.ToInt16(prop.Value.Data, 0); 98 | break; 99 | case MessageProperty.Subject: 100 | Subject = Encoding.Unicode.GetString(prop.Value.Data); 101 | if (Subject.Length > 0) 102 | { 103 | var chars = Subject.ToCharArray(); 104 | if (chars[0] == 0x001) 105 | { 106 | var length = (int)chars[1]; 107 | int i = 0; 108 | if (length > 1) 109 | i++; 110 | SubjectPrefix = Subject.Substring(2, length - 1); 111 | Subject = Subject.Substring(2 + length - 1); 112 | } 113 | } 114 | break; 115 | case MessageProperty.ClientSubmitTime: 116 | ClientSubmitTime = DateTime.FromFileTimeUtc(BitConverter.ToInt64(prop.Value.Data, 0)); 117 | break; 118 | case MessageProperty.SentRepresentingName: 119 | SentRepresentingName = Encoding.Unicode.GetString(prop.Value.Data); 120 | break; 121 | case MessageProperty.ConversationTopic: 122 | ConversationTopic = Encoding.Unicode.GetString(prop.Value.Data); 123 | break; 124 | 125 | //already done in base 126 | //case MessageProperty.MessageClass: 127 | // //MessageClassBuffer = prop.Value.Data; 128 | // MessageClass = Encoding.Unicode.GetString(prop.Value.Data); 129 | // //IsIPMNote = prop.Value.Data.Length == 16 && prop.Value.Data[14]== (byte)'e'; 130 | // break; 131 | case MessageProperty.SenderAddress: 132 | SenderAddress = Encoding.Unicode.GetString(prop.Value.Data); 133 | break; 134 | case MessageProperty.SenderAddressType: 135 | SenderAddressType = Encoding.Unicode.GetString(prop.Value.Data); 136 | break; 137 | case MessageProperty.SenderSMTPAddress: 138 | SenderSMTPAddress = Encoding.Unicode.GetString(prop.Value.Data); 139 | break; 140 | case MessageProperty.SenderName: 141 | SenderName = Encoding.Unicode.GetString(prop.Value.Data); 142 | break; 143 | case MessageProperty.MessageDeliveryTime: 144 | MessageDeliveryTime = DateTime.FromFileTimeUtc(BitConverter.ToInt64(prop.Value.Data, 0)); 145 | break; 146 | case MessageProperty.MessageFlags: 147 | MessageFlags = BitConverter.ToUInt32(prop.Value.Data, 0); 148 | 149 | Read = (MessageFlags & 0x1) != 0; 150 | Unsent = (MessageFlags & 0x8) != 0; 151 | Unmodified = (MessageFlags & 0x2) != 0; 152 | HasAttachments = (MessageFlags & 0x10) != 0; 153 | FromMe = (MessageFlags & 0x20) != 0; 154 | IsFAI = (MessageFlags & 0x40) != 0; 155 | NotifyReadRequested = (MessageFlags & 0x100) != 0; 156 | NotifyUnreadRequested = (MessageFlags & 0x200) != 0; 157 | EverRead = (MessageFlags & 0x400) != 0; 158 | break; 159 | case MessageProperty.MessageSize: 160 | MessageSize = BitConverter.ToUInt32(prop.Value.Data, 0); 161 | break; 162 | case MessageProperty.InternetArticleNumber: 163 | InternetArticleNumber = BitConverter.ToUInt32(prop.Value.Data, 0); 164 | break; 165 | case (MessageProperty)0xe27: 166 | //unknown 167 | break; 168 | case MessageProperty.NextSentAccount: 169 | //nextSentAccount, ignore this, string 170 | break; 171 | case (MessageProperty)0xe62: 172 | //unknown 173 | break; 174 | case MessageProperty.TrustedSender: 175 | //trusted sender 176 | break; 177 | case MessageProperty.Headers: 178 | Headers = Encoding.Unicode.GetString(prop.Value.Data); 179 | break; 180 | case MessageProperty.BodyPlainText: 181 | BodyPlainText = Encoding.Unicode.GetString(prop.Value.Data); 182 | break; 183 | case MessageProperty.BodyCompressedRTF: 184 | BodyCompressedRTFString = new RtfDecompressor().Decompress(prop.Value.Data); 185 | break; 186 | case MessageProperty.BodyHtml: 187 | //var temp = MessagePropertyTypes.PropertyToString(true, prop.Value); 188 | BodyHtml = Encoding.ASCII.GetString(prop.Value.Data); 189 | break; 190 | case MessageProperty.MessageID: 191 | InternetMessageID = Encoding.Unicode.GetString(prop.Value.Data); 192 | break; 193 | case MessageProperty.UrlCompositeName: 194 | UrlCompositeName = Encoding.Unicode.GetString(prop.Value.Data); 195 | break; 196 | case MessageProperty.AttributeHidden: 197 | AttributeHidden = prop.Value.Data[0] == 0x01; 198 | break; 199 | case (MessageProperty)0x10F5: 200 | //unknown 201 | break; 202 | case MessageProperty.ReadOnly: 203 | ReadOnly = prop.Value.Data[0] == 0x01; 204 | break; 205 | case MessageProperty.CreationTime: 206 | CreationTime = DateTime.FromFileTimeUtc(BitConverter.ToInt64(prop.Value.Data, 0)); 207 | break; 208 | case MessageProperty.LastModificationTime: 209 | LastModificationTime = DateTime.FromFileTimeUtc(BitConverter.ToInt64(prop.Value.Data, 0)); 210 | break; 211 | case MessageProperty.SearchKey: 212 | //seach key 213 | break; 214 | case MessageProperty.CodePage: 215 | CodePage = BitConverter.ToUInt32(prop.Value.Data, 0); 216 | break; 217 | case MessageProperty.LocaleID: 218 | //localeID 219 | break; 220 | case MessageProperty.CreatorName: 221 | CreatorName = Encoding.Unicode.GetString(prop.Value.Data); 222 | break; 223 | case MessageProperty.CreatorEntryID: 224 | //creator entryid 225 | break; 226 | case MessageProperty.LastModifierName: 227 | //last modifier name 228 | break; 229 | case MessageProperty.LastModifierEntryID: 230 | //last modifier entryid 231 | break; 232 | case MessageProperty.NonUnicodeCodePage: 233 | NonUnicodeCodePage = BitConverter.ToUInt32(prop.Value.Data, 0); 234 | break; 235 | case (MessageProperty)0x4019: 236 | //unknown 237 | break; 238 | case MessageProperty.SentRepresentingFlags: 239 | //sentrepresentingflags 240 | break; 241 | case MessageProperty.UserEntryID: 242 | //userentryid 243 | break; 244 | default: 245 | break; 246 | } 247 | } 248 | } 249 | 250 | private Recipients GetRecipients() 251 | { 252 | const ulong recipientsFlag = 1682; 253 | var recipients = new Recipients(); 254 | 255 | var exists = SubNodeDataDto.TryGetValue(recipientsFlag, out NodeDataDTO subNode); 256 | if (!exists) return recipients; 257 | 258 | var recipientTable = new TableContext(subNode); 259 | foreach (var row in recipientTable.RowMatrix.Rows) 260 | { 261 | var recipient = new Recipient(row); 262 | switch (recipient.Type) 263 | { 264 | case Recipient.RecipientType.TO: 265 | recipients.To.Add(recipient); 266 | break; 267 | case Recipient.RecipientType.CC: 268 | recipients.CC.Add(recipient); 269 | break; 270 | case Recipient.RecipientType.BCC: 271 | recipients.BCC.Add(recipient); 272 | break; 273 | } 274 | } 275 | return recipients; 276 | } 277 | 278 | private IEnumerable GetAttachmentHeaders() 279 | { 280 | if (!HasAttachments) yield break; 281 | 282 | var attachmentSet = new HashSet(); 283 | foreach (var subNode in SubNodeHeaderDataDto) 284 | { 285 | if ((NodeValue)subNode.Key != NodeValue.AttachmentTable) 286 | { 287 | throw new Exception("expected node to be an attachment table"); 288 | } 289 | 290 | var attachmentTable = new TableContext(subNode.Value); 291 | var attachmentRows = attachmentTable.RowMatrix.Rows; 292 | 293 | foreach (var attachmentRow in attachmentRows) 294 | { 295 | var attachment = new Attachment(attachmentRow); 296 | yield return attachment; 297 | } 298 | } 299 | } 300 | 301 | private List GetAttachments() 302 | { 303 | var attachments = new List(); 304 | if (!HasAttachments) return attachments; 305 | 306 | var attachmentSet = new HashSet(); 307 | foreach (var subNode in SubNodeDataDto) 308 | { 309 | var nodeType = NID.GetNodeType(subNode.Key); 310 | if (nodeType != NID.NodeType.ATTACHMENT_PC) continue; 311 | 312 | var attachmentPC = new PropertyContext(subNode.Value); 313 | var attachment = new Attachment(attachmentPC); 314 | if (attachmentSet.Contains(attachment.AttachmentLongFileName)) 315 | { 316 | var smallGuid = Guid.NewGuid().ToString().Substring(0, 5); 317 | attachment.AttachmentLongFileName = $"{smallGuid}-{attachment.AttachmentLongFileName}"; 318 | } 319 | attachmentSet.Add(attachment.AttachmentLongFileName); 320 | attachments.Add(attachment); 321 | } 322 | return attachments; 323 | } 324 | 325 | private bool GetIsRMSEncrypted() 326 | { 327 | if (!HasAttachments) return false; 328 | 329 | foreach (var attachment in Attachments) 330 | { 331 | if (attachment.AttachmentLongFileName?.ToLowerInvariant().EndsWith(".rpmsg") ?? false) 332 | { 333 | if (Attachments.Count > 1) throw new NotSupportedException("too many attachments for rms"); 334 | return true; 335 | } 336 | if (attachment.Filename?.ToLowerInvariant().EndsWith(".rpmsg") ?? false) 337 | { 338 | if (Attachments.Count > 1) throw new NotSupportedException("too many attachments for rms"); 339 | return true; 340 | } 341 | if (attachment.DisplayName?.ToLowerInvariant().EndsWith(".rpmsg") ?? false) 342 | { 343 | if (Attachments.Count > 1) throw new NotSupportedException("too many attachments for rms"); 344 | return true; 345 | } 346 | } 347 | return false; 348 | } 349 | 350 | private bool GetIsRMSEncryptedHeaders() 351 | { 352 | if (!HasAttachments) return false; 353 | 354 | foreach (var attachment in AttachmentHeaders) 355 | { 356 | Debug.Assert(!(attachment.AttachmentLongFileName?.ToLowerInvariant().EndsWith(".rpm") ?? false)); 357 | Debug.Assert(!(attachment.DisplayName?.ToLowerInvariant().EndsWith(".rpm") ?? false)); 358 | 359 | if (attachment.Filename?.ToLowerInvariant().EndsWith(".rpm") ?? false) 360 | { 361 | Debug.Assert(Attachments.Count == 1, "too many attachments for rms"); 362 | return true; 363 | } 364 | 365 | return false; 366 | } 367 | return false; 368 | } 369 | } 370 | 371 | public enum Importance 372 | { 373 | LOW = 0x00, 374 | NORMAL = 0x01, 375 | HIGH = 0x02 376 | } 377 | 378 | public enum Sensitivity 379 | { 380 | Normal = 0x00, 381 | Personal = 0x01, 382 | Private = 0x02, 383 | Confidential = 0x03 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /PSTParse/MessageLayer/MessageProperty.cs: -------------------------------------------------------------------------------- 1 | namespace PSTParse.MessageLayer 2 | { 3 | public enum MessageProperty : uint 4 | { 5 | GuidList = 0x2, 6 | EntryList = 0x3, 7 | StringList = 0x4, 8 | Importance = 0x17, 9 | MessageClass = 0x1a, 10 | DeliveryReportRequested = 0x23, 11 | Priority = 0x26, 12 | ReadReceiptRequested = 0x29, 13 | RecipientReassignmentProhibited = 0x2B, 14 | SensitivityOriginal = 0x2E, 15 | ReportTime = 0x32, 16 | Sensitivity = 0x36, 17 | Subject = 0x37, 18 | ClientSubmitTime = 0x39, 19 | OriginalSenderWithScheme = 0x3b, 20 | ReceivedByEntryID = 0x3F, 21 | ReceivedByName = 0x40, 22 | SentRepresentingEntryID = 0x41, 23 | SentRepresentingName = 0x42, 24 | ReceivedRepresentingEntryID = 0x43, 25 | ReceivedRepresentingName = 0x44, 26 | ReplyRecipientEntries = 0x4F, 27 | ReplyRecipientNames = 0x50, 28 | ReceivedBySearchKey = 0x51, 29 | ReceivedRepresentingSearchKey = 0x52, 30 | MessageToMe = 0x57, 31 | MessageCCMe = 0x58, 32 | MessageRecipientMe = 0x59, 33 | ResponseRequested = 0x60, 34 | SentRepresentingAddressType = 0x64, 35 | SentRepresentingAddress = 0x65, 36 | ConversationTopic = 0x70, 37 | ConversationIndex = 0x71, 38 | OriginalDisplayBcc = 0x72, 39 | OriginalDisplayCc = 0x73, 40 | OriginalDisplayTo = 0x74, 41 | ReceivedByAddressType = 0x75, 42 | ReceivedByAddress = 0x76, 43 | ReceivedRepresentingAddressType = 0x77, 44 | ReceivedRepresentingAddress = 0x78, 45 | Headers = 0x7d, 46 | UserEntryID = 0x619, 47 | NdrReasonCode = 0xC04, 48 | NdrDiagCode = 0xC05, 49 | NonReceiptNotificationRequested = 0xC06, 50 | RecipientType = 0xc15, 51 | ReplyRequested = 0xc17, 52 | SenderEntryID = 0xc19, 53 | SenderName = 0xc1a, 54 | SupplementaryInfo = 0xc1b, 55 | SenderSearchKey = 0xc1d, 56 | SenderAddressType = 0xc1e, 57 | SenderAddress = 0xc1f, 58 | DeleteAfterSubmit = 0xe01, 59 | DisplayBccAddresses = 0xe02, 60 | DisplayCcAddresses = 0xe03, 61 | RecipientName = 0xe04, 62 | MessageDeliveryTime = 0xe06, 63 | MessageFlags = 0xe07, 64 | MessageSize = 0xe08, 65 | SentMailEntryID = 0xe0a, 66 | RecipientResponsibility = 0xe0f, 67 | NormalizedSubject = 0xe1d, 68 | RtfInSync = 0xe1f, 69 | AttachmentSize = 0xe20, 70 | InternetArticleNumber = 0xe23, 71 | NextSentAccount = 0xe29, 72 | TrustedSender = 0xe79, 73 | RecordKey = 0xff9, 74 | RecipientObjType = 0xffe, 75 | RecipientEntryID = 0xfff, 76 | BodyPlainText = 0x1000, 77 | ReportText = 0x1001, 78 | BodyRtfCrc = 0x1006, 79 | BodyRtfSyncCount = 0x1007, 80 | BodyRtfSyncTag = 0x1008, 81 | BodyCompressedRTF = 0x1009, 82 | BodyRtfSyncPrefixCount = 0x1010, 83 | BodyRtfSyncTrailingCount = 0x1011, 84 | BodyHtml = 0x1013, 85 | MessageID = 0x1035, 86 | ReferencesMessageID = 0x1039, 87 | ReplyToMessageID = 0x1042, 88 | UnsubscribeAddress = 0x1045, 89 | ReturnPath = 0x1046, 90 | UrlCompositeName = 0x10F3, 91 | AttributeHidden = 0x10F4, 92 | ReadOnly = 0x10F6, 93 | DisplayName = 0x3001, 94 | AddressType = 0x3002, 95 | AddressName = 0x3003, 96 | Comment = 0x3004, 97 | CreationTime = 0x3007, 98 | LastModificationTime = 0x3008, 99 | SearchKey = 0x300B, 100 | ValidFolderMask = 0x35df, 101 | RootFolder = 0x35e0, 102 | OutboxFolder = 0x35e2, 103 | DeletedItemsFolder = 0x35e3, 104 | SentFolder = 0x35e4, 105 | UserViewsFolder = 0x35e5, 106 | CommonViewsFolder = 0x35e6, 107 | SearchFolder = 0x35e7, 108 | FolderContentCount = 0x3602, 109 | FolderUnreadCount = 0x3603, 110 | FolderHasChildren = 0x360a, 111 | ContainerClass = 0x3613, 112 | AssocContentCount = 0x3617, 113 | AttachmentData = 0x3701, 114 | AttachmentFileName = 0x3704, 115 | AttachmentMethod = 0x3705, 116 | AttachmentLongFileName = 0x3707, 117 | AttachmentRenderPosition = 0x370b, 118 | AttachmentMimeType = 0x370e, 119 | AttachmentMimeSequence = 0x3710, 120 | AttachmentContentID = 0x3712, 121 | AttachmentFlags = 0x3714, 122 | SMTPAddress = 0x39fe, 123 | CodePage = 0x3fDE, 124 | CreatorName = 0x3ff8, 125 | NonUnicodeCodePage = 0x3ffd, 126 | LocaleID = 0x3ff1, 127 | CreatorEntryID = 0x3ff9, 128 | LastModifierName = 0x3ffa, 129 | LastModifierEntryID = 0x3ffb, 130 | SentRepresentingFlags = 0x401a, 131 | SenderSMTPAddress = 0x5d01, 132 | BodyPlainText2 = 0x6619, 133 | AttachmentLTPRowID = 0x67F2, 134 | AttachmentLTPRowVer = 0x67F3, 135 | BodyPlainText3 = 0x8008, 136 | ContentClass = 0x8009, 137 | PopAccountName = 0x800d, 138 | PopUri = 0x8011, 139 | ContentType2 = 0x8013, 140 | TransferEncoding2 = 0x8014, 141 | BodyPlainText4 = 0x8015, 142 | PopUri2 = 0x804c, 143 | PopServerName = 0x8070, 144 | ContentType = 0x8076, 145 | TransferEncoding = 0x807b, 146 | BodyPlainText5 = 0x807e, 147 | MailSoftwareName = 0x8088, 148 | PopAccountName2 = 0x808a, 149 | MailSoftwareEngine = 0x808b, 150 | } 151 | } -------------------------------------------------------------------------------- /PSTParse/MessageLayer/Recipient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using PSTParse.ListsTablesPropertiesLayer; 5 | 6 | namespace PSTParse.MessageLayer 7 | { 8 | public class Recipient 9 | { 10 | public enum RecipientType 11 | { 12 | FROM = 0x00, 13 | TO = 0x01, 14 | CC = 0x02, 15 | BCC = 0x03 16 | } 17 | 18 | public RecipientType Type; 19 | public PSTEnums.ObjectType ObjType; 20 | public bool Responsibility; 21 | public byte[] Tag; 22 | public EntryID EntryID; 23 | public string DisplayName; 24 | public string EmailAddress; 25 | public string EmailSMTPAddress; 26 | public string EmailAddressType; 27 | 28 | public Recipient(TCRowMatrixData row) 29 | { 30 | foreach (var exProp in row) 31 | { 32 | var data = exProp.Data ?? new byte[0]; 33 | switch (exProp.ID) 34 | { 35 | case MessageProperty.RecipientType: 36 | Type = (RecipientType)BitConverter.ToUInt32(data, 0); 37 | break; 38 | case MessageProperty.RecipientResponsibility: 39 | Responsibility = data.Any() ? data[0] == 0x01 : false; 40 | break; 41 | case MessageProperty.RecordKey: 42 | Tag = data; 43 | break; 44 | case MessageProperty.RecipientObjType: 45 | ObjType = (PSTEnums.ObjectType)BitConverter.ToUInt32(data, 0); 46 | break; 47 | case MessageProperty.RecipientEntryID: 48 | EntryID = new EntryID(data); 49 | break; 50 | case MessageProperty.DisplayName: 51 | DisplayName = Encoding.Unicode.GetString(data); 52 | break; 53 | case MessageProperty.AddressType: 54 | EmailAddressType = Encoding.Unicode.GetString(data); 55 | break; 56 | case MessageProperty.AddressName: 57 | EmailAddress = Encoding.Unicode.GetString(data); 58 | break; 59 | case MessageProperty.SMTPAddress: 60 | EmailSMTPAddress = Encoding.Unicode.GetString(data); 61 | break; 62 | default: 63 | break; 64 | } 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /PSTParse/MessageLayer/Recipients.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PSTParse.MessageLayer 4 | { 5 | public class Recipients 6 | { 7 | public List To { get; } = new List(); 8 | public List CC { get; } = new List(); 9 | public List BCC { get; } = new List(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PSTParse/MessageLayer/unused/MessagePropertyTypes.cs: -------------------------------------------------------------------------------- 1 | // using PSTParse.ListsTablesPropertiesLayer; 2 | // using System; 3 | // using System.Text; 4 | 5 | // namespace PSTParse.MessageLayer 6 | // { 7 | // public static class MessagePropertyTypes 8 | // { 9 | // public static string PropertyToString(bool unicode, ExchangeProperty prop, bool enforceMaxLength = false) 10 | // { 11 | // int maxStringBytes = enforceMaxLength ? 2048 : Int32.MaxValue; 12 | // try 13 | // { 14 | // if (prop.Type == ExchangeProperty.PropType.Binary && prop.Data.Length > 0) 15 | // { 16 | // return Encoding.ASCII.GetString(prop.Data, 0, prop.Data.Length); 17 | // } 18 | // else if (prop.Type == ExchangeProperty.PropType.Boolean && prop.Data.Length > 0) 19 | // { 20 | // // since it's little-endian, we can just take the value of the first byte, 21 | // // regardless of the total width of the value. 22 | // return (prop.Data[0] != 0).ToString(); 23 | // } 24 | // else if (prop.Type == ExchangeProperty.PropType.Currency) 25 | // { 26 | // } 27 | // else if (prop.Type == ExchangeProperty.PropType.ErrorCode) 28 | // { 29 | // } 30 | // else if (prop.Type == ExchangeProperty.PropType.Floating32 && prop.Data.Length >= 4) 31 | // { 32 | // return BitConverter.ToSingle(prop.Data, 0).ToString(); 33 | // } 34 | // else if (prop.Type == ExchangeProperty.PropType.Floating64 && prop.Data.Length >= 8) 35 | // { 36 | // return BitConverter.ToDouble(prop.Data, 0).ToString(); 37 | // } 38 | // else if (prop.Type == ExchangeProperty.PropType.FloatingTime && prop.Data.Length >= 8) 39 | // { 40 | // return DateTime.FromBinary(BitConverter.ToInt64(prop.Data, 0)).ToString(); 41 | // } 42 | // else if (prop.Type == ExchangeProperty.PropType.Guid && prop.Data.Length >= 16) 43 | // { 44 | // return (new Guid(prop.Data)).ToString(); 45 | // } 46 | // else if (prop.Type == ExchangeProperty.PropType.Integer16 && prop.Data.Length >= 2) 47 | // { 48 | // return BitConverter.ToUInt16(prop.Data, 0).ToString(); 49 | // } 50 | // else if (prop.Type == ExchangeProperty.PropType.Integer32 && prop.Data.Length >= 4) 51 | // { 52 | // return BitConverter.ToUInt32(prop.Data, 0).ToString(); 53 | // } 54 | // else if (prop.Type == ExchangeProperty.PropType.Integer64 && prop.Data.Length >= 8) 55 | // { 56 | // return BitConverter.ToUInt64(prop.Data, 0).ToString(); 57 | // } 58 | // else if (prop.Type == ExchangeProperty.PropType.MultipleBinary) 59 | // { 60 | // } 61 | // else if (prop.Type == ExchangeProperty.PropType.MultipleCurrency) 62 | // { 63 | // } 64 | // else if (prop.Type == ExchangeProperty.PropType.MultipleFloating32) 65 | // { 66 | // } 67 | // else if (prop.Type == ExchangeProperty.PropType.MultipleFloating64) 68 | // { 69 | // } 70 | // else if (prop.Type == ExchangeProperty.PropType.MultipleFloatingTime) 71 | // { 72 | // } 73 | // else if (prop.Type == ExchangeProperty.PropType.MultipleGuid) 74 | // { 75 | // } 76 | // else if (prop.Type == ExchangeProperty.PropType.MultipleInteger16) 77 | // { 78 | // } 79 | // else if (prop.Type == ExchangeProperty.PropType.MultipleInteger32) 80 | // { 81 | // } 82 | // else if (prop.Type == ExchangeProperty.PropType.MultipleInteger64) 83 | // { 84 | // } 85 | // else if (prop.Type == ExchangeProperty.PropType.MultipleString && prop.Data.Length > 8) 86 | // { 87 | // uint numStrings = BitConverter.ToUInt32(prop.Data, 0); 88 | // // screw it, just render the first string, up until the end of the data. 89 | // return unicode 90 | // ? Encoding.Unicode.GetString(prop.Data, 8, Math.Min(maxStringBytes, prop.Data.Length - 8)) 91 | // : Encoding.ASCII.GetString(prop.Data, 8, Math.Min(maxStringBytes, prop.Data.Length - 8)); 92 | // } 93 | // else if (prop.Type == ExchangeProperty.PropType.MultipleString8) 94 | // { 95 | // } 96 | // else if (prop.Type == ExchangeProperty.PropType.MultipleTime) 97 | // { 98 | // } 99 | // else if (prop.Type == ExchangeProperty.PropType.Restriction) 100 | // { 101 | // } 102 | // else if (prop.Type == ExchangeProperty.PropType.RuleAction) 103 | // { 104 | // } 105 | // else if (prop.Type == ExchangeProperty.PropType.ServerId) 106 | // { 107 | // } 108 | // else if (prop.Type == ExchangeProperty.PropType.String) 109 | // { 110 | // return unicode 111 | // ? Encoding.Unicode.GetString(prop.Data, 0, Math.Min(maxStringBytes, prop.Data.Length)) 112 | // : Encoding.ASCII.GetString(prop.Data, 0, Math.Min(maxStringBytes, prop.Data.Length)); 113 | // } 114 | // else if (prop.Type == ExchangeProperty.PropType.String8) 115 | // { 116 | // return Encoding.ASCII.GetString(prop.Data, 0, Math.Min(maxStringBytes, prop.Data.Length)); 117 | // } 118 | // else if (prop.Type == ExchangeProperty.PropType.Time && prop.Data.Length >= 8) 119 | // { 120 | // return DateTime.FromFileTimeUtc(BitConverter.ToInt64(prop.Data, 0)).ToString(); 121 | // } 122 | 123 | // // If we fall through to here, then just try to render it as ascii... 124 | // Encoding.ASCII.GetString(prop.Data, 0, Math.Min(maxStringBytes, prop.Data.Length)); 125 | 126 | // } 127 | // catch { } 128 | // return ""; 129 | // } 130 | // } 131 | // } -------------------------------------------------------------------------------- /PSTParse/MessageLayer/unused/NAMEID.cs: -------------------------------------------------------------------------------- 1 | // using PSTParse.Utilities; 2 | // using System; 3 | 4 | // namespace PSTParse.MessageLayer 5 | // { 6 | // public class NAMEID 7 | // { 8 | // public uint PropertyID; 9 | // public bool PropertyIDStringOffset; 10 | // public Guid Guid; 11 | // public ushort PropIndex; 12 | 13 | // public NAMEID(byte[] bytes, int offset, NamedToPropertyLookup lookup) 14 | // { 15 | // this.PropertyID = BitConverter.ToUInt32(bytes, offset); 16 | // this.PropertyIDStringOffset = (bytes[offset + 4] & 0x1) == 1; 17 | // var guidType = BitConverter.ToUInt16(bytes, offset + 4) >>1; 18 | // if (guidType == 1) 19 | // { 20 | // this.Guid = new Guid("00020328-0000-0000-C000-000000000046");//PS-MAPI 21 | // } else if (guidType == 2) 22 | // { 23 | // this.Guid = new Guid("00020329-0000-0000-C000-000000000046");//PS_PUBLIC_STRINGS 24 | // } else 25 | // { 26 | // this.Guid = new Guid(lookup.GUIDs.RangeSubset((guidType - 3)*16, 16)); 27 | // } 28 | 29 | // this.PropIndex = (ushort)(0x8000 + BitConverter.ToUInt16(bytes, offset + 6)); 30 | // } 31 | // } 32 | // } 33 | -------------------------------------------------------------------------------- /PSTParse/MessageLayer/unused/NamedProperty.cs: -------------------------------------------------------------------------------- 1 | // using System; 2 | // using System.Collections.Generic; 3 | // using System.Linq; 4 | // using System.Text; 5 | 6 | // namespace PSTParse.MessageLayer 7 | // { 8 | // public class NamedProperty 9 | // { 10 | // } 11 | // } 12 | -------------------------------------------------------------------------------- /PSTParse/MessageLayer/unused/NamedToPropertyLookup.cs: -------------------------------------------------------------------------------- 1 | // using System.Collections.Generic; 2 | // using PSTParse.ListsTablesPropertiesLayer; 3 | 4 | // namespace PSTParse.MessageLayer 5 | // { 6 | // public class NamedToPropertyLookup 7 | // { 8 | // private const ulong NodeId = 0x61; 9 | 10 | // public PropertyContext PC { get; set; } 11 | // public Dictionary Lookup { get; set; } 12 | 13 | // internal byte[] GUIDs { get; set; } 14 | // internal byte[] Entries { get; set; } 15 | // internal byte[] String { get; set; } 16 | 17 | // public NamedToPropertyLookup(PSTFile pst) 18 | // { 19 | 20 | // PC = new PropertyContext(NodeId, pst); 21 | // GUIDs = PC.Properties[(MessageProperty)0x0002].Data; 22 | // Entries = PC.Properties[(MessageProperty)0x0003].Data; 23 | // String = PC.Properties[(MessageProperty)0x0004].Data; 24 | 25 | // Lookup = new Dictionary(); 26 | // for (int i = 0; i < Entries.Length; i += 8) 27 | // { 28 | // var cur = new NAMEID(Entries, i, this); 29 | // Lookup.Add(cur.PropIndex, cur); 30 | // } 31 | // } 32 | // } 33 | // } 34 | -------------------------------------------------------------------------------- /PSTParse/MessageLayer/unused/PropType.cs: -------------------------------------------------------------------------------- 1 | // using System; 2 | // using System.Collections.Generic; 3 | // using System.Linq; 4 | // using System.Text; 5 | 6 | // namespace PSTParse.MessageLayer 7 | // { 8 | // public enum PropType 9 | // { 10 | // PtypInteger16 = 0x0002, 11 | // PtypInteger32 = 0x0003, 12 | // PtypFloating32 = 0x0004, 13 | // PtypFloating64 = 0x0005, 14 | // PtypCurrency = 0x0006, 15 | // PtypFloatingTime = 0x0007, 16 | // PtypErrorCode = 0x000A, 17 | // PtypBoolean = 0x000B, 18 | // PtypInteger64 = 0x0014, 19 | // PtypString = 0x001F, 20 | // PtypString8 = 0x001E, 21 | // PtypTime = 0x0040, 22 | // PtypGuid = 0x0048, 23 | // PtypServerId = 0x00FB, 24 | // PtypRestriction = 0x00FD, 25 | // PtypRuleAction = 0x00FE, 26 | // PtypBinary = 0x0102, 27 | // PtypMultipleInteger16 = 0x1002, 28 | // PtypMultipleInteger32 = 0x1003, 29 | // PtypMultipleFloating32 = 0x1004, 30 | // PtypMultipleFloating64 = 0x1005, 31 | // PtypMultipleCurrency = 0x1006, 32 | // PtypMultipleFloatingTime = 0x1007, 33 | // PtypMultipleInteger64 = 0x1014, 34 | // PtypMultipleString = 0x101F, 35 | // PtypMultipleString8 = 0x101E, 36 | // PtypMultipleTime = 0x1040, 37 | // PtypMultipleGuid = 0x1048, 38 | // PtypMultipleBinary = 0x1102, 39 | // PtypUnspecified = 0x0000, 40 | // PtypNull = 0x0001, 41 | // PtypObject = 0x000D 42 | // } 43 | // } 44 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/BBTENTRY.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PSTParse.NodeDatabaseLayer 4 | { 5 | /// 6 | /// Leaf Block B Tree Entry.
7 | /// BBTENTRY records contain information about blocks and are found in BTPAGES with cLevel equal to 0, with the ptype of "ptypeBBT".
8 | /// These are the leaf entries of the BBT.
9 | /// As noted in section 2.2.2.7.7.1, these structures might not be tightly packed and the cbEnt field of the BTPAGE SHOULD be used to iterate over the entries.
10 | ///
11 | public class BBTENTRY : BTPAGEENTRY 12 | { 13 | public BREF BREF { get; } 14 | /// 15 | /// (CB)
16 | /// The count of bytes of the raw data contained in the block referenced by BREF excluding the block trailer and alignment padding, if any. 17 | ///
18 | public ushort BlockByteCount { get; } 19 | /// 20 | /// (CRef)
21 | /// Reference count indicating the count of references to this block.
22 | /// See section 2.2.2.7.7.3.1 regarding how reference counts work. 23 | ///
24 | public ushort RefCount { get; } 25 | public ulong Key => BREF.BID; 26 | public bool Internal => BREF.IsInternal; 27 | 28 | public BBTENTRY(byte[] bytes) 29 | { 30 | BREF = new BREF(bytes); 31 | /*this.BREF = new BREF_UNICODE 32 | {BID_raw = BitConverter.ToUInt64(bytes, 0), ByteIndex = BitConverter.ToUInt64(bytes, 8)};*/ 33 | BlockByteCount = BitConverter.ToUInt16(bytes, 16); 34 | RefCount = BitConverter.ToUInt16(bytes, 18); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/BREF.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PSTParse.NodeDatabaseLayer 4 | { 5 | /// 6 | /// Block Reference (BREF)
7 | /// A record that maps a BID to its absolute file offset location. 8 | ///
9 | public class BREF 10 | { 11 | /// 12 | /// Block ID (BID) 13 | /// 14 | public ulong BID { get; } 15 | /// 16 | /// Byte Index (IB) 64 bits.
17 | /// An absolute offset within the PST file with respect to the beginning of the file. 18 | ///
19 | public ulong IB { get; } 20 | public bool IsInternal => (BID & 0x02) > 0; 21 | 22 | public BREF(byte[] bref, int offset = 0) 23 | { 24 | BID = BitConverter.ToUInt64(bref, offset); 25 | BID = BID & 0xfffffffffffffffe; 26 | IB = BitConverter.ToUInt64(bref, offset + 8); 27 | 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/BTENTRY.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace PSTParse.NodeDatabaseLayer 5 | { 6 | /// 7 | /// BTENTRY records contain a key value (NID or BID) and a reference to a child BTPAGE page in the BTree. 8 | /// 9 | public class BTENTRY : BTPAGEENTRY 10 | { 11 | /// 12 | /// The key value associated with this BTENTRY. 13 | /// All the entries in the child BTPAGE referenced by BREF have key values greater than or equal to this key value. 14 | /// The btkey is either an NID (zero extended to 8 bytes for Unicode PSTs) or a BID, depending on the ptype of the page. 15 | /// 16 | public ulong BtKey { get; } 17 | /// 18 | /// BREF structure (section 2.2.2.4) that points to the child BTPAGE. 19 | /// 20 | public BREF BREF { get; } 21 | 22 | public BTENTRY(byte[] bytes) 23 | { 24 | BtKey = BitConverter.ToUInt64(bytes, 0); 25 | BREF = new BREF(bytes.Skip(8).Take(16).ToArray()); 26 | /*this.BREF = new BREF_UNICODE 27 | {BID_raw = BitConverter.ToUInt64(bytes, 8), ByteIndex = BitConverter.ToUInt64(bytes, 16)};*/ 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/BTPAGEENTRY.cs: -------------------------------------------------------------------------------- 1 | namespace PSTParse.NodeDatabaseLayer 2 | { 3 | public interface BTPAGEENTRY { } 4 | } 5 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/BTPage.cs: -------------------------------------------------------------------------------- 1 | using PSTParse.Utilities; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace PSTParse.NodeDatabaseLayer 6 | { 7 | /// 8 | /// B-Tree Page (BTPage)
9 | /// Implements a generic BTree using 512-byte pages. 10 | ///
11 | public class BTPage 12 | { 13 | /// 14 | /// cEnt
15 | /// The number of BTree entries stored in the page data. 16 | ///
17 | private readonly int _numEntries; 18 | /// 19 | /// cEntMax
20 | /// The maximum number of entries that can fit inside the page data. 21 | ///
22 | private readonly int _maxEntries; 23 | private readonly int _cbEnt; 24 | private readonly int _cLevel; 25 | //privatreadonly e bool _isNBT; 26 | private readonly PageTrailer _trailer; 27 | private readonly BREF _bRef; 28 | 29 | public List Entries { get; } 30 | public List InternalChildren { get; } 31 | public bool IsNode => _trailer.PageType == PageType.NBT; 32 | public bool IsBlock => _trailer.PageType == PageType.BBT; 33 | public ulong BID => _trailer.BID; 34 | 35 | public BTPage(byte[] pageData, BREF bRef, PSTFile pst) 36 | { 37 | _bRef = bRef; 38 | InternalChildren = new List(); 39 | _numEntries = pageData[488]; 40 | _maxEntries = pageData[489]; 41 | _cbEnt = pageData[490]; 42 | _cLevel = pageData[491]; 43 | _trailer = new PageTrailer(pageData.RangeSubset(496, 16)); 44 | 45 | Entries = new List(); 46 | for (var i = 0; i < _numEntries; i++) 47 | { 48 | var curEntryBytes = pageData.RangeSubset(i * _cbEnt, _cbEnt); 49 | if (_cLevel == 0) 50 | { 51 | if (_trailer.PageType == PageType.NBT) 52 | Entries.Add(new NBTENTRY(curEntryBytes)); 53 | else 54 | { 55 | var curEntry = new BBTENTRY(curEntryBytes); 56 | Entries.Add(curEntry); 57 | } 58 | } 59 | else 60 | { 61 | //btentries 62 | var entry = new BTENTRY(curEntryBytes); 63 | Entries.Add(entry); 64 | using (var view = pst.PSTMMF.CreateViewAccessor((long)entry.BREF.IB, 512)) 65 | { 66 | var bytes = new byte[512]; 67 | view.ReadArray(0, bytes, 0, 512); 68 | InternalChildren.Add(new BTPage(bytes, entry.BREF, pst)); 69 | } 70 | } 71 | } 72 | } 73 | 74 | public BBTENTRY GetBIDBBTEntry(ulong BID) 75 | { 76 | int ii = 0; 77 | if (BID % 2 == 1) 78 | ii++; 79 | BID = BID & 0xfffffffffffffffe; 80 | for (int i = 0; i < Entries.Count; i++) 81 | { 82 | var entry = Entries[i]; 83 | if (i == Entries.Count - 1) 84 | { 85 | 86 | if (entry is BTENTRY) 87 | return InternalChildren[i].GetBIDBBTEntry(BID); 88 | else 89 | { 90 | var temp = entry as BBTENTRY; 91 | if (BID == temp.Key) 92 | return temp; 93 | } 94 | 95 | } 96 | else 97 | { 98 | var entry2 = Entries[i + 1]; 99 | if (entry is BTENTRY) 100 | { 101 | var cur = entry as BTENTRY; 102 | var next = entry2 as BTENTRY; 103 | if (BID >= cur.BtKey && BID < next.BtKey) 104 | return InternalChildren[i].GetBIDBBTEntry(BID); 105 | } 106 | else if (entry is BBTENTRY) 107 | { 108 | var cur = entry as BBTENTRY; 109 | if (BID == cur.Key) 110 | return cur; 111 | } 112 | } 113 | } 114 | return null; 115 | } 116 | 117 | public Tuple GetNIDBID(ulong NID) 118 | { 119 | var isBTEntry = Entries[0] is BTENTRY; 120 | for (int i = 0; i < Entries.Count; i++) 121 | { 122 | if (i == Entries.Count - 1) 123 | { 124 | if (isBTEntry) 125 | return InternalChildren[i].GetNIDBID(NID); 126 | var cur = Entries[i] as NBTENTRY; 127 | return new Tuple(cur.BID_Data, cur.BID_SUB); 128 | } 129 | 130 | var curEntry = Entries[i]; 131 | var nextEntry = Entries[i + 1]; 132 | if (isBTEntry) 133 | { 134 | var cur = curEntry as BTENTRY; 135 | var next = nextEntry as BTENTRY; 136 | if (NID >= cur.BtKey && NID < next.BtKey) 137 | return InternalChildren[i].GetNIDBID(NID); 138 | } 139 | else 140 | { 141 | var cur = curEntry as NBTENTRY; 142 | if (NID == cur.NID) 143 | return new Tuple(cur.BID_Data, cur.BID_SUB); 144 | } 145 | } 146 | return new Tuple(0, 0); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/BlockBO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace PSTParse.NodeDatabaseLayer 7 | { 8 | public static class BlockBO 9 | { 10 | public static NodeDataDTO GetNodeData(ulong nid, PSTFile pst) 11 | { 12 | var nodeBIDs = pst.GetNodeBIDs(nid); 13 | var blockBBT_Entry = pst.GetBlockBBTEntry(nodeBIDs.Item1); 14 | var mainData = GetBBTEntryData(blockBBT_Entry, pst); 15 | var subNodeData = new Dictionary(); 16 | 17 | if (nodeBIDs.Item2 != 0) 18 | subNodeData = GetSubNodeData(pst.GetBlockBBTEntry(nodeBIDs.Item2), pst); 19 | 20 | return new NodeDataDTO { NodeData = mainData, SubNodeData = subNodeData }; 21 | } 22 | 23 | public static Dictionary GetSubNodeData(ulong nid, PSTFile pst, int take = int.MaxValue) 24 | { 25 | var nodeBIDs = pst.GetNodeBIDs(nid); 26 | 27 | Dictionary subNodeData; 28 | if (nodeBIDs.Item2 == 0) 29 | subNodeData = new Dictionary(); 30 | else 31 | subNodeData = GetSubNodeData(pst.GetBlockBBTEntry(nodeBIDs.Item2), pst, take); 32 | 33 | return subNodeData; 34 | } 35 | 36 | private static Dictionary GetSubNodeData(BBTENTRY entry, PSTFile pst, int take = int.MaxValue) 37 | { 38 | var allData = GetBBTEntryData(entry, pst); 39 | var dataBlock = allData[0]; 40 | if (entry.Internal) 41 | { 42 | var type = dataBlock.Data[0]; 43 | var cLevel = dataBlock.Data[1]; 44 | if (cLevel == 0) //SLBlock, no intermediate 45 | { 46 | return GetSLBlockData(new SLBLOCK(dataBlock), pst, take); 47 | } 48 | else //SIBlock 49 | { 50 | return GetSIBlockData(new SIBLOCK(dataBlock), pst); 51 | } 52 | } 53 | else 54 | { 55 | throw new Exception("Whoops"); 56 | } 57 | } 58 | 59 | private static Dictionary GetSIBlockData(SIBLOCK siblock, PSTFile pst) 60 | { 61 | var ret = new Dictionary(); 62 | 63 | foreach (var entry in siblock.Entries) 64 | { 65 | var curSLBlockBBT = pst.GetBlockBBTEntry(entry.SLBlockBID); 66 | var slblock = new SLBLOCK(GetBBTEntryData(curSLBlockBBT, pst)[0]); 67 | var data = GetSLBlockData(slblock, pst); 68 | foreach (var item in data) 69 | ret.Add(item.Key, item.Value); 70 | } 71 | 72 | return ret; 73 | } 74 | 75 | /// 76 | /// Gets all the data for an SL block.
77 | /// An SL block points directly to all the immediate subnodes 78 | ///
79 | private static Dictionary GetSLBlockData(SLBLOCK slblock, PSTFile pst, int take = int.MaxValue) 80 | { 81 | var ret = new Dictionary(); 82 | foreach (var entry in slblock.Entries.Take(take)) 83 | { 84 | //this data should represent the main data part of the subnode 85 | var data = GetBBTEntryData(pst.GetBlockBBTEntry(entry.SubNodeBID), pst); 86 | var cur = new NodeDataDTO { NodeData = data }; 87 | ret.Add(entry.SubNodeNID, cur); 88 | 89 | //see if there are sub nodes of this current sub node 90 | if (entry.SubSubNodeBID != 0) 91 | //if there are subnodes, treat them like any other subnode 92 | cur.SubNodeData = GetSubNodeData(pst.GetBlockBBTEntry(entry.SubSubNodeBID), pst); 93 | } 94 | return ret; 95 | } 96 | 97 | public static NodeDataDTO GetNodeData(NBTENTRY entry, PSTFile pst) 98 | { 99 | var mainData = BlockBO.GetBBTEntryData(pst.GetBlockBBTEntry(entry.BID_Data), pst); 100 | if (entry.BID_SUB != 0) 101 | { 102 | var subnodeData = BlockBO.GetSubNodeData(pst.GetBlockBBTEntry(entry.BID_SUB), pst); 103 | return new NodeDataDTO { NodeData = mainData, SubNodeData = subnodeData }; 104 | } 105 | 106 | return new NodeDataDTO { NodeData = mainData, SubNodeData = null }; 107 | } 108 | 109 | public static NodeDataDTO GetNodeData(SLENTRY entry, PSTFile pst) 110 | { 111 | var mainData = BlockBO.GetBBTEntryData(pst.GetBlockBBTEntry(entry.SubNodeBID), pst); 112 | if (entry.SubSubNodeBID != 0) 113 | { 114 | var subNodeData = BlockBO.GetSubNodeData(pst.GetBlockBBTEntry(entry.SubSubNodeBID), pst); 115 | return new NodeDataDTO { NodeData = mainData, SubNodeData = subNodeData }; 116 | } 117 | 118 | return new NodeDataDTO { NodeData = mainData, SubNodeData = null }; 119 | } 120 | 121 | /// 122 | /// For a given bbt entry, retrieve the raw bytes associated with the BID.
123 | /// This includes retrieving data trees via xblocks. 124 | ///
125 | public static List GetBBTEntryData(BBTENTRY entry, PSTFile pst) 126 | { 127 | if (entry == null) 128 | throw new InvalidDataException("Failed while parsing BBTEntry, the data block was invalid, try running a PST repair"); 129 | 130 | var dataSize = entry.BlockByteCount; 131 | var blockSize = entry.BlockByteCount + 16; 132 | if (blockSize % 64 != 0) 133 | blockSize += 64 - (blockSize % 64); 134 | List dataBlocks; 135 | 136 | /*if (isSubNode) 137 | { 138 | using (var viewer = PSTFile.PSTMMF.CreateViewAccessor((long)entry.BREF.IB, blockSize)) 139 | { 140 | var blockBytes = new byte[dataSize]; 141 | viewer.ReadArray(0, blockBytes, 0, dataSize); 142 | dataBlocks = new List 143 | {new BlockDataDTO {Data = blockBytes, PstOffset = entry.BREF.IB, BBTEntry = entry}}; 144 | return dataBlocks; 145 | } 146 | } else */ 147 | if (entry.Internal) 148 | { 149 | using (var viewer = pst.PSTMMF.CreateViewAccessor((long)entry.BREF.IB, blockSize)) 150 | { 151 | var blockBytes = new byte[dataSize]; 152 | viewer.ReadArray(0, blockBytes, 0, dataSize); 153 | 154 | var trailerBytes = new byte[16]; 155 | viewer.ReadArray(blockSize - 16, trailerBytes, 0, 16); 156 | var trailer = new BlockTrailer(trailerBytes, 0); 157 | 158 | var dataBlockDTO = new BlockDataDTO 159 | { 160 | Data = blockBytes, 161 | PstOffset = entry.BREF.IB, 162 | CRCOffset = (uint)((long)entry.BREF.IB + (blockSize - 12)), 163 | BBTEntry = entry 164 | }; 165 | var type = blockBytes[0]; 166 | var level = blockBytes[1]; 167 | 168 | if (type == 2) //si or sl entry 169 | { 170 | return new List { dataBlockDTO }; 171 | } 172 | else if (type == 1) 173 | { 174 | if (blockBytes[1] == 0x01) //XBLOCK 175 | { 176 | var xblock = new XBLOCK(dataBlockDTO); 177 | return BlockBO.GetXBlockData(xblock, pst); 178 | 179 | } 180 | else //XXBLOCK 181 | { 182 | var xxblock = new XXBLOCK(dataBlockDTO); 183 | return BlockBO.GetXXBlockData(xxblock, pst); 184 | } 185 | } 186 | else 187 | { 188 | throw new NotImplementedException(); 189 | } 190 | } 191 | } 192 | else 193 | { 194 | using (var viewer = pst.PSTMMF.CreateViewAccessor((long)entry.BREF.IB, blockSize)) 195 | { 196 | var dataBytes = new byte[dataSize]; 197 | viewer.ReadArray(0, dataBytes, 0, dataSize); 198 | 199 | var trailerBytes = new byte[16]; 200 | viewer.ReadArray(blockSize - 16, trailerBytes, 0, 16); 201 | var trailer = new BlockTrailer(trailerBytes, 0); 202 | dataBlocks = new List 203 | { 204 | new BlockDataDTO 205 | { 206 | Data = dataBytes, 207 | PstOffset = entry.BREF.IB, 208 | CRC32 = trailer.CRC, 209 | CRCOffset = (uint) (blockSize -12), 210 | BBTEntry = entry 211 | } 212 | }; 213 | } 214 | } 215 | 216 | for (int i = 0; i < dataBlocks.Count; i++) 217 | { 218 | var temp = dataBlocks[i].Data; 219 | DatatEncoder.CryptPermute(temp, temp.Length, false, pst.Header.EncodingAlgotihm); 220 | } 221 | return dataBlocks; 222 | } 223 | 224 | private static List GetXBlockData(XBLOCK xblock, PSTFile pst) 225 | { 226 | var ret = new List(); 227 | foreach (var bid in xblock.BIDEntries) 228 | { 229 | var bbtEntry = pst.GetBlockBBTEntry(bid); 230 | ret.AddRange(BlockBO.GetBBTEntryData(bbtEntry, pst)); 231 | } 232 | return ret; 233 | } 234 | 235 | private static List GetXXBlockData(XXBLOCK xxblock, PSTFile pst) 236 | { 237 | var ret = new List(); 238 | foreach (var bid in xxblock.XBlockBIDs) 239 | { 240 | var bbtEntry = pst.GetBlockBBTEntry(bid); 241 | var curXblockData = BlockBO.GetBBTEntryData(bbtEntry, pst); 242 | //var curXblockData = BlockBO.GetXBlockData(curXblock); 243 | foreach (var block in curXblockData) 244 | ret.Add(block); 245 | } 246 | return ret; 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/BlockDataDTO.cs: -------------------------------------------------------------------------------- 1 | namespace PSTParse.NodeDatabaseLayer 2 | { 3 | public class BlockDataDTO 4 | { 5 | public BlockDataDTO Parent { get; set; } 6 | public byte[] Data { get; set; } 7 | public ulong PstOffset { get; set; } 8 | public uint CRC32 { get; set; } 9 | public uint CRCOffset { get; set; } 10 | public BBTENTRY BBTEntry { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/BlockTrailer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PSTParse.NodeDatabaseLayer 4 | { 5 | /// 6 | /// Stores metadata for a block at the end of a block 7 | /// 8 | public class BlockTrailer 9 | { 10 | /// 11 | /// The amount of data, in bytes, contained within the data section of the block (CB) 12 | /// 13 | public uint DataSize { get; set; } 14 | /// 15 | /// Block signature (wSig)
16 | /// See section 5.5 for the algorithm to calculate the block signature. 17 | ///
18 | public uint WSig { get; set; } 19 | /// 20 | /// 32-bit CRC of the CB bytes of raw data 21 | /// 22 | public uint CRC { get; set; } 23 | /// 24 | /// The Block ID of the data block 25 | /// 26 | public ulong BID_Raw { get; set; } 27 | 28 | public BlockTrailer(byte[] bytes, int offset) 29 | { 30 | DataSize = BitConverter.ToUInt16(bytes, offset); 31 | WSig = BitConverter.ToUInt16(bytes, 2 + offset); 32 | CRC = BitConverter.ToUInt32(bytes, 4 + offset); 33 | BID_Raw = BitConverter.ToUInt64(bytes, 8 + offset); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/DatatEncoder.cs: -------------------------------------------------------------------------------- 1 | using static PSTParse.PSTHeader; 2 | 3 | namespace PSTParse.NodeDatabaseLayer 4 | { 5 | public static class DatatEncoder 6 | { 7 | private static readonly byte[] mpbbCrypt = 8 | { 9 | 65, 54, 19, 98, 168, 33, 110, 187, 10 | 244, 22, 204, 4, 127, 100, 232, 93, 11 | 30, 242, 203, 42, 116, 197, 94, 53, 12 | 210, 149, 71, 158, 150, 45, 154, 136, 13 | 76, 125, 132, 63, 219, 172, 49, 182, 14 | 72, 95, 246, 196, 216, 57, 139, 231, 15 | 35, 59, 56, 142, 200, 193, 223, 37, 16 | 177, 32, 165, 70, 96, 78, 156, 251, 17 | 170, 211, 86, 81, 69, 124, 85, 0, 18 | 7, 201, 43, 157, 133, 155, 9, 160, 19 | 143, 173, 179, 15, 99, 171, 137, 75, 20 | 215, 167, 21, 90, 113, 102, 66, 191, 21 | 38, 74, 107, 152, 250, 234, 119, 83, 22 | 178, 112, 5, 44, 253, 89, 58, 134, 23 | 126, 206, 6, 235, 130, 120, 87, 199, 24 | 141, 67, 175, 180, 28, 212, 91, 205, 25 | 226, 233, 39, 79, 195, 8, 114, 128, 26 | 207, 176, 239, 245, 40, 109, 190, 48, 27 | 77, 52, 146, 213, 14, 60, 34, 50, 28 | 229, 228, 249, 159, 194, 209, 10, 129, 29 | 18, 225, 238, 145, 131, 118, 227, 151, 30 | 230, 97, 138, 23, 121, 164, 183, 220, 31 | 144, 122, 92, 140, 2, 166, 202, 105, 32 | 222, 80, 26, 17, 147, 185, 82, 135, 33 | 88, 252, 237, 29, 55, 73, 27, 106, 34 | 224, 41, 51, 153, 189, 108, 217, 148, 35 | 243, 64, 84, 111, 240, 198, 115, 184, 36 | 214, 62, 101, 24, 68, 31, 221, 103, 37 | 16, 241, 12, 25, 236, 174, 3, 161, 38 | 20, 123, 169, 11, 255, 248, 163, 192, 39 | 162, 1, 247, 46, 188, 36, 104, 117, 40 | 13, 254, 186, 47, 181, 208, 218, 61, 41 | 20, 83, 15, 86, 179, 200, 122, 156, 42 | 235, 101, 72, 23, 22, 21, 159, 2, 43 | 204, 84, 124, 131, 0, 13, 12, 11, 44 | 162, 98, 168, 118, 219, 217, 237, 199, 45 | 197, 164, 220, 172, 133, 116, 214, 208, 46 | 167, 155, 174, 154, 150, 113, 102, 195, 47 | 99, 153, 184, 221, 115, 146, 142, 132, 48 | 125, 165, 94, 209, 93, 147, 177, 87, 49 | 81, 80, 128, 137, 82, 148, 79, 78, 50 | 10, 107, 188, 141, 127, 110, 71, 70, 51 | 65, 64, 68, 1, 17, 203, 3, 63, 52 | 247, 244, 225, 169, 143, 60, 58, 249, 53 | 251, 240, 25, 48, 130, 9, 46, 201, 54 | 157, 160, 134, 73, 238, 111, 77, 109, 55 | 196, 45, 129, 52, 37, 135, 27, 136, 56 | 170, 252, 6, 161, 18, 56, 253, 76, 57 | 66, 114, 100, 19, 55, 36, 106, 117, 58 | 119, 67, 255, 230, 180, 75, 54, 92, 59 | 228, 216, 53, 61, 69, 185, 44, 236, 60 | 183, 49, 43, 41, 7, 104, 163, 14, 61 | 105, 123, 24, 158, 33, 57, 190, 40, 62 | 26, 91, 120, 245, 35, 202, 42, 176, 63 | 175, 62, 254, 4, 140, 231, 229, 152, 64 | 50, 149, 211, 246, 74, 232, 166, 234, 65 | 233, 243, 213, 47, 112, 32, 242, 31, 66 | 5, 103, 173, 85, 16, 206, 205, 227, 67 | 39, 59, 218, 186, 215, 194, 38, 212, 68 | 145, 29, 210, 28, 34, 51, 248, 250, 69 | 241, 90, 239, 207, 144, 182, 139, 181, 70 | 189, 192, 191, 8, 151, 30, 108, 226, 71 | 97, 224, 198, 193, 89, 171, 187, 88, 72 | 222, 95, 223, 96, 121, 126, 178, 138, 73 | 71, 241, 180, 230, 11, 106, 114, 72, 74 | 133, 78, 158, 235, 226, 248, 148, 83, 75 | 224, 187, 160, 2, 232, 90, 9, 171, 76 | 219, 227, 186, 198, 124, 195, 16, 221, 77 | 57, 5, 150, 48, 245, 55, 96, 130, 78 | 140, 201, 19, 74, 107, 29, 243, 251, 79 | 143, 38, 151, 202, 145, 23, 1, 196, 80 | 50, 45, 110, 49, 149, 255, 217, 35, 81 | 209, 0, 94, 121, 220, 68, 59, 26, 82 | 40, 197, 97, 87, 32, 144, 61, 131, 83 | 185, 67, 190, 103, 210, 70, 66, 118, 84 | 192, 109, 91, 126, 178, 15, 22, 41, 85 | 60, 169, 3, 84, 13, 218, 93, 223, 86 | 246, 183, 199, 98, 205, 141, 6, 211, 87 | 105, 92, 134, 214, 20, 247, 165, 102, 88 | 117, 172, 177, 233, 69, 33, 112, 12, 89 | 135, 159, 116, 164, 34, 76, 111, 191, 90 | 31, 86, 170, 46, 179, 120, 51, 80, 91 | 176, 163, 146, 188, 207, 25, 28, 167, 92 | 99, 203, 30, 77, 62, 75, 27, 155, 93 | 79, 231, 240, 238, 173, 58, 181, 89, 94 | 4, 234, 64, 85, 37, 81, 229, 122, 95 | 137, 56, 104, 82, 123, 252, 39, 174, 96 | 215, 189, 250, 7, 244, 204, 142, 95, 97 | 239, 53, 156, 132, 43, 21, 213, 119, 98 | 52, 73, 182, 18, 10, 127, 113, 136, 99 | 253, 157, 24, 65, 125, 147, 216, 88, 100 | 44, 206, 254, 36, 175, 222, 184, 54, 101 | 200, 161, 128, 166, 153, 152, 168, 47, 102 | 14, 129, 101, 115, 228, 194, 162, 138, 103 | 212, 225, 17, 208, 8, 139, 42, 242, 104 | 237, 154, 100, 63, 193, 108, 249, 236 105 | }; 106 | 107 | public static void CryptPermute(byte[] pv, int cb, bool fEncrypt, BlockEncoding blockEncoding) 108 | { 109 | if (blockEncoding == BlockEncoding.NONE) return; 110 | if (blockEncoding == BlockEncoding.PERMUTE) 111 | { 112 | int idx = (fEncrypt ? 0 : 512); 113 | int temp = 0; 114 | 115 | for (int pvIndex = 0; pvIndex < cb; pvIndex++) 116 | { 117 | temp = pv[pvIndex]; 118 | temp = temp & 0xFF; 119 | pv[pvIndex] = mpbbCrypt[temp + idx]; 120 | } 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/IBLOCK.cs: -------------------------------------------------------------------------------- 1 | namespace PSTParse.NodeDatabaseLayer 2 | { 3 | /// 4 | /// Blocks are the fundamental units of data storage at the NDB layer.
5 | /// Blocks are assigned in sizes that are multiples of 64 bytes and are aligned on 64-byte boundaries.
6 | /// The maximum size of any block is 8 kilobytes (8192 bytes). 7 | ///
8 | public interface IBLOCK { } 9 | } 10 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/NBTENTRY.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PSTParse.NodeDatabaseLayer 4 | { 5 | /// 6 | /// NBTENTRY records contain information about nodes and are found in BTPAGES with cLevel equal to 0, with the ptype of ptypeNBT. 7 | /// 8 | public class NBTENTRY : BTPAGEENTRY 9 | { 10 | /// 11 | /// Node ID 12 | /// 13 | public ulong NID { get; set; } 14 | /// 15 | /// The Block ID of the data block for this node 16 | /// 17 | public ulong BID_Data { get; set; } 18 | /// 19 | /// The Block ID of the subnode block for this node.
20 | /// If this value is zero, a subnode block does not exist for this node. 21 | ///
22 | public ulong BID_SUB { get; set; } 23 | /// 24 | /// If this node represents a child of a Folder object defined in the Messaging Layer, then this value is nonzero and contains the NID of the parent Folder object's node.
25 | /// Otherwise, this value is zero. See section 2.2.2.7.7.4.1 for more information. 26 | ///
This field is not interpreted by any structure defined at the NDB Layer. 27 | ///
28 | public uint NID_Parent { get; set; } 29 | public ulong NID_TYPE { get; set; } 30 | 31 | public NBTENTRY(byte[] curEntryBytes) 32 | { 33 | NID = BitConverter.ToUInt64(curEntryBytes, 0); 34 | BID_Data = BitConverter.ToUInt64(curEntryBytes,8); 35 | BID_SUB = BitConverter.ToUInt64(curEntryBytes,16); 36 | NID_Parent = BitConverter.ToUInt32(curEntryBytes, 24); 37 | NID_TYPE = NID & 0x1f; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/NID.cs: -------------------------------------------------------------------------------- 1 | namespace PSTParse.NodeDatabaseLayer 2 | { 3 | /// 4 | /// Node ID (NID) 5 | /// 6 | public class NID 7 | { 8 | public enum NodeType 9 | { 10 | //heap node 11 | HID = 0x00, 12 | INTERNAL = 0x01, 13 | NORMAL_FOLDER = 0x02, 14 | SEARCH_FOLDER = 0x03, 15 | NORMAL_MESSAGE_PC = 0x03, 16 | ATTACHMENT_PC = 0x05, 17 | // queue of changed objects for search folder object 18 | SEARCH_UPDATE_QUEUE = 0x06, 19 | SEARCH_CRITERIA_OBJECT = 0x07, 20 | ASSOC_MESSAGE = 0X08, 21 | CONTENTS_TABLE_INDEX = 0X0A, 22 | //inbox 23 | RECEIVE_FOLDER_TABLE = 0X0B, 24 | //outbox 25 | OUTGOING_QUEUE_TABLE = 0X0C, 26 | HIERARCHY_TABLE = 0X0D, 27 | CONTENTS_TABLE = 0X0E, 28 | ASSOC_CONTENTS_TABLE = 0X0F, 29 | SEARCH_CONTENTS_TABLE = 0X10, 30 | ATTACHMENT_TABLE = 0X11, 31 | RECIPIENT_TABLE = 0X12, 32 | SEARCH_TABLE_INDEX = 0X13, 33 | LTP = 0X14 34 | } 35 | public static NodeType GetNodeType(ulong nid) => (NodeType)(nid & 0x1f); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/NodeDataDTO.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace PSTParse.NodeDatabaseLayer 4 | { 5 | public class NodeDataDTO 6 | { 7 | public List NodeData { get; set; } 8 | public Dictionary SubNodeData { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/PSTBTree.cs: -------------------------------------------------------------------------------- 1 | namespace PSTParse.NodeDatabaseLayer 2 | { 3 | public class PSTBTree 4 | { 5 | public BTPage Root { get; } 6 | 7 | public PSTBTree(BREF bref, PSTFile pst) 8 | { 9 | using (var viewer = pst.PSTMMF.CreateViewAccessor((long)bref.IB, 512)) 10 | { 11 | var data = new byte[512]; 12 | viewer.ReadArray(0, data, 0, 512); 13 | Root = new BTPage(data, bref, pst); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/PageTrailer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PSTParse.NodeDatabaseLayer 4 | { 5 | public enum PageType 6 | { 7 | /// 8 | /// Block B Tree 9 | /// 10 | BBT = 0x80, 11 | /// 12 | /// Node B Tree 13 | /// 14 | NBT = 0x81, 15 | FreeMap = 0x82, 16 | PageMap = 0x83, 17 | AMap = 0x84, 18 | FreePageMap = 0x85, 19 | DensityList = 0x86 20 | } 21 | 22 | /// 23 | /// A PAGETRAILER structure contains information about the page in which it is contained.
24 | /// It is present at the very end of each page in a PST file. 25 | ///
26 | public class PageTrailer 27 | { 28 | /// 29 | /// The type of data contained within the page. 30 | /// 31 | public PageType PageType { get; set; } 32 | /// 33 | /// The BID of the page's block. 34 | /// 35 | public ulong BID { get; set; } 36 | 37 | public PageTrailer(byte[] trailer) 38 | { 39 | PageType = (PageType)trailer[0]; 40 | BID = BitConverter.ToUInt64(trailer, 8); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/SIBLOCK.cs: -------------------------------------------------------------------------------- 1 | using PSTParse.Utilities; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace PSTParse.NodeDatabaseLayer 6 | { 7 | public class SIBLOCK : IBLOCK 8 | { 9 | public BlockDataDTO DataBlock; 10 | public UInt16 EntryCount; 11 | public List Entries; 12 | 13 | public SIBLOCK(BlockDataDTO dataBlock) 14 | { 15 | this.DataBlock = dataBlock; 16 | var type = dataBlock.Data[0]; 17 | var cLevel = dataBlock.Data[1]; 18 | this.EntryCount = BitConverter.ToUInt16(dataBlock.Data, 2); 19 | this.Entries = new List(); 20 | for(int i =0;i < EntryCount;i++) 21 | this.Entries.Add(new SIENTRY(dataBlock.Data.RangeSubset(8 + 16*i, 16))); 22 | } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/SIENTRY.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PSTParse.NodeDatabaseLayer 7 | { 8 | public class SIENTRY 9 | { 10 | public ulong NextChildNID; 11 | public ulong SLBlockBID; 12 | 13 | public SIENTRY(byte[] bytes) 14 | { 15 | this.NextChildNID = BitConverter.ToUInt64(bytes, 0); 16 | this.SLBlockBID = BitConverter.ToUInt64(bytes, 8); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/SLBLOCK.cs: -------------------------------------------------------------------------------- 1 | using PSTParse.Utilities; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace PSTParse.NodeDatabaseLayer 6 | { 7 | public class SLBLOCK : IBLOCK 8 | { 9 | public BlockDataDTO BlockData; 10 | public UInt16 EntryCount; 11 | public List Entries; 12 | 13 | public SLBLOCK(BlockDataDTO blockData) 14 | { 15 | BlockData = blockData; 16 | var type = blockData.Data[0]; 17 | var clevel = blockData.Data[1]; 18 | EntryCount = BitConverter.ToUInt16(blockData.Data, 2); 19 | Entries = new List(); 20 | for (int i = 0; i < EntryCount; i++) 21 | { 22 | Entries.Add(new SLENTRY(blockData.Data.RangeSubset(8 + 24 * i, 24))); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/SLENTRY.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PSTParse.NodeDatabaseLayer 7 | { 8 | public class SLENTRY 9 | { 10 | public ulong SubNodeNID; 11 | public ulong SubNodeBID; 12 | public ulong SubSubNodeBID; 13 | 14 | public SLENTRY(byte[] bytes) 15 | { 16 | this.SubNodeNID = BitConverter.ToUInt64(bytes, 0); 17 | this.SubNodeBID = BitConverter.ToUInt64(bytes, 8); 18 | this.SubSubNodeBID = BitConverter.ToUInt64(bytes, 16); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/SpecialNIDs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace PSTParse.NodeDatabaseLayer 7 | { 8 | public static class SpecialNIDs 9 | { 10 | public static uint NID_MESSAGE_STORE = 0x21; 11 | public static uint NID_NMAE_TO_ID_MAP = 0x61; 12 | public static uint NID_ROOT_FOLDER = 0x122; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/XBLOCK.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PSTParse.NodeDatabaseLayer 4 | { 5 | public class XBLOCK : IBLOCK 6 | { 7 | public BlockDataDTO Block; 8 | public uint BlockType; 9 | public uint HeaderLevel; 10 | public uint BIDEntryCount; 11 | public uint TotalBytes; 12 | 13 | public ulong[] BIDEntries; 14 | 15 | public XBLOCK(BlockDataDTO block) 16 | { 17 | this.Block = block; 18 | this.BlockType = block.Data[0]; 19 | this.HeaderLevel = block.Data[1]; 20 | this.BIDEntryCount = BitConverter.ToUInt16(block.Data, 2); 21 | this.TotalBytes = BitConverter.ToUInt32(block.Data, 4); 22 | this.BIDEntries = new ulong[BIDEntryCount]; 23 | for (int i = 0; i < BIDEntryCount; i++) 24 | BIDEntries[i] = BitConverter.ToUInt64(block.Data, 8 + i*8); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/XXBLOCK.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PSTParse.NodeDatabaseLayer 4 | { 5 | public class XXBLOCK : IBLOCK 6 | { 7 | public byte Type; 8 | public byte CLevel; 9 | public UInt16 TotalChildren; 10 | public uint TotalBytes; 11 | public BlockDataDTO Block; 12 | 13 | public ulong[] XBlockBIDs; 14 | 15 | public XXBLOCK(BlockDataDTO block) 16 | { 17 | this.Block = block; 18 | 19 | this.Type = block.Data[0]; 20 | this.CLevel = block.Data[1]; 21 | this.TotalChildren = BitConverter.ToUInt16(block.Data, 2); 22 | this.TotalBytes = BitConverter.ToUInt32(block.Data, 4); 23 | this.XBlockBIDs = new ulong[this.TotalChildren]; 24 | for (var i = 0; i < TotalChildren; i++) 25 | this.XBlockBIDs[i] = BitConverter.ToUInt64(block.Data, 8 + 8*i); 26 | 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/unused/BID.cs: -------------------------------------------------------------------------------- 1 | // using System; 2 | 3 | // namespace PSTParse.NodeDatabaseLayer 4 | // { 5 | // /// 6 | // /// Block ID (BID) 7 | // /// 8 | // public class BID 9 | // { 10 | // public ulong BlockID { get; } 11 | 12 | // public BID(byte[] bytes, int offset = 0) 13 | // { 14 | // BlockID = BitConverter.ToUInt64(bytes, offset) & 0xfffffffffffffffe; 15 | // } 16 | // } 17 | // } 18 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/unused/BlockFactory.cs: -------------------------------------------------------------------------------- 1 | // using System; 2 | // using System.Collections.Generic; 3 | // using System.Linq; 4 | // using System.Text; 5 | 6 | // namespace PSTParse.NodeDatabaseLayer 7 | // { 8 | // public static class BlockFactory 9 | // { 10 | // public static IBLOCK GetBlock(byte[] bytes, int blockDataSize, 11 | // bool isInternal, bool isSubNode) 12 | // { 13 | // var trailerOffset = bytes.Length - 16; 14 | // var trailer = new BlockTrailer(bytes, trailerOffset); 15 | 16 | 17 | 18 | // if (!isInternal) 19 | // return new DataBlock(bytes, blockDataSize); 20 | // else 21 | // { 22 | // var bType = bytes[0]; 23 | // var headerLevel = bytes[1]; 24 | 25 | // if (isSubNode) 26 | // { 27 | // if (headerLevel == 0) 28 | // return new SLBLOCK(bytes); 29 | // else 30 | // return new SIBLOCK(bytes); 31 | // } else 32 | // { 33 | // if (headerLevel == 1) 34 | // return new XBLOCK(bytes); 35 | // else 36 | // return new XXBLOCK(bytes); 37 | // } 38 | // } 39 | // } 40 | // } 41 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/unused/NodeBTree.cs: -------------------------------------------------------------------------------- 1 | //namespace PSTParse.NodeDatabaseLayer 2 | //{ 3 | // public class NodeBTree 4 | // { 5 | // public BREF RootLocation; 6 | // public BTPage Root; 7 | 8 | // public NodeBTree(BREF root) 9 | // { 10 | // this.RootLocation = root; 11 | // } 12 | // } 13 | //} 14 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/unused/PSTBTreeNode.cs: -------------------------------------------------------------------------------- 1 | // using System; 2 | // using System.Collections.Generic; 3 | // using System.IO.MemoryMappedFiles; 4 | // using System.Linq; 5 | // using System.Text; 6 | 7 | // namespace PSTParse.NodeDatabaseLayer 8 | // { 9 | // public class PSTBTreeNode 10 | // { 11 | 12 | 13 | // public bool Internal { get; set; } 14 | // public long Offset { get; set; } 15 | // public BTPage Page { get; set; } 16 | // private MemoryMappedFile _mmf; 17 | 18 | // public List Children { get; set; } 19 | 20 | 21 | 22 | // public PSTBTreeNode(BREF root, MemoryMappedFile pstmmf, bool isNode) 23 | // { 24 | // this.Internal = root.IsInternal; 25 | // this.Offset = (long)root.IB; 26 | // this._mmf = pstmmf; 27 | // this.Children = new List(); 28 | 29 | // if (isNode || this.Internal) 30 | // { 31 | // using (var mmfview = this._mmf.CreateViewAccessor(Offset, 512)) 32 | // { 33 | // var bytes = new byte[512]; 34 | // mmfview.ReadArray(0, bytes, 0, 512); 35 | // this.Page = new BTPage(bytes, root); 36 | // foreach (var child in this.Page.Entries) 37 | // { 38 | // if (child is BTENTRY) 39 | // { 40 | // var cur = child as BTENTRY; 41 | // this.Children.Add(new PSTBTreeNode(cur.BREF, this._mmf, isNode)); 42 | // } 43 | // else if (child is BBTENTRY) 44 | // { 45 | // var cur = child as BBTENTRY; 46 | // var dataSize = cur.BlockByteCount; 47 | // using(var mffview2 = this._mmf.CreateViewAccessor((long)cur.BREF.IB,dataSize+16)) 48 | // { 49 | // var b = new byte[dataSize + 16]; 50 | // mffview2.ReadArray(0, b, 0, dataSize + 16); 51 | // var block = BlockFactory.GetBlock(b, dataSize, true, false); 52 | // if (!(block is DataBlock)) 53 | // this.Children.Add(new PSTBTreeNode(cur.BREF, this._mmf, isNode)); 54 | // } 55 | // } 56 | // else if (child is NBTENTRY) 57 | // { 58 | // //var cur = child as NBTENTRY; 59 | // //this.Children.Add(new PSTBTreeNode(cur.)); 60 | // } 61 | // } 62 | // } 63 | // } 64 | // } 65 | // } 66 | -------------------------------------------------------------------------------- /PSTParse/NodeDatabaseLayer/unused/SubNodeDataDTO.cs: -------------------------------------------------------------------------------- 1 | // using System; 2 | // using System.Collections.Generic; 3 | // using System.Linq; 4 | // using System.Text; 5 | 6 | // namespace PSTParse.NDB 7 | // { 8 | // public class SubNodeDataDTO 9 | // { 10 | // public List Data; 11 | // //public ulong PstOffset; 12 | // public Dictionary SubNodeData; 13 | // } 14 | // } 15 | -------------------------------------------------------------------------------- /PSTParse/PSTEnums.cs: -------------------------------------------------------------------------------- 1 | namespace PSTParse 2 | { 3 | public class PSTEnums 4 | { 5 | public enum ObjectType 6 | { 7 | STORE = 0x01, 8 | ADDRESS_BOOK = 0x02, 9 | ADDRESS_BOOK_CONTAINER = 0x04, 10 | MESSAGE_OBJECT = 0x05, 11 | MAIL_USER = 0x06, 12 | ATTACHMENT = 0x07, 13 | DISTRIBUTION_LIST = 0x08 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PSTParse/PSTFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.MemoryMappedFiles; 5 | using PSTParse.MessageLayer; 6 | using PSTParse.NodeDatabaseLayer; 7 | using PSTParse.ListsTablesPropertiesLayer; 8 | using System.Linq; 9 | using PSTParse.Utilities; 10 | 11 | namespace PSTParse 12 | { 13 | public class PSTFile : IDisposable 14 | { 15 | public const int MinFileSizeBytes = 1_000; 16 | 17 | public string Path { get; } 18 | public MemoryMappedFile PSTMMF { get; private set; } 19 | public PSTHeader Header { get; } 20 | public MailStore MailStore { get; } 21 | public MailFolder TopOfPST { get; } 22 | //public NamedToPropertyLookup NamedPropertyLookup { get; } 23 | public ulong Size => Header.Root.FileSizeBytes; 24 | 25 | public PSTFile(string path) 26 | { 27 | if (new FileInfo(path).Length < MinFileSizeBytes) 28 | { 29 | throw new Exception($"Failed opening PST, file size must be greater than {MinFileSizeBytes} bytes"); 30 | } 31 | Path = path ?? throw new ArgumentNullException(nameof(path)); 32 | PSTMMF = MemoryMappedFile.CreateFromFile(path, FileMode.Open); 33 | 34 | Header = new PSTHeader(this); 35 | 36 | /*var messageStoreData = BlockBO.GetNodeData(SpecialNIDs.NID_MESSAGE_STORE); 37 | var temp = BlockBO.GetNodeData(SpecialNIDs.NID_ROOT_FOLDER);*/ 38 | MailStore = new MailStore(this); 39 | 40 | TopOfPST = new MailFolder(MailStore.RootFolder.NID, new List(), this); 41 | //NamedPropertyLookup = new NamedToPropertyLookup(this); 42 | 43 | //var temp = new TableContext(rootEntryID.NID); 44 | } 45 | 46 | public bool IsPasswordProtected() 47 | { 48 | var messageStore = new PropertyContext(SpecialNIDs.NID_MESSAGE_STORE, this); 49 | var rootDataNode = messageStore.BTH.Root.Data; 50 | const int unknown2Bytes = 2; 51 | var passwordKey = new byte[] { 0xFF, 0x67 }; 52 | foreach (var entry in rootDataNode.DataEntries) 53 | { 54 | if (entry.Key.SequenceEqual(passwordKey)) 55 | { 56 | var dataBlockOffset = (int)entry.DataOffset + (int)rootDataNode.Data.BlockOffset + unknown2Bytes; 57 | var slice = rootDataNode.Data.Parent.Data.Skip(dataBlockOffset).Take(4).ToList(); 58 | var isProtected = !slice.SequenceEqual(new byte[] { 0, 0, 0, 0 }); 59 | return isProtected; 60 | } 61 | } 62 | return false; 63 | } 64 | 65 | public bool RemovePassword() 66 | { 67 | var messageStore = new PropertyContext(SpecialNIDs.NID_MESSAGE_STORE, this); 68 | var rootDataNode = messageStore.BTH.Root.Data; 69 | const int unknown2Bytes = 2; 70 | var passwordKey = new byte[] { 0xFF, 0x67 }; 71 | foreach (var entry in rootDataNode.DataEntries) 72 | { 73 | if (entry.Key.SequenceEqual(passwordKey)) 74 | { 75 | var dataBlockOffset = (int)entry.DataOffset + (int)rootDataNode.Data.BlockOffset + unknown2Bytes; 76 | var slice = rootDataNode.Data.Parent.Data.Skip(dataBlockOffset).Take(4).ToList(); 77 | var isProtected = !slice.SequenceEqual(new byte[] { 0, 0, 0, 0 }); 78 | if (!isProtected) return false; 79 | 80 | CloseMMF(); 81 | 82 | using (var stream = new FileStream(Path, FileMode.Open)) 83 | { 84 | rootDataNode.Data.Parent.Data[dataBlockOffset] = 0x00; 85 | rootDataNode.Data.Parent.Data[dataBlockOffset + 1] = 0x00; 86 | rootDataNode.Data.Parent.Data[dataBlockOffset + 2] = 0x00; 87 | rootDataNode.Data.Parent.Data[dataBlockOffset + 3] = 0x00; 88 | 89 | DatatEncoder.CryptPermute(rootDataNode.Data.Parent.Data, rootDataNode.Data.Parent.Data.Length, true, Header.EncodingAlgotihm); 90 | 91 | // seems to always be [65, 65, 65, 65] 92 | var permutationBytes = rootDataNode.Data.Parent.Data.Skip(dataBlockOffset).Take(4).ToArray(); 93 | stream.Seek((long)rootDataNode.Data.Parent.PstOffset + dataBlockOffset, SeekOrigin.Begin); 94 | stream.Write(permutationBytes, 0, 4); 95 | 96 | var newCRC = new CRC32().ComputeCRC(0, rootDataNode.Data.Parent.Data, (uint)rootDataNode.Data.Parent.Data.Length); 97 | DatatEncoder.CryptPermute(rootDataNode.Data.Parent.Data, rootDataNode.Data.Parent.Data.Length, false, Header.EncodingAlgotihm); 98 | var crcoffset = (long)(rootDataNode.Data.Parent.PstOffset + rootDataNode.Data.Parent.CRCOffset); 99 | stream.Seek(crcoffset, SeekOrigin.Begin); 100 | var crcBuffer = BitConverter.GetBytes(newCRC); 101 | stream.Write(crcBuffer, 0, 4); 102 | } 103 | OpenMMF(); 104 | return true; 105 | } 106 | } 107 | return false; 108 | } 109 | 110 | public void CloseMMF() 111 | { 112 | PSTMMF.Dispose(); 113 | } 114 | 115 | public void OpenMMF() 116 | { 117 | PSTMMF = MemoryMappedFile.CreateFromFile(Path, FileMode.Open); 118 | } 119 | 120 | public Tuple GetNodeBIDs(ulong NID) 121 | { 122 | return Header.NodeBT.Root.GetNIDBID(NID); 123 | } 124 | 125 | public void Dispose() 126 | { 127 | CloseMMF(); 128 | } 129 | 130 | public BBTENTRY GetBlockBBTEntry(ulong item1) 131 | { 132 | return Header.BlockBT.Root.GetBIDBBTEntry(item1); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /PSTParse/PSTHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using PSTParse.NodeDatabaseLayer; 4 | 5 | namespace PSTParse 6 | { 7 | public class PSTHeader 8 | { 9 | public string DWMagic { get; } 10 | public PstVersion Version { get; } 11 | public PSTBTree NodeBT { get; private set; } 12 | public PSTBTree BlockBT { get; private set; } 13 | public BlockEncoding EncodingAlgotihm { get; private set; } 14 | public PSTRoot Root { get; } 15 | 16 | public PSTHeader(PSTFile pst) 17 | { 18 | using (var mmfView = pst.PSTMMF.CreateViewAccessor(0, 684)) 19 | { 20 | var dwMagicBuffer = new byte[4]; 21 | mmfView.ReadArray(0, dwMagicBuffer, 0, 4); 22 | DWMagic = Encoding.Default.GetString(dwMagicBuffer); 23 | 24 | var ver = mmfView.ReadInt16(10); 25 | Version = ver == 23 ? PstVersion.UNICODE : PstVersion.ANSI; 26 | if (Version == PstVersion.ANSI) 27 | { 28 | throw new Exception("ANSI encoded PST not supported"); 29 | } 30 | 31 | var rootBuffer = new byte[72]; 32 | mmfView.ReadArray(180, rootBuffer, 0, rootBuffer.Length); 33 | 34 | Root = new PSTRoot(rootBuffer); 35 | 36 | var sentinel = mmfView.ReadByte(512); 37 | var cryptMethod = (uint)mmfView.ReadByte(513); 38 | 39 | EncodingAlgotihm = (BlockEncoding)cryptMethod; 40 | 41 | var bytes = new byte[16]; 42 | mmfView.ReadArray(216, bytes, 0, 16); 43 | var nbt_bref = new BREF(bytes); 44 | 45 | mmfView.ReadArray(232, bytes, 0, 16); 46 | var bbt_bref = new BREF(bytes); 47 | 48 | NodeBT = new PSTBTree(nbt_bref, pst); 49 | BlockBT = new PSTBTree(bbt_bref, pst); 50 | } 51 | } 52 | 53 | public enum BlockEncoding 54 | { 55 | NONE = 0, 56 | PERMUTE = 1, 57 | CYCLIC = 2 58 | } 59 | 60 | public enum PstVersion 61 | { 62 | ANSI, 63 | UNICODE, 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /PSTParse/PSTParse.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net60;netstandard2.0 5 | 1.3.2 6 | latest 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /PSTParse/PSTRoot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PSTParse 4 | { 5 | public class PSTRoot 6 | { 7 | public uint D_W_Reserved { get; set; } 8 | /// 9 | /// The size of the PST file, in bytes. 10 | /// 11 | public ulong FileSizeBytes { get; } 12 | public byte F_A_MapValid { get; set; } 13 | 14 | public PSTRoot(byte[] rootBuffer) 15 | { 16 | D_W_Reserved = BitConverter.ToUInt32(rootBuffer, 0); 17 | FileSizeBytes = BitConverter.ToUInt64(rootBuffer, 4); 18 | F_A_MapValid = rootBuffer[68]; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PSTParse/Utilities/ArrayUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace PSTParse.Utilities 5 | { 6 | public static class ArrayUtilities 7 | { 8 | // create a subset from a range of indices 9 | public static T[] RangeSubset(this T[] array, int startIndex, int length) 10 | { 11 | T[] subset = new T[length]; 12 | Array.Copy(array, startIndex, subset, 0, length); 13 | return subset; 14 | } 15 | 16 | // create a subset from a specific list of indices 17 | public static T[] Subset(this T[] array, params int[] indices) 18 | { 19 | T[] subset = new T[indices.Length]; 20 | for (int i = 0; i < indices.Length; i++) 21 | { 22 | subset[i] = array[indices[i]]; 23 | } 24 | return subset; 25 | } 26 | 27 | //this exists so byte arrays can be used as keys in dictionaries 28 | //http://stackoverflow.com/questions/1440392/use-byte-as-key-in-dictionary 29 | public class ByteArrayComparer : IEqualityComparer 30 | { 31 | public bool Equals(byte[] left, byte[] right) 32 | { 33 | if (left == null || right == null) 34 | return left == right; 35 | 36 | if (left.Length != right.Length) 37 | return false; 38 | 39 | for (int i = 0; i < left.Length; i++) 40 | if (left[i] != right[i]) 41 | return false; 42 | 43 | return true; 44 | } 45 | 46 | public int GetHashCode(byte[] key) 47 | { 48 | if (key == null) 49 | throw new ArgumentNullException("key"); 50 | 51 | int sum = 0; 52 | foreach (byte cur in key) 53 | sum += cur; 54 | return sum; 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /PSTParse/Utilities/CRC32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PSTParse.Utilities 4 | { 5 | public class CRC32 6 | { 7 | #region constants 8 | public static uint[] CrcTableOffset32 = 9 | { 10 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 11 | 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 12 | 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 13 | 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 14 | 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 15 | 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 16 | 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 17 | 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 18 | 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 19 | 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 20 | 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 21 | 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 22 | 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 23 | 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 24 | 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 25 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 26 | 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 27 | 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 28 | 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 29 | 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 30 | 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 31 | 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 32 | 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 33 | 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 34 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 35 | 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 36 | 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 37 | 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 38 | 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 39 | 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 40 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 41 | 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D 42 | }; 43 | 44 | private uint[] CrcTableOffset40 = 45 | { 46 | 0x00000000, 0x191B3141, 0x32366282, 0x2B2D53C3, 0x646CC504, 0x7D77F445, 0x565AA786, 0x4F4196C7, 47 | 0xC8D98A08, 0xD1C2BB49, 0xFAEFE88A, 0xE3F4D9CB, 0xACB54F0C, 0xB5AE7E4D, 0x9E832D8E, 0x87981CCF, 48 | 0x4AC21251, 0x53D92310, 0x78F470D3, 0x61EF4192, 0x2EAED755, 0x37B5E614, 0x1C98B5D7, 0x05838496, 49 | 0x821B9859, 0x9B00A918, 0xB02DFADB, 0xA936CB9A, 0xE6775D5D, 0xFF6C6C1C, 0xD4413FDF, 0xCD5A0E9E, 50 | 0x958424A2, 0x8C9F15E3, 0xA7B24620, 0xBEA97761, 0xF1E8E1A6, 0xE8F3D0E7, 0xC3DE8324, 0xDAC5B265, 51 | 0x5D5DAEAA, 0x44469FEB, 0x6F6BCC28, 0x7670FD69, 0x39316BAE, 0x202A5AEF, 0x0B07092C, 0x121C386D, 52 | 0xDF4636F3, 0xC65D07B2, 0xED705471, 0xF46B6530, 0xBB2AF3F7, 0xA231C2B6, 0x891C9175, 0x9007A034, 53 | 0x179FBCFB, 0x0E848DBA, 0x25A9DE79, 0x3CB2EF38, 0x73F379FF, 0x6AE848BE, 0x41C51B7D, 0x58DE2A3C, 54 | 0xF0794F05, 0xE9627E44, 0xC24F2D87, 0xDB541CC6, 0x94158A01, 0x8D0EBB40, 0xA623E883, 0xBF38D9C2, 55 | 0x38A0C50D, 0x21BBF44C, 0x0A96A78F, 0x138D96CE, 0x5CCC0009, 0x45D73148, 0x6EFA628B, 0x77E153CA, 56 | 0xBABB5D54, 0xA3A06C15, 0x888D3FD6, 0x91960E97, 0xDED79850, 0xC7CCA911, 0xECE1FAD2, 0xF5FACB93, 57 | 0x7262D75C, 0x6B79E61D, 0x4054B5DE, 0x594F849F, 0x160E1258, 0x0F152319, 0x243870DA, 0x3D23419B, 58 | 0x65FD6BA7, 0x7CE65AE6, 0x57CB0925, 0x4ED03864, 0x0191AEA3, 0x188A9FE2, 0x33A7CC21, 0x2ABCFD60, 59 | 0xAD24E1AF, 0xB43FD0EE, 0x9F12832D, 0x8609B26C, 0xC94824AB, 0xD05315EA, 0xFB7E4629, 0xE2657768, 60 | 0x2F3F79F6, 0x362448B7, 0x1D091B74, 0x04122A35, 0x4B53BCF2, 0x52488DB3, 0x7965DE70, 0x607EEF31, 61 | 0xE7E6F3FE, 0xFEFDC2BF, 0xD5D0917C, 0xCCCBA03D, 0x838A36FA, 0x9A9107BB, 0xB1BC5478, 0xA8A76539, 62 | 0x3B83984B, 0x2298A90A, 0x09B5FAC9, 0x10AECB88, 0x5FEF5D4F, 0x46F46C0E, 0x6DD93FCD, 0x74C20E8C, 63 | 0xF35A1243, 0xEA412302, 0xC16C70C1, 0xD8774180, 0x9736D747, 0x8E2DE606, 0xA500B5C5, 0xBC1B8484, 64 | 0x71418A1A, 0x685ABB5B, 0x4377E898, 0x5A6CD9D9, 0x152D4F1E, 0x0C367E5F, 0x271B2D9C, 0x3E001CDD, 65 | 0xB9980012, 0xA0833153, 0x8BAE6290, 0x92B553D1, 0xDDF4C516, 0xC4EFF457, 0xEFC2A794, 0xF6D996D5, 66 | 0xAE07BCE9, 0xB71C8DA8, 0x9C31DE6B, 0x852AEF2A, 0xCA6B79ED, 0xD37048AC, 0xF85D1B6F, 0xE1462A2E, 67 | 0x66DE36E1, 0x7FC507A0, 0x54E85463, 0x4DF36522, 0x02B2F3E5, 0x1BA9C2A4, 0x30849167, 0x299FA026, 68 | 0xE4C5AEB8, 0xFDDE9FF9, 0xD6F3CC3A, 0xCFE8FD7B, 0x80A96BBC, 0x99B25AFD, 0xB29F093E, 0xAB84387F, 69 | 0x2C1C24B0, 0x350715F1, 0x1E2A4632, 0x07317773, 0x4870E1B4, 0x516BD0F5, 0x7A468336, 0x635DB277, 70 | 0xCBFAD74E, 0xD2E1E60F, 0xF9CCB5CC, 0xE0D7848D, 0xAF96124A, 0xB68D230B, 0x9DA070C8, 0x84BB4189, 71 | 0x03235D46, 0x1A386C07, 0x31153FC4, 0x280E0E85, 0x674F9842, 0x7E54A903, 0x5579FAC0, 0x4C62CB81, 72 | 0x8138C51F, 0x9823F45E, 0xB30EA79D, 0xAA1596DC, 0xE554001B, 0xFC4F315A, 0xD7626299, 0xCE7953D8, 73 | 0x49E14F17, 0x50FA7E56, 0x7BD72D95, 0x62CC1CD4, 0x2D8D8A13, 0x3496BB52, 0x1FBBE891, 0x06A0D9D0, 74 | 0x5E7EF3EC, 0x4765C2AD, 0x6C48916E, 0x7553A02F, 0x3A1236E8, 0x230907A9, 0x0824546A, 0x113F652B, 75 | 0x96A779E4, 0x8FBC48A5, 0xA4911B66, 0xBD8A2A27, 0xF2CBBCE0, 0xEBD08DA1, 0xC0FDDE62, 0xD9E6EF23, 76 | 0x14BCE1BD, 0x0DA7D0FC, 0x268A833F, 0x3F91B27E, 0x70D024B9, 0x69CB15F8, 0x42E6463B, 0x5BFD777A, 77 | 0xDC656BB5, 0xC57E5AF4, 0xEE530937, 0xF7483876, 0xB809AEB1, 0xA1129FF0, 0x8A3FCC33, 0x9324FD72 78 | }; 79 | 80 | private uint[] CrcTableOffset48 = 81 | { 82 | 0x00000000, 0x01C26A37, 0x0384D46E, 0x0246BE59, 0x0709A8DC, 0x06CBC2EB, 0x048D7CB2, 0x054F1685, 83 | 0x0E1351B8, 0x0FD13B8F, 0x0D9785D6, 0x0C55EFE1, 0x091AF964, 0x08D89353, 0x0A9E2D0A, 0x0B5C473D, 84 | 0x1C26A370, 0x1DE4C947, 0x1FA2771E, 0x1E601D29, 0x1B2F0BAC, 0x1AED619B, 0x18ABDFC2, 0x1969B5F5, 85 | 0x1235F2C8, 0x13F798FF, 0x11B126A6, 0x10734C91, 0x153C5A14, 0x14FE3023, 0x16B88E7A, 0x177AE44D, 86 | 0x384D46E0, 0x398F2CD7, 0x3BC9928E, 0x3A0BF8B9, 0x3F44EE3C, 0x3E86840B, 0x3CC03A52, 0x3D025065, 87 | 0x365E1758, 0x379C7D6F, 0x35DAC336, 0x3418A901, 0x3157BF84, 0x3095D5B3, 0x32D36BEA, 0x331101DD, 88 | 0x246BE590, 0x25A98FA7, 0x27EF31FE, 0x262D5BC9, 0x23624D4C, 0x22A0277B, 0x20E69922, 0x2124F315, 89 | 0x2A78B428, 0x2BBADE1F, 0x29FC6046, 0x283E0A71, 0x2D711CF4, 0x2CB376C3, 0x2EF5C89A, 0x2F37A2AD, 90 | 0x709A8DC0, 0x7158E7F7, 0x731E59AE, 0x72DC3399, 0x7793251C, 0x76514F2B, 0x7417F172, 0x75D59B45, 91 | 0x7E89DC78, 0x7F4BB64F, 0x7D0D0816, 0x7CCF6221, 0x798074A4, 0x78421E93, 0x7A04A0CA, 0x7BC6CAFD, 92 | 0x6CBC2EB0, 0x6D7E4487, 0x6F38FADE, 0x6EFA90E9, 0x6BB5866C, 0x6A77EC5B, 0x68315202, 0x69F33835, 93 | 0x62AF7F08, 0x636D153F, 0x612BAB66, 0x60E9C151, 0x65A6D7D4, 0x6464BDE3, 0x662203BA, 0x67E0698D, 94 | 0x48D7CB20, 0x4915A117, 0x4B531F4E, 0x4A917579, 0x4FDE63FC, 0x4E1C09CB, 0x4C5AB792, 0x4D98DDA5, 95 | 0x46C49A98, 0x4706F0AF, 0x45404EF6, 0x448224C1, 0x41CD3244, 0x400F5873, 0x4249E62A, 0x438B8C1D, 96 | 0x54F16850, 0x55330267, 0x5775BC3E, 0x56B7D609, 0x53F8C08C, 0x523AAABB, 0x507C14E2, 0x51BE7ED5, 97 | 0x5AE239E8, 0x5B2053DF, 0x5966ED86, 0x58A487B1, 0x5DEB9134, 0x5C29FB03, 0x5E6F455A, 0x5FAD2F6D, 98 | 0xE1351B80, 0xE0F771B7, 0xE2B1CFEE, 0xE373A5D9, 0xE63CB35C, 0xE7FED96B, 0xE5B86732, 0xE47A0D05, 99 | 0xEF264A38, 0xEEE4200F, 0xECA29E56, 0xED60F461, 0xE82FE2E4, 0xE9ED88D3, 0xEBAB368A, 0xEA695CBD, 100 | 0xFD13B8F0, 0xFCD1D2C7, 0xFE976C9E, 0xFF5506A9, 0xFA1A102C, 0xFBD87A1B, 0xF99EC442, 0xF85CAE75, 101 | 0xF300E948, 0xF2C2837F, 0xF0843D26, 0xF1465711, 0xF4094194, 0xF5CB2BA3, 0xF78D95FA, 0xF64FFFCD, 102 | 0xD9785D60, 0xD8BA3757, 0xDAFC890E, 0xDB3EE339, 0xDE71F5BC, 0xDFB39F8B, 0xDDF521D2, 0xDC374BE5, 103 | 0xD76B0CD8, 0xD6A966EF, 0xD4EFD8B6, 0xD52DB281, 0xD062A404, 0xD1A0CE33, 0xD3E6706A, 0xD2241A5D, 104 | 0xC55EFE10, 0xC49C9427, 0xC6DA2A7E, 0xC7184049, 0xC25756CC, 0xC3953CFB, 0xC1D382A2, 0xC011E895, 105 | 0xCB4DAFA8, 0xCA8FC59F, 0xC8C97BC6, 0xC90B11F1, 0xCC440774, 0xCD866D43, 0xCFC0D31A, 0xCE02B92D, 106 | 0x91AF9640, 0x906DFC77, 0x922B422E, 0x93E92819, 0x96A63E9C, 0x976454AB, 0x9522EAF2, 0x94E080C5, 107 | 0x9FBCC7F8, 0x9E7EADCF, 0x9C381396, 0x9DFA79A1, 0x98B56F24, 0x99770513, 0x9B31BB4A, 0x9AF3D17D, 108 | 0x8D893530, 0x8C4B5F07, 0x8E0DE15E, 0x8FCF8B69, 0x8A809DEC, 0x8B42F7DB, 0x89044982, 0x88C623B5, 109 | 0x839A6488, 0x82580EBF, 0x801EB0E6, 0x81DCDAD1, 0x8493CC54, 0x8551A663, 0x8717183A, 0x86D5720D, 110 | 0xA9E2D0A0, 0xA820BA97, 0xAA6604CE, 0xABA46EF9, 0xAEEB787C, 0xAF29124B, 0xAD6FAC12, 0xACADC625, 111 | 0xA7F18118, 0xA633EB2F, 0xA4755576, 0xA5B73F41, 0xA0F829C4, 0xA13A43F3, 0xA37CFDAA, 0xA2BE979D, 112 | 0xB5C473D0, 0xB40619E7, 0xB640A7BE, 0xB782CD89, 0xB2CDDB0C, 0xB30FB13B, 0xB1490F62, 0xB08B6555, 113 | 0xBBD72268, 0xBA15485F, 0xB853F606, 0xB9919C31, 0xBCDE8AB4, 0xBD1CE083, 0xBF5A5EDA, 0xBE9834ED 114 | }; 115 | 116 | private uint[] CrcTableOffset56 = 117 | { 118 | 0x00000000, 0xB8BC6765, 0xAA09C88B, 0x12B5AFEE, 0x8F629757, 0x37DEF032, 0x256B5FDC, 0x9DD738B9, 119 | 0xC5B428EF, 0x7D084F8A, 0x6FBDE064, 0xD7018701, 0x4AD6BFB8, 0xF26AD8DD, 0xE0DF7733, 0x58631056, 120 | 0x5019579F, 0xE8A530FA, 0xFA109F14, 0x42ACF871, 0xDF7BC0C8, 0x67C7A7AD, 0x75720843, 0xCDCE6F26, 121 | 0x95AD7F70, 0x2D111815, 0x3FA4B7FB, 0x8718D09E, 0x1ACFE827, 0xA2738F42, 0xB0C620AC, 0x087A47C9, 122 | 0xA032AF3E, 0x188EC85B, 0x0A3B67B5, 0xB28700D0, 0x2F503869, 0x97EC5F0C, 0x8559F0E2, 0x3DE59787, 123 | 0x658687D1, 0xDD3AE0B4, 0xCF8F4F5A, 0x7733283F, 0xEAE41086, 0x525877E3, 0x40EDD80D, 0xF851BF68, 124 | 0xF02BF8A1, 0x48979FC4, 0x5A22302A, 0xE29E574F, 0x7F496FF6, 0xC7F50893, 0xD540A77D, 0x6DFCC018, 125 | 0x359FD04E, 0x8D23B72B, 0x9F9618C5, 0x272A7FA0, 0xBAFD4719, 0x0241207C, 0x10F48F92, 0xA848E8F7, 126 | 0x9B14583D, 0x23A83F58, 0x311D90B6, 0x89A1F7D3, 0x1476CF6A, 0xACCAA80F, 0xBE7F07E1, 0x06C36084, 127 | 0x5EA070D2, 0xE61C17B7, 0xF4A9B859, 0x4C15DF3C, 0xD1C2E785, 0x697E80E0, 0x7BCB2F0E, 0xC377486B, 128 | 0xCB0D0FA2, 0x73B168C7, 0x6104C729, 0xD9B8A04C, 0x446F98F5, 0xFCD3FF90, 0xEE66507E, 0x56DA371B, 129 | 0x0EB9274D, 0xB6054028, 0xA4B0EFC6, 0x1C0C88A3, 0x81DBB01A, 0x3967D77F, 0x2BD27891, 0x936E1FF4, 130 | 0x3B26F703, 0x839A9066, 0x912F3F88, 0x299358ED, 0xB4446054, 0x0CF80731, 0x1E4DA8DF, 0xA6F1CFBA, 131 | 0xFE92DFEC, 0x462EB889, 0x549B1767, 0xEC277002, 0x71F048BB, 0xC94C2FDE, 0xDBF98030, 0x6345E755, 132 | 0x6B3FA09C, 0xD383C7F9, 0xC1366817, 0x798A0F72, 0xE45D37CB, 0x5CE150AE, 0x4E54FF40, 0xF6E89825, 133 | 0xAE8B8873, 0x1637EF16, 0x048240F8, 0xBC3E279D, 0x21E91F24, 0x99557841, 0x8BE0D7AF, 0x335CB0CA, 134 | 0xED59B63B, 0x55E5D15E, 0x47507EB0, 0xFFEC19D5, 0x623B216C, 0xDA874609, 0xC832E9E7, 0x708E8E82, 135 | 0x28ED9ED4, 0x9051F9B1, 0x82E4565F, 0x3A58313A, 0xA78F0983, 0x1F336EE6, 0x0D86C108, 0xB53AA66D, 136 | 0xBD40E1A4, 0x05FC86C1, 0x1749292F, 0xAFF54E4A, 0x322276F3, 0x8A9E1196, 0x982BBE78, 0x2097D91D, 137 | 0x78F4C94B, 0xC048AE2E, 0xD2FD01C0, 0x6A4166A5, 0xF7965E1C, 0x4F2A3979, 0x5D9F9697, 0xE523F1F2, 138 | 0x4D6B1905, 0xF5D77E60, 0xE762D18E, 0x5FDEB6EB, 0xC2098E52, 0x7AB5E937, 0x680046D9, 0xD0BC21BC, 139 | 0x88DF31EA, 0x3063568F, 0x22D6F961, 0x9A6A9E04, 0x07BDA6BD, 0xBF01C1D8, 0xADB46E36, 0x15080953, 140 | 0x1D724E9A, 0xA5CE29FF, 0xB77B8611, 0x0FC7E174, 0x9210D9CD, 0x2AACBEA8, 0x38191146, 0x80A57623, 141 | 0xD8C66675, 0x607A0110, 0x72CFAEFE, 0xCA73C99B, 0x57A4F122, 0xEF189647, 0xFDAD39A9, 0x45115ECC, 142 | 0x764DEE06, 0xCEF18963, 0xDC44268D, 0x64F841E8, 0xF92F7951, 0x41931E34, 0x5326B1DA, 0xEB9AD6BF, 143 | 0xB3F9C6E9, 0x0B45A18C, 0x19F00E62, 0xA14C6907, 0x3C9B51BE, 0x842736DB, 0x96929935, 0x2E2EFE50, 144 | 0x2654B999, 0x9EE8DEFC, 0x8C5D7112, 0x34E11677, 0xA9362ECE, 0x118A49AB, 0x033FE645, 0xBB838120, 145 | 0xE3E09176, 0x5B5CF613, 0x49E959FD, 0xF1553E98, 0x6C820621, 0xD43E6144, 0xC68BCEAA, 0x7E37A9CF, 146 | 0xD67F4138, 0x6EC3265D, 0x7C7689B3, 0xC4CAEED6, 0x591DD66F, 0xE1A1B10A, 0xF3141EE4, 0x4BA87981, 147 | 0x13CB69D7, 0xAB770EB2, 0xB9C2A15C, 0x017EC639, 0x9CA9FE80, 0x241599E5, 0x36A0360B, 0x8E1C516E, 148 | 0x866616A7, 0x3EDA71C2, 0x2C6FDE2C, 0x94D3B949, 0x090481F0, 0xB1B8E695, 0xA30D497B, 0x1BB12E1E, 149 | 0x43D23E48, 0xFB6E592D, 0xE9DBF6C3, 0x516791A6, 0xCCB0A91F, 0x740CCE7A, 0x66B96194, 0xDE0506F1 150 | }; 151 | 152 | private uint[] CrcTableOffset64 = 153 | { 154 | 0x00000000, 0x3D6029B0, 0x7AC05360, 0x47A07AD0, 0xF580A6C0, 0xC8E08F70, 0x8F40F5A0, 0xB220DC10, 155 | 0x30704BC1, 0x0D106271, 0x4AB018A1, 0x77D03111, 0xC5F0ED01, 0xF890C4B1, 0xBF30BE61, 0x825097D1, 156 | 0x60E09782, 0x5D80BE32, 0x1A20C4E2, 0x2740ED52, 0x95603142, 0xA80018F2, 0xEFA06222, 0xD2C04B92, 157 | 0x5090DC43, 0x6DF0F5F3, 0x2A508F23, 0x1730A693, 0xA5107A83, 0x98705333, 0xDFD029E3, 0xE2B00053, 158 | 0xC1C12F04, 0xFCA106B4, 0xBB017C64, 0x866155D4, 0x344189C4, 0x0921A074, 0x4E81DAA4, 0x73E1F314, 159 | 0xF1B164C5, 0xCCD14D75, 0x8B7137A5, 0xB6111E15, 0x0431C205, 0x3951EBB5, 0x7EF19165, 0x4391B8D5, 160 | 0xA121B886, 0x9C419136, 0xDBE1EBE6, 0xE681C256, 0x54A11E46, 0x69C137F6, 0x2E614D26, 0x13016496, 161 | 0x9151F347, 0xAC31DAF7, 0xEB91A027, 0xD6F18997, 0x64D15587, 0x59B17C37, 0x1E1106E7, 0x23712F57, 162 | 0x58F35849, 0x659371F9, 0x22330B29, 0x1F532299, 0xAD73FE89, 0x9013D739, 0xD7B3ADE9, 0xEAD38459, 163 | 0x68831388, 0x55E33A38, 0x124340E8, 0x2F236958, 0x9D03B548, 0xA0639CF8, 0xE7C3E628, 0xDAA3CF98, 164 | 0x3813CFCB, 0x0573E67B, 0x42D39CAB, 0x7FB3B51B, 0xCD93690B, 0xF0F340BB, 0xB7533A6B, 0x8A3313DB, 165 | 0x0863840A, 0x3503ADBA, 0x72A3D76A, 0x4FC3FEDA, 0xFDE322CA, 0xC0830B7A, 0x872371AA, 0xBA43581A, 166 | 0x9932774D, 0xA4525EFD, 0xE3F2242D, 0xDE920D9D, 0x6CB2D18D, 0x51D2F83D, 0x167282ED, 0x2B12AB5D, 167 | 0xA9423C8C, 0x9422153C, 0xD3826FEC, 0xEEE2465C, 0x5CC29A4C, 0x61A2B3FC, 0x2602C92C, 0x1B62E09C, 168 | 0xF9D2E0CF, 0xC4B2C97F, 0x8312B3AF, 0xBE729A1F, 0x0C52460F, 0x31326FBF, 0x7692156F, 0x4BF23CDF, 169 | 0xC9A2AB0E, 0xF4C282BE, 0xB362F86E, 0x8E02D1DE, 0x3C220DCE, 0x0142247E, 0x46E25EAE, 0x7B82771E, 170 | 0xB1E6B092, 0x8C869922, 0xCB26E3F2, 0xF646CA42, 0x44661652, 0x79063FE2, 0x3EA64532, 0x03C66C82, 171 | 0x8196FB53, 0xBCF6D2E3, 0xFB56A833, 0xC6368183, 0x74165D93, 0x49767423, 0x0ED60EF3, 0x33B62743, 172 | 0xD1062710, 0xEC660EA0, 0xABC67470, 0x96A65DC0, 0x248681D0, 0x19E6A860, 0x5E46D2B0, 0x6326FB00, 173 | 0xE1766CD1, 0xDC164561, 0x9BB63FB1, 0xA6D61601, 0x14F6CA11, 0x2996E3A1, 0x6E369971, 0x5356B0C1, 174 | 0x70279F96, 0x4D47B626, 0x0AE7CCF6, 0x3787E546, 0x85A73956, 0xB8C710E6, 0xFF676A36, 0xC2074386, 175 | 0x4057D457, 0x7D37FDE7, 0x3A978737, 0x07F7AE87, 0xB5D77297, 0x88B75B27, 0xCF1721F7, 0xF2770847, 176 | 0x10C70814, 0x2DA721A4, 0x6A075B74, 0x576772C4, 0xE547AED4, 0xD8278764, 0x9F87FDB4, 0xA2E7D404, 177 | 0x20B743D5, 0x1DD76A65, 0x5A7710B5, 0x67173905, 0xD537E515, 0xE857CCA5, 0xAFF7B675, 0x92979FC5, 178 | 0xE915E8DB, 0xD475C16B, 0x93D5BBBB, 0xAEB5920B, 0x1C954E1B, 0x21F567AB, 0x66551D7B, 0x5B3534CB, 179 | 0xD965A31A, 0xE4058AAA, 0xA3A5F07A, 0x9EC5D9CA, 0x2CE505DA, 0x11852C6A, 0x562556BA, 0x6B457F0A, 180 | 0x89F57F59, 0xB49556E9, 0xF3352C39, 0xCE550589, 0x7C75D999, 0x4115F029, 0x06B58AF9, 0x3BD5A349, 181 | 0xB9853498, 0x84E51D28, 0xC34567F8, 0xFE254E48, 0x4C059258, 0x7165BBE8, 0x36C5C138, 0x0BA5E888, 182 | 0x28D4C7DF, 0x15B4EE6F, 0x521494BF, 0x6F74BD0F, 0xDD54611F, 0xE03448AF, 0xA794327F, 0x9AF41BCF, 183 | 0x18A48C1E, 0x25C4A5AE, 0x6264DF7E, 0x5F04F6CE, 0xED242ADE, 0xD044036E, 0x97E479BE, 0xAA84500E, 184 | 0x4834505D, 0x755479ED, 0x32F4033D, 0x0F942A8D, 0xBDB4F69D, 0x80D4DF2D, 0xC774A5FD, 0xFA148C4D, 185 | 0x78441B9C, 0x4524322C, 0x028448FC, 0x3FE4614C, 0x8DC4BD5C, 0xB0A494EC, 0xF704EE3C, 0xCA64C78C 186 | }; 187 | 188 | private uint[] CrcTableOffset72 = 189 | { 190 | 0x00000000, 0xCB5CD3A5, 0x4DC8A10B, 0x869472AE, 0x9B914216, 0x50CD91B3, 0xD659E31D, 0x1D0530B8, 191 | 0xEC53826D, 0x270F51C8, 0xA19B2366, 0x6AC7F0C3, 0x77C2C07B, 0xBC9E13DE, 0x3A0A6170, 0xF156B2D5, 192 | 0x03D6029B, 0xC88AD13E, 0x4E1EA390, 0x85427035, 0x9847408D, 0x531B9328, 0xD58FE186, 0x1ED33223, 193 | 0xEF8580F6, 0x24D95353, 0xA24D21FD, 0x6911F258, 0x7414C2E0, 0xBF481145, 0x39DC63EB, 0xF280B04E, 194 | 0x07AC0536, 0xCCF0D693, 0x4A64A43D, 0x81387798, 0x9C3D4720, 0x57619485, 0xD1F5E62B, 0x1AA9358E, 195 | 0xEBFF875B, 0x20A354FE, 0xA6372650, 0x6D6BF5F5, 0x706EC54D, 0xBB3216E8, 0x3DA66446, 0xF6FAB7E3, 196 | 0x047A07AD, 0xCF26D408, 0x49B2A6A6, 0x82EE7503, 0x9FEB45BB, 0x54B7961E, 0xD223E4B0, 0x197F3715, 197 | 0xE82985C0, 0x23755665, 0xA5E124CB, 0x6EBDF76E, 0x73B8C7D6, 0xB8E41473, 0x3E7066DD, 0xF52CB578, 198 | 0x0F580A6C, 0xC404D9C9, 0x4290AB67, 0x89CC78C2, 0x94C9487A, 0x5F959BDF, 0xD901E971, 0x125D3AD4, 199 | 0xE30B8801, 0x28575BA4, 0xAEC3290A, 0x659FFAAF, 0x789ACA17, 0xB3C619B2, 0x35526B1C, 0xFE0EB8B9, 200 | 0x0C8E08F7, 0xC7D2DB52, 0x4146A9FC, 0x8A1A7A59, 0x971F4AE1, 0x5C439944, 0xDAD7EBEA, 0x118B384F, 201 | 0xE0DD8A9A, 0x2B81593F, 0xAD152B91, 0x6649F834, 0x7B4CC88C, 0xB0101B29, 0x36846987, 0xFDD8BA22, 202 | 0x08F40F5A, 0xC3A8DCFF, 0x453CAE51, 0x8E607DF4, 0x93654D4C, 0x58399EE9, 0xDEADEC47, 0x15F13FE2, 203 | 0xE4A78D37, 0x2FFB5E92, 0xA96F2C3C, 0x6233FF99, 0x7F36CF21, 0xB46A1C84, 0x32FE6E2A, 0xF9A2BD8F, 204 | 0x0B220DC1, 0xC07EDE64, 0x46EAACCA, 0x8DB67F6F, 0x90B34FD7, 0x5BEF9C72, 0xDD7BEEDC, 0x16273D79, 205 | 0xE7718FAC, 0x2C2D5C09, 0xAAB92EA7, 0x61E5FD02, 0x7CE0CDBA, 0xB7BC1E1F, 0x31286CB1, 0xFA74BF14, 206 | 0x1EB014D8, 0xD5ECC77D, 0x5378B5D3, 0x98246676, 0x852156CE, 0x4E7D856B, 0xC8E9F7C5, 0x03B52460, 207 | 0xF2E396B5, 0x39BF4510, 0xBF2B37BE, 0x7477E41B, 0x6972D4A3, 0xA22E0706, 0x24BA75A8, 0xEFE6A60D, 208 | 0x1D661643, 0xD63AC5E6, 0x50AEB748, 0x9BF264ED, 0x86F75455, 0x4DAB87F0, 0xCB3FF55E, 0x006326FB, 209 | 0xF135942E, 0x3A69478B, 0xBCFD3525, 0x77A1E680, 0x6AA4D638, 0xA1F8059D, 0x276C7733, 0xEC30A496, 210 | 0x191C11EE, 0xD240C24B, 0x54D4B0E5, 0x9F886340, 0x828D53F8, 0x49D1805D, 0xCF45F2F3, 0x04192156, 211 | 0xF54F9383, 0x3E134026, 0xB8873288, 0x73DBE12D, 0x6EDED195, 0xA5820230, 0x2316709E, 0xE84AA33B, 212 | 0x1ACA1375, 0xD196C0D0, 0x5702B27E, 0x9C5E61DB, 0x815B5163, 0x4A0782C6, 0xCC93F068, 0x07CF23CD, 213 | 0xF6999118, 0x3DC542BD, 0xBB513013, 0x700DE3B6, 0x6D08D30E, 0xA65400AB, 0x20C07205, 0xEB9CA1A0, 214 | 0x11E81EB4, 0xDAB4CD11, 0x5C20BFBF, 0x977C6C1A, 0x8A795CA2, 0x41258F07, 0xC7B1FDA9, 0x0CED2E0C, 215 | 0xFDBB9CD9, 0x36E74F7C, 0xB0733DD2, 0x7B2FEE77, 0x662ADECF, 0xAD760D6A, 0x2BE27FC4, 0xE0BEAC61, 216 | 0x123E1C2F, 0xD962CF8A, 0x5FF6BD24, 0x94AA6E81, 0x89AF5E39, 0x42F38D9C, 0xC467FF32, 0x0F3B2C97, 217 | 0xFE6D9E42, 0x35314DE7, 0xB3A53F49, 0x78F9ECEC, 0x65FCDC54, 0xAEA00FF1, 0x28347D5F, 0xE368AEFA, 218 | 0x16441B82, 0xDD18C827, 0x5B8CBA89, 0x90D0692C, 0x8DD55994, 0x46898A31, 0xC01DF89F, 0x0B412B3A, 219 | 0xFA1799EF, 0x314B4A4A, 0xB7DF38E4, 0x7C83EB41, 0x6186DBF9, 0xAADA085C, 0x2C4E7AF2, 0xE712A957, 220 | 0x15921919, 0xDECECABC, 0x585AB812, 0x93066BB7, 0x8E035B0F, 0x455F88AA, 0xC3CBFA04, 0x089729A1, 221 | 0xF9C19B74, 0x329D48D1, 0xB4093A7F, 0x7F55E9DA, 0x6250D962, 0xA90C0AC7, 0x2F987869, 0xE4C4ABCC 222 | }; 223 | 224 | private uint[] CrcTableOffset80 = 225 | { 226 | 0x00000000, 0xA6770BB4, 0x979F1129, 0x31E81A9D, 0xF44F2413, 0x52382FA7, 0x63D0353A, 0xC5A73E8E, 227 | 0x33EF4E67, 0x959845D3, 0xA4705F4E, 0x020754FA, 0xC7A06A74, 0x61D761C0, 0x503F7B5D, 0xF64870E9, 228 | 0x67DE9CCE, 0xC1A9977A, 0xF0418DE7, 0x56368653, 0x9391B8DD, 0x35E6B369, 0x040EA9F4, 0xA279A240, 229 | 0x5431D2A9, 0xF246D91D, 0xC3AEC380, 0x65D9C834, 0xA07EF6BA, 0x0609FD0E, 0x37E1E793, 0x9196EC27, 230 | 0xCFBD399C, 0x69CA3228, 0x582228B5, 0xFE552301, 0x3BF21D8F, 0x9D85163B, 0xAC6D0CA6, 0x0A1A0712, 231 | 0xFC5277FB, 0x5A257C4F, 0x6BCD66D2, 0xCDBA6D66, 0x081D53E8, 0xAE6A585C, 0x9F8242C1, 0x39F54975, 232 | 0xA863A552, 0x0E14AEE6, 0x3FFCB47B, 0x998BBFCF, 0x5C2C8141, 0xFA5B8AF5, 0xCBB39068, 0x6DC49BDC, 233 | 0x9B8CEB35, 0x3DFBE081, 0x0C13FA1C, 0xAA64F1A8, 0x6FC3CF26, 0xC9B4C492, 0xF85CDE0F, 0x5E2BD5BB, 234 | 0x440B7579, 0xE27C7ECD, 0xD3946450, 0x75E36FE4, 0xB044516A, 0x16335ADE, 0x27DB4043, 0x81AC4BF7, 235 | 0x77E43B1E, 0xD19330AA, 0xE07B2A37, 0x460C2183, 0x83AB1F0D, 0x25DC14B9, 0x14340E24, 0xB2430590, 236 | 0x23D5E9B7, 0x85A2E203, 0xB44AF89E, 0x123DF32A, 0xD79ACDA4, 0x71EDC610, 0x4005DC8D, 0xE672D739, 237 | 0x103AA7D0, 0xB64DAC64, 0x87A5B6F9, 0x21D2BD4D, 0xE47583C3, 0x42028877, 0x73EA92EA, 0xD59D995E, 238 | 0x8BB64CE5, 0x2DC14751, 0x1C295DCC, 0xBA5E5678, 0x7FF968F6, 0xD98E6342, 0xE86679DF, 0x4E11726B, 239 | 0xB8590282, 0x1E2E0936, 0x2FC613AB, 0x89B1181F, 0x4C162691, 0xEA612D25, 0xDB8937B8, 0x7DFE3C0C, 240 | 0xEC68D02B, 0x4A1FDB9F, 0x7BF7C102, 0xDD80CAB6, 0x1827F438, 0xBE50FF8C, 0x8FB8E511, 0x29CFEEA5, 241 | 0xDF879E4C, 0x79F095F8, 0x48188F65, 0xEE6F84D1, 0x2BC8BA5F, 0x8DBFB1EB, 0xBC57AB76, 0x1A20A0C2, 242 | 0x8816EAF2, 0x2E61E146, 0x1F89FBDB, 0xB9FEF06F, 0x7C59CEE1, 0xDA2EC555, 0xEBC6DFC8, 0x4DB1D47C, 243 | 0xBBF9A495, 0x1D8EAF21, 0x2C66B5BC, 0x8A11BE08, 0x4FB68086, 0xE9C18B32, 0xD82991AF, 0x7E5E9A1B, 244 | 0xEFC8763C, 0x49BF7D88, 0x78576715, 0xDE206CA1, 0x1B87522F, 0xBDF0599B, 0x8C184306, 0x2A6F48B2, 245 | 0xDC27385B, 0x7A5033EF, 0x4BB82972, 0xEDCF22C6, 0x28681C48, 0x8E1F17FC, 0xBFF70D61, 0x198006D5, 246 | 0x47ABD36E, 0xE1DCD8DA, 0xD034C247, 0x7643C9F3, 0xB3E4F77D, 0x1593FCC9, 0x247BE654, 0x820CEDE0, 247 | 0x74449D09, 0xD23396BD, 0xE3DB8C20, 0x45AC8794, 0x800BB91A, 0x267CB2AE, 0x1794A833, 0xB1E3A387, 248 | 0x20754FA0, 0x86024414, 0xB7EA5E89, 0x119D553D, 0xD43A6BB3, 0x724D6007, 0x43A57A9A, 0xE5D2712E, 249 | 0x139A01C7, 0xB5ED0A73, 0x840510EE, 0x22721B5A, 0xE7D525D4, 0x41A22E60, 0x704A34FD, 0xD63D3F49, 250 | 0xCC1D9F8B, 0x6A6A943F, 0x5B828EA2, 0xFDF58516, 0x3852BB98, 0x9E25B02C, 0xAFCDAAB1, 0x09BAA105, 251 | 0xFFF2D1EC, 0x5985DA58, 0x686DC0C5, 0xCE1ACB71, 0x0BBDF5FF, 0xADCAFE4B, 0x9C22E4D6, 0x3A55EF62, 252 | 0xABC30345, 0x0DB408F1, 0x3C5C126C, 0x9A2B19D8, 0x5F8C2756, 0xF9FB2CE2, 0xC813367F, 0x6E643DCB, 253 | 0x982C4D22, 0x3E5B4696, 0x0FB35C0B, 0xA9C457BF, 0x6C636931, 0xCA146285, 0xFBFC7818, 0x5D8B73AC, 254 | 0x03A0A617, 0xA5D7ADA3, 0x943FB73E, 0x3248BC8A, 0xF7EF8204, 0x519889B0, 0x6070932D, 0xC6079899, 255 | 0x304FE870, 0x9638E3C4, 0xA7D0F959, 0x01A7F2ED, 0xC400CC63, 0x6277C7D7, 0x539FDD4A, 0xF5E8D6FE, 256 | 0x647E3AD9, 0xC209316D, 0xF3E12BF0, 0x55962044, 0x90311ECA, 0x3646157E, 0x07AE0FE3, 0xA1D90457, 257 | 0x579174BE, 0xF1E67F0A, 0xC00E6597, 0x66796E23, 0xA3DE50AD, 0x05A95B19, 0x34414184, 0x92364A30 258 | }; 259 | 260 | private uint[] CrcTableOffset88 = 261 | { 262 | 0x00000000, 0xCCAA009E, 0x4225077D, 0x8E8F07E3, 0x844A0EFA, 0x48E00E64, 0xC66F0987, 0x0AC50919, 263 | 0xD3E51BB5, 0x1F4F1B2B, 0x91C01CC8, 0x5D6A1C56, 0x57AF154F, 0x9B0515D1, 0x158A1232, 0xD92012AC, 264 | 0x7CBB312B, 0xB01131B5, 0x3E9E3656, 0xF23436C8, 0xF8F13FD1, 0x345B3F4F, 0xBAD438AC, 0x767E3832, 265 | 0xAF5E2A9E, 0x63F42A00, 0xED7B2DE3, 0x21D12D7D, 0x2B142464, 0xE7BE24FA, 0x69312319, 0xA59B2387, 266 | 0xF9766256, 0x35DC62C8, 0xBB53652B, 0x77F965B5, 0x7D3C6CAC, 0xB1966C32, 0x3F196BD1, 0xF3B36B4F, 267 | 0x2A9379E3, 0xE639797D, 0x68B67E9E, 0xA41C7E00, 0xAED97719, 0x62737787, 0xECFC7064, 0x205670FA, 268 | 0x85CD537D, 0x496753E3, 0xC7E85400, 0x0B42549E, 0x01875D87, 0xCD2D5D19, 0x43A25AFA, 0x8F085A64, 269 | 0x562848C8, 0x9A824856, 0x140D4FB5, 0xD8A74F2B, 0xD2624632, 0x1EC846AC, 0x9047414F, 0x5CED41D1, 270 | 0x299DC2ED, 0xE537C273, 0x6BB8C590, 0xA712C50E, 0xADD7CC17, 0x617DCC89, 0xEFF2CB6A, 0x2358CBF4, 271 | 0xFA78D958, 0x36D2D9C6, 0xB85DDE25, 0x74F7DEBB, 0x7E32D7A2, 0xB298D73C, 0x3C17D0DF, 0xF0BDD041, 272 | 0x5526F3C6, 0x998CF358, 0x1703F4BB, 0xDBA9F425, 0xD16CFD3C, 0x1DC6FDA2, 0x9349FA41, 0x5FE3FADF, 273 | 0x86C3E873, 0x4A69E8ED, 0xC4E6EF0E, 0x084CEF90, 0x0289E689, 0xCE23E617, 0x40ACE1F4, 0x8C06E16A, 274 | 0xD0EBA0BB, 0x1C41A025, 0x92CEA7C6, 0x5E64A758, 0x54A1AE41, 0x980BAEDF, 0x1684A93C, 0xDA2EA9A2, 275 | 0x030EBB0E, 0xCFA4BB90, 0x412BBC73, 0x8D81BCED, 0x8744B5F4, 0x4BEEB56A, 0xC561B289, 0x09CBB217, 276 | 0xAC509190, 0x60FA910E, 0xEE7596ED, 0x22DF9673, 0x281A9F6A, 0xE4B09FF4, 0x6A3F9817, 0xA6959889, 277 | 0x7FB58A25, 0xB31F8ABB, 0x3D908D58, 0xF13A8DC6, 0xFBFF84DF, 0x37558441, 0xB9DA83A2, 0x7570833C, 278 | 0x533B85DA, 0x9F918544, 0x111E82A7, 0xDDB48239, 0xD7718B20, 0x1BDB8BBE, 0x95548C5D, 0x59FE8CC3, 279 | 0x80DE9E6F, 0x4C749EF1, 0xC2FB9912, 0x0E51998C, 0x04949095, 0xC83E900B, 0x46B197E8, 0x8A1B9776, 280 | 0x2F80B4F1, 0xE32AB46F, 0x6DA5B38C, 0xA10FB312, 0xABCABA0B, 0x6760BA95, 0xE9EFBD76, 0x2545BDE8, 281 | 0xFC65AF44, 0x30CFAFDA, 0xBE40A839, 0x72EAA8A7, 0x782FA1BE, 0xB485A120, 0x3A0AA6C3, 0xF6A0A65D, 282 | 0xAA4DE78C, 0x66E7E712, 0xE868E0F1, 0x24C2E06F, 0x2E07E976, 0xE2ADE9E8, 0x6C22EE0B, 0xA088EE95, 283 | 0x79A8FC39, 0xB502FCA7, 0x3B8DFB44, 0xF727FBDA, 0xFDE2F2C3, 0x3148F25D, 0xBFC7F5BE, 0x736DF520, 284 | 0xD6F6D6A7, 0x1A5CD639, 0x94D3D1DA, 0x5879D144, 0x52BCD85D, 0x9E16D8C3, 0x1099DF20, 0xDC33DFBE, 285 | 0x0513CD12, 0xC9B9CD8C, 0x4736CA6F, 0x8B9CCAF1, 0x8159C3E8, 0x4DF3C376, 0xC37CC495, 0x0FD6C40B, 286 | 0x7AA64737, 0xB60C47A9, 0x3883404A, 0xF42940D4, 0xFEEC49CD, 0x32464953, 0xBCC94EB0, 0x70634E2E, 287 | 0xA9435C82, 0x65E95C1C, 0xEB665BFF, 0x27CC5B61, 0x2D095278, 0xE1A352E6, 0x6F2C5505, 0xA386559B, 288 | 0x061D761C, 0xCAB77682, 0x44387161, 0x889271FF, 0x825778E6, 0x4EFD7878, 0xC0727F9B, 0x0CD87F05, 289 | 0xD5F86DA9, 0x19526D37, 0x97DD6AD4, 0x5B776A4A, 0x51B26353, 0x9D1863CD, 0x1397642E, 0xDF3D64B0, 290 | 0x83D02561, 0x4F7A25FF, 0xC1F5221C, 0x0D5F2282, 0x079A2B9B, 0xCB302B05, 0x45BF2CE6, 0x89152C78, 291 | 0x50353ED4, 0x9C9F3E4A, 0x121039A9, 0xDEBA3937, 0xD47F302E, 0x18D530B0, 0x965A3753, 0x5AF037CD, 292 | 0xFF6B144A, 0x33C114D4, 0xBD4E1337, 0x71E413A9, 0x7B211AB0, 0xB78B1A2E, 0x39041DCD, 0xF5AE1D53, 293 | 0x2C8E0FFF, 0xE0240F61, 0x6EAB0882, 0xA201081C, 0xA8C40105, 0x646E019B, 0xEAE10678, 0x264B06E6 294 | }; 295 | #endregion 296 | public uint ComputeCRC(uint dwCRC, byte[] pv, uint cbLength) 297 | { 298 | uint i; 299 | uint dw2nd32; 300 | 301 | var cbRunningLength = ((cbLength < 4) ? 0 : ((cbLength)/8)*8); 302 | var cbEndUnalignedBytes = cbLength - cbRunningLength; 303 | var index = 0; 304 | for (i = 0; i < cbRunningLength/8; ++i) 305 | { 306 | dwCRC ^= BitConverter.ToUInt32(pv, index); 307 | dwCRC = CrcTableOffset88[dwCRC & 0x000000FF] ^ 308 | CrcTableOffset80[(dwCRC >> 8) & 0x000000FF] ^ 309 | CrcTableOffset72[(dwCRC >> 16) & 0x000000FF] ^ 310 | CrcTableOffset64[(dwCRC >> 24) & 0x000000FF]; 311 | index += 4; 312 | 313 | dw2nd32 = BitConverter.ToUInt32(pv, index); 314 | dwCRC = dwCRC ^ 315 | CrcTableOffset56[dw2nd32 & 0x000000FF] ^ 316 | CrcTableOffset48[(dw2nd32 >> 8) & 0x000000FF] ^ 317 | CrcTableOffset40[(dw2nd32 >> 16) & 0x000000FF] ^ 318 | CrcTableOffset32[(dw2nd32 >> 24) & 0x000000FF]; 319 | index += 4; 320 | } 321 | 322 | for (i = 0; i < cbEndUnalignedBytes; ++i) 323 | { 324 | dwCRC = CrcTableOffset32[(dwCRC ^ pv[index]) & 0x000000FF] ^ (dwCRC >> 8); 325 | index++; 326 | } 327 | 328 | return dwCRC; 329 | } 330 | } 331 | } -------------------------------------------------------------------------------- /PSTParse/Utilities/RtfDecompressor.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace PSTParse.Utilities 4 | { 5 | using System; 6 | using System.Collections; 7 | using System.IO; 8 | using System.Text; 9 | 10 | 11 | /// 12 | /// Copyright 2018 Dmitry Brant 13 | /// 14 | /// Documentation for Microsoft Compressed RTF: 15 | /// https://msdn.microsoft.com/en-us/library/cc463890(v=exchg.80).aspx 16 | /// 17 | public class RtfDecompressor 18 | { 19 | private const int DictionaryLength = 0x1000; 20 | private byte[] InitialDictionary; 21 | 22 | public RtfDecompressor() 23 | { 24 | var builder = new StringBuilder(); 25 | builder.Append(@"{\rtf1\ansi\mac\deff0\deftab720{\fonttbl;}{\f0\fnil \froman \fswiss \fmodern \fscript ") 26 | .Append(@"\fdecor MS Sans SerifSymbolArialTimes New RomanCourier{\colortbl\red0\green0\blue0") 27 | .Append("\r\n").Append(@"\par \pard\plain\f0\fs20\b\i\u\tab\tx"); 28 | InitialDictionary = Encoding.ASCII.GetBytes(builder.ToString()); 29 | } 30 | 31 | public string Decompress(Stream stream) 32 | { 33 | byte[] bytes = new byte[stream.Length]; 34 | stream.Read(bytes, 0, bytes.Length); 35 | return Decompress(bytes); 36 | } 37 | 38 | public string Decompress(byte[] bytes) 39 | { 40 | try 41 | { 42 | byte[] decompressed = Decompress(bytes, false); 43 | return Encoding.ASCII.GetString(decompressed); 44 | } 45 | catch (Exception) { } 46 | return ""; 47 | } 48 | 49 | public byte[] Decompress(byte[] data, bool enforceCrc) 50 | { 51 | RtfHeader header = new RtfHeader(data); 52 | 53 | if (header.compressionType.Equals("MELA")) 54 | { 55 | // uncompressed! 56 | byte[] outBytes = new byte[data.Length]; 57 | data.CopyTo(outBytes, 0); 58 | return outBytes; 59 | } 60 | else if (header.compressionType.Equals("LZFu")) 61 | { 62 | if (enforceCrc) 63 | { 64 | var headerCrc = CRC(data, RtfHeader.Length); 65 | if (headerCrc != header.crc) 66 | { 67 | throw new ArgumentException("Header CRC is incorrect."); 68 | } 69 | } 70 | 71 | var dictionary = new byte[DictionaryLength]; 72 | using (var destination = new MemoryStream(header.uncompressedSize)) 73 | { 74 | Array.Copy(InitialDictionary, 0, dictionary, 0, InitialDictionary.Length); 75 | var dictionaryPtr = InitialDictionary.Length; 76 | try 77 | { 78 | int bytePtr = RtfHeader.Length; 79 | while (bytePtr < data.Length) 80 | { 81 | var control = new RtfControl(data[bytePtr]); 82 | var offset = 1; 83 | 84 | for (int j = 0; j < control.flags.Length; j++) 85 | { 86 | if (control.flags[j]) 87 | { 88 | destination.WriteByte(data[bytePtr + offset]); 89 | dictionary[dictionaryPtr] = data[bytePtr + offset]; 90 | dictionaryPtr++; 91 | dictionaryPtr %= DictionaryLength; 92 | } 93 | else 94 | { 95 | var word = (((int)(data[bytePtr + offset])) << 8) | data[bytePtr + offset + 1]; 96 | var upper = (word & 0xFFF0) >> 4; 97 | var lower = (word & 0xF) + 2; 98 | 99 | if (upper == dictionaryPtr) 100 | { 101 | return destination.ToArray(); 102 | } 103 | 104 | for (int k = 0; k < lower; k++) 105 | { 106 | var correctedOffset = (upper + k); 107 | correctedOffset %= DictionaryLength; 108 | if (destination.Position == header.uncompressedSize) 109 | { 110 | return destination.ToArray(); 111 | } 112 | destination.WriteByte(dictionary[correctedOffset]); 113 | dictionary[dictionaryPtr] = dictionary[correctedOffset]; 114 | dictionaryPtr++; 115 | dictionaryPtr %= DictionaryLength; 116 | } 117 | offset++; 118 | } 119 | offset++; 120 | } 121 | bytePtr += control.length; 122 | } 123 | } 124 | catch (Exception) { } 125 | 126 | // return partial result in case of error 127 | return destination.ToArray(); 128 | } 129 | } 130 | throw new ApplicationException("Unrecognized compression type."); 131 | } 132 | 133 | private struct RtfHeader 134 | { 135 | public const int Length = 0x10; 136 | public int compressedSize; 137 | public int uncompressedSize; 138 | public string compressionType; 139 | public uint crc; 140 | 141 | public RtfHeader(byte[] bytes) 142 | { 143 | compressedSize = BitConverter.ToInt32(bytes, 0); 144 | uncompressedSize = BitConverter.ToInt32(bytes, 4); 145 | compressionType = Encoding.ASCII.GetString(bytes, 8, 4); 146 | crc = BitConverter.ToUInt32(bytes, 12); 147 | } 148 | } 149 | 150 | private struct RtfControl 151 | { 152 | public BitArray flags; 153 | public int length; 154 | 155 | public RtfControl(byte b) 156 | { 157 | flags = new BitArray(8); 158 | int zeros = 0; 159 | for (int i = 0; i < flags.Length; i++) 160 | { 161 | flags[i] = ((b & (0x1 << i)) == 0); 162 | zeros += flags[i] ? 1 : 0; 163 | } 164 | length = ((8 - zeros) * 2) + zeros + 1; 165 | } 166 | } 167 | 168 | private static uint CRC(byte[] buffer, int offset) 169 | { 170 | uint crc = 0; 171 | for (int i = offset; i < buffer.Length; i++) 172 | { 173 | crc = CRC32.CrcTableOffset32[(crc ^ buffer[i]) & 0xFF] ^ (crc >> 8); 174 | } 175 | return crc; 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /PSTParse/Utilities/Utilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace PSTParse.Utilities 5 | { 6 | public static class Utilities 7 | { 8 | public static T Lazy(ref T field, Func func) where T : class 9 | { 10 | return LazyInitializer.EnsureInitialized(ref field, func); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PSTParseApp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSTParse", "PSTParse\PSTParse.csproj", "{03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSTParseApp", "PSTParseApp\PSTParseApp.csproj", "{4DE372B6-A640-46FC-BE30-3E1C71E35469}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|x64.Build.0 = Debug|Any CPU 27 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Debug|x86.Build.0 = Debug|Any CPU 29 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|x64.ActiveCfg = Release|Any CPU 32 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|x64.Build.0 = Release|Any CPU 33 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|x86.ActiveCfg = Release|Any CPU 34 | {03A3E15E-CBB7-4DD7-99D2-CA21ABBF1C71}.Release|x86.Build.0 = Release|Any CPU 35 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|x64.ActiveCfg = Debug|Any CPU 38 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|x64.Build.0 = Debug|Any CPU 39 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Debug|x86.Build.0 = Debug|Any CPU 41 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|x64.ActiveCfg = Release|Any CPU 44 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|x64.Build.0 = Release|Any CPU 45 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|x86.ActiveCfg = Release|Any CPU 46 | {4DE372B6-A640-46FC-BE30-3E1C71E35469}.Release|x86.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /PSTParseApp/PSTParseApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Exe 9 | net60 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /PSTParseApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using PSTParse; 5 | using static System.Console; 6 | 7 | namespace PSTParseApp 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | var sw = new Stopwatch(); 14 | sw.Start(); 15 | // var pstPath = @"single_item.pst"; 16 | var pstPath = @"passwordprotectedPST.pst"; 17 | var fileInfo = new FileInfo(pstPath); 18 | var pstSizeGigabytes = ((double)fileInfo.Length / 1000 / 1000 / 1000).ToString("0.000"); 19 | using (var file = new PSTFile(pstPath)) 20 | { 21 | var isProtected = file.IsPasswordProtected(); 22 | Debug.Assert((ulong)fileInfo.Length == file.Size); 23 | //Console.WriteLine("Magic value: " + file.Header.DWMagic); 24 | //Console.WriteLine("Is Ansi? " + file.Header.IsANSI); 25 | 26 | var stack = new Stack(); 27 | stack.Push(file.TopOfPST); 28 | var totalCount = 0; 29 | //var maxSearchSize = 1500; 30 | var maxSearchSize = int.MaxValue; 31 | var totalEncryptedCount = 0; 32 | var totalUnsentCount = 0; 33 | var skippedFolders = new List(); 34 | while (stack.Count > 0) 35 | { 36 | var curFolder = stack.Pop(); 37 | 38 | foreach (var child in curFolder.SubFolders) 39 | { 40 | stack.Push(child); 41 | } 42 | var count = curFolder.Count; 43 | var line = $"{string.Join(" -> ", curFolder.Path)}({curFolder.ContainerClass}) ({count} messages)"; 44 | if (curFolder.Path.Count > 1 && curFolder.ContainerClass != "" && curFolder.ContainerClass != "IPF.Note") 45 | { 46 | skippedFolders.Add(line); 47 | continue; 48 | } 49 | WriteLine(line); 50 | 51 | var currentFolderCount = 0; 52 | foreach (var message in curFolder.GetIpmNotes()) 53 | //foreach (var ipmItem in curFolder.GetIpmItems()) 54 | { 55 | WriteLine(message.BodyPlainText); 56 | totalCount++; 57 | currentFolderCount++; 58 | 59 | //if (!(ipmItem is Message message)) 60 | //{ 61 | // nonMessageTypes++; 62 | // continue; 63 | //} 64 | if (message.Unsent) 65 | { 66 | totalUnsentCount++; 67 | continue; 68 | } 69 | 70 | //if (message.IsRMSEncrypted) 71 | //{ 72 | // totalEncryptedCount++; 73 | // //stack.Clear(); 74 | // //break; 75 | //} 76 | Debug.Assert(message.IsRMSEncryptedHeaders == message.IsRMSEncrypted, "encryption mismatch, big problems"); 77 | if (message.IsRMSEncryptedHeaders) 78 | { 79 | totalEncryptedCount++; 80 | //stack.Clear(); 81 | //break; 82 | } 83 | if (totalCount == maxSearchSize) 84 | { 85 | stack.Clear(); 86 | break; 87 | } 88 | 89 | //var recipients = message.Recipients; 90 | //if (!message.HasAttachments) continue; 91 | //foreach (var attachment in message.AttachmentHeaders) 92 | //{ 93 | 94 | //} 95 | } 96 | } 97 | sw.Stop(); 98 | var elapsedSeconds = (double)sw.ElapsedMilliseconds / 1000; 99 | WriteLine("{0} messages total", totalCount); 100 | WriteLine("{0} encrypted messages total", totalEncryptedCount); 101 | WriteLine("{0} totalUnsentCount", totalUnsentCount); 102 | WriteLine("Parsed {0} ({1} GB) in {2:0.00} seconds", Path.GetFileName(pstPath), pstSizeGigabytes, elapsedSeconds); 103 | 104 | WriteLine("\r\nSkipped Folders:\r\n"); 105 | foreach (var line in skippedFolders) 106 | { 107 | WriteLine(line); 108 | } 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PST-Parser 2 | 3 | A library for reading the [PST mailstore file format.](http://msdn.microsoft.com/en-us/library/ff385210(v=office.12).aspx) 4 | 5 | This library is intended to be as accurate, fast implementation of the PST mailstore file format specification. The original motivation for this project came from my experiences with other mailstore libraries that either 1) required Outlook to be installed in order to function or 2) were developed inconsistently by a third party. Such inconsistencies range from libraries that "missed" items and other libraries that failed when encountering errors. The intention of this project is to provide a basis to developers of applications that need to read and write to the PST format. 6 | 7 | ## PST Structure Overview 8 | 9 | The structure of the PST file format is divided into 3 layers: NDB layer, LTP layer, and the Messaging Layer. Each layer is implemented on top of the preceding layer. For example, the LTP layer may implement a heap which is stored on a node in the NDB layer. Each layer is divided into it's own namespace. The main entry point of parsing a PST is through the header. In the header, information about the format and encoding is stored. The first offsets for the NDB layer are contained Root structure in the header. 10 | 11 | The Node Database (NDB) layer layer consists of two [B-trees](http://en.wikipedia.org/wiki/Btree) : one for nodes and another for data blocks. Each B-tree implementation consists of intermediate blocks and leaf blocks. The node B-tree consists of nodes that reference block IDs (BIDs) and sub nodes. BIDs are used to traverse the data block B-tree to resolve to absolute offsets to data streams in the PST. Data stream themselves can be in one data block or stored in another BTree if the data stream is too large to fit in one page. XBLOCK and XXBLOCKs structures are used to store the B-trees that are used to store large data streams. 12 | 13 | The LTP layer provides the interface for the messaging layer to access properties and variable arrays of content. The base of the LTP layer is a heap which can be stored on a node (heap-on-node or HN). On the HN, yet another B-tree (B-tree-on-heap or BTH) is implemented and is used to store values on the HN using keys. The BTH (can be thought of just as a heap) is used to store Property Contexts (PCs) and Table Contexts (TCs). 14 | 15 | The messaging layer uses the LTP layer to represent folder hierarchies and the messages that exist in a give folder. 16 | 17 | ## Installation 18 | ```ps 19 | Install-Package PSTParse 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```c# 25 | using System.Collections.Generic; 26 | using PSTParse; 27 | 28 | namespace PSTParseApp 29 | { 30 | class Program 31 | { 32 | static void Main(string[] args) 33 | { 34 | var pstPath = @"path\to\file.pst"; 35 | using (var file = new PSTFile(pstPath)) 36 | { 37 | var stack = new Stack(); 38 | stack.Push(file.TopOfPST); 39 | while (stack.Count > 0) 40 | { 41 | var curFolder = stack.Pop(); 42 | foreach (var child in curFolder.SubFolders) 43 | { 44 | stack.Push(child); 45 | } 46 | 47 | foreach (var ipmItem in curFolder.GetIpmItems()) 48 | { 49 | // process item 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | ``` 57 | --------------------------------------------------------------------------------