ff7tk  1.3.1.5
Work with Final Fantasy 7 game data
PsxCdromEdcEcc.h
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: 2026 DLPB
2 // SPDX-License-Identifier: LGPL-3.0-or-later
3 // CD-ROM raw-sector EDC/ECC helpers.
4 #pragma once
5 
6 #include <IsoArchive.h>
7 
8 #include <array>
9 #include <cstring>
10 
11 namespace ff7tk_psx_cd {
12 
13 constexpr int Mode1EdcOffset = 2064;
14 constexpr int Mode2Form1EdcOffset = 2072;
15 constexpr int Mode2Form2EdcOffset = 2348;
16 constexpr int EccPOffset = 2076;
17 constexpr int EccQOffset = 2248;
18 
19 inline const std::array<quint32, 256> &edcTable()
20 {
21  static const std::array<quint32, 256> table = []() {
22  std::array<quint32, 256> result{};
23  for (quint32 i = 0; i < 256; ++i) {
24  quint32 value = i;
25  for (int bit = 0; bit < 8; ++bit) {
26  value = (value >> 1) ^ ((value & 1U) ? 0xD8018001U : 0U);
27  }
28  result[i] = value;
29  }
30  return result;
31  }();
32  return table;
33 }
34 
35 inline quint32 computeEdc(const quint8 *data, qsizetype size)
36 {
37  const auto &table = edcTable();
38  quint32 edc = 0;
39  for (qsizetype i = 0; i < size; ++i) {
40  edc = (edc >> 8) ^ table[(edc ^ data[i]) & 0xffU];
41  }
42  return edc;
43 }
44 
45 struct EccTables {
46  std::array<quint8, 256> forward{};
47  std::array<quint8, 256> backward{};
48 
50  {
51  for (int i = 0; i < 256; ++i) {
52  const int doubled = (i << 1) ^ ((i & 0x80) ? 0x11d : 0);
53  forward[i] = quint8(doubled);
54  backward[i ^ doubled] = quint8(i);
55  }
56  }
57 };
58 
59 inline const EccTables &eccTables()
60 {
61  static const EccTables tables;
62  return tables;
63 }
64 
65 inline void computeEcc(const quint8 *source, quint32 majorCount, quint32 minorCount,
66  quint32 majorMultiplier, quint32 minorIncrement, quint8 *destination)
67 {
68  const auto &tables = eccTables();
69  const quint32 size = majorCount * minorCount;
70 
71  for (quint32 major = 0; major < majorCount; ++major) {
72  quint32 index = (major >> 1) * majorMultiplier + (major & 1U);
73  quint8 eccA = 0;
74  quint8 eccB = 0;
75 
76  for (quint32 minor = 0; minor < minorCount; ++minor) {
77  const quint8 value = source[index];
78  index += minorIncrement;
79  if (index >= size) {
80  index -= size;
81  }
82  eccA ^= value;
83  eccB ^= value;
84  eccA = tables.forward[eccA];
85  }
86 
87  eccA = tables.backward[tables.forward[eccA] ^ eccB];
88  destination[major] = eccA;
89  destination[major + majorCount] = eccA ^ eccB;
90  }
91 }
92 
93 inline void writeEdcLittleEndian(quint8 *destination, quint32 edc)
94 {
95  destination[0] = quint8(edc);
96  destination[1] = quint8(edc >> 8);
97  destination[2] = quint8(edc >> 16);
98  destination[3] = quint8(edc >> 24);
99 }
100 
101 inline bool hasCdRomSync(const quint8 *sector)
102 {
103  if (sector[0] != 0 || sector[11] != 0) {
104  return false;
105  }
106  for (int i = 1; i < 11; ++i) {
107  if (sector[i] != 0xff) {
108  return false;
109  }
110  }
111  return true;
112 }
113 
114 // Recompute integrity fields for a sector whose payload has actually changed.
115 // Untouched sectors are never passed through this function by the ff7tk changes.
116 inline bool updateSectorEdcEcc(QByteArray &sectorData)
117 {
118  if (sectorData.size() != SECTOR_SIZE) {
119  return false;
120  }
121 
122  auto *sector = reinterpret_cast<quint8 *>(sectorData.data());
123  if (!hasCdRomSync(sector)) {
124  return true; // Audio/non-data: nothing to regenerate.
125  }
126 
127  const quint8 mode = sector[15];
128  if (mode == 1) {
129  const quint32 edc = computeEdc(sector, Mode1EdcOffset);
130  writeEdcLittleEndian(sector + Mode1EdcOffset, edc);
131  std::memset(sector + Mode1EdcOffset + 4, 0, 8);
132  computeEcc(sector + 12, 86, 24, 2, 86, sector + EccPOffset);
133  computeEcc(sector + 12, 52, 43, 86, 88, sector + EccQOffset);
134  return true;
135  }
136 
137  if (mode != 2) {
138  return true;
139  }
140 
141  // XA repeats the four-byte subheader at 16..19 and 20..23.
142  if (std::memcmp(sector + 16, sector + 20, 4) != 0) {
143  return true; // Plain Mode 2, not XA Form 1/2.
144  }
145 
146  // XA Mode 2 Form 2: 2324 user bytes + EDC, no P/Q ECC.
147  if ((sector[18] & 0x20U) != 0) {
148  const quint32 edc = computeEdc(sector + 16, Mode2Form2EdcOffset - 16);
150  return true;
151  }
152 
153  // XA Mode 2 Form 1: EDC covers bytes 16..2071.
154  const quint32 edc = computeEdc(sector + 16, Mode2Form1EdcOffset - 16);
156 
157  // P/Q ECC is position-independent for XA Form 1: bytes 12..15 are
158  // treated as zero while parity is calculated.
159  quint8 addressAndMode[4];
160  std::memcpy(addressAndMode, sector + 12, sizeof(addressAndMode));
161  std::memset(sector + 12, 0, sizeof(addressAndMode));
162  computeEcc(sector + 12, 86, 24, 2, 86, sector + EccPOffset);
163  computeEcc(sector + 12, 52, 43, 86, 88, sector + EccQOffset);
164  std::memcpy(sector + 12, addressAndMode, sizeof(addressAndMode));
165 
166  return true;
167 }
168 
169 inline bool repairSector(IsoArchiveIO &io, quint32 num)
170 {
171  const qint64 previousPos = io.pos();
172  const qint64 sectorPos = qint64(num) * SECTOR_SIZE;
173 
174  if (!io.seek(sectorPos)) {
175  return false;
176  }
177 
178  QByteArray sectorData = io.read(SECTOR_SIZE);
179  bool ok = sectorData.size() == SECTOR_SIZE && updateSectorEdcEcc(sectorData);
180  if (ok) {
181  ok = io.seek(sectorPos) && io.write(sectorData) == SECTOR_SIZE;
182  }
183 
184  if (!io.seek(previousPos)) {
185  ok = false;
186  }
187  return ok;
188 }
189 
190 // Relocate an existing raw sector. For PS1 XA Mode 2 sectors the EDC/ECC does
191 // not depend on the MSF address, so preserve its original integrity bytes
192 // exactly (including any intentional anomaly). Mode 1 does include the address
193 // in EDC, so it must be regenerated after moving.
194 inline bool writeRelocatedSector(IsoArchiveIO &io, QByteArray sectorData)
195 {
196  if (sectorData.size() != SECTOR_SIZE || io.pos() % SECTOR_SIZE != 0) {
197  return false;
198  }
199 
200  auto *sector = reinterpret_cast<quint8 *>(sectorData.data());
201  if (hasCdRomSync(sector) && (sector[15] == 1 || sector[15] == 2)) {
202  const quint8 mode = sector[15];
203  const QByteArray address = IsoArchiveIO::int2Header(io.currentSector());
204  if (address.size() != 3) {
205  return false;
206  }
207  std::memcpy(sector + 12, address.constData(), 3);
208 
209  if (mode == 1 && !updateSectorEdcEcc(sectorData)) {
210  return false;
211  }
212  }
213 
214  return io.write(sectorData) == SECTOR_SIZE;
215 }
216 
217 } // namespace ff7tk_psx_cd
#define SECTOR_SIZE
Definition: IsoArchive.h:16
Definition: IsoArchive.h:200
static QByteArray int2Header(quint32 id)
Definition: IsoArchive.h:218
quint32 currentSector() const
Definition: IsoArchive.cpp:488
Definition: PsxCdromEdcEcc.h:11
bool updateSectorEdcEcc(QByteArray &sectorData)
Definition: PsxCdromEdcEcc.h:116
bool hasCdRomSync(const quint8 *sector)
Definition: PsxCdromEdcEcc.h:101
constexpr int Mode2Form1EdcOffset
Definition: PsxCdromEdcEcc.h:14
const std::array< quint32, 256 > & edcTable()
Definition: PsxCdromEdcEcc.h:19
void computeEcc(const quint8 *source, quint32 majorCount, quint32 minorCount, quint32 majorMultiplier, quint32 minorIncrement, quint8 *destination)
Definition: PsxCdromEdcEcc.h:65
bool repairSector(IsoArchiveIO &io, quint32 num)
Definition: PsxCdromEdcEcc.h:169
void writeEdcLittleEndian(quint8 *destination, quint32 edc)
Definition: PsxCdromEdcEcc.h:93
constexpr int EccPOffset
Definition: PsxCdromEdcEcc.h:16
constexpr int Mode2Form2EdcOffset
Definition: PsxCdromEdcEcc.h:15
const EccTables & eccTables()
Definition: PsxCdromEdcEcc.h:59
constexpr int Mode1EdcOffset
Definition: PsxCdromEdcEcc.h:13
quint32 computeEdc(const quint8 *data, qsizetype size)
Definition: PsxCdromEdcEcc.h:35
constexpr int EccQOffset
Definition: PsxCdromEdcEcc.h:17
bool writeRelocatedSector(IsoArchiveIO &io, QByteArray sectorData)
Definition: PsxCdromEdcEcc.h:194
Definition: PsxCdromEdcEcc.h:45
std::array< quint8, 256 > backward
Definition: PsxCdromEdcEcc.h:47
std::array< quint8, 256 > forward
Definition: PsxCdromEdcEcc.h:46
EccTables()
Definition: PsxCdromEdcEcc.h:49