Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
Loading...
Searching...
No Matches
savefiletoolset.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2019 Twilight
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15*/
16
23#include "savefiletoolset.h"
24#include "savefile.h"
25#include <pse-db/fontsdb.h>
26
28{
29 saveFile = newSaveFile;
30}
31
33{
34 var32 n = 0;
35 var32 m = 1;
36 for (var8 i = 0; i < bcd.size(); i += 1) {
37 n += (bcd[bcd.size() - 1 - i] & 0x0F) * m;
38 n += ((bcd[bcd.size() - 1 - i] >> 4) & 0x0F) * m * 10;
39 m *= 100;
40 }
41 return n;
42}
43
44QVector<var8> SaveFileToolset::int2bcd(var32 number, var8 size)
45{
46 var32 s = size; // Rough conversion from it's Javascript counterpart
47 QVector<var8> bcd = QVector<var8>(s);
48 bcd.fill(0);
49 while (number != 0 && s != 0) {
50 s -= 1;
51 bcd[s] = (number % 10);
52 number = (number / 10) | 0;
53 bcd[s] += (number % 10) << 4;
54 number = (number / 10) | 0;
55 }
56 return bcd;
57}
58
59QVector<var8> SaveFileToolset::getRange(var16 from, var16 size, bool reverse)
60{
61 QVector<var8> ret;
62 var8* data = saveFile->data;
63
64 if(!reverse)
65 for(var16 i = from; i < from + size; i++)
66 ret.append(data[i]);
67 else
68 for(var16 i = from + size - 1; i >= from; i--)
69 ret.append(data[i]);
70
71 return ret;
72}
73
74void SaveFileToolset::copyRange(var16 addr, var16 size, QVector<var8> newData, bool reverse)
75{
76 var8* data = saveFile->data;
77
78 if(!reverse)
79 for (var16 i = addr, j = 0;
80 j < newData.length() && i < (addr + size); i++, j++)
81 data[i] = newData[j];
82 else
83 for (var16 i = addr + size - 1, j = 0;
84 j < newData.length() && i >= addr; i--, j++)
85 data[i] = newData[j];
86}
87
88QString SaveFileToolset::getStr(var16 addr, var16 size, var8 maxLen)
89{
90 QVector<var8> raw = getRange(addr, size);
91 QVector<int> codes;
92 codes.reserve(raw.size());
93 for (var8 b : raw) codes.append(static_cast<int>(b));
94 return FontsDB::inst()->convertFromCode(codes, maxLen);
95}
96
97void SaveFileToolset::setStr(var16 addr, var16 size, var8 maxLen, QString str)
98{
99 QVector<int> strCode = FontsDB::inst()->convertToCode(str, maxLen);
100 QVector<var8> bytes;
101 bytes.reserve(strCode.size());
102 for (int v : strCode) bytes.append(static_cast<var8>(v));
103 copyRange(addr, size, bytes);
104}
105
106QString SaveFileToolset::getHex(var16 addr, var16 size, bool reverse)
107{
108 QVector<var8> rawHex = getRange(addr, size, reverse);
109
110 QString hexStr = "";
111 for (var16 i = 0; i < rawHex.length(); i++)
112 hexStr += QString::number(rawHex[i], 16);
113
114 hexStr = hexStr.toUpper();
115
116 // Fixes the issue where a 16-bit hex value of 1 should be 0x0001 not 0x1
117 // or a 16-bit val of 0xCAD should be 0x0CAD
118 // Properly sizes the end result
119 var8 endLen = size * 2; // A byte is represented by 2 hex characters
120 if(hexStr.length() < endLen)
121 hexStr = hexStr.rightJustified(endLen, '0');
122
123 return hexStr;
124}
125
126void SaveFileToolset::setHex(var16 addr, var16 size, QString hex, bool reverse)
127{
128 // Convert to number
129 var16 hexValue = hex.toInt(nullptr, 16);
130 var16 hexValueOrig = hexValue;
131 QVector<var8> hexArr;
132
133 // Break apart number into seperate bytes and store them in an
134 // array. This also places it big-endian style which is how the
135 // save data is structured
136
137 if (hexValue == 0)
138 hexArr.append(0);
139 else
140 while (hexValue > 0) {
141 hexArr.append(hexValue & 0xFF);
142 hexValue >>= 8;
143 }
144
145 // Account for bug where 16-bit value under 0xFF still needs to take up
146 // 16-bits
147 if (hexValueOrig <= 0xFF && size == 2)
148 hexArr.append(0x00);
149
150 // Copy to save data
151 copyRange(addr, size, hexArr, !reverse);
152}
153
155{
156 return bcd2int(getRange(addr, size));
157}
158
160{
161 copyRange(addr, size, int2bcd(val, size));
162}
163
164bool SaveFileToolset::getBit(var16 addr, var8 size, var8 bit, bool reverse)
165{
166 // Get bytes first
167 QVector<var8> value = getRange(addr, size, reverse);
168
169 // Merge together into a single data type
170 // This method cannot test larger than this which is 32-bits or 4-bytes
171 var32 res = value.at(0);
172
173 if (size > 0)
174 for (var8 i = 1; i < size; i++) {
175 res <<= 8;
176 res |= value[i];
177 }
178
179 // Do the test
180 return (res & (1 << bit)) != 0;
181}
182
183void SaveFileToolset::setBit(var16 addr, var8 size, var8 bit, bool value, bool reverse)
184{
185 // Get bytes first
186 QVector<var8> val = getRange(addr, size, reverse);
187
188 // Merge together into a single data type
189 // This method cannot test larger than this which is 32-bits or 4-bytes
190 var32 res = val.at(0);
191
192 if (size > 0)
193 for (var8 i = 1; i < size; i++) {
194 res <<= 8;
195 res |= val[i];
196 }
197
198 // Set
199 if(value)
200 res |= (1 << bit);
201 else
202 res &= ~(1 << bit);
203
204 // Convert to hex and do a hex insert
205 QString hex = QString::number(res, 16);
206 setHex(addr, size, hex, reverse);
207}
208
210{
211 auto value = getRange(addr, 2, reverse);
212
213 var16 res = value[0];
214 res <<= 8;
215 res |= value[1];
216
217 return res;
218}
219
220void SaveFileToolset::setWord(var16 addr, var16 val, bool reverse)
221{
222 var8 byteL = val & 0x00FF;
223 var8 byteH = (val & 0xFF00) >> 8;
224
225 QVector<var8> tmp;
226 tmp.append(byteH);
227 tmp.append(byteL);
228
229 copyRange(addr, 2, tmp, reverse);
230}
231
233{
234 return saveFile->data[addr];
235}
236
238{
239 saveFile->data[addr] = val;
240}
241
242QVector<bool> SaveFileToolset::getBitField(var16 addr, var16 size)
243{
244 QVector<bool> ret;
245
246 // Basically extract all the bits in a bitfield of a given size and dump them
247 // in the vector
248 for(var16 i = 0; i < size; i++) {
249 ret.append(getBit(addr, 1, 0));
250 ret.append(getBit(addr, 1, 1));
251 ret.append(getBit(addr, 1, 2));
252 ret.append(getBit(addr, 1, 3));
253 ret.append(getBit(addr, 1, 4));
254 ret.append(getBit(addr, 1, 5));
255 ret.append(getBit(addr, 1, 6));
256 ret.append(getBit(addr, 1, 7));
257
258 addr++;
259 }
260
261 // Return that
262 return ret;
263}
264
265void SaveFileToolset::setBitField(var16 addr, var16 size, QVector<bool> src)
266{
267 // The opposite
268 // Dump all the bits one-by-one from the bit field into a given area
269 // We perform extra checks to make sure we're not extending past the given
270 // src array which is more complicated when working with a bit-field
271 for(var16 i = 0; i < size * 8 && i < src.size(); i +=8 ) {
272 if((i + 0) < src.size())
273 setBit(addr, 1, 0, src.at(i + 0));
274 if((i + 1) < src.size())
275 setBit(addr, 1, 1, src.at(i + 1));
276 if((i + 2) < src.size())
277 setBit(addr, 1, 2, src.at(i + 2));
278 if((i + 3) < src.size())
279 setBit(addr, 1, 3, src.at(i + 3));
280 if((i + 4) < src.size())
281 setBit(addr, 1, 4, src.at(i + 4));
282 if((i + 5) < src.size())
283 setBit(addr, 1, 5, src.at(i + 5));
284 if((i + 6) < src.size())
285 setBit(addr, 1, 6, src.at(i + 6));
286 if((i + 7) < src.size())
287 setBit(addr, 1, 7, src.at(i + 7));
288
289 addr++;
290 }
291}
292
294{
295 // Get the bytes to checksum
296 auto toChecksum = getRange(addr, size);
297
298 // This is the checksum to return, it's always 8-bit and uses a more efficient
299 // algorithm which is to start at 255. The actual game uses a less efficient
300 // version that starts at 0 and requires some more steps.
301 var8 checksum = 0xFF;
302
303 // Gen 1 games were super simple especially with the improved algorithm
304 // Just subtract from 255 each byte in order, underflowing back around to 255
305 // and onward til you're done and then you have the checksum gen 1 games will
306 // accept
307 for (var16 i = 0; i < toChecksum.length(); i++) {
308 checksum -= toChecksum.at(i);
309 }
310
311 // Return that checksum
312 return checksum;
313}
314
316{
317 // Bank 2 individual checksums for Boxes 1-6
318 QVector<var8> bank2IndvChecksums;
319 bank2IndvChecksums.append(getChecksum(0x4000, 0x462));
320 bank2IndvChecksums.append(getChecksum(0x4462, 0x462));
321 bank2IndvChecksums.append(getChecksum(0x48C4, 0x462));
322 bank2IndvChecksums.append(getChecksum(0x4D26, 0x462));
323 bank2IndvChecksums.append(getChecksum(0x5188, 0x462));
324 bank2IndvChecksums.append(getChecksum(0x55EA, 0x462));
325
326 // Apply the individual checksums for boxes 1-6 in bank 2
327 copyRange(0x5A4D, 0x6, bank2IndvChecksums);
328
329 // Apply the checksum that covers all of bank 2, excluding individual
330 // checksums
331 saveFile->data[0x5A4C] = getChecksum(0x4000, 0x1A4C);
332
333 // Bank 3 Checksums for Boxes 7-12
334 // Same as bank 2, apply values and such the same way
335 // Bank 2 and 3 are 100% identical in every way just duplicates for more
336 // storage
337 QVector<var8> bank3IndvChecksums;
338 bank2IndvChecksums.append(getChecksum(0x6000, 0x462));
339 bank2IndvChecksums.append(getChecksum(0x6462, 0x462));
340 bank2IndvChecksums.append(getChecksum(0x68C4, 0x462));
341 bank2IndvChecksums.append(getChecksum(0x6D26, 0x462));
342 bank2IndvChecksums.append(getChecksum(0x7188, 0x462));
343 bank2IndvChecksums.append(getChecksum(0x75EA, 0x462));
344
345 copyRange(0x7A4D, 0x6, bank3IndvChecksums);
346
347 // Bank 3 Checksum
348 saveFile->data[0x7A4C] = getChecksum(0x6000, 0x1A4C);
349}
350
352{
353 // NOTE: `force` is currently unused -- the box-checksum block below self-gates on
354 // whether the game has formatted the boxes yet. Kept in the signature (callers
355 // pass it) and flagged for review in notes/plans/testing.md. (clang-tidy
356 // misc-unused-parameters.)
357 // Apply Bank 1 Checksum
358 saveFile->data[0x3523] = getChecksum(0x2598, 0xF8B);
359
360 // Has the player even switched boxes before?
361 // If not, then the game hasn't even formatted them yet and there's no need
362 // to calculate checksums for them.
363 if(saveFile->data[0x284C] == 0)
364 return;
365
366 // Apply Bank 2 Checksum.
367 // The bank-2 all-boxes checksum lives at 0x5A4C and covers 0x4000..0x5A4B
368 // (size 0x1A4C = 6 boxes x 0x462). This matches recalcBoxesChecksums() above,
369 // the saves/structure.bt map, and real RBY (sBank2AllBoxesChecksum).
370 // (Was 0x5A4B / 0x1A4B -- off by one, which clobbered Box 6's last data byte
371 // and stored the checksum one byte early; found by tst_roundtrip 2026-06-07.)
372 saveFile->data[0x5A4C] = getChecksum(0x4000, 0x1A4C);
373}
static FontsDB * inst()
< Number of font glyphs.
Definition fontsdb.cpp:367
const QVector< int > convertToCode(QString str, int maxLen=11, const bool autoEnd=true) const
String -> font codes (see note; expensive).
Definition fontsdb.cpp:109
const QString convertFromCode(const QVector< int > codes, const int maxLen=11) const
Font codes -> string (fast).
Definition fontsdb.cpp:174
void copyRange(var16 addr, var16 size, QVector< var8 > data, bool reverse=false)
Copies data into the save at a particular place, going no further than the maximum size desired and/o...
QString getHex(var16 addr, var16 size, bool reverse=false)
Gets a value in hex from the save file.
QVector< var8 > int2bcd(var32 number, var8 size=2)
number2bcd -> takes a number and returns the corresponding BCD in an array input: 16 bit positive num...
SaveFile * saveFile
Owning save; its raw data is what every method here reads/writes.
void setHex(var16 addr, var16 size, QString hex, bool reverse=false)
Saves a hex value to bytes in the save file.
void setWord(var16 addr, var16 val, bool reverse=false)
Set a 16-bit value. 0x12,0x34 <=> 0x1234. If you want the reverse then reverse it.
void recalcChecksums(bool force=false)
Recalculates all checksums in all banks, following the strict rule of only calculating what's needed ...
var32 bcd2int(QVector< var8 > val)
bcd2number -> takes an array with a BCD and returns the corresponding number.
QString getStr(var16 addr, var16 size, var8 maxLen)
Gets a string from the sav file, converted from in-game font encoding to UTF-8 for easy reading and m...
QVector< var8 > getRange(var16 from, var16 size, bool reverse=false)
Copies a range of bytes to a buffer of size and returns them.
bool getBit(var16 addr, var8 size, var8 bit, bool reverse=false)
Get a bit from a value.
void setBitField(var16 addr, var16 size, QVector< bool > src)
Sets an entire bitfield from a vector of bools.
void setStr(var16 addr, var16 size, var8 maxLen, QString str)
Sets a string to the sav file, converted from UTF-8 to in-game font encoding.
void recalcBoxesChecksums()
Recalculates the checksum for all the boxes.
QVector< bool > getBitField(var16 addr, var16 size)
Gets an entire bitfield as a vector of bools.
var16 getWord(var16 addr, bool reverse=false)
Get a 16-bit value. 0x12,0x34 <=> 0x1234. If you want the reverse then reverse it.
SaveFileToolset(SaveFile *newSaveFile)
void setByte(var16 addr, var8 val)
Simply sets a byte.
var8 getChecksum(var16 addr, var16 size)
Gets a single checksum from a given range.
void setBit(var16 addr, var8 size, var8 bit, bool value, bool reverse=false)
Set a bit into a value.
var8 getByte(var16 addr)
Simply gets a byte.
void setBCD(var16 addr, var8 size, var32 val)
Set a raw BCD val from a given number.
var32 getBCD(var16 addr, var8 size)
Get a raw BCD value as a number.
One loaded save: the raw 32 KB bytes, their expanded object tree, and the tools that move between the...
Definition savefile.h:46
var8e var8
Everyday 8-bit alias. Exact (not "fastest") to dodge the pointer-width bug noted above.
Definition types.h:124
var16e var16
Everyday 16-bit alias. Exact width to avoid the "fastest" widening bug.
Definition types.h:125
var32e var32
Everyday 32-bit alias. Exact width to avoid the "fastest" widening bug.
Definition types.h:126