HMX Forge (RB4/DCS/Amp16/FME) save game decryptor/encryptor
1/*
2 * EncryptedWriteRevisionStream.cs
3 *
4 * Copyright (c) 2015,2016,2017 maxton. All rights reserved.
5 * Copyright (c) 2025 Emma / InvoxiPlayGames
6 *
7 * 14-Oct-2025(Emma): Adapted the EncryptedWriteStream class to be
8 * more well-equipped for writing encrypted RevisionStreams.
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 3.0 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; If not, see
22 * <http://www.gnu.org/licenses/>.
23 */
24using System;
25using System.IO;
26
27namespace LibForge.Util
28{
29 public class EncryptedWriteRevisionStream : Stream
30 {
31 private long position;
32 private int key;
33 private int curKey;
34 private long keypos;
35 private Stream file;
36 public byte xor;
37
38
39 internal EncryptedWriteRevisionStream(Stream file, int key, bool ps3 = false)
40 {
41 file.Position = 0;
42 // write the RevisionStream header
43 file.WriteUInt8(0x7A);
44 file.WriteUInt32LE(0); // 0 = 0 regardless of endianness
45 if (ps3)
46 {
47 file.WriteUInt32BE((uint)key);
48 } else
49 {
50 file.WriteInt32LE(key);
51 }
52 position = 0;
53 keypos = 0;
54 // The initial key is found in the first 4 bytes.
55 this.key = cryptRound(key);
56 this.curKey = this.key;
57 this.file = file;
58 if (key < 0)
59 this.xor = 0xFF;
60 else
61 this.xor = 0x00;
62 }
63
64 public override bool CanRead => false;
65 public override bool CanSeek => true;
66 public override bool CanWrite => file.CanWrite;
67 public override long Length => file.Length - (4 + 5);
68
69 public override long Position
70 {
71 get
72 {
73 return position;
74 }
75
76 set
77 {
78 Seek(value, SeekOrigin.Begin);
79 }
80 }
81
82 private void updateKey()
83 {
84 if (keypos == position)
85 return;
86 if (keypos > position) // reset key
87 {
88 keypos = 0;
89 curKey = key;
90 }
91 while (keypos < position) // don't think there's a faster way to do this
92 {
93 curKey = cryptRound(curKey);
94 keypos++;
95 }
96 }
97
98 private int cryptRound(int key)
99 {
100 int ret = (key - ((key / 0x1F31D) * 0x1F31D)) * 0x41A7 - (key / 0x1F31D) * 0xB14;
101 if (ret <= 0)
102 ret += 0x7FFFFFFF;
103 return ret;
104 }
105
106 public override int Read(byte[] buffer, int offset, int count)
107 {
108 throw new NotImplementedException();
109 }
110
111 public override long Seek(long offset, SeekOrigin origin)
112 {
113 int adjust = origin == SeekOrigin.Current ? 0 : (4 + 5);
114 this.position = file.Seek(offset + adjust, origin) - (4 + 5);
115 updateKey();
116 return position;
117 }
118
119 public void FinishWriting()
120 {
121 file.WriteUInt8(0x7B);
122 }
123
124 #region Not Used
125
126 public override void Flush()
127 {
128 throw new NotSupportedException();
129 }
130
131 public override void SetLength(long value)
132 {
133 throw new NotSupportedException();
134 }
135
136 public override void Write(byte[] buffer, int offset, int count)
137 {
138 if (offset + count > buffer.Length)
139 {
140 throw new IndexOutOfRangeException("Attempt to read buffer past its end");
141 }
142 updateKey();
143 var copy = new byte[count];
144 Buffer.BlockCopy(buffer, offset, copy, 0, count);
145 for (uint i = 0; i < count; i++)
146 {
147 copy[i] ^= (byte)(this.curKey ^ xor);
148 position++;
149 updateKey();
150 }
151 // ensure file is at correct offset
152 file.Seek(this.position + (4 + 5) - count, SeekOrigin.Begin);
153 file.Write(copy, 0, count);
154 }
155
156 #endregion
157 }
158}