The full list of source changes is below.
chdman and the parts of MAME it uses are licensed under the BSD 3-Clause license (copyright Aaron Giles, R. Belmont, Vas Crabb and the MAMEdev team). MAME as a whole is GPL-2.0-or-later. Bundled libraries: zlib (zlib license, © Jean-loup Gailly and Mark Adler), LZMA SDK (public domain, Igor Pavlov), FLAC (BSD 3-Clause, © Josh Coalson, Xiph.Org Foundation), Zstandard (BSD, © Meta Platforms, Inc. and affiliates), MD5 (public domain, Colin Plumb), the FLAC encoder's cosf and log from Arm's optimized-routines (MIT, © Arm Limited), libdeflate's compressor (MIT, © Eric Biggers and Google LLC). Discpress is not affiliated with the MAME team.
Source changes made to MAME 0.289, and the new files
--- a/3rdparty/flac/src/libFLAC/fixed.c
+++ b/3rdparty/flac/src/libFLAC/fixed.c
@@ -41,6 +41,7 @@
#include "private/fixed.h"
#include "private/macros.h"
#include "FLAC/assert.h"
+#include "flac_libm.h" /* Discpress: the same log in every build (engine/libm) */
#ifdef local_abs
#undef local_abs
@@ -281,11 +282,11 @@
FLAC__ASSERT(data_len > 0 || total_error_3 == 0);
FLAC__ASSERT(data_len > 0 || total_error_4 == 0);
#ifndef FLAC__INTEGER_ONLY_LIBRARY
- residual_bits_per_sample[0] = (float)((total_error_0 > 0) ? log(M_LN2 * (double)total_error_0 / (double)data_len) / M_LN2 : 0.0);
- residual_bits_per_sample[1] = (float)((total_error_1 > 0) ? log(M_LN2 * (double)total_error_1 / (double)data_len) / M_LN2 : 0.0);
- residual_bits_per_sample[2] = (float)((total_error_2 > 0) ? log(M_LN2 * (double)total_error_2 / (double)data_len) / M_LN2 : 0.0);
- residual_bits_per_sample[3] = (float)((total_error_3 > 0) ? log(M_LN2 * (double)total_error_3 / (double)data_len) / M_LN2 : 0.0);
- residual_bits_per_sample[4] = (float)((total_error_4 > 0) ? log(M_LN2 * (double)total_error_4 / (double)data_len) / M_LN2 : 0.0);
+ residual_bits_per_sample[0] = (float)((total_error_0 > 0) ? discpress_log(M_LN2 * (double)total_error_0 / (double)data_len) / M_LN2 : 0.0);
+ residual_bits_per_sample[1] = (float)((total_error_1 > 0) ? discpress_log(M_LN2 * (double)total_error_1 / (double)data_len) / M_LN2 : 0.0);
+ residual_bits_per_sample[2] = (float)((total_error_2 > 0) ? discpress_log(M_LN2 * (double)total_error_2 / (double)data_len) / M_LN2 : 0.0);
+ residual_bits_per_sample[3] = (float)((total_error_3 > 0) ? discpress_log(M_LN2 * (double)total_error_3 / (double)data_len) / M_LN2 : 0.0);
+ residual_bits_per_sample[4] = (float)((total_error_4 > 0) ? discpress_log(M_LN2 * (double)total_error_4 / (double)data_len) / M_LN2 : 0.0);
#else
residual_bits_per_sample[0] = (total_error_0 > 0) ? local__compute_rbps_integerized(total_error_0, data_len) : 0;
residual_bits_per_sample[1] = (total_error_1 > 0) ? local__compute_rbps_integerized(total_error_1, data_len) : 0;
@@ -336,11 +337,11 @@
FLAC__ASSERT(data_len > 0 || total_error_3 == 0);
FLAC__ASSERT(data_len > 0 || total_error_4 == 0);
#ifndef FLAC__INTEGER_ONLY_LIBRARY
- residual_bits_per_sample[0] = (float)((total_error_0 > 0) ? log(M_LN2 * (double)total_error_0 / (double)data_len) / M_LN2 : 0.0);
- residual_bits_per_sample[1] = (float)((total_error_1 > 0) ? log(M_LN2 * (double)total_error_1 / (double)data_len) / M_LN2 : 0.0);
- residual_bits_per_sample[2] = (float)((total_error_2 > 0) ? log(M_LN2 * (double)total_error_2 / (double)data_len) / M_LN2 : 0.0);
- residual_bits_per_sample[3] = (float)((total_error_3 > 0) ? log(M_LN2 * (double)total_error_3 / (double)data_len) / M_LN2 : 0.0);
- residual_bits_per_sample[4] = (float)((total_error_4 > 0) ? log(M_LN2 * (double)total_error_4 / (double)data_len) / M_LN2 : 0.0);
+ residual_bits_per_sample[0] = (float)((total_error_0 > 0) ? discpress_log(M_LN2 * (double)total_error_0 / (double)data_len) / M_LN2 : 0.0);
+ residual_bits_per_sample[1] = (float)((total_error_1 > 0) ? discpress_log(M_LN2 * (double)total_error_1 / (double)data_len) / M_LN2 : 0.0);
+ residual_bits_per_sample[2] = (float)((total_error_2 > 0) ? discpress_log(M_LN2 * (double)total_error_2 / (double)data_len) / M_LN2 : 0.0);
+ residual_bits_per_sample[3] = (float)((total_error_3 > 0) ? discpress_log(M_LN2 * (double)total_error_3 / (double)data_len) / M_LN2 : 0.0);
+ residual_bits_per_sample[4] = (float)((total_error_4 > 0) ? discpress_log(M_LN2 * (double)total_error_4 / (double)data_len) / M_LN2 : 0.0);
#else
residual_bits_per_sample[0] = (total_error_0 > 0) ? local__compute_rbps_wide_integerized(total_error_0, data_len) : 0;
residual_bits_per_sample[1] = (total_error_1 > 0) ? local__compute_rbps_wide_integerized(total_error_1, data_len) : 0;
@@ -357,7 +358,7 @@
if(order_##macro_order##_is_valid && total_error_##macro_order < smallest_error) { \
order = macro_order; \
smallest_error = total_error_##macro_order ; \
- residual_bits_per_sample[ macro_order ] = (float)((total_error_0 > 0) ? log(M_LN2 * (double)total_error_0 / (double)data_len) / M_LN2 : 0.0); \
+ residual_bits_per_sample[ macro_order ] = (float)((total_error_0 > 0) ? discpress_log(M_LN2 * (double)total_error_0 / (double)data_len) / M_LN2 : 0.0); \
} \
else \
residual_bits_per_sample[ macro_order ] = 34.0f;
@@ -571,6 +572,9 @@
void FLAC__fixed_restore_signal(const FLAC__int32 residual[], uint32_t data_len, uint32_t order, FLAC__int32 data[])
{
int i, idata_len = (int)data_len;
+ /* Discpress: in unsigned arithmetic, so damaged data wraps as two's complement instead of overflowing
+ (undefined), as in lpc.c */
+#define U(x) ((FLAC__uint32)(x))
switch(order) {
case 0:
@@ -579,23 +583,24 @@
break;
case 1:
for(i = 0; i < idata_len; i++)
- data[i] = residual[i] + data[i-1];
+ data[i] = (FLAC__int32)(U(residual[i]) + U(data[i-1]));
break;
case 2:
for(i = 0; i < idata_len; i++)
- data[i] = residual[i] + 2*data[i-1] - data[i-2];
+ data[i] = (FLAC__int32)(U(residual[i]) + 2*U(data[i-1]) - U(data[i-2]));
break;
case 3:
for(i = 0; i < idata_len; i++)
- data[i] = residual[i] + 3*data[i-1] - 3*data[i-2] + data[i-3];
+ data[i] = (FLAC__int32)(U(residual[i]) + 3*U(data[i-1]) - 3*U(data[i-2]) + U(data[i-3]));
break;
case 4:
for(i = 0; i < idata_len; i++)
- data[i] = residual[i] + 4*data[i-1] - 6*data[i-2] + 4*data[i-3] - data[i-4];
+ data[i] = (FLAC__int32)(U(residual[i]) + 4*U(data[i-1]) - 6*U(data[i-2]) + 4*U(data[i-3]) - U(data[i-4]));
break;
default:
FLAC__ASSERT(0);
}
+#undef U
}
void FLAC__fixed_restore_signal_wide(const FLAC__int32 residual[], uint32_t data_len, uint32_t order, FLAC__int32 data[])
--- a/3rdparty/flac/src/libFLAC/lpc.c
+++ b/3rdparty/flac/src/libFLAC/lpc.c
@@ -43,6 +43,7 @@
#include "private/bitmath.h"
#include "private/lpc.h"
#include "private/macros.h"
+#include "flac_libm.h" /* Discpress: the same log in every build (engine/libm) */
#if !defined(NDEBUG) || defined FLAC__OVERFLOW_DETECT || defined FLAC__OVERFLOW_DETECT_VERBOSE
#include <stdio.h>
@@ -977,7 +978,7 @@
{
FLAC__int64 sumo;
uint32_t i, j;
- FLAC__int32 sum;
+ FLAC__uint32 sum; /* Discpress: unsigned, so damaged data wraps as two's complement instead of overflowing (undefined) */
const FLAC__int32 *r = residual, *history;
#ifdef FLAC__OVERFLOW_DETECT_VERBOSE
@@ -993,14 +994,14 @@
sum = 0;
history = data;
for(j = 0; j < order; j++) {
- sum += qlp_coeff[j] * (*(--history));
+ sum += (FLAC__uint32)qlp_coeff[j] * (FLAC__uint32)(*(--history));
sumo += (FLAC__int64)qlp_coeff[j] * (FLAC__int64)(*history);
#ifdef FLAC__OVERFLOW_DETECT
if(sumo > 2147483647ll || sumo < -2147483648ll)
fprintf(stderr,"FLAC__lpc_restore_signal: OVERFLOW, i=%u, j=%u, c=%d, d=%d, sumo=%" PRId64 "\n",i,j,qlp_coeff[j],*history,sumo);
#endif
}
- *(data++) = *(r++) + (sum >> lp_quantization);
+ *(data++) = (FLAC__int32)((FLAC__uint32)*(r++) + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
/* Here's a slower but clearer version:
@@ -1015,7 +1016,7 @@
#else /* fully unrolled version for normal use */
{
int i;
- FLAC__int32 sum;
+ FLAC__uint32 sum; /* Discpress: unsigned, so damaged data wraps as two's complement instead of overflowing (undefined) */
FLAC__ASSERT(order > 0);
FLAC__ASSERT(order <= 32);
@@ -1031,36 +1032,36 @@
if(order == 12) {
for(i = 0; i < (int)data_len; i++) {
sum = 0;
- sum += qlp_coeff[11] * data[i-12];
- sum += qlp_coeff[10] * data[i-11];
- sum += qlp_coeff[9] * data[i-10];
- sum += qlp_coeff[8] * data[i-9];
- sum += qlp_coeff[7] * data[i-8];
- sum += qlp_coeff[6] * data[i-7];
- sum += qlp_coeff[5] * data[i-6];
- sum += qlp_coeff[4] * data[i-5];
- sum += qlp_coeff[3] * data[i-4];
- sum += qlp_coeff[2] * data[i-3];
- sum += qlp_coeff[1] * data[i-2];
- sum += qlp_coeff[0] * data[i-1];
- data[i] = residual[i] + (sum >> lp_quantization);
+ sum += (FLAC__uint32)qlp_coeff[11] * (FLAC__uint32)data[i-12];
+ sum += (FLAC__uint32)qlp_coeff[10] * (FLAC__uint32)data[i-11];
+ sum += (FLAC__uint32)qlp_coeff[9] * (FLAC__uint32)data[i-10];
+ sum += (FLAC__uint32)qlp_coeff[8] * (FLAC__uint32)data[i-9];
+ sum += (FLAC__uint32)qlp_coeff[7] * (FLAC__uint32)data[i-8];
+ sum += (FLAC__uint32)qlp_coeff[6] * (FLAC__uint32)data[i-7];
+ sum += (FLAC__uint32)qlp_coeff[5] * (FLAC__uint32)data[i-6];
+ sum += (FLAC__uint32)qlp_coeff[4] * (FLAC__uint32)data[i-5];
+ sum += (FLAC__uint32)qlp_coeff[3] * (FLAC__uint32)data[i-4];
+ sum += (FLAC__uint32)qlp_coeff[2] * (FLAC__uint32)data[i-3];
+ sum += (FLAC__uint32)qlp_coeff[1] * (FLAC__uint32)data[i-2];
+ sum += (FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1];
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
else { /* order == 11 */
for(i = 0; i < (int)data_len; i++) {
sum = 0;
- sum += qlp_coeff[10] * data[i-11];
- sum += qlp_coeff[9] * data[i-10];
- sum += qlp_coeff[8] * data[i-9];
- sum += qlp_coeff[7] * data[i-8];
- sum += qlp_coeff[6] * data[i-7];
- sum += qlp_coeff[5] * data[i-6];
- sum += qlp_coeff[4] * data[i-5];
- sum += qlp_coeff[3] * data[i-4];
- sum += qlp_coeff[2] * data[i-3];
- sum += qlp_coeff[1] * data[i-2];
- sum += qlp_coeff[0] * data[i-1];
- data[i] = residual[i] + (sum >> lp_quantization);
+ sum += (FLAC__uint32)qlp_coeff[10] * (FLAC__uint32)data[i-11];
+ sum += (FLAC__uint32)qlp_coeff[9] * (FLAC__uint32)data[i-10];
+ sum += (FLAC__uint32)qlp_coeff[8] * (FLAC__uint32)data[i-9];
+ sum += (FLAC__uint32)qlp_coeff[7] * (FLAC__uint32)data[i-8];
+ sum += (FLAC__uint32)qlp_coeff[6] * (FLAC__uint32)data[i-7];
+ sum += (FLAC__uint32)qlp_coeff[5] * (FLAC__uint32)data[i-6];
+ sum += (FLAC__uint32)qlp_coeff[4] * (FLAC__uint32)data[i-5];
+ sum += (FLAC__uint32)qlp_coeff[3] * (FLAC__uint32)data[i-4];
+ sum += (FLAC__uint32)qlp_coeff[2] * (FLAC__uint32)data[i-3];
+ sum += (FLAC__uint32)qlp_coeff[1] * (FLAC__uint32)data[i-2];
+ sum += (FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1];
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
}
@@ -1068,32 +1069,32 @@
if(order == 10) {
for(i = 0; i < (int)data_len; i++) {
sum = 0;
- sum += qlp_coeff[9] * data[i-10];
- sum += qlp_coeff[8] * data[i-9];
- sum += qlp_coeff[7] * data[i-8];
- sum += qlp_coeff[6] * data[i-7];
- sum += qlp_coeff[5] * data[i-6];
- sum += qlp_coeff[4] * data[i-5];
- sum += qlp_coeff[3] * data[i-4];
- sum += qlp_coeff[2] * data[i-3];
- sum += qlp_coeff[1] * data[i-2];
- sum += qlp_coeff[0] * data[i-1];
- data[i] = residual[i] + (sum >> lp_quantization);
+ sum += (FLAC__uint32)qlp_coeff[9] * (FLAC__uint32)data[i-10];
+ sum += (FLAC__uint32)qlp_coeff[8] * (FLAC__uint32)data[i-9];
+ sum += (FLAC__uint32)qlp_coeff[7] * (FLAC__uint32)data[i-8];
+ sum += (FLAC__uint32)qlp_coeff[6] * (FLAC__uint32)data[i-7];
+ sum += (FLAC__uint32)qlp_coeff[5] * (FLAC__uint32)data[i-6];
+ sum += (FLAC__uint32)qlp_coeff[4] * (FLAC__uint32)data[i-5];
+ sum += (FLAC__uint32)qlp_coeff[3] * (FLAC__uint32)data[i-4];
+ sum += (FLAC__uint32)qlp_coeff[2] * (FLAC__uint32)data[i-3];
+ sum += (FLAC__uint32)qlp_coeff[1] * (FLAC__uint32)data[i-2];
+ sum += (FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1];
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
else { /* order == 9 */
for(i = 0; i < (int)data_len; i++) {
sum = 0;
- sum += qlp_coeff[8] * data[i-9];
- sum += qlp_coeff[7] * data[i-8];
- sum += qlp_coeff[6] * data[i-7];
- sum += qlp_coeff[5] * data[i-6];
- sum += qlp_coeff[4] * data[i-5];
- sum += qlp_coeff[3] * data[i-4];
- sum += qlp_coeff[2] * data[i-3];
- sum += qlp_coeff[1] * data[i-2];
- sum += qlp_coeff[0] * data[i-1];
- data[i] = residual[i] + (sum >> lp_quantization);
+ sum += (FLAC__uint32)qlp_coeff[8] * (FLAC__uint32)data[i-9];
+ sum += (FLAC__uint32)qlp_coeff[7] * (FLAC__uint32)data[i-8];
+ sum += (FLAC__uint32)qlp_coeff[6] * (FLAC__uint32)data[i-7];
+ sum += (FLAC__uint32)qlp_coeff[5] * (FLAC__uint32)data[i-6];
+ sum += (FLAC__uint32)qlp_coeff[4] * (FLAC__uint32)data[i-5];
+ sum += (FLAC__uint32)qlp_coeff[3] * (FLAC__uint32)data[i-4];
+ sum += (FLAC__uint32)qlp_coeff[2] * (FLAC__uint32)data[i-3];
+ sum += (FLAC__uint32)qlp_coeff[1] * (FLAC__uint32)data[i-2];
+ sum += (FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1];
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
}
@@ -1103,28 +1104,28 @@
if(order == 8) {
for(i = 0; i < (int)data_len; i++) {
sum = 0;
- sum += qlp_coeff[7] * data[i-8];
- sum += qlp_coeff[6] * data[i-7];
- sum += qlp_coeff[5] * data[i-6];
- sum += qlp_coeff[4] * data[i-5];
- sum += qlp_coeff[3] * data[i-4];
- sum += qlp_coeff[2] * data[i-3];
- sum += qlp_coeff[1] * data[i-2];
- sum += qlp_coeff[0] * data[i-1];
- data[i] = residual[i] + (sum >> lp_quantization);
+ sum += (FLAC__uint32)qlp_coeff[7] * (FLAC__uint32)data[i-8];
+ sum += (FLAC__uint32)qlp_coeff[6] * (FLAC__uint32)data[i-7];
+ sum += (FLAC__uint32)qlp_coeff[5] * (FLAC__uint32)data[i-6];
+ sum += (FLAC__uint32)qlp_coeff[4] * (FLAC__uint32)data[i-5];
+ sum += (FLAC__uint32)qlp_coeff[3] * (FLAC__uint32)data[i-4];
+ sum += (FLAC__uint32)qlp_coeff[2] * (FLAC__uint32)data[i-3];
+ sum += (FLAC__uint32)qlp_coeff[1] * (FLAC__uint32)data[i-2];
+ sum += (FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1];
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
else { /* order == 7 */
for(i = 0; i < (int)data_len; i++) {
sum = 0;
- sum += qlp_coeff[6] * data[i-7];
- sum += qlp_coeff[5] * data[i-6];
- sum += qlp_coeff[4] * data[i-5];
- sum += qlp_coeff[3] * data[i-4];
- sum += qlp_coeff[2] * data[i-3];
- sum += qlp_coeff[1] * data[i-2];
- sum += qlp_coeff[0] * data[i-1];
- data[i] = residual[i] + (sum >> lp_quantization);
+ sum += (FLAC__uint32)qlp_coeff[6] * (FLAC__uint32)data[i-7];
+ sum += (FLAC__uint32)qlp_coeff[5] * (FLAC__uint32)data[i-6];
+ sum += (FLAC__uint32)qlp_coeff[4] * (FLAC__uint32)data[i-5];
+ sum += (FLAC__uint32)qlp_coeff[3] * (FLAC__uint32)data[i-4];
+ sum += (FLAC__uint32)qlp_coeff[2] * (FLAC__uint32)data[i-3];
+ sum += (FLAC__uint32)qlp_coeff[1] * (FLAC__uint32)data[i-2];
+ sum += (FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1];
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
}
@@ -1132,24 +1133,24 @@
if(order == 6) {
for(i = 0; i < (int)data_len; i++) {
sum = 0;
- sum += qlp_coeff[5] * data[i-6];
- sum += qlp_coeff[4] * data[i-5];
- sum += qlp_coeff[3] * data[i-4];
- sum += qlp_coeff[2] * data[i-3];
- sum += qlp_coeff[1] * data[i-2];
- sum += qlp_coeff[0] * data[i-1];
- data[i] = residual[i] + (sum >> lp_quantization);
+ sum += (FLAC__uint32)qlp_coeff[5] * (FLAC__uint32)data[i-6];
+ sum += (FLAC__uint32)qlp_coeff[4] * (FLAC__uint32)data[i-5];
+ sum += (FLAC__uint32)qlp_coeff[3] * (FLAC__uint32)data[i-4];
+ sum += (FLAC__uint32)qlp_coeff[2] * (FLAC__uint32)data[i-3];
+ sum += (FLAC__uint32)qlp_coeff[1] * (FLAC__uint32)data[i-2];
+ sum += (FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1];
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
else { /* order == 5 */
for(i = 0; i < (int)data_len; i++) {
sum = 0;
- sum += qlp_coeff[4] * data[i-5];
- sum += qlp_coeff[3] * data[i-4];
- sum += qlp_coeff[2] * data[i-3];
- sum += qlp_coeff[1] * data[i-2];
- sum += qlp_coeff[0] * data[i-1];
- data[i] = residual[i] + (sum >> lp_quantization);
+ sum += (FLAC__uint32)qlp_coeff[4] * (FLAC__uint32)data[i-5];
+ sum += (FLAC__uint32)qlp_coeff[3] * (FLAC__uint32)data[i-4];
+ sum += (FLAC__uint32)qlp_coeff[2] * (FLAC__uint32)data[i-3];
+ sum += (FLAC__uint32)qlp_coeff[1] * (FLAC__uint32)data[i-2];
+ sum += (FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1];
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
}
@@ -1159,20 +1160,20 @@
if(order == 4) {
for(i = 0; i < (int)data_len; i++) {
sum = 0;
- sum += qlp_coeff[3] * data[i-4];
- sum += qlp_coeff[2] * data[i-3];
- sum += qlp_coeff[1] * data[i-2];
- sum += qlp_coeff[0] * data[i-1];
- data[i] = residual[i] + (sum >> lp_quantization);
+ sum += (FLAC__uint32)qlp_coeff[3] * (FLAC__uint32)data[i-4];
+ sum += (FLAC__uint32)qlp_coeff[2] * (FLAC__uint32)data[i-3];
+ sum += (FLAC__uint32)qlp_coeff[1] * (FLAC__uint32)data[i-2];
+ sum += (FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1];
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
else { /* order == 3 */
for(i = 0; i < (int)data_len; i++) {
sum = 0;
- sum += qlp_coeff[2] * data[i-3];
- sum += qlp_coeff[1] * data[i-2];
- sum += qlp_coeff[0] * data[i-1];
- data[i] = residual[i] + (sum >> lp_quantization);
+ sum += (FLAC__uint32)qlp_coeff[2] * (FLAC__uint32)data[i-3];
+ sum += (FLAC__uint32)qlp_coeff[1] * (FLAC__uint32)data[i-2];
+ sum += (FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1];
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
}
@@ -1180,14 +1181,14 @@
if(order == 2) {
for(i = 0; i < (int)data_len; i++) {
sum = 0;
- sum += qlp_coeff[1] * data[i-2];
- sum += qlp_coeff[0] * data[i-1];
- data[i] = residual[i] + (sum >> lp_quantization);
+ sum += (FLAC__uint32)qlp_coeff[1] * (FLAC__uint32)data[i-2];
+ sum += (FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1];
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
else { /* order == 1 */
for(i = 0; i < (int)data_len; i++)
- data[i] = residual[i] + ((qlp_coeff[0] * data[i-1]) >> lp_quantization);
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)((FLAC__uint32)qlp_coeff[0] * (FLAC__uint32)data[i-1]) >> lp_quantization));
}
}
}
@@ -1196,40 +1197,40 @@
for(i = 0; i < (int)data_len; i++) {
sum = 0;
switch(order) {
- case 32: sum += qlp_coeff[31] * data[i-32]; /* Falls through. */
- case 31: sum += qlp_coeff[30] * data[i-31]; /* Falls through. */
- case 30: sum += qlp_coeff[29] * data[i-30]; /* Falls through. */
- case 29: sum += qlp_coeff[28] * data[i-29]; /* Falls through. */
- case 28: sum += qlp_coeff[27] * data[i-28]; /* Falls through. */
- case 27: sum += qlp_coeff[26] * data[i-27]; /* Falls through. */
- case 26: sum += qlp_coeff[25] * data[i-26]; /* Falls through. */
- case 25: sum += qlp_coeff[24] * data[i-25]; /* Falls through. */
- case 24: sum += qlp_coeff[23] * data[i-24]; /* Falls through. */
- case 23: sum += qlp_coeff[22] * data[i-23]; /* Falls through. */
- case 22: sum += qlp_coeff[21] * data[i-22]; /* Falls through. */
- case 21: sum += qlp_coeff[20] * data[i-21]; /* Falls through. */
- case 20: sum += qlp_coeff[19] * data[i-20]; /* Falls through. */
- case 19: sum += qlp_coeff[18] * data[i-19]; /* Falls through. */
- case 18: sum += qlp_coeff[17] * data[i-18]; /* Falls through. */
- case 17: sum += qlp_coeff[16] * data[i-17]; /* Falls through. */
- case 16: sum += qlp_coeff[15] * data[i-16]; /* Falls through. */
- case 15: sum += qlp_coeff[14] * data[i-15]; /* Falls through. */
- case 14: sum += qlp_coeff[13] * data[i-14]; /* Falls through. */
- case 13: sum += qlp_coeff[12] * data[i-13];
- sum += qlp_coeff[11] * data[i-12];
- sum += qlp_coeff[10] * data[i-11];
- sum += qlp_coeff[ 9] * data[i-10];
- sum += qlp_coeff[ 8] * data[i- 9];
- sum += qlp_coeff[ 7] * data[i- 8];
- sum += qlp_coeff[ 6] * data[i- 7];
- sum += qlp_coeff[ 5] * data[i- 6];
- sum += qlp_coeff[ 4] * data[i- 5];
- sum += qlp_coeff[ 3] * data[i- 4];
- sum += qlp_coeff[ 2] * data[i- 3];
- sum += qlp_coeff[ 1] * data[i- 2];
- sum += qlp_coeff[ 0] * data[i- 1];
+ case 32: sum += (FLAC__uint32)qlp_coeff[31] * (FLAC__uint32)data[i-32]; /* Falls through. */
+ case 31: sum += (FLAC__uint32)qlp_coeff[30] * (FLAC__uint32)data[i-31]; /* Falls through. */
+ case 30: sum += (FLAC__uint32)qlp_coeff[29] * (FLAC__uint32)data[i-30]; /* Falls through. */
+ case 29: sum += (FLAC__uint32)qlp_coeff[28] * (FLAC__uint32)data[i-29]; /* Falls through. */
+ case 28: sum += (FLAC__uint32)qlp_coeff[27] * (FLAC__uint32)data[i-28]; /* Falls through. */
+ case 27: sum += (FLAC__uint32)qlp_coeff[26] * (FLAC__uint32)data[i-27]; /* Falls through. */
+ case 26: sum += (FLAC__uint32)qlp_coeff[25] * (FLAC__uint32)data[i-26]; /* Falls through. */
+ case 25: sum += (FLAC__uint32)qlp_coeff[24] * (FLAC__uint32)data[i-25]; /* Falls through. */
+ case 24: sum += (FLAC__uint32)qlp_coeff[23] * (FLAC__uint32)data[i-24]; /* Falls through. */
+ case 23: sum += (FLAC__uint32)qlp_coeff[22] * (FLAC__uint32)data[i-23]; /* Falls through. */
+ case 22: sum += (FLAC__uint32)qlp_coeff[21] * (FLAC__uint32)data[i-22]; /* Falls through. */
+ case 21: sum += (FLAC__uint32)qlp_coeff[20] * (FLAC__uint32)data[i-21]; /* Falls through. */
+ case 20: sum += (FLAC__uint32)qlp_coeff[19] * (FLAC__uint32)data[i-20]; /* Falls through. */
+ case 19: sum += (FLAC__uint32)qlp_coeff[18] * (FLAC__uint32)data[i-19]; /* Falls through. */
+ case 18: sum += (FLAC__uint32)qlp_coeff[17] * (FLAC__uint32)data[i-18]; /* Falls through. */
+ case 17: sum += (FLAC__uint32)qlp_coeff[16] * (FLAC__uint32)data[i-17]; /* Falls through. */
+ case 16: sum += (FLAC__uint32)qlp_coeff[15] * (FLAC__uint32)data[i-16]; /* Falls through. */
+ case 15: sum += (FLAC__uint32)qlp_coeff[14] * (FLAC__uint32)data[i-15]; /* Falls through. */
+ case 14: sum += (FLAC__uint32)qlp_coeff[13] * (FLAC__uint32)data[i-14]; /* Falls through. */
+ case 13: sum += (FLAC__uint32)qlp_coeff[12] * (FLAC__uint32)data[i-13];
+ sum += (FLAC__uint32)qlp_coeff[11] * (FLAC__uint32)data[i-12];
+ sum += (FLAC__uint32)qlp_coeff[10] * (FLAC__uint32)data[i-11];
+ sum += (FLAC__uint32)qlp_coeff[ 9] * (FLAC__uint32)data[i-10];
+ sum += (FLAC__uint32)qlp_coeff[ 8] * (FLAC__uint32)data[i- 9];
+ sum += (FLAC__uint32)qlp_coeff[ 7] * (FLAC__uint32)data[i- 8];
+ sum += (FLAC__uint32)qlp_coeff[ 6] * (FLAC__uint32)data[i- 7];
+ sum += (FLAC__uint32)qlp_coeff[ 5] * (FLAC__uint32)data[i- 6];
+ sum += (FLAC__uint32)qlp_coeff[ 4] * (FLAC__uint32)data[i- 5];
+ sum += (FLAC__uint32)qlp_coeff[ 3] * (FLAC__uint32)data[i- 4];
+ sum += (FLAC__uint32)qlp_coeff[ 2] * (FLAC__uint32)data[i- 3];
+ sum += (FLAC__uint32)qlp_coeff[ 1] * (FLAC__uint32)data[i- 2];
+ sum += (FLAC__uint32)qlp_coeff[ 0] * (FLAC__uint32)data[i- 1];
}
- data[i] = residual[i] + (sum >> lp_quantization);
+ data[i] = (FLAC__int32)((FLAC__uint32)residual[i] + (FLAC__uint32)((FLAC__int32)sum >> lp_quantization));
}
}
}
@@ -1588,7 +1589,7 @@
double FLAC__lpc_compute_expected_bits_per_residual_sample_with_error_scale(double lpc_error, double error_scale)
{
if(lpc_error > 0.0) {
- double bps = (double)0.5 * log(error_scale * lpc_error) / M_LN2;
+ double bps = (double)0.5 * discpress_log(error_scale * lpc_error) / M_LN2;
if(bps >= 0.0)
return bps;
else
--- a/3rdparty/flac/src/libFLAC/window.c
+++ b/3rdparty/flac/src/libFLAC/window.c
@@ -39,6 +39,7 @@
#include "FLAC/assert.h"
#include "FLAC/format.h"
#include "private/window.h"
+#include "flac_libm.h" /* Discpress: the same cosf in every build (engine/libm) */
#ifndef FLAC__INTEGER_ONLY_LIBRARY
@@ -72,7 +73,7 @@
FLAC__int32 n;
for (n = 0; n < L; n++)
- window[n] = (FLAC__real)(0.62f - 0.48f * fabsf((float)n/(float)N-0.5f) - 0.38f * cosf(2.0f * M_PI * ((float)n/(float)N)));
+ window[n] = (FLAC__real)(0.62f - 0.48f * fabsf((float)n/(float)N-0.5f) - 0.38f * discpress_cosf(2.0f * M_PI * ((float)n/(float)N)));
}
void FLAC__window_blackman(FLAC__real *window, const FLAC__int32 L)
@@ -81,7 +82,7 @@
FLAC__int32 n;
for (n = 0; n < L; n++)
- window[n] = (FLAC__real)(0.42f - 0.5f * cosf(2.0f * M_PI * n / N) + 0.08f * cosf(4.0f * M_PI * n / N));
+ window[n] = (FLAC__real)(0.42f - 0.5f * discpress_cosf(2.0f * M_PI * n / N) + 0.08f * discpress_cosf(4.0f * M_PI * n / N));
}
/* 4-term -92dB side-lobe */
@@ -91,7 +92,7 @@
FLAC__int32 n;
for (n = 0; n <= N; n++)
- window[n] = (FLAC__real)(0.35875f - 0.48829f * cosf(2.0f * M_PI * n / N) + 0.14128f * cosf(4.0f * M_PI * n / N) - 0.01168f * cosf(6.0f * M_PI * n / N));
+ window[n] = (FLAC__real)(0.35875f - 0.48829f * discpress_cosf(2.0f * M_PI * n / N) + 0.14128f * discpress_cosf(4.0f * M_PI * n / N) - 0.01168f * discpress_cosf(6.0f * M_PI * n / N));
}
void FLAC__window_connes(FLAC__real *window, const FLAC__int32 L)
@@ -113,7 +114,7 @@
FLAC__int32 n;
for (n = 0; n < L; n++)
- window[n] = (FLAC__real)(0.21557895f - 0.41663158f * cosf(2.0f * M_PI * n / N) + 0.277263158f * cosf(4.0f * M_PI * n / N) - 0.083578947f * cosf(6.0f * M_PI * n / N) + 0.006947368f * cosf(8.0f * M_PI * n / N));
+ window[n] = (FLAC__real)(0.21557895f - 0.41663158f * discpress_cosf(2.0f * M_PI * n / N) + 0.277263158f * discpress_cosf(4.0f * M_PI * n / N) - 0.083578947f * discpress_cosf(6.0f * M_PI * n / N) + 0.006947368f * discpress_cosf(8.0f * M_PI * n / N));
}
void FLAC__window_gauss(FLAC__real *window, const FLAC__int32 L, const FLAC__real stddev)
@@ -140,7 +141,7 @@
FLAC__int32 n;
for (n = 0; n < L; n++)
- window[n] = (FLAC__real)(0.54f - 0.46f * cosf(2.0f * M_PI * n / N));
+ window[n] = (FLAC__real)(0.54f - 0.46f * discpress_cosf(2.0f * M_PI * n / N));
}
void FLAC__window_hann(FLAC__real *window, const FLAC__int32 L)
@@ -149,7 +150,7 @@
FLAC__int32 n;
for (n = 0; n < L; n++)
- window[n] = (FLAC__real)(0.5f - 0.5f * cosf(2.0f * M_PI * n / N));
+ window[n] = (FLAC__real)(0.5f - 0.5f * discpress_cosf(2.0f * M_PI * n / N));
}
void FLAC__window_kaiser_bessel(FLAC__real *window, const FLAC__int32 L)
@@ -158,7 +159,7 @@
FLAC__int32 n;
for (n = 0; n < L; n++)
- window[n] = (FLAC__real)(0.402f - 0.498f * cosf(2.0f * M_PI * n / N) + 0.098f * cosf(4.0f * M_PI * n / N) - 0.001f * cosf(6.0f * M_PI * n / N));
+ window[n] = (FLAC__real)(0.402f - 0.498f * discpress_cosf(2.0f * M_PI * n / N) + 0.098f * discpress_cosf(4.0f * M_PI * n / N) - 0.001f * discpress_cosf(6.0f * M_PI * n / N));
}
void FLAC__window_nuttall(FLAC__real *window, const FLAC__int32 L)
@@ -167,7 +168,7 @@
FLAC__int32 n;
for (n = 0; n < L; n++)
- window[n] = (FLAC__real)(0.3635819f - 0.4891775f*cosf(2.0f*M_PI*n/N) + 0.1365995f*cosf(4.0f*M_PI*n/N) - 0.0106411f*cosf(6.0f*M_PI*n/N));
+ window[n] = (FLAC__real)(0.3635819f - 0.4891775f*discpress_cosf(2.0f*M_PI*n/N) + 0.1365995f*discpress_cosf(4.0f*M_PI*n/N) - 0.0106411f*discpress_cosf(6.0f*M_PI*n/N));
}
void FLAC__window_rectangle(FLAC__real *window, const FLAC__int32 L)
@@ -214,8 +215,8 @@
/* ...replace ends with hann */
if (Np > 0) {
for (n = 0; n <= Np; n++) {
- window[n] = (FLAC__real)(0.5f - 0.5f * cosf(M_PI * n / Np));
- window[L-Np-1+n] = (FLAC__real)(0.5f - 0.5f * cosf(M_PI * (n+Np) / Np));
+ window[n] = (FLAC__real)(0.5f - 0.5f * discpress_cosf(M_PI * n / Np));
+ window[L-Np-1+n] = (FLAC__real)(0.5f - 0.5f * discpress_cosf(M_PI * (n+Np) / Np));
}
}
}
@@ -243,11 +244,11 @@
for (n = 0; n < start_n && n < L; n++)
window[n] = 0.0f;
for (i = 1; n < (start_n+Np) && n < L; n++, i++)
- window[n] = (FLAC__real)(0.5f - 0.5f * cosf(M_PI * i / Np));
+ window[n] = (FLAC__real)(0.5f - 0.5f * discpress_cosf(M_PI * i / Np));
for (; n < (end_n-Np) && n < L; n++)
window[n] = 1.0f;
for (i = Np; n < end_n && n < L; n++, i--)
- window[n] = (FLAC__real)(0.5f - 0.5f * cosf(M_PI * i / Np));
+ window[n] = (FLAC__real)(0.5f - 0.5f * discpress_cosf(M_PI * i / Np));
for (; n < L; n++)
window[n] = 0.0f;
}
@@ -273,19 +274,19 @@
Ne = (FLAC__int32)(p / 2.0f * (L - end_n));
for (n = 0, i = 1; n < Ns && n < L; n++, i++)
- window[n] = (FLAC__real)(0.5f - 0.5f * cosf(M_PI * i / Ns));
+ window[n] = (FLAC__real)(0.5f - 0.5f * discpress_cosf(M_PI * i / Ns));
for (; n < start_n-Ns && n < L; n++)
window[n] = 1.0f;
for (i = Ns; n < start_n && n < L; n++, i--)
- window[n] = (FLAC__real)(0.5f - 0.5f * cosf(M_PI * i / Ns));
+ window[n] = (FLAC__real)(0.5f - 0.5f * discpress_cosf(M_PI * i / Ns));
for (; n < end_n && n < L; n++)
window[n] = 0.0f;
for (i = 1; n < end_n+Ne && n < L; n++, i++)
- window[n] = (FLAC__real)(0.5f - 0.5f * cosf(M_PI * i / Ne));
+ window[n] = (FLAC__real)(0.5f - 0.5f * discpress_cosf(M_PI * i / Ne));
for (; n < L - (Ne) && n < L; n++)
window[n] = 1.0f;
for (i = Ne; n < L; n++, i--)
- window[n] = (FLAC__real)(0.5f - 0.5f * cosf(M_PI * i / Ne));
+ window[n] = (FLAC__real)(0.5f - 0.5f * discpress_cosf(M_PI * i / Ne));
}
}
--- a/3rdparty/lzma/C/LzmaEnc.c
+++ b/3rdparty/lzma/C/LzmaEnc.c
@@ -411,6 +411,7 @@
BoolInt fastMode;
BoolInt writeEndMark;
+ size_t outLimit; /* Discpress: LzmaEnc_MemEncode's buffer size, to stop once the output can't fit; 0 = none */
BoolInt finished;
BoolInt multiThread;
BoolInt needInit;
@@ -2321,6 +2322,7 @@
static void LzmaEnc_Construct(CLzmaEnc *p)
{
RangeEnc_Construct(&p->rc);
+ p->outLimit = 0;
MatchFinder_Construct(&MFB);
#ifndef Z7_ST
@@ -2672,6 +2674,10 @@
p->nowPos64 += nowPos32 - startPos32;
return CheckErrors(p);
}
+
+ /* Discpress: the output only grows, so once it is past the buffer the result can't fit */
+ if (p->outLimit && RangeEnc_GetProcessed_sizet(&p->rc) > p->outLimit)
+ return SZ_ERROR_OUTPUT_EOF;
}
}
@@ -3064,7 +3070,12 @@
while (v < dictSize);
}
- SetUi32(props + 1, v)
+ /* Discpress: props + 1 is not 4-byte aligned, and SetUi32 stores there through a UInt32 pointer where the CPU
+ allows unaligned accesses, which C leaves undefined; memcpy of the little-endian bytes is the same store */
+ {
+ const Byte le[4] = { (Byte)v, (Byte)(v >> 8), (Byte)(v >> 16), (Byte)(v >> 24) };
+ memcpy(props + 1, le, 4);
+ }
return SZ_OK;
}
}
@@ -3092,6 +3103,7 @@
p->writeEndMark = writeEndMark;
p->rc.outStream = &outStream.vt;
+ p->outLimit = *destLen;
res = LzmaEnc_MemPrepare(p, src, srcLen, 0, alloc, allocBig);
@@ -3101,6 +3113,7 @@
if (res == SZ_OK && p->nowPos64 != srcLen)
res = SZ_ERROR_FAIL;
}
+ p->outLimit = 0;
*destLen -= (SizeT)outStream.rem;
if (outStream.overflow)
--- a/src/lib/util/cdrom.cpp
+++ b/src/lib/util/cdrom.cpp
@@ -28,6 +28,8 @@
#include <cstdlib>
#include <cstring>
#include <tuple>
+#include <utility>
+#include <vector>
/***************************************************************************
@@ -103,9 +105,12 @@
uint32_t cdrom_file::logical_to_chd_lba(uint32_t loglba, uint32_t &tracknum) const
{
// loop until our current LBA is less than the start LBA of the next track
+ // Discpress: where the next track's pregap starts (its INDEX 00), stored or not: 0.289 went on to its INDEX 01,
+ // so its pregap was read from this track's end, shifted by this track's padding, or for a pregap or postgap
+ // that isn't in the CHD, from the next track's data
for (int track = 0; track < cdtoc.numtrks; track++)
{
- if (loglba < cdtoc.tracks[track + 1].logframeofs)
+ if (loglba < cdtoc.tracks[track + 1].logframeofs - ((track + 1 < cdtoc.numtrks) ? cdtoc.tracks[track + 1].pregap : 0))
{
// convert to physical and proceed
uint32_t physlba = cdtoc.tracks[track].physframeofs + (loglba - cdtoc.tracks[track].logframeofs);
@@ -148,6 +153,13 @@
for (int i = 0; i < cdtoc.numtrks; i++)
{
+ // Discpress: only chdman reads a track in pieces
+ if (!cdtrack_info.track[i].pieces.empty())
+ {
+ osd_printf_error("Track %d is in several files, or holds zeros that no file does: convert it with chdman\n", i + 1);
+ throw nullptr;
+ }
+
osd_file::ptr file;
std::uint64_t length;
std::error_condition const filerr = osd_file::open(cdtrack_info.track[i].fname, OPEN_FLAG_READ, file, length);
@@ -385,6 +397,14 @@
memset(dest, 0, length);
return result;
}
+
+ // Discpress: and so is a postgap, or a gap between sessions, up to the next track's pregap
+ const track_info &track = cdtoc.tracks[tracknum];
+ if ((lbasector >= track.logframeofs + track.frames - (track.pgdatasize ? track.pregap : 0)) && (lbasector < cdtoc.tracks[cdtoc.numtrks].logframeofs))
+ {
+ memset(dest, 0, length);
+ return result;
+ }
}
// if a CHD, just read
@@ -603,6 +623,10 @@
{
const uint32_t track = get_track(frame);
const uint32_t track_start = get_track_start(track);
+ // Discpress: a frame before the track's INDEX 01 is in its pregap, which logical_to_chd_lba gives the track now
+ // (0.289 gave it the track before); frame - track_start would wrap
+ if (frame < track_start)
+ return 0;
const uint32_t index_offset = frame - track_start;
int index = 0;
@@ -1153,204 +1177,106 @@
0x50, 0xa4, 0xa5, 0x51, 0xa7, 0x53, 0x52, 0xa6, 0xa3, 0x57, 0x56, 0xa2, 0x54, 0xa0, 0xa1, 0x55
};
-/**
- * @brief -------------------------------------------------
- * poffsets - each row represents the addresses used to calculate a byte of the ECC P
- * data 86 (*2) ECC P bytes, 24 values represented by each
- * -------------------------------------------------.
- */
+//-------------------------------------------------
+// Discpress: P and Q are computed for eight vectors at a time, a byte each in 64-bit words.
+// The results are those of 0.289's byte-at-a-time loops over tables of offsets (poffsets and
+// qoffsets, removed), about 3.4 times faster.
+//
+// The ECC covers a sector from its header (zeros in Mode 2) up to its P: 26 rows of 43 16-bit
+// words, the last two rows being P. P protects the 86 byte columns of the first 24 rows; Q the
+// 52 byte diagonals of all 26 rows, 43 words each, one per column and a row further down per
+// column, wrapping around. For a vector x[0..n), with a = sum x[k] * 2^(n-k) and b = sum x[k]
+// over GF(2^8), the parity pair is p0 = (a * 2 + b) / 3 and p1 = p0 + b (ecclow multiplies
+// by 2, ecchigh divides by 3).
+//-------------------------------------------------
-const uint16_t cdrom_file::poffsets[cdrom_file::ECC_P_NUM_BYTES][cdrom_file::ECC_P_COMP] =
-{
- { 0x000,0x056,0x0ac,0x102,0x158,0x1ae,0x204,0x25a,0x2b0,0x306,0x35c,0x3b2,0x408,0x45e,0x4b4,0x50a,0x560,0x5b6,0x60c,0x662,0x6b8,0x70e,0x764,0x7ba },
- { 0x001,0x057,0x0ad,0x103,0x159,0x1af,0x205,0x25b,0x2b1,0x307,0x35d,0x3b3,0x409,0x45f,0x4b5,0x50b,0x561,0x5b7,0x60d,0x663,0x6b9,0x70f,0x765,0x7bb },
- { 0x002,0x058,0x0ae,0x104,0x15a,0x1b0,0x206,0x25c,0x2b2,0x308,0x35e,0x3b4,0x40a,0x460,0x4b6,0x50c,0x562,0x5b8,0x60e,0x664,0x6ba,0x710,0x766,0x7bc },
- { 0x003,0x059,0x0af,0x105,0x15b,0x1b1,0x207,0x25d,0x2b3,0x309,0x35f,0x3b5,0x40b,0x461,0x4b7,0x50d,0x563,0x5b9,0x60f,0x665,0x6bb,0x711,0x767,0x7bd },
- { 0x004,0x05a,0x0b0,0x106,0x15c,0x1b2,0x208,0x25e,0x2b4,0x30a,0x360,0x3b6,0x40c,0x462,0x4b8,0x50e,0x564,0x5ba,0x610,0x666,0x6bc,0x712,0x768,0x7be },
- { 0x005,0x05b,0x0b1,0x107,0x15d,0x1b3,0x209,0x25f,0x2b5,0x30b,0x361,0x3b7,0x40d,0x463,0x4b9,0x50f,0x565,0x5bb,0x611,0x667,0x6bd,0x713,0x769,0x7bf },
- { 0x006,0x05c,0x0b2,0x108,0x15e,0x1b4,0x20a,0x260,0x2b6,0x30c,0x362,0x3b8,0x40e,0x464,0x4ba,0x510,0x566,0x5bc,0x612,0x668,0x6be,0x714,0x76a,0x7c0 },
- { 0x007,0x05d,0x0b3,0x109,0x15f,0x1b5,0x20b,0x261,0x2b7,0x30d,0x363,0x3b9,0x40f,0x465,0x4bb,0x511,0x567,0x5bd,0x613,0x669,0x6bf,0x715,0x76b,0x7c1 },
- { 0x008,0x05e,0x0b4,0x10a,0x160,0x1b6,0x20c,0x262,0x2b8,0x30e,0x364,0x3ba,0x410,0x466,0x4bc,0x512,0x568,0x5be,0x614,0x66a,0x6c0,0x716,0x76c,0x7c2 },
- { 0x009,0x05f,0x0b5,0x10b,0x161,0x1b7,0x20d,0x263,0x2b9,0x30f,0x365,0x3bb,0x411,0x467,0x4bd,0x513,0x569,0x5bf,0x615,0x66b,0x6c1,0x717,0x76d,0x7c3 },
- { 0x00a,0x060,0x0b6,0x10c,0x162,0x1b8,0x20e,0x264,0x2ba,0x310,0x366,0x3bc,0x412,0x468,0x4be,0x514,0x56a,0x5c0,0x616,0x66c,0x6c2,0x718,0x76e,0x7c4 },
- { 0x00b,0x061,0x0b7,0x10d,0x163,0x1b9,0x20f,0x265,0x2bb,0x311,0x367,0x3bd,0x413,0x469,0x4bf,0x515,0x56b,0x5c1,0x617,0x66d,0x6c3,0x719,0x76f,0x7c5 },
- { 0x00c,0x062,0x0b8,0x10e,0x164,0x1ba,0x210,0x266,0x2bc,0x312,0x368,0x3be,0x414,0x46a,0x4c0,0x516,0x56c,0x5c2,0x618,0x66e,0x6c4,0x71a,0x770,0x7c6 },
- { 0x00d,0x063,0x0b9,0x10f,0x165,0x1bb,0x211,0x267,0x2bd,0x313,0x369,0x3bf,0x415,0x46b,0x4c1,0x517,0x56d,0x5c3,0x619,0x66f,0x6c5,0x71b,0x771,0x7c7 },
- { 0x00e,0x064,0x0ba,0x110,0x166,0x1bc,0x212,0x268,0x2be,0x314,0x36a,0x3c0,0x416,0x46c,0x4c2,0x518,0x56e,0x5c4,0x61a,0x670,0x6c6,0x71c,0x772,0x7c8 },
- { 0x00f,0x065,0x0bb,0x111,0x167,0x1bd,0x213,0x269,0x2bf,0x315,0x36b,0x3c1,0x417,0x46d,0x4c3,0x519,0x56f,0x5c5,0x61b,0x671,0x6c7,0x71d,0x773,0x7c9 },
- { 0x010,0x066,0x0bc,0x112,0x168,0x1be,0x214,0x26a,0x2c0,0x316,0x36c,0x3c2,0x418,0x46e,0x4c4,0x51a,0x570,0x5c6,0x61c,0x672,0x6c8,0x71e,0x774,0x7ca },
- { 0x011,0x067,0x0bd,0x113,0x169,0x1bf,0x215,0x26b,0x2c1,0x317,0x36d,0x3c3,0x419,0x46f,0x4c5,0x51b,0x571,0x5c7,0x61d,0x673,0x6c9,0x71f,0x775,0x7cb },
- { 0x012,0x068,0x0be,0x114,0x16a,0x1c0,0x216,0x26c,0x2c2,0x318,0x36e,0x3c4,0x41a,0x470,0x4c6,0x51c,0x572,0x5c8,0x61e,0x674,0x6ca,0x720,0x776,0x7cc },
- { 0x013,0x069,0x0bf,0x115,0x16b,0x1c1,0x217,0x26d,0x2c3,0x319,0x36f,0x3c5,0x41b,0x471,0x4c7,0x51d,0x573,0x5c9,0x61f,0x675,0x6cb,0x721,0x777,0x7cd },
- { 0x014,0x06a,0x0c0,0x116,0x16c,0x1c2,0x218,0x26e,0x2c4,0x31a,0x370,0x3c6,0x41c,0x472,0x4c8,0x51e,0x574,0x5ca,0x620,0x676,0x6cc,0x722,0x778,0x7ce },
- { 0x015,0x06b,0x0c1,0x117,0x16d,0x1c3,0x219,0x26f,0x2c5,0x31b,0x371,0x3c7,0x41d,0x473,0x4c9,0x51f,0x575,0x5cb,0x621,0x677,0x6cd,0x723,0x779,0x7cf },
- { 0x016,0x06c,0x0c2,0x118,0x16e,0x1c4,0x21a,0x270,0x2c6,0x31c,0x372,0x3c8,0x41e,0x474,0x4ca,0x520,0x576,0x5cc,0x622,0x678,0x6ce,0x724,0x77a,0x7d0 },
- { 0x017,0x06d,0x0c3,0x119,0x16f,0x1c5,0x21b,0x271,0x2c7,0x31d,0x373,0x3c9,0x41f,0x475,0x4cb,0x521,0x577,0x5cd,0x623,0x679,0x6cf,0x725,0x77b,0x7d1 },
- { 0x018,0x06e,0x0c4,0x11a,0x170,0x1c6,0x21c,0x272,0x2c8,0x31e,0x374,0x3ca,0x420,0x476,0x4cc,0x522,0x578,0x5ce,0x624,0x67a,0x6d0,0x726,0x77c,0x7d2 },
- { 0x019,0x06f,0x0c5,0x11b,0x171,0x1c7,0x21d,0x273,0x2c9,0x31f,0x375,0x3cb,0x421,0x477,0x4cd,0x523,0x579,0x5cf,0x625,0x67b,0x6d1,0x727,0x77d,0x7d3 },
- { 0x01a,0x070,0x0c6,0x11c,0x172,0x1c8,0x21e,0x274,0x2ca,0x320,0x376,0x3cc,0x422,0x478,0x4ce,0x524,0x57a,0x5d0,0x626,0x67c,0x6d2,0x728,0x77e,0x7d4 },
- { 0x01b,0x071,0x0c7,0x11d,0x173,0x1c9,0x21f,0x275,0x2cb,0x321,0x377,0x3cd,0x423,0x479,0x4cf,0x525,0x57b,0x5d1,0x627,0x67d,0x6d3,0x729,0x77f,0x7d5 },
- { 0x01c,0x072,0x0c8,0x11e,0x174,0x1ca,0x220,0x276,0x2cc,0x322,0x378,0x3ce,0x424,0x47a,0x4d0,0x526,0x57c,0x5d2,0x628,0x67e,0x6d4,0x72a,0x780,0x7d6 },
- { 0x01d,0x073,0x0c9,0x11f,0x175,0x1cb,0x221,0x277,0x2cd,0x323,0x379,0x3cf,0x425,0x47b,0x4d1,0x527,0x57d,0x5d3,0x629,0x67f,0x6d5,0x72b,0x781,0x7d7 },
- { 0x01e,0x074,0x0ca,0x120,0x176,0x1cc,0x222,0x278,0x2ce,0x324,0x37a,0x3d0,0x426,0x47c,0x4d2,0x528,0x57e,0x5d4,0x62a,0x680,0x6d6,0x72c,0x782,0x7d8 },
- { 0x01f,0x075,0x0cb,0x121,0x177,0x1cd,0x223,0x279,0x2cf,0x325,0x37b,0x3d1,0x427,0x47d,0x4d3,0x529,0x57f,0x5d5,0x62b,0x681,0x6d7,0x72d,0x783,0x7d9 },
- { 0x020,0x076,0x0cc,0x122,0x178,0x1ce,0x224,0x27a,0x2d0,0x326,0x37c,0x3d2,0x428,0x47e,0x4d4,0x52a,0x580,0x5d6,0x62c,0x682,0x6d8,0x72e,0x784,0x7da },
- { 0x021,0x077,0x0cd,0x123,0x179,0x1cf,0x225,0x27b,0x2d1,0x327,0x37d,0x3d3,0x429,0x47f,0x4d5,0x52b,0x581,0x5d7,0x62d,0x683,0x6d9,0x72f,0x785,0x7db },
- { 0x022,0x078,0x0ce,0x124,0x17a,0x1d0,0x226,0x27c,0x2d2,0x328,0x37e,0x3d4,0x42a,0x480,0x4d6,0x52c,0x582,0x5d8,0x62e,0x684,0x6da,0x730,0x786,0x7dc },
- { 0x023,0x079,0x0cf,0x125,0x17b,0x1d1,0x227,0x27d,0x2d3,0x329,0x37f,0x3d5,0x42b,0x481,0x4d7,0x52d,0x583,0x5d9,0x62f,0x685,0x6db,0x731,0x787,0x7dd },
- { 0x024,0x07a,0x0d0,0x126,0x17c,0x1d2,0x228,0x27e,0x2d4,0x32a,0x380,0x3d6,0x42c,0x482,0x4d8,0x52e,0x584,0x5da,0x630,0x686,0x6dc,0x732,0x788,0x7de },
- { 0x025,0x07b,0x0d1,0x127,0x17d,0x1d3,0x229,0x27f,0x2d5,0x32b,0x381,0x3d7,0x42d,0x483,0x4d9,0x52f,0x585,0x5db,0x631,0x687,0x6dd,0x733,0x789,0x7df },
- { 0x026,0x07c,0x0d2,0x128,0x17e,0x1d4,0x22a,0x280,0x2d6,0x32c,0x382,0x3d8,0x42e,0x484,0x4da,0x530,0x586,0x5dc,0x632,0x688,0x6de,0x734,0x78a,0x7e0 },
- { 0x027,0x07d,0x0d3,0x129,0x17f,0x1d5,0x22b,0x281,0x2d7,0x32d,0x383,0x3d9,0x42f,0x485,0x4db,0x531,0x587,0x5dd,0x633,0x689,0x6df,0x735,0x78b,0x7e1 },
- { 0x028,0x07e,0x0d4,0x12a,0x180,0x1d6,0x22c,0x282,0x2d8,0x32e,0x384,0x3da,0x430,0x486,0x4dc,0x532,0x588,0x5de,0x634,0x68a,0x6e0,0x736,0x78c,0x7e2 },
- { 0x029,0x07f,0x0d5,0x12b,0x181,0x1d7,0x22d,0x283,0x2d9,0x32f,0x385,0x3db,0x431,0x487,0x4dd,0x533,0x589,0x5df,0x635,0x68b,0x6e1,0x737,0x78d,0x7e3 },
- { 0x02a,0x080,0x0d6,0x12c,0x182,0x1d8,0x22e,0x284,0x2da,0x330,0x386,0x3dc,0x432,0x488,0x4de,0x534,0x58a,0x5e0,0x636,0x68c,0x6e2,0x738,0x78e,0x7e4 },
- { 0x02b,0x081,0x0d7,0x12d,0x183,0x1d9,0x22f,0x285,0x2db,0x331,0x387,0x3dd,0x433,0x489,0x4df,0x535,0x58b,0x5e1,0x637,0x68d,0x6e3,0x739,0x78f,0x7e5 },
- { 0x02c,0x082,0x0d8,0x12e,0x184,0x1da,0x230,0x286,0x2dc,0x332,0x388,0x3de,0x434,0x48a,0x4e0,0x536,0x58c,0x5e2,0x638,0x68e,0x6e4,0x73a,0x790,0x7e6 },
- { 0x02d,0x083,0x0d9,0x12f,0x185,0x1db,0x231,0x287,0x2dd,0x333,0x389,0x3df,0x435,0x48b,0x4e1,0x537,0x58d,0x5e3,0x639,0x68f,0x6e5,0x73b,0x791,0x7e7 },
- { 0x02e,0x084,0x0da,0x130,0x186,0x1dc,0x232,0x288,0x2de,0x334,0x38a,0x3e0,0x436,0x48c,0x4e2,0x538,0x58e,0x5e4,0x63a,0x690,0x6e6,0x73c,0x792,0x7e8 },
- { 0x02f,0x085,0x0db,0x131,0x187,0x1dd,0x233,0x289,0x2df,0x335,0x38b,0x3e1,0x437,0x48d,0x4e3,0x539,0x58f,0x5e5,0x63b,0x691,0x6e7,0x73d,0x793,0x7e9 },
- { 0x030,0x086,0x0dc,0x132,0x188,0x1de,0x234,0x28a,0x2e0,0x336,0x38c,0x3e2,0x438,0x48e,0x4e4,0x53a,0x590,0x5e6,0x63c,0x692,0x6e8,0x73e,0x794,0x7ea },
- { 0x031,0x087,0x0dd,0x133,0x189,0x1df,0x235,0x28b,0x2e1,0x337,0x38d,0x3e3,0x439,0x48f,0x4e5,0x53b,0x591,0x5e7,0x63d,0x693,0x6e9,0x73f,0x795,0x7eb },
- { 0x032,0x088,0x0de,0x134,0x18a,0x1e0,0x236,0x28c,0x2e2,0x338,0x38e,0x3e4,0x43a,0x490,0x4e6,0x53c,0x592,0x5e8,0x63e,0x694,0x6ea,0x740,0x796,0x7ec },
- { 0x033,0x089,0x0df,0x135,0x18b,0x1e1,0x237,0x28d,0x2e3,0x339,0x38f,0x3e5,0x43b,0x491,0x4e7,0x53d,0x593,0x5e9,0x63f,0x695,0x6eb,0x741,0x797,0x7ed },
- { 0x034,0x08a,0x0e0,0x136,0x18c,0x1e2,0x238,0x28e,0x2e4,0x33a,0x390,0x3e6,0x43c,0x492,0x4e8,0x53e,0x594,0x5ea,0x640,0x696,0x6ec,0x742,0x798,0x7ee },
- { 0x035,0x08b,0x0e1,0x137,0x18d,0x1e3,0x239,0x28f,0x2e5,0x33b,0x391,0x3e7,0x43d,0x493,0x4e9,0x53f,0x595,0x5eb,0x641,0x697,0x6ed,0x743,0x799,0x7ef },
- { 0x036,0x08c,0x0e2,0x138,0x18e,0x1e4,0x23a,0x290,0x2e6,0x33c,0x392,0x3e8,0x43e,0x494,0x4ea,0x540,0x596,0x5ec,0x642,0x698,0x6ee,0x744,0x79a,0x7f0 },
- { 0x037,0x08d,0x0e3,0x139,0x18f,0x1e5,0x23b,0x291,0x2e7,0x33d,0x393,0x3e9,0x43f,0x495,0x4eb,0x541,0x597,0x5ed,0x643,0x699,0x6ef,0x745,0x79b,0x7f1 },
- { 0x038,0x08e,0x0e4,0x13a,0x190,0x1e6,0x23c,0x292,0x2e8,0x33e,0x394,0x3ea,0x440,0x496,0x4ec,0x542,0x598,0x5ee,0x644,0x69a,0x6f0,0x746,0x79c,0x7f2 },
- { 0x039,0x08f,0x0e5,0x13b,0x191,0x1e7,0x23d,0x293,0x2e9,0x33f,0x395,0x3eb,0x441,0x497,0x4ed,0x543,0x599,0x5ef,0x645,0x69b,0x6f1,0x747,0x79d,0x7f3 },
- { 0x03a,0x090,0x0e6,0x13c,0x192,0x1e8,0x23e,0x294,0x2ea,0x340,0x396,0x3ec,0x442,0x498,0x4ee,0x544,0x59a,0x5f0,0x646,0x69c,0x6f2,0x748,0x79e,0x7f4 },
- { 0x03b,0x091,0x0e7,0x13d,0x193,0x1e9,0x23f,0x295,0x2eb,0x341,0x397,0x3ed,0x443,0x499,0x4ef,0x545,0x59b,0x5f1,0x647,0x69d,0x6f3,0x749,0x79f,0x7f5 },
- { 0x03c,0x092,0x0e8,0x13e,0x194,0x1ea,0x240,0x296,0x2ec,0x342,0x398,0x3ee,0x444,0x49a,0x4f0,0x546,0x59c,0x5f2,0x648,0x69e,0x6f4,0x74a,0x7a0,0x7f6 },
- { 0x03d,0x093,0x0e9,0x13f,0x195,0x1eb,0x241,0x297,0x2ed,0x343,0x399,0x3ef,0x445,0x49b,0x4f1,0x547,0x59d,0x5f3,0x649,0x69f,0x6f5,0x74b,0x7a1,0x7f7 },
- { 0x03e,0x094,0x0ea,0x140,0x196,0x1ec,0x242,0x298,0x2ee,0x344,0x39a,0x3f0,0x446,0x49c,0x4f2,0x548,0x59e,0x5f4,0x64a,0x6a0,0x6f6,0x74c,0x7a2,0x7f8 },
- { 0x03f,0x095,0x0eb,0x141,0x197,0x1ed,0x243,0x299,0x2ef,0x345,0x39b,0x3f1,0x447,0x49d,0x4f3,0x549,0x59f,0x5f5,0x64b,0x6a1,0x6f7,0x74d,0x7a3,0x7f9 },
- { 0x040,0x096,0x0ec,0x142,0x198,0x1ee,0x244,0x29a,0x2f0,0x346,0x39c,0x3f2,0x448,0x49e,0x4f4,0x54a,0x5a0,0x5f6,0x64c,0x6a2,0x6f8,0x74e,0x7a4,0x7fa },
- { 0x041,0x097,0x0ed,0x143,0x199,0x1ef,0x245,0x29b,0x2f1,0x347,0x39d,0x3f3,0x449,0x49f,0x4f5,0x54b,0x5a1,0x5f7,0x64d,0x6a3,0x6f9,0x74f,0x7a5,0x7fb },
- { 0x042,0x098,0x0ee,0x144,0x19a,0x1f0,0x246,0x29c,0x2f2,0x348,0x39e,0x3f4,0x44a,0x4a0,0x4f6,0x54c,0x5a2,0x5f8,0x64e,0x6a4,0x6fa,0x750,0x7a6,0x7fc },
- { 0x043,0x099,0x0ef,0x145,0x19b,0x1f1,0x247,0x29d,0x2f3,0x349,0x39f,0x3f5,0x44b,0x4a1,0x4f7,0x54d,0x5a3,0x5f9,0x64f,0x6a5,0x6fb,0x751,0x7a7,0x7fd },
- { 0x044,0x09a,0x0f0,0x146,0x19c,0x1f2,0x248,0x29e,0x2f4,0x34a,0x3a0,0x3f6,0x44c,0x4a2,0x4f8,0x54e,0x5a4,0x5fa,0x650,0x6a6,0x6fc,0x752,0x7a8,0x7fe },
- { 0x045,0x09b,0x0f1,0x147,0x19d,0x1f3,0x249,0x29f,0x2f5,0x34b,0x3a1,0x3f7,0x44d,0x4a3,0x4f9,0x54f,0x5a5,0x5fb,0x651,0x6a7,0x6fd,0x753,0x7a9,0x7ff },
- { 0x046,0x09c,0x0f2,0x148,0x19e,0x1f4,0x24a,0x2a0,0x2f6,0x34c,0x3a2,0x3f8,0x44e,0x4a4,0x4fa,0x550,0x5a6,0x5fc,0x652,0x6a8,0x6fe,0x754,0x7aa,0x800 },
- { 0x047,0x09d,0x0f3,0x149,0x19f,0x1f5,0x24b,0x2a1,0x2f7,0x34d,0x3a3,0x3f9,0x44f,0x4a5,0x4fb,0x551,0x5a7,0x5fd,0x653,0x6a9,0x6ff,0x755,0x7ab,0x801 },
- { 0x048,0x09e,0x0f4,0x14a,0x1a0,0x1f6,0x24c,0x2a2,0x2f8,0x34e,0x3a4,0x3fa,0x450,0x4a6,0x4fc,0x552,0x5a8,0x5fe,0x654,0x6aa,0x700,0x756,0x7ac,0x802 },
- { 0x049,0x09f,0x0f5,0x14b,0x1a1,0x1f7,0x24d,0x2a3,0x2f9,0x34f,0x3a5,0x3fb,0x451,0x4a7,0x4fd,0x553,0x5a9,0x5ff,0x655,0x6ab,0x701,0x757,0x7ad,0x803 },
- { 0x04a,0x0a0,0x0f6,0x14c,0x1a2,0x1f8,0x24e,0x2a4,0x2fa,0x350,0x3a6,0x3fc,0x452,0x4a8,0x4fe,0x554,0x5aa,0x600,0x656,0x6ac,0x702,0x758,0x7ae,0x804 },
- { 0x04b,0x0a1,0x0f7,0x14d,0x1a3,0x1f9,0x24f,0x2a5,0x2fb,0x351,0x3a7,0x3fd,0x453,0x4a9,0x4ff,0x555,0x5ab,0x601,0x657,0x6ad,0x703,0x759,0x7af,0x805 },
- { 0x04c,0x0a2,0x0f8,0x14e,0x1a4,0x1fa,0x250,0x2a6,0x2fc,0x352,0x3a8,0x3fe,0x454,0x4aa,0x500,0x556,0x5ac,0x602,0x658,0x6ae,0x704,0x75a,0x7b0,0x806 },
- { 0x04d,0x0a3,0x0f9,0x14f,0x1a5,0x1fb,0x251,0x2a7,0x2fd,0x353,0x3a9,0x3ff,0x455,0x4ab,0x501,0x557,0x5ad,0x603,0x659,0x6af,0x705,0x75b,0x7b1,0x807 },
- { 0x04e,0x0a4,0x0fa,0x150,0x1a6,0x1fc,0x252,0x2a8,0x2fe,0x354,0x3aa,0x400,0x456,0x4ac,0x502,0x558,0x5ae,0x604,0x65a,0x6b0,0x706,0x75c,0x7b2,0x808 },
- { 0x04f,0x0a5,0x0fb,0x151,0x1a7,0x1fd,0x253,0x2a9,0x2ff,0x355,0x3ab,0x401,0x457,0x4ad,0x503,0x559,0x5af,0x605,0x65b,0x6b1,0x707,0x75d,0x7b3,0x809 },
- { 0x050,0x0a6,0x0fc,0x152,0x1a8,0x1fe,0x254,0x2aa,0x300,0x356,0x3ac,0x402,0x458,0x4ae,0x504,0x55a,0x5b0,0x606,0x65c,0x6b2,0x708,0x75e,0x7b4,0x80a },
- { 0x051,0x0a7,0x0fd,0x153,0x1a9,0x1ff,0x255,0x2ab,0x301,0x357,0x3ad,0x403,0x459,0x4af,0x505,0x55b,0x5b1,0x607,0x65d,0x6b3,0x709,0x75f,0x7b5,0x80b },
- { 0x052,0x0a8,0x0fe,0x154,0x1aa,0x200,0x256,0x2ac,0x302,0x358,0x3ae,0x404,0x45a,0x4b0,0x506,0x55c,0x5b2,0x608,0x65e,0x6b4,0x70a,0x760,0x7b6,0x80c },
- { 0x053,0x0a9,0x0ff,0x155,0x1ab,0x201,0x257,0x2ad,0x303,0x359,0x3af,0x405,0x45b,0x4b1,0x507,0x55d,0x5b3,0x609,0x65f,0x6b5,0x70b,0x761,0x7b7,0x80d },
- { 0x054,0x0aa,0x100,0x156,0x1ac,0x202,0x258,0x2ae,0x304,0x35a,0x3b0,0x406,0x45c,0x4b2,0x508,0x55e,0x5b4,0x60a,0x660,0x6b6,0x70c,0x762,0x7b8,0x80e },
- { 0x055,0x0ab,0x101,0x157,0x1ad,0x203,0x259,0x2af,0x305,0x35b,0x3b1,0x407,0x45d,0x4b3,0x509,0x55f,0x5b5,0x60b,0x661,0x6b7,0x70d,0x763,0x7b9,0x80f }
-};
+namespace {
-/**
- * @brief -------------------------------------------------
- * qoffsets - each row represents the addresses used to calculate a byte of the ECC Q
- * data 52 (*2) ECC Q bytes, 43 values represented by each
- * -------------------------------------------------.
- */
+constexpr int ECC_REGION_BYTES = 2236; // the header, the 2,060 bytes after it, and P
-const uint16_t cdrom_file::qoffsets[ECC_Q_NUM_BYTES][ECC_Q_COMP] =
+inline uint64_t ecc_load64(const uint8_t *p)
{
- { 0x000,0x058,0x0b0,0x108,0x160,0x1b8,0x210,0x268,0x2c0,0x318,0x370,0x3c8,0x420,0x478,0x4d0,0x528,0x580,0x5d8,0x630,0x688,0x6e0,0x738,0x790,0x7e8,0x840,0x898,0x034,0x08c,0x0e4,0x13c,0x194,0x1ec,0x244,0x29c,0x2f4,0x34c,0x3a4,0x3fc,0x454,0x4ac,0x504,0x55c,0x5b4 },
- { 0x001,0x059,0x0b1,0x109,0x161,0x1b9,0x211,0x269,0x2c1,0x319,0x371,0x3c9,0x421,0x479,0x4d1,0x529,0x581,0x5d9,0x631,0x689,0x6e1,0x739,0x791,0x7e9,0x841,0x899,0x035,0x08d,0x0e5,0x13d,0x195,0x1ed,0x245,0x29d,0x2f5,0x34d,0x3a5,0x3fd,0x455,0x4ad,0x505,0x55d,0x5b5 },
- { 0x056,0x0ae,0x106,0x15e,0x1b6,0x20e,0x266,0x2be,0x316,0x36e,0x3c6,0x41e,0x476,0x4ce,0x526,0x57e,0x5d6,0x62e,0x686,0x6de,0x736,0x78e,0x7e6,0x83e,0x896,0x032,0x08a,0x0e2,0x13a,0x192,0x1ea,0x242,0x29a,0x2f2,0x34a,0x3a2,0x3fa,0x452,0x4aa,0x502,0x55a,0x5b2,0x60a },
- { 0x057,0x0af,0x107,0x15f,0x1b7,0x20f,0x267,0x2bf,0x317,0x36f,0x3c7,0x41f,0x477,0x4cf,0x527,0x57f,0x5d7,0x62f,0x687,0x6df,0x737,0x78f,0x7e7,0x83f,0x897,0x033,0x08b,0x0e3,0x13b,0x193,0x1eb,0x243,0x29b,0x2f3,0x34b,0x3a3,0x3fb,0x453,0x4ab,0x503,0x55b,0x5b3,0x60b },
- { 0x0ac,0x104,0x15c,0x1b4,0x20c,0x264,0x2bc,0x314,0x36c,0x3c4,0x41c,0x474,0x4cc,0x524,0x57c,0x5d4,0x62c,0x684,0x6dc,0x734,0x78c,0x7e4,0x83c,0x894,0x030,0x088,0x0e0,0x138,0x190,0x1e8,0x240,0x298,0x2f0,0x348,0x3a0,0x3f8,0x450,0x4a8,0x500,0x558,0x5b0,0x608,0x660 },
- { 0x0ad,0x105,0x15d,0x1b5,0x20d,0x265,0x2bd,0x315,0x36d,0x3c5,0x41d,0x475,0x4cd,0x525,0x57d,0x5d5,0x62d,0x685,0x6dd,0x735,0x78d,0x7e5,0x83d,0x895,0x031,0x089,0x0e1,0x139,0x191,0x1e9,0x241,0x299,0x2f1,0x349,0x3a1,0x3f9,0x451,0x4a9,0x501,0x559,0x5b1,0x609,0x661 },
- { 0x102,0x15a,0x1b2,0x20a,0x262,0x2ba,0x312,0x36a,0x3c2,0x41a,0x472,0x4ca,0x522,0x57a,0x5d2,0x62a,0x682,0x6da,0x732,0x78a,0x7e2,0x83a,0x892,0x02e,0x086,0x0de,0x136,0x18e,0x1e6,0x23e,0x296,0x2ee,0x346,0x39e,0x3f6,0x44e,0x4a6,0x4fe,0x556,0x5ae,0x606,0x65e,0x6b6 },
- { 0x103,0x15b,0x1b3,0x20b,0x263,0x2bb,0x313,0x36b,0x3c3,0x41b,0x473,0x4cb,0x523,0x57b,0x5d3,0x62b,0x683,0x6db,0x733,0x78b,0x7e3,0x83b,0x893,0x02f,0x087,0x0df,0x137,0x18f,0x1e7,0x23f,0x297,0x2ef,0x347,0x39f,0x3f7,0x44f,0x4a7,0x4ff,0x557,0x5af,0x607,0x65f,0x6b7 },
- { 0x158,0x1b0,0x208,0x260,0x2b8,0x310,0x368,0x3c0,0x418,0x470,0x4c8,0x520,0x578,0x5d0,0x628,0x680,0x6d8,0x730,0x788,0x7e0,0x838,0x890,0x02c,0x084,0x0dc,0x134,0x18c,0x1e4,0x23c,0x294,0x2ec,0x344,0x39c,0x3f4,0x44c,0x4a4,0x4fc,0x554,0x5ac,0x604,0x65c,0x6b4,0x70c },
- { 0x159,0x1b1,0x209,0x261,0x2b9,0x311,0x369,0x3c1,0x419,0x471,0x4c9,0x521,0x579,0x5d1,0x629,0x681,0x6d9,0x731,0x789,0x7e1,0x839,0x891,0x02d,0x085,0x0dd,0x135,0x18d,0x1e5,0x23d,0x295,0x2ed,0x345,0x39d,0x3f5,0x44d,0x4a5,0x4fd,0x555,0x5ad,0x605,0x65d,0x6b5,0x70d },
- { 0x1ae,0x206,0x25e,0x2b6,0x30e,0x366,0x3be,0x416,0x46e,0x4c6,0x51e,0x576,0x5ce,0x626,0x67e,0x6d6,0x72e,0x786,0x7de,0x836,0x88e,0x02a,0x082,0x0da,0x132,0x18a,0x1e2,0x23a,0x292,0x2ea,0x342,0x39a,0x3f2,0x44a,0x4a2,0x4fa,0x552,0x5aa,0x602,0x65a,0x6b2,0x70a,0x762 },
- { 0x1af,0x207,0x25f,0x2b7,0x30f,0x367,0x3bf,0x417,0x46f,0x4c7,0x51f,0x577,0x5cf,0x627,0x67f,0x6d7,0x72f,0x787,0x7df,0x837,0x88f,0x02b,0x083,0x0db,0x133,0x18b,0x1e3,0x23b,0x293,0x2eb,0x343,0x39b,0x3f3,0x44b,0x4a3,0x4fb,0x553,0x5ab,0x603,0x65b,0x6b3,0x70b,0x763 },
- { 0x204,0x25c,0x2b4,0x30c,0x364,0x3bc,0x414,0x46c,0x4c4,0x51c,0x574,0x5cc,0x624,0x67c,0x6d4,0x72c,0x784,0x7dc,0x834,0x88c,0x028,0x080,0x0d8,0x130,0x188,0x1e0,0x238,0x290,0x2e8,0x340,0x398,0x3f0,0x448,0x4a0,0x4f8,0x550,0x5a8,0x600,0x658,0x6b0,0x708,0x760,0x7b8 },
- { 0x205,0x25d,0x2b5,0x30d,0x365,0x3bd,0x415,0x46d,0x4c5,0x51d,0x575,0x5cd,0x625,0x67d,0x6d5,0x72d,0x785,0x7dd,0x835,0x88d,0x029,0x081,0x0d9,0x131,0x189,0x1e1,0x239,0x291,0x2e9,0x341,0x399,0x3f1,0x449,0x4a1,0x4f9,0x551,0x5a9,0x601,0x659,0x6b1,0x709,0x761,0x7b9 },
- { 0x25a,0x2b2,0x30a,0x362,0x3ba,0x412,0x46a,0x4c2,0x51a,0x572,0x5ca,0x622,0x67a,0x6d2,0x72a,0x782,0x7da,0x832,0x88a,0x026,0x07e,0x0d6,0x12e,0x186,0x1de,0x236,0x28e,0x2e6,0x33e,0x396,0x3ee,0x446,0x49e,0x4f6,0x54e,0x5a6,0x5fe,0x656,0x6ae,0x706,0x75e,0x7b6,0x80e },
- { 0x25b,0x2b3,0x30b,0x363,0x3bb,0x413,0x46b,0x4c3,0x51b,0x573,0x5cb,0x623,0x67b,0x6d3,0x72b,0x783,0x7db,0x833,0x88b,0x027,0x07f,0x0d7,0x12f,0x187,0x1df,0x237,0x28f,0x2e7,0x33f,0x397,0x3ef,0x447,0x49f,0x4f7,0x54f,0x5a7,0x5ff,0x657,0x6af,0x707,0x75f,0x7b7,0x80f },
- { 0x2b0,0x308,0x360,0x3b8,0x410,0x468,0x4c0,0x518,0x570,0x5c8,0x620,0x678,0x6d0,0x728,0x780,0x7d8,0x830,0x888,0x024,0x07c,0x0d4,0x12c,0x184,0x1dc,0x234,0x28c,0x2e4,0x33c,0x394,0x3ec,0x444,0x49c,0x4f4,0x54c,0x5a4,0x5fc,0x654,0x6ac,0x704,0x75c,0x7b4,0x80c,0x864 },
- { 0x2b1,0x309,0x361,0x3b9,0x411,0x469,0x4c1,0x519,0x571,0x5c9,0x621,0x679,0x6d1,0x729,0x781,0x7d9,0x831,0x889,0x025,0x07d,0x0d5,0x12d,0x185,0x1dd,0x235,0x28d,0x2e5,0x33d,0x395,0x3ed,0x445,0x49d,0x4f5,0x54d,0x5a5,0x5fd,0x655,0x6ad,0x705,0x75d,0x7b5,0x80d,0x865 },
- { 0x306,0x35e,0x3b6,0x40e,0x466,0x4be,0x516,0x56e,0x5c6,0x61e,0x676,0x6ce,0x726,0x77e,0x7d6,0x82e,0x886,0x022,0x07a,0x0d2,0x12a,0x182,0x1da,0x232,0x28a,0x2e2,0x33a,0x392,0x3ea,0x442,0x49a,0x4f2,0x54a,0x5a2,0x5fa,0x652,0x6aa,0x702,0x75a,0x7b2,0x80a,0x862,0x8ba },
- { 0x307,0x35f,0x3b7,0x40f,0x467,0x4bf,0x517,0x56f,0x5c7,0x61f,0x677,0x6cf,0x727,0x77f,0x7d7,0x82f,0x887,0x023,0x07b,0x0d3,0x12b,0x183,0x1db,0x233,0x28b,0x2e3,0x33b,0x393,0x3eb,0x443,0x49b,0x4f3,0x54b,0x5a3,0x5fb,0x653,0x6ab,0x703,0x75b,0x7b3,0x80b,0x863,0x8bb },
- { 0x35c,0x3b4,0x40c,0x464,0x4bc,0x514,0x56c,0x5c4,0x61c,0x674,0x6cc,0x724,0x77c,0x7d4,0x82c,0x884,0x020,0x078,0x0d0,0x128,0x180,0x1d8,0x230,0x288,0x2e0,0x338,0x390,0x3e8,0x440,0x498,0x4f0,0x548,0x5a0,0x5f8,0x650,0x6a8,0x700,0x758,0x7b0,0x808,0x860,0x8b8,0x054 },
- { 0x35d,0x3b5,0x40d,0x465,0x4bd,0x515,0x56d,0x5c5,0x61d,0x675,0x6cd,0x725,0x77d,0x7d5,0x82d,0x885,0x021,0x079,0x0d1,0x129,0x181,0x1d9,0x231,0x289,0x2e1,0x339,0x391,0x3e9,0x441,0x499,0x4f1,0x549,0x5a1,0x5f9,0x651,0x6a9,0x701,0x759,0x7b1,0x809,0x861,0x8b9,0x055 },
- { 0x3b2,0x40a,0x462,0x4ba,0x512,0x56a,0x5c2,0x61a,0x672,0x6ca,0x722,0x77a,0x7d2,0x82a,0x882,0x01e,0x076,0x0ce,0x126,0x17e,0x1d6,0x22e,0x286,0x2de,0x336,0x38e,0x3e6,0x43e,0x496,0x4ee,0x546,0x59e,0x5f6,0x64e,0x6a6,0x6fe,0x756,0x7ae,0x806,0x85e,0x8b6,0x052,0x0aa },
- { 0x3b3,0x40b,0x463,0x4bb,0x513,0x56b,0x5c3,0x61b,0x673,0x6cb,0x723,0x77b,0x7d3,0x82b,0x883,0x01f,0x077,0x0cf,0x127,0x17f,0x1d7,0x22f,0x287,0x2df,0x337,0x38f,0x3e7,0x43f,0x497,0x4ef,0x547,0x59f,0x5f7,0x64f,0x6a7,0x6ff,0x757,0x7af,0x807,0x85f,0x8b7,0x053,0x0ab },
- { 0x408,0x460,0x4b8,0x510,0x568,0x5c0,0x618,0x670,0x6c8,0x720,0x778,0x7d0,0x828,0x880,0x01c,0x074,0x0cc,0x124,0x17c,0x1d4,0x22c,0x284,0x2dc,0x334,0x38c,0x3e4,0x43c,0x494,0x4ec,0x544,0x59c,0x5f4,0x64c,0x6a4,0x6fc,0x754,0x7ac,0x804,0x85c,0x8b4,0x050,0x0a8,0x100 },
- { 0x409,0x461,0x4b9,0x511,0x569,0x5c1,0x619,0x671,0x6c9,0x721,0x779,0x7d1,0x829,0x881,0x01d,0x075,0x0cd,0x125,0x17d,0x1d5,0x22d,0x285,0x2dd,0x335,0x38d,0x3e5,0x43d,0x495,0x4ed,0x545,0x59d,0x5f5,0x64d,0x6a5,0x6fd,0x755,0x7ad,0x805,0x85d,0x8b5,0x051,0x0a9,0x101 },
- { 0x45e,0x4b6,0x50e,0x566,0x5be,0x616,0x66e,0x6c6,0x71e,0x776,0x7ce,0x826,0x87e,0x01a,0x072,0x0ca,0x122,0x17a,0x1d2,0x22a,0x282,0x2da,0x332,0x38a,0x3e2,0x43a,0x492,0x4ea,0x542,0x59a,0x5f2,0x64a,0x6a2,0x6fa,0x752,0x7aa,0x802,0x85a,0x8b2,0x04e,0x0a6,0x0fe,0x156 },
- { 0x45f,0x4b7,0x50f,0x567,0x5bf,0x617,0x66f,0x6c7,0x71f,0x777,0x7cf,0x827,0x87f,0x01b,0x073,0x0cb,0x123,0x17b,0x1d3,0x22b,0x283,0x2db,0x333,0x38b,0x3e3,0x43b,0x493,0x4eb,0x543,0x59b,0x5f3,0x64b,0x6a3,0x6fb,0x753,0x7ab,0x803,0x85b,0x8b3,0x04f,0x0a7,0x0ff,0x157 },
- { 0x4b4,0x50c,0x564,0x5bc,0x614,0x66c,0x6c4,0x71c,0x774,0x7cc,0x824,0x87c,0x018,0x070,0x0c8,0x120,0x178,0x1d0,0x228,0x280,0x2d8,0x330,0x388,0x3e0,0x438,0x490,0x4e8,0x540,0x598,0x5f0,0x648,0x6a0,0x6f8,0x750,0x7a8,0x800,0x858,0x8b0,0x04c,0x0a4,0x0fc,0x154,0x1ac },
- { 0x4b5,0x50d,0x565,0x5bd,0x615,0x66d,0x6c5,0x71d,0x775,0x7cd,0x825,0x87d,0x019,0x071,0x0c9,0x121,0x179,0x1d1,0x229,0x281,0x2d9,0x331,0x389,0x3e1,0x439,0x491,0x4e9,0x541,0x599,0x5f1,0x649,0x6a1,0x6f9,0x751,0x7a9,0x801,0x859,0x8b1,0x04d,0x0a5,0x0fd,0x155,0x1ad },
- { 0x50a,0x562,0x5ba,0x612,0x66a,0x6c2,0x71a,0x772,0x7ca,0x822,0x87a,0x016,0x06e,0x0c6,0x11e,0x176,0x1ce,0x226,0x27e,0x2d6,0x32e,0x386,0x3de,0x436,0x48e,0x4e6,0x53e,0x596,0x5ee,0x646,0x69e,0x6f6,0x74e,0x7a6,0x7fe,0x856,0x8ae,0x04a,0x0a2,0x0fa,0x152,0x1aa,0x202 },
- { 0x50b,0x563,0x5bb,0x613,0x66b,0x6c3,0x71b,0x773,0x7cb,0x823,0x87b,0x017,0x06f,0x0c7,0x11f,0x177,0x1cf,0x227,0x27f,0x2d7,0x32f,0x387,0x3df,0x437,0x48f,0x4e7,0x53f,0x597,0x5ef,0x647,0x69f,0x6f7,0x74f,0x7a7,0x7ff,0x857,0x8af,0x04b,0x0a3,0x0fb,0x153,0x1ab,0x203 },
- { 0x560,0x5b8,0x610,0x668,0x6c0,0x718,0x770,0x7c8,0x820,0x878,0x014,0x06c,0x0c4,0x11c,0x174,0x1cc,0x224,0x27c,0x2d4,0x32c,0x384,0x3dc,0x434,0x48c,0x4e4,0x53c,0x594,0x5ec,0x644,0x69c,0x6f4,0x74c,0x7a4,0x7fc,0x854,0x8ac,0x048,0x0a0,0x0f8,0x150,0x1a8,0x200,0x258 },
- { 0x561,0x5b9,0x611,0x669,0x6c1,0x719,0x771,0x7c9,0x821,0x879,0x015,0x06d,0x0c5,0x11d,0x175,0x1cd,0x225,0x27d,0x2d5,0x32d,0x385,0x3dd,0x435,0x48d,0x4e5,0x53d,0x595,0x5ed,0x645,0x69d,0x6f5,0x74d,0x7a5,0x7fd,0x855,0x8ad,0x049,0x0a1,0x0f9,0x151,0x1a9,0x201,0x259 },
- { 0x5b6,0x60e,0x666,0x6be,0x716,0x76e,0x7c6,0x81e,0x876,0x012,0x06a,0x0c2,0x11a,0x172,0x1ca,0x222,0x27a,0x2d2,0x32a,0x382,0x3da,0x432,0x48a,0x4e2,0x53a,0x592,0x5ea,0x642,0x69a,0x6f2,0x74a,0x7a2,0x7fa,0x852,0x8aa,0x046,0x09e,0x0f6,0x14e,0x1a6,0x1fe,0x256,0x2ae },
- { 0x5b7,0x60f,0x667,0x6bf,0x717,0x76f,0x7c7,0x81f,0x877,0x013,0x06b,0x0c3,0x11b,0x173,0x1cb,0x223,0x27b,0x2d3,0x32b,0x383,0x3db,0x433,0x48b,0x4e3,0x53b,0x593,0x5eb,0x643,0x69b,0x6f3,0x74b,0x7a3,0x7fb,0x853,0x8ab,0x047,0x09f,0x0f7,0x14f,0x1a7,0x1ff,0x257,0x2af },
- { 0x60c,0x664,0x6bc,0x714,0x76c,0x7c4,0x81c,0x874,0x010,0x068,0x0c0,0x118,0x170,0x1c8,0x220,0x278,0x2d0,0x328,0x380,0x3d8,0x430,0x488,0x4e0,0x538,0x590,0x5e8,0x640,0x698,0x6f0,0x748,0x7a0,0x7f8,0x850,0x8a8,0x044,0x09c,0x0f4,0x14c,0x1a4,0x1fc,0x254,0x2ac,0x304 },
- { 0x60d,0x665,0x6bd,0x715,0x76d,0x7c5,0x81d,0x875,0x011,0x069,0x0c1,0x119,0x171,0x1c9,0x221,0x279,0x2d1,0x329,0x381,0x3d9,0x431,0x489,0x4e1,0x539,0x591,0x5e9,0x641,0x699,0x6f1,0x749,0x7a1,0x7f9,0x851,0x8a9,0x045,0x09d,0x0f5,0x14d,0x1a5,0x1fd,0x255,0x2ad,0x305 },
- { 0x662,0x6ba,0x712,0x76a,0x7c2,0x81a,0x872,0x00e,0x066,0x0be,0x116,0x16e,0x1c6,0x21e,0x276,0x2ce,0x326,0x37e,0x3d6,0x42e,0x486,0x4de,0x536,0x58e,0x5e6,0x63e,0x696,0x6ee,0x746,0x79e,0x7f6,0x84e,0x8a6,0x042,0x09a,0x0f2,0x14a,0x1a2,0x1fa,0x252,0x2aa,0x302,0x35a },
- { 0x663,0x6bb,0x713,0x76b,0x7c3,0x81b,0x873,0x00f,0x067,0x0bf,0x117,0x16f,0x1c7,0x21f,0x277,0x2cf,0x327,0x37f,0x3d7,0x42f,0x487,0x4df,0x537,0x58f,0x5e7,0x63f,0x697,0x6ef,0x747,0x79f,0x7f7,0x84f,0x8a7,0x043,0x09b,0x0f3,0x14b,0x1a3,0x1fb,0x253,0x2ab,0x303,0x35b },
- { 0x6b8,0x710,0x768,0x7c0,0x818,0x870,0x00c,0x064,0x0bc,0x114,0x16c,0x1c4,0x21c,0x274,0x2cc,0x324,0x37c,0x3d4,0x42c,0x484,0x4dc,0x534,0x58c,0x5e4,0x63c,0x694,0x6ec,0x744,0x79c,0x7f4,0x84c,0x8a4,0x040,0x098,0x0f0,0x148,0x1a0,0x1f8,0x250,0x2a8,0x300,0x358,0x3b0 },
- { 0x6b9,0x711,0x769,0x7c1,0x819,0x871,0x00d,0x065,0x0bd,0x115,0x16d,0x1c5,0x21d,0x275,0x2cd,0x325,0x37d,0x3d5,0x42d,0x485,0x4dd,0x535,0x58d,0x5e5,0x63d,0x695,0x6ed,0x745,0x79d,0x7f5,0x84d,0x8a5,0x041,0x099,0x0f1,0x149,0x1a1,0x1f9,0x251,0x2a9,0x301,0x359,0x3b1 },
- { 0x70e,0x766,0x7be,0x816,0x86e,0x00a,0x062,0x0ba,0x112,0x16a,0x1c2,0x21a,0x272,0x2ca,0x322,0x37a,0x3d2,0x42a,0x482,0x4da,0x532,0x58a,0x5e2,0x63a,0x692,0x6ea,0x742,0x79a,0x7f2,0x84a,0x8a2,0x03e,0x096,0x0ee,0x146,0x19e,0x1f6,0x24e,0x2a6,0x2fe,0x356,0x3ae,0x406 },
- { 0x70f,0x767,0x7bf,0x817,0x86f,0x00b,0x063,0x0bb,0x113,0x16b,0x1c3,0x21b,0x273,0x2cb,0x323,0x37b,0x3d3,0x42b,0x483,0x4db,0x533,0x58b,0x5e3,0x63b,0x693,0x6eb,0x743,0x79b,0x7f3,0x84b,0x8a3,0x03f,0x097,0x0ef,0x147,0x19f,0x1f7,0x24f,0x2a7,0x2ff,0x357,0x3af,0x407 },
- { 0x764,0x7bc,0x814,0x86c,0x008,0x060,0x0b8,0x110,0x168,0x1c0,0x218,0x270,0x2c8,0x320,0x378,0x3d0,0x428,0x480,0x4d8,0x530,0x588,0x5e0,0x638,0x690,0x6e8,0x740,0x798,0x7f0,0x848,0x8a0,0x03c,0x094,0x0ec,0x144,0x19c,0x1f4,0x24c,0x2a4,0x2fc,0x354,0x3ac,0x404,0x45c },
- { 0x765,0x7bd,0x815,0x86d,0x009,0x061,0x0b9,0x111,0x169,0x1c1,0x219,0x271,0x2c9,0x321,0x379,0x3d1,0x429,0x481,0x4d9,0x531,0x589,0x5e1,0x639,0x691,0x6e9,0x741,0x799,0x7f1,0x849,0x8a1,0x03d,0x095,0x0ed,0x145,0x19d,0x1f5,0x24d,0x2a5,0x2fd,0x355,0x3ad,0x405,0x45d },
- { 0x7ba,0x812,0x86a,0x006,0x05e,0x0b6,0x10e,0x166,0x1be,0x216,0x26e,0x2c6,0x31e,0x376,0x3ce,0x426,0x47e,0x4d6,0x52e,0x586,0x5de,0x636,0x68e,0x6e6,0x73e,0x796,0x7ee,0x846,0x89e,0x03a,0x092,0x0ea,0x142,0x19a,0x1f2,0x24a,0x2a2,0x2fa,0x352,0x3aa,0x402,0x45a,0x4b2 },
- { 0x7bb,0x813,0x86b,0x007,0x05f,0x0b7,0x10f,0x167,0x1bf,0x217,0x26f,0x2c7,0x31f,0x377,0x3cf,0x427,0x47f,0x4d7,0x52f,0x587,0x5df,0x637,0x68f,0x6e7,0x73f,0x797,0x7ef,0x847,0x89f,0x03b,0x093,0x0eb,0x143,0x19b,0x1f3,0x24b,0x2a3,0x2fb,0x353,0x3ab,0x403,0x45b,0x4b3 },
- { 0x810,0x868,0x004,0x05c,0x0b4,0x10c,0x164,0x1bc,0x214,0x26c,0x2c4,0x31c,0x374,0x3cc,0x424,0x47c,0x4d4,0x52c,0x584,0x5dc,0x634,0x68c,0x6e4,0x73c,0x794,0x7ec,0x844,0x89c,0x038,0x090,0x0e8,0x140,0x198,0x1f0,0x248,0x2a0,0x2f8,0x350,0x3a8,0x400,0x458,0x4b0,0x508 },
- { 0x811,0x869,0x005,0x05d,0x0b5,0x10d,0x165,0x1bd,0x215,0x26d,0x2c5,0x31d,0x375,0x3cd,0x425,0x47d,0x4d5,0x52d,0x585,0x5dd,0x635,0x68d,0x6e5,0x73d,0x795,0x7ed,0x845,0x89d,0x039,0x091,0x0e9,0x141,0x199,0x1f1,0x249,0x2a1,0x2f9,0x351,0x3a9,0x401,0x459,0x4b1,0x509 },
- { 0x866,0x002,0x05a,0x0b2,0x10a,0x162,0x1ba,0x212,0x26a,0x2c2,0x31a,0x372,0x3ca,0x422,0x47a,0x4d2,0x52a,0x582,0x5da,0x632,0x68a,0x6e2,0x73a,0x792,0x7ea,0x842,0x89a,0x036,0x08e,0x0e6,0x13e,0x196,0x1ee,0x246,0x29e,0x2f6,0x34e,0x3a6,0x3fe,0x456,0x4ae,0x506,0x55e },
- { 0x867,0x003,0x05b,0x0b3,0x10b,0x163,0x1bb,0x213,0x26b,0x2c3,0x31b,0x373,0x3cb,0x423,0x47b,0x4d3,0x52b,0x583,0x5db,0x633,0x68b,0x6e3,0x73b,0x793,0x7eb,0x843,0x89b,0x037,0x08f,0x0e7,0x13f,0x197,0x1ef,0x247,0x29f,0x2f7,0x34f,0x3a7,0x3ff,0x457,0x4af,0x507,0x55f }
-};
+ uint64_t v;
+ memcpy(&v, p, 8);
+ return v;
+}
+
+// eight bytes, each multiplied by 2 in GF(2^8)
+inline uint64_t ecc_mul2x8(uint64_t a)
+{
+ uint64_t const high = a & 0x8080808080808080ull;
+ return ((a & 0x7f7f7f7f7f7f7f7full) << 1) ^ ((high >> 7) * 0x1d);
+}
+} // anonymous namespace
-//-------------------------------------------------
-// ecc_source_byte - return data from the sector
-// at the given offset, masking anything
-// particular to a mode
-//-------------------------------------------------
+/**
+ * @fn void ecc_region(const uint8_t *sector, uint8_t *region)
+ *
+ * @brief Discpress: what the ECC covers of a sector, from its header (zeros in Mode 2) up to
+ * and including its P.
+ */
-uint8_t cdrom_file::ecc_source_byte(const uint8_t *sector, uint32_t offset)
+void cdrom_file::ecc_region(const uint8_t *sector, uint8_t *region)
{
- // in mode 2 always treat these as 0 bytes
- return (sector[MODE_OFFSET] == 2 && offset < 4) ? 0x00 : sector[SYNC_OFFSET + SYNC_NUM_BYTES + offset];
+ if (sector[MODE_OFFSET] == 2)
+ memset(region, 0, 4);
+ else
+ memcpy(region, §or[SYNC_OFFSET + SYNC_NUM_BYTES], 4);
+ memcpy(region + 4, §or[SYNC_OFFSET + SYNC_NUM_BYTES + 4], ECC_REGION_BYTES - 4);
}
/**
- * @fn void ecc_compute_bytes(const uint8_t *sector, const uint16_t *row, int rowlen, uint8_t &val1, uint8_t &val2)
+ * @fn void ecc_parity(const uint8_t *region, bool q, uint8_t *parity)
*
- * @brief -------------------------------------------------
- * ecc_compute_bytes - calculate an ECC value (P or Q)
- * -------------------------------------------------.
- *
- * @param sector The sector.
- * @param row The row.
- * @param rowlen The rowlen.
- * @param [in,out] val1 The first value.
- * @param [in,out] val2 The second value.
+ * @brief Discpress: the P (2 x 86 bytes) or Q (2 x 52 bytes) parity of a region.
*/
-void cdrom_file::ecc_compute_bytes(const uint8_t *sector, const uint16_t *row, int rowlen, uint8_t &val1, uint8_t &val2)
+void cdrom_file::ecc_parity(const uint8_t *region, bool q, uint8_t *parity)
{
- val1 = val2 = 0;
- for (int component = 0; component < rowlen; component++)
+ uint64_t a[11] = { }, b[11] = { };
+ int const count = q ? ECC_Q_NUM_BYTES : ECC_P_NUM_BYTES;
+ if (!q)
{
- val1 ^= ecc_source_byte(sector, row[component]);
- val2 ^= ecc_source_byte(sector, row[component]);
- val1 = ecclow[val1];
+ // the columns of the first 24 rows (a row's last word also reads 2 bytes of the next row, unused)
+ for (int row = 0; row < ECC_P_COMP; row++)
+ for (int l = 0; l < 11; l++)
+ {
+ uint64_t const x = ecc_load64(®ion[ECC_P_NUM_BYTES * row + 8 * l]);
+ a[l] = ecc_mul2x8(a[l] ^ x);
+ b[l] ^= x;
+ }
+ }
+ else
+ {
+ uint8_t v[56] = { };
+ for (int col = 0; col < ECC_Q_COMP; col++)
+ {
+ // diagonal d crosses this column at row d + col: the column's words in that order
+ int row = col % 26;
+ for (int d = 0; d < 26; d++)
+ {
+ memcpy(&v[2 * d], ®ion[ECC_P_NUM_BYTES * row + 2 * col], 2);
+ if (++row == 26)
+ row = 0;
+ }
+ for (int l = 0; l < 7; l++)
+ {
+ uint64_t const x = ecc_load64(&v[8 * l]);
+ a[l] = ecc_mul2x8(a[l] ^ x);
+ b[l] ^= x;
+ }
+ }
+ }
+ uint8_t av[sizeof(a)], bv[sizeof(b)];
+ memcpy(av, a, sizeof(a));
+ memcpy(bv, b, sizeof(b));
+ for (int i = 0; i < count; i++)
+ {
+ uint8_t const p0 = ecchigh[ecclow[av[i]] ^ bv[i]];
+ parity[i] = p0;
+ parity[count + i] = p0 ^ bv[i];
}
- val1 = ecchigh[ecclow[val1] ^ val2];
- val2 ^= val1;
}
/**
@@ -1367,24 +1293,17 @@
bool cdrom_file::ecc_verify(const uint8_t *sector)
{
+ uint8_t region[ECC_REGION_BYTES], parity[2 * ECC_P_NUM_BYTES];
+ ecc_region(sector, region);
+
// first verify P bytes
- for (int byte = 0; byte < ECC_P_NUM_BYTES; byte++)
- {
- uint8_t val1, val2;
- ecc_compute_bytes(sector, poffsets[byte], ECC_P_COMP, val1, val2);
- if (sector[ECC_P_OFFSET + byte] != val1 || sector[ECC_P_OFFSET + ECC_P_NUM_BYTES + byte] != val2)
- return false;
- }
+ ecc_parity(region, false, parity);
+ if (memcmp(parity, §or[ECC_P_OFFSET], 2 * ECC_P_NUM_BYTES) != 0)
+ return false;
// then verify Q bytes
- for (int byte = 0; byte < ECC_Q_NUM_BYTES; byte++)
- {
- uint8_t val1, val2;
- ecc_compute_bytes(sector, qoffsets[byte], ECC_Q_COMP, val1, val2);
- if (sector[ECC_Q_OFFSET + byte] != val1 || sector[ECC_Q_OFFSET + ECC_Q_NUM_BYTES + byte] != val2)
- return false;
- }
- return true;
+ ecc_parity(region, true, parity);
+ return memcmp(parity, §or[ECC_Q_OFFSET], 2 * ECC_Q_NUM_BYTES) == 0;
}
/**
@@ -1400,13 +1319,13 @@
void cdrom_file::ecc_generate(uint8_t *sector)
{
- // first verify P bytes
- for (int byte = 0; byte < ECC_P_NUM_BYTES; byte++)
- ecc_compute_bytes(sector, poffsets[byte], ECC_P_COMP, sector[ECC_P_OFFSET + byte], sector[ECC_P_OFFSET + ECC_P_NUM_BYTES + byte]);
+ uint8_t region[ECC_REGION_BYTES];
+ ecc_region(sector, region);
- // then verify Q bytes
- for (int byte = 0; byte < ECC_Q_NUM_BYTES; byte++)
- ecc_compute_bytes(sector, qoffsets[byte], ECC_Q_COMP, sector[ECC_Q_OFFSET + byte], sector[ECC_Q_OFFSET + ECC_Q_NUM_BYTES + byte]);
+ // first P, then Q, which covers the new P
+ ecc_parity(region, false, §or[ECC_P_OFFSET]);
+ memcpy(®ion[ECC_REGION_BYTES - 2 * ECC_P_NUM_BYTES], §or[ECC_P_OFFSET], 2 * ECC_P_NUM_BYTES);
+ ecc_parity(region, true, §or[ECC_Q_OFFSET]);
}
/**
@@ -1578,22 +1497,25 @@
int cdrom_file::msf_to_frames(const char *token)
{
- int m = 0;
- int s = 0;
- int f = 0;
-
- if (sscanf(token, "%d:%d:%d", &m, &s, &f) == 1)
- {
- f = m;
- }
- else
- {
- // convert to just frames
- s += (m * 60);
- f += (s * 75);
+ // Discpress: as sscanf("%d:%d:%d") read it, but in 64 bits and clamped to an int (0.289 overflowed, which C
+ // leaves undefined, on a field or a total past 2^31)
+ long long field[3] = { 0, 0, 0 };
+ int fields = 0;
+ for (const char *p = token; fields < 3; )
+ {
+ char *end;
+ errno = 0;
+ long long const v = strtoll(p, &end, 10);
+ if (end == p)
+ break;
+ field[fields++] = std::clamp(v, -(1LL << 40), 1LL << 40); // (strtoll clamps on ERANGE)
+ if (*end != ':')
+ break;
+ p = end + 1;
}
- return f;
+ long long const f = (fields == 1) ? field[0] : (field[0] * 60 + field[1]) * 75 + field[2];
+ return int(std::clamp<long long>(f, INT_MIN, INT_MAX));
}
/*-------------------------------------------------
@@ -1604,17 +1526,18 @@
-------------------------------------------------*/
/**
- * @fn static uint32_t parse_wav_sample(std::string_view filename, uint32_t *dataoffs)
+ * @fn static uint32_t parse_wav_sample(std::string_view filename, uint32_t *dataoffs, uint64_t start)
*
* @brief Parse WAV sample.
*
* @param filename Filename of the file.
* @param [in,out] dataoffs If non-null, the dataoffs.
+ * @param start Discpress: where the WAVE file starts in the file (a cdrdao TOC's #offset).
*
* @return An uint32_t.
*/
-uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *dataoffs)
+uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *dataoffs, uint64_t start)
{
unsigned long offset = 0;
uint32_t length, rate, filesize;
@@ -1633,7 +1556,7 @@
}
/* read the core header and make sure it's a WAVE file */
- file->read(buf, 0, 4, actual);
+ file->read(buf, start, 4, actual); // Discpress: from where the WAVE file starts (0.289: 0)
offset += actual;
if (offset < 4)
{
@@ -1647,7 +1570,7 @@
}
/* get the total size */
- file->read(&filesize, offset, 4, actual);
+ file->read(&filesize, start + offset, 4, actual);
offset += actual;
if (offset < 8)
{
@@ -1657,7 +1580,7 @@
filesize = little_endianize_int32(filesize);
/* read the RIFF file type and make sure it's a WAVE file */
- file->read(buf, offset, 4, actual);
+ file->read(buf, start + offset, 4, actual);
offset += actual;
if (offset < 12)
{
@@ -1673,9 +1596,9 @@
/* seek until we find a format tag */
while (true)
{
- file->read(buf, offset, 4, actual);
+ file->read(buf, start + offset, 4, actual);
offset += actual;
- file->read(&length, offset, 4, actual);
+ file->read(&length, start + offset, 4, actual);
offset += actual;
length = little_endianize_int32(length);
if (memcmp(&buf[0], "fmt ", 4) == 0)
@@ -1691,7 +1614,7 @@
}
/* read the format -- make sure it is PCM */
- file->read(&temp16, offset, 2, actual);
+ file->read(&temp16, start + offset, 2, actual);
offset += actual;
temp16 = little_endianize_int16(temp16);
if (temp16 != 1)
@@ -1701,7 +1624,7 @@
}
/* number of channels -- only stereo is supported */
- file->read(&temp16, offset, 2, actual);
+ file->read(&temp16, start + offset, 2, actual);
offset += actual;
temp16 = little_endianize_int16(temp16);
if (temp16 != 2)
@@ -1711,7 +1634,7 @@
}
/* sample rate */
- file->read(&rate, offset, 4, actual);
+ file->read(&rate, start + offset, 4, actual);
offset += actual;
rate = little_endianize_int32(rate);
if (rate != 44100)
@@ -1721,11 +1644,11 @@
}
/* bytes/second and block alignment are ignored */
- file->read(buf, offset, 6, actual);
+ file->read(buf, start + offset, 6, actual);
offset += actual;
/* bits/sample */
- file->read(&bits, offset, 2, actual);
+ file->read(&bits, start + offset, 2, actual);
offset += actual;
bits = little_endianize_int16(bits);
if (bits != 16)
@@ -1740,9 +1663,9 @@
/* seek until we find a data tag */
while (true)
{
- file->read(buf, offset, 4, actual);
+ file->read(buf, start + offset, 4, actual);
offset += actual;
- file->read(&length, offset, 4, actual);
+ file->read(&length, start + offset, 4, actual);
offset += actual;
length = little_endianize_int32(length);
if (memcmp(&buf[0], "data", 4) == 0)
@@ -1764,7 +1687,12 @@
return 0;
}
- *dataoffs = offset;
+ if (start + offset > UINT32_MAX) // Discpress: dataoffs is 32 bits
+ {
+ osd_printf_error("ERROR: samples too far into the file (%s)\n", fname);
+ return 0;
+ }
+ *dataoffs = uint32_t(start + offset);
return length;
}
@@ -1864,11 +1792,11 @@
outtoc.numsessions = 1;
- // seek to 12 bytes before the end
+ // seek to 12 bytes before the end (Discpress: a file shorter than that isn't one either)
+ fseek(infile, 0, SEEK_END);
+ uint64_t const filesize = ftell(infile);
fseek(infile, -12, SEEK_END);
- fread(buffer, 12, 1, infile);
-
- if (memcmp(buffer, "NER5", 4))
+ if (filesize < 12 || fread(buffer, 12, 1, infile) != 1 || memcmp(buffer, "NER5", 4))
{
osd_printf_error("ERROR: Not a Nero 5.5 or later image!\n");
fclose(infile);
@@ -1888,8 +1816,13 @@
while (!done)
{
- fseek(infile, chain_offs, SEEK_SET);
- fread(buffer, 8, 1, infile);
+ // Discpress: a chain of chunks without END!, or one that leaves the file, made 0.289 loop forever
+ if (uint64_t(chain_offs) + 8 > filesize || fseek(infile, chain_offs, SEEK_SET) || fread(buffer, 8, 1, infile) != 1)
+ {
+ osd_printf_error("ERROR: NRG image's chunks end without an END! chunk\n");
+ fclose(infile);
+ return chd_file::error::INVALID_DATA;
+ }
chunk_size = get_u32be(&buffer[4]);
@@ -1901,15 +1834,20 @@
// skip second chunk size and UPC code
fseek(infile, 20, SEEK_CUR);
- uint8_t start, end;
+ uint8_t start = 0, end = 0; // Discpress: 0 (refused below) if the file ends here
fread(&start, 1, 1, infile);
fread(&end, 1, 1, infile);
// printf("Start track %d End track: %d\n", start, end);
outtoc.numtrks = (end-start) + 1;
+ if (start < 1 || end < start || outtoc.numtrks > MAX_TRACKS || end > MAX_TRACKS) // Discpress: 0.289 wrote outside its track table
+ {
+ fclose(infile);
+ osd_printf_error("ERROR: NRG image has tracks %d to %d, only 1 to %d are possible\n", start, end, MAX_TRACKS);
+ return chd_file::error::INVALID_DATA;
+ }
- uint32_t offset = 0;
for (int track = start; track <= end; track++)
{
uint32_t size, mode;
@@ -1925,7 +1863,15 @@
// printf("Track %d: sector size %d mode %x index0 %llx index1 %llx track_end %llx (pregap %d sectors, length %d sectors)\n", track, size, mode, index0, index1, track_end, (uint32_t)(index1-index0)/size, (uint32_t)(track_end-index1)/size);
outinfo.track[track-1].fname.assign(tocfname);
- outinfo.track[track-1].offset = offset + (uint32_t)(index1-index0);
+ // Discpress: the track starts at its INDEX 00, where the image holds it (0.289 added up the
+ // lengths of the tracks before it without their pregaps, wrong after a track with one)
+ if (index0 > index1 || index1 > track_end || track_end > filesize)
+ {
+ osd_printf_error("ERROR: NRG image's track %d isn't in the file\n", track);
+ fclose(infile);
+ return chd_file::error::INVALID_DATA;
+ }
+ outinfo.track[track-1].offset = (uint32_t)index0;
outinfo.track[track-1].idx[0] = outinfo.track[track-1].idx[1] = 0;
switch (mode)
@@ -1976,21 +1922,29 @@
return chd_file::error::UNSUPPORTED_FORMAT;
}
+ // Discpress: the sector size the mode has (0.289 divided by a size of 0, and read one larger than a
+ // frame into it, a heap overflow)
+ if (size != ((mode == 0x0000) ? 2048 : 2352))
+ {
+ osd_printf_error("ERROR: NRG image's track %d has sectors of %d bytes, which its mode (%x) doesn't have\n", track, size, mode);
+ fclose(infile);
+ return chd_file::error::INVALID_DATA;
+ }
outtoc.tracks[track-1].datasize = size;
outtoc.tracks[track-1].subtype = CD_SUB_NONE;
outtoc.tracks[track-1].subsize = 0;
outtoc.tracks[track-1].pregap = (uint32_t)(index1-index0)/size;
- outtoc.tracks[track-1].frames = (uint32_t)(track_end-index1)/size;
+ // Discpress: the pregap's frames (INDEX 00 to 01) are the image's, stored with the track as a cue
+ // sheet's INDEX 00 in the same file is (0.289 dropped them and made the pregap virtual)
+ outtoc.tracks[track-1].frames = (uint32_t)(track_end-index0)/size;
outtoc.tracks[track-1].postgap = 0;
- outtoc.tracks[track-1].pgtype = 0;
+ outtoc.tracks[track-1].pgtype = outtoc.tracks[track-1].pregap ? outtoc.tracks[track-1].trktype : 0;
outtoc.tracks[track-1].pgsub = CD_SUB_NONE;
- outtoc.tracks[track-1].pgdatasize = 0;
+ outtoc.tracks[track-1].pgdatasize = outtoc.tracks[track-1].pregap ? size : 0;
outtoc.tracks[track-1].pgsubsize = 0;
outtoc.tracks[track-1].padframes = 0;
-
- offset += (uint32_t)track_end-index1;
}
}
@@ -2000,7 +1954,15 @@
}
else
{
- chain_offs += chunk_size + 8;
+ // Discpress: each chunk must lead further into the file
+ uint64_t const next = uint64_t(chain_offs) + chunk_size + 8;
+ if (next + 8 > filesize || next > UINT32_MAX)
+ {
+ osd_printf_error("ERROR: NRG image's chunks end without an END! chunk\n");
+ fclose(infile);
+ return chd_file::error::INVALID_DATA;
+ }
+ chain_offs = uint32_t(next);
}
}
@@ -2042,6 +2004,20 @@
outinfo.reset();
uint64_t size = get_file_size(tocfname);
+
+ // Discpress: an image of 2352-byte sectors opens with a sector's sync pattern and a mode byte of 1 or 2, and so
+ // does its second sector. 0.289 typed it by its size alone, so one of 128 sectors times any number (a size
+ // that is also a multiple of 2048) came out as 2048-byte sectors, and one of 146 (a multiple of 2336) as
+ // 2336-byte sectors.
+ bool raw = false;
+ if ((size % 2352) == 0 && size >= 2 * 2352)
+ {
+ static const uint8_t sync[12] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
+ uint8_t sector[2 * 2352];
+ raw = fread(sector, sizeof(sector), 1, infile) == 1;
+ for (int s = 0; raw && s < 2; s++)
+ raw = !memcmp(§or[s * 2352], sync, sizeof(sync)) && (sector[s * 2352 + 15] == 1 || sector[s * 2352 + 15] == 2);
+ }
fclose(infile);
@@ -2052,7 +2028,15 @@
outinfo.track[0].offset = 0;
outinfo.track[0].idx[0] = outinfo.track[0].idx[1] = 0;
- if ((size % 2048) == 0)
+ if (raw)
+ {
+ // 2352 byte mode 2 raw, as 0.289 types every other image of 2352-byte sectors
+ outtoc.tracks[0].trktype = CD_TRACK_MODE2_RAW;
+ outtoc.tracks[0].frames = size / 2352;
+ outtoc.tracks[0].datasize = 2352;
+ outinfo.track[0].swap = false;
+ }
+ else if ((size % 2048) == 0)
{
outtoc.tracks[0].trktype = CD_TRACK_MODE1;
outtoc.tracks[0].frames = size / 2048;
@@ -2149,7 +2133,7 @@
const int numtracks = atoi(token);
- if (numtracks > 0 && numtracks - 1 > std::size(outinfo.track))
+ if (numtracks > int(std::size(outinfo.track))) // Discpress: 0.289 let 100 tracks through, then aborted
{
osd_printf_error("GDI expects too many tracks. Expected %d tracks but only up to %zu tracks allowed\n", numtracks, std::size(outinfo.track) + 1);
return chd_file::error::INVALID_DATA;
@@ -2178,7 +2162,7 @@
paramcnt++;
const int trknum = atoi(token) - 1;
- if (trknum < 0 || trknum > std::size(outinfo.track) || trknum + 1 > numtracks)
+ if (trknum < 0 || trknum >= int(std::size(outinfo.track)) || trknum + 1 > numtracks)
{
osd_printf_error("Track %d is out of expected range of 1 to %d\n", trknum + 1, numtracks);
return chd_file::error::INVALID_DATA;
@@ -2222,7 +2206,7 @@
outtoc.tracks[trknum].trktype = CD_TRACK_MODE1;
outtoc.tracks[trknum].datasize = 2048;
}
- else if (trktype == 0)
+ else if (trktype == 0 && trksize > 0) // Discpress: 0.289 divided by a size of 0
{
outtoc.tracks[trknum].trktype = CD_TRACK_AUDIO;
outtoc.tracks[trknum].datasize = 2352;
@@ -2252,6 +2236,12 @@
outinfo.track[trknum].fname.assign(path).append(token);
const uint64_t sz = get_file_size(outinfo.track[trknum].fname);
+ if (sz == 0) // Discpress: as a cue sheet does (0.289 made the track empty and went on)
+ {
+ fclose(infile);
+ osd_printf_error("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum].fname);
+ return std::errc::no_such_file_or_directory;
+ }
outtoc.tracks[trknum].frames = sz / trksize;
outtoc.tracks[trknum].padframes = 0;
@@ -2345,6 +2335,19 @@
enum gdi_area current_area = SINGLE_DENSITY;
bool is_multibin = false;
int leadin = -1;
+ // Discpress: per track, whether its FILE is MOTOROLA (big-endian audio), and where the samples of its
+ // .WAV file are (offset, length; 0 for other files), for .WAV files that hold several tracks
+ bool curmotorola = false;
+ uint32_t curwavoffs = 0, curwavlen = 0;
+ std::vector<bool> motorola(MAX_TRACKS + 1);
+ std::vector<std::pair<uint32_t, uint32_t> > wavdata(MAX_TRACKS + 1);
+ // Discpress: for a track whose INDEX 01 is in a later FILE than its TRACK, the FILE it started in: where its
+ // INDEX 00 is in it (-1: none), that file's .WAV samples and byte order, and the pregap's frames in it
+ struct pregap_file { std::string fname; int32_t idx0 = -1; std::pair<uint32_t, uint32_t> wavdata; bool motorola = false; uint64_t start = 0; uint32_t frames = 0; };
+ std::vector<pregap_file> pgfile(MAX_TRACKS + 1);
+ // Discpress: per track, its last INDEX: number, frames and FILE
+ struct index_seen { int number = -1; int frames = 0; std::string fname; };
+ std::vector<index_seen> lastindex(MAX_TRACKS + 1);
FILE *infile = fopen(path.c_str(), "rt");
if (!infile)
@@ -2371,6 +2374,14 @@
char linebuffer[512];
memset(linebuffer, 0, sizeof(linebuffer));
+ // Discpress: a line that belongs to a track, before the first TRACK, wrote before the track table
+ auto const before_track = [&infile] (const char *what)
+ {
+ fclose(infile);
+ osd_printf_error("ERROR: %s before the first TRACK\n", what);
+ return std::error_condition(chd_file::error::INVALID_DATA);
+ };
+
while (!feof(infile))
{
/* get the next line */
@@ -2427,6 +2438,8 @@
/* get lead-out time */
TOKENIZE
int leadout_offset = msf_to_frames(token);
+ if (trknum < 0)
+ return before_track("REM LEAD-OUT");
outinfo.track[trknum].leadout = leadout_offset;
}
else if (!strncmp(linebuffer+i, "LEAD-IN", 7))
@@ -2472,13 +2485,18 @@
/* get the file type */
TOKENIZE
+ curmotorola = false;
+ curwavoffs = curwavlen = 0;
if (!strcmp(token, "BINARY"))
{
- outinfo.track[trknum+1].swap = false;
+ if (trknum + 1 < int(MAX_TRACKS))
+ outinfo.track[trknum+1].swap = false;
}
else if (!strcmp(token, "MOTOROLA"))
{
- outinfo.track[trknum+1].swap = true;
+ if (trknum + 1 < int(MAX_TRACKS))
+ outinfo.track[trknum+1].swap = true;
+ curmotorola = true;
}
else if (!strcmp(token, "WAVE"))
{
@@ -2489,6 +2507,8 @@
osd_printf_error("ERROR: couldn't read [%s] or not a valid .WAV\n", lastfname);
return chd_file::error::INVALID_DATA;
}
+ curwavoffs = wavoffs;
+ curwavlen = wavlen;
}
else
{
@@ -2502,6 +2522,12 @@
/* get the track number */
TOKENIZE
trknum = strtoul(token, nullptr, 10) - 1;
+ if (trknum < 0 || trknum >= int(MAX_TRACKS)) // Discpress: TRACK 00 or 100 wrote outside the track table
+ {
+ fclose(infile);
+ osd_printf_error("ERROR: track number %s is not between 1 and %d\n", token, MAX_TRACKS);
+ return chd_file::error::INVALID_DATA;
+ }
/* next token on the line is the track type */
TOKENIZE
@@ -2541,6 +2567,10 @@
outinfo.track[trknum].offset = wavoffs;
wavoffs = wavlen = 0;
}
+ motorola[trknum] = curmotorola;
+ wavdata[trknum] = std::make_pair(curwavoffs, curwavlen);
+ pgfile[trknum] = pregap_file();
+ lastindex[trknum] = index_seen();
outinfo.track[trknum].fname.assign(lastfname); /* default filename to the last one */
@@ -2586,6 +2616,54 @@
osd_printf_error("ERROR: encountered invalid index %d\n", idx);
return chd_file::error::INVALID_DATA;
}
+ if (trknum < 0)
+ return before_track("INDEX");
+
+ // Discpress: a track's INDEX points in order, and not back in the same file (0.289 made a negative
+ // pregap or length of them)
+ index_seen &seen = lastindex[trknum];
+ if (idx <= seen.number || (frames < seen.frames && lastfname == seen.fname) || frames < 0)
+ {
+ fclose(infile);
+ osd_printf_error("ERROR: track %d's INDEX %02d is out of order\n", trknum + 1, idx);
+ return chd_file::error::INVALID_DATA;
+ }
+ seen = index_seen { idx, frames, lastfname };
+
+ // Discpress: INDEX 01 in a later FILE than the track's TRACK: the track is in that file, and its
+ // pregap starts at its INDEX 00 in the previous one (EAC's "gaps appended to the previous track").
+ // 0.289 read the track from the previous file at the new file's INDEX points.
+ if (idx == 0)
+ {
+ pgfile[trknum].fname.assign(lastfname);
+ pgfile[trknum].wavdata = std::make_pair(curwavoffs, curwavlen);
+ pgfile[trknum].motorola = curmotorola;
+ }
+ if (idx == 1 && outinfo.track[trknum].fname != lastfname)
+ {
+ if (outinfo.track[trknum].idx[0] != -1 && pgfile[trknum].fname != lastfname)
+ {
+ if (is_gdrom || outtoc.tracks[trknum].pregap != 0)
+ {
+ fclose(infile);
+ osd_printf_error("ERROR: track %d's INDEX 00 and INDEX 01 are in different FILEs, which isn't supported %s\n", trknum + 1, is_gdrom ? "for a GD-ROM" : "with PREGAP");
+ return chd_file::error::UNSUPPORTED_FORMAT;
+ }
+ pgfile[trknum].idx0 = outinfo.track[trknum].idx[0];
+ // the rest of the pregap is at the start of the new file
+ outinfo.track[trknum].idx[0] = 0;
+ }
+ outinfo.track[trknum].fname.assign(lastfname);
+ motorola[trknum] = curmotorola;
+ wavdata[trknum] = std::make_pair(curwavoffs, curwavlen);
+ if (wavlen != 0)
+ {
+ // the new file's samples, as TRACK takes them after a WAVE file
+ outtoc.tracks[trknum].frames = wavlen/2352;
+ outinfo.track[trknum].offset = wavoffs;
+ wavoffs = wavlen = 0;
+ }
+ }
outinfo.track[trknum].idx[idx] = frames;
@@ -2610,6 +2688,8 @@
/* get index */
TOKENIZE
frames = msf_to_frames(token);
+ if (trknum < 0)
+ return before_track("PREGAP");
outtoc.tracks[trknum].pregap = frames;
}
@@ -2620,11 +2700,15 @@
/* get index */
TOKENIZE
frames = msf_to_frames(token);
+ if (trknum < 0)
+ return before_track("POSTGAP");
outtoc.tracks[trknum].postgap = frames;
}
else if (!strcmp(token, "FLAGS"))
{
+ if (trknum < 0)
+ return before_track("FLAGS");
outtoc.tracks[trknum].control_flags = 0;
/* keep looping over remaining tokens in FLAGS line until there's no more to read */
@@ -2667,18 +2751,51 @@
}
/* this is true for cue/bin and cue/iso, and we need it for cue/wav since .WAV is little-endian */
+ /* Discpress: but not for MOTOROLA files, whose audio is big-endian already (0.289 swapped it) */
if (outtoc.tracks[trknum].trktype == CD_TRACK_AUDIO)
{
- outinfo.track[trknum].swap = true;
+ outinfo.track[trknum].swap = !motorola[trknum];
+ }
+ else
+ {
+ /* Discpress: and never data, which isn't samples (0.289 swapped a data track that opens a MOTOROLA file) */
+ outinfo.track[trknum].swap = false;
+ }
+
+ const bool sameasprev = trknum > 0 && outinfo.track[trknum].fname.compare(outinfo.track[trknum-1].fname) == 0;
+ const bool sameasnext = trknum + 1 < outtoc.numtrks && outinfo.track[trknum].fname.compare(outinfo.track[trknum+1].fname) == 0;
+
+ // Discpress: the next track in the same file starts after this one (0.289 made a negative length of it)
+ if (sameasnext && outinfo.track[trknum+1].idx[1] != -1 && outinfo.track[trknum+1].idx[0] <= outinfo.track[trknum].idx[0])
+ {
+ osd_printf_error("ERROR: track %d starts where track %d does, or before\n", trknum + 2, trknum + 1);
+ return chd_file::error::INVALID_DATA;
+ }
+
+ /* Discpress: a .WAV file with several tracks is split at their INDEX points, like a .bin (0.289 gave
+ the first track all of its samples and read the others from past its end) */
+ if (wavdata[trknum].second != 0 && (sameasprev || sameasnext))
+ {
+ const uint32_t framesize = outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize;
+ if (sameasprev)
+ outinfo.track[trknum].offset = outinfo.track[trknum-1].offset + outtoc.tracks[trknum-1].frames * (outtoc.tracks[trknum-1].datasize + outtoc.tracks[trknum-1].subsize);
+ else
+ outinfo.track[trknum].offset = wavdata[trknum].first + outinfo.track[trknum].idx[0] * framesize;
+ if (sameasnext)
+ outtoc.tracks[trknum].frames = outinfo.track[trknum+1].idx[0] - outinfo.track[trknum].idx[0];
+ else
+ outtoc.tracks[trknum].frames = (wavdata[trknum].first + wavdata[trknum].second - outinfo.track[trknum].offset) / framesize;
+ continue;
}
/* don't do this for .WAV tracks, we already have their length and offset filled out */
if (outinfo.track[trknum].offset != 0)
continue;
- if (trknum+1 >= outtoc.numtrks && trknum > 0 && (outinfo.track[trknum].fname.compare(outinfo.track[trknum-1].fname) == 0))
+ if (sameasprev && !sameasnext)
{
/* if the last track's filename is the same as the previous track */
+ /* Discpress: or the last track in a file that another file follows (0.289 gave it the whole file) */
tlen = get_file_size(outinfo.track[trknum].fname);
if (tlen == 0)
{
@@ -2700,11 +2817,17 @@
return chd_file::error::INVALID_DATA;
}
- if (trknum > 0)
+ if (sameasprev)
{
const uint32_t previous_track_raw_size = outtoc.tracks[trknum-1].frames * (outtoc.tracks[trknum-1].datasize + outtoc.tracks[trknum-1].subsize);
outinfo.track[trknum].offset = outinfo.track[trknum-1].offset + previous_track_raw_size;
}
+ else if (trknum > 0)
+ {
+ /* Discpress: the first of several tracks in a file that follows another file starts at its own
+ INDEX point in it (0.289 went on from the previous file's offsets) */
+ outinfo.track[trknum].offset = outinfo.track[trknum].idx[0] * (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize);
+ }
}
else if (outtoc.tracks[trknum].frames == 0)
{
@@ -2777,6 +2900,77 @@
}
}
+ /* Discpress: a track whose INDEX 00 is in the FILE before its INDEX 01's: its pregap runs from there to the end
+ of that file, which the track before it no longer takes; the track is then read in two pieces, as one file of
+ both would give it */
+ for (trknum = 0; trknum < outtoc.numtrks; trknum++)
+ {
+ pregap_file &pg = pgfile[trknum];
+ if (pg.idx0 == -1)
+ continue;
+ const uint32_t framesize = outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize;
+ uint64_t end;
+ if (pg.wavdata.second != 0)
+ {
+ pg.start = pg.wavdata.first + uint64_t(pg.idx0) * framesize;
+ end = uint64_t(pg.wavdata.first) + pg.wavdata.second;
+ }
+ else
+ {
+ pg.start = uint64_t(pg.idx0) * framesize;
+ end = get_file_size(pg.fname);
+ if (end == 0)
+ {
+ osd_printf_error("ERROR: couldn't find bin file [%s]\n", pg.fname);
+ return std::errc::no_such_file_or_directory;
+ }
+ }
+ pg.frames = end > pg.start ? (end - pg.start) / framesize : 0;
+ if (trknum > 0 && outinfo.track[trknum-1].fname == pg.fname)
+ {
+ track_info &prev = outtoc.tracks[trknum-1];
+ const uint32_t prevsize = prev.datasize + prev.subsize;
+ if (pg.start < outinfo.track[trknum-1].offset)
+ {
+ osd_printf_error("ERROR: track %d's INDEX 00 is before track %d\n", trknum+1, trknum);
+ return chd_file::error::INVALID_DATA;
+ }
+ prev.frames = (pg.start - outinfo.track[trknum-1].offset) / prevsize;
+ }
+ }
+ for (trknum = 0; trknum < outtoc.numtrks; trknum++)
+ {
+ const pregap_file &pg = pgfile[trknum];
+ if (pg.idx0 == -1)
+ continue;
+ track_info &track = outtoc.tracks[trknum];
+ if (track.trktype == CD_TRACK_AUDIO && pg.motorola != motorola[trknum])
+ {
+ osd_printf_error("ERROR: track %d is in a MOTOROLA file and another that isn't\n", trknum+1);
+ return chd_file::error::UNSUPPORTED_FORMAT;
+ }
+ if (pg.frames == 0)
+ continue;
+ outinfo.track[trknum].pieces.push_back(track_input_piece { pg.fname, pg.start, pg.frames });
+ outinfo.track[trknum].pieces.push_back(track_input_piece { outinfo.track[trknum].fname, outinfo.track[trknum].offset, track.frames });
+ track.frames += pg.frames;
+ track.pregap += pg.frames;
+ track.pgtype = track.trktype;
+ track.pgdatasize = track.datasize;
+ }
+
+ // Discpress: every track has frames from its INDEX 01 on (0.289 made tracks of none, or of a negative number, and
+ // TOCs of them that don't read back)
+ for (trknum = 0; trknum < outtoc.numtrks; trknum++)
+ {
+ const track_info &track = outtoc.tracks[trknum];
+ if (int32_t(track.frames) <= 0 || (track.pgdatasize != 0 && track.frames <= track.pregap))
+ {
+ osd_printf_error("ERROR: track %d has no data from its INDEX 01 on\n", trknum + 1);
+ return chd_file::error::INVALID_DATA;
+ }
+ }
+
if (is_gdrom)
{
/*
@@ -2787,6 +2981,18 @@
uint32_t this_pregap = outtoc.tracks[trknum].pregap;
uint32_t this_offset = this_pregap * (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize);
+ // Discpress: a PREGAP is in no file: it pads the previous track with zeros, as a GDI's gap before a track
+ // does (and as extractcd writes that gap). 0.289 took it from the start of the track's file, which then
+ // lacked that many frames at its end: a read error, or the track's data moved by the pregap.
+ if (this_pregap != 0 && outtoc.tracks[trknum].pgdatasize == 0)
+ {
+ outtoc.tracks[trknum-1].frames += this_pregap;
+ outtoc.tracks[trknum-1].padframes += this_pregap;
+ outtoc.tracks[trknum].pregap = 0;
+ outtoc.tracks[trknum].pgtype = 0;
+ continue;
+ }
+
outtoc.tracks[trknum-1].frames += this_pregap;
outtoc.tracks[trknum-1].splitframes += this_pregap;
@@ -2808,7 +3014,7 @@
outtoc.tracks[trknum].physframeofs = 45000;
int dif=outtoc.tracks[trknum].physframeofs-(outtoc.tracks[trknum-1].frames+outtoc.tracks[trknum-1].physframeofs);
outtoc.tracks[trknum-1].frames += dif;
- outtoc.tracks[trknum-1].padframes = dif;
+ outtoc.tracks[trknum-1].padframes += dif; // Discpress: after a PREGAP's padding (0.289: =, and had none)
}
else
{
@@ -2965,6 +3171,67 @@
char linebuffer[512];
memset(linebuffer, 0, sizeof(linebuffer));
+ // Discpress: a line that belongs to a track, before the first TRACK, wrote before the track table
+ auto const before_track = [&infile] (const char *what)
+ {
+ fclose(infile);
+ osd_printf_error("ERROR: %s before the first TRACK\n", what);
+ return std::error_condition(chd_file::error::INVALID_DATA);
+ };
+
+ // Discpress: what each track holds, in order, as cdrdao reads it: runs of a file's bytes (FILE, AUDIOFILE,
+ // DATAFILE) and of zeros that no file holds (ZERO, SILENCE, PREGAP), and where START puts INDEX 01. After
+ // the last line they become the track's frames, pregap and pieces. 0.289 kept only the last file statement
+ // of a track, ignored ZERO, SILENCE and PREGAP, and made START a pregap that isn't in the file.
+ // (tail: when not 0, the statement ends this many bytes into its last frame, which zeros fill)
+ struct toc_run { std::string fname; uint64_t offset; uint32_t frames; bool swap; uint32_t tail = 0; };
+ constexpr uint64_t max_frames = 0x7fffffff / FRAME_SIZE; // 2 GB
+ std::vector<std::vector<toc_run> > runs(MAX_TRACKS);
+ std::vector<int> startpos(MAX_TRACKS, -1);
+ auto const track_length = [&runs] (int trk)
+ {
+ uint64_t frames = 0;
+ for (const toc_run &run : runs[trk])
+ frames += run.frames;
+ return frames;
+ };
+ auto const fail = [&infile] (std::error_condition err)
+ {
+ if (infile)
+ fclose(infile);
+ return err;
+ };
+ // a time (MM:SS:FF, frames of framesize bytes) or a number (of unit bytes) in bytes, false past 2^40 bytes, far
+ // more than a CD holds (so the arithmetic after it can't wrap, as it could in the engine before)
+ auto const toc_bytes = [] (const char *token, uint32_t framesize, uint32_t unit, uint64_t &bytes)
+ {
+ constexpr uint64_t limit = uint64_t(1) << 40;
+ if (strchr(token, ':'))
+ {
+ int const frames = msf_to_frames(token);
+ bytes = uint64_t(std::max(frames, 0)) * framesize;
+ }
+ else
+ {
+ errno = 0;
+ uint64_t const n = strtoull(token, nullptr, 10);
+ if (errno == ERANGE || n > limit)
+ return false;
+ bytes = n * unit;
+ }
+ return bytes <= limit;
+ };
+ auto const too_large = [&fail, &trknum] (const char *token)
+ {
+ osd_printf_error("ERROR: track %d has a length or offset that is too large (%s)\n", trknum + 1, token);
+ return fail(chd_file::error::INVALID_DATA);
+ };
+ auto const sample_subcode = [&fail, &trknum] (const char *what)
+ {
+ osd_printf_error("ERROR: track %d has sub-channel data, which a %s of samples can't have\n", trknum + 1, what);
+ return fail(chd_file::error::UNSUPPORTED_FORMAT);
+ };
+
while (!feof(infile))
{
/* get the next line */
@@ -2989,6 +3256,8 @@
*/
if (!strcmp(token, "NO"))
{
+ if (trknum < 0)
+ return before_track("NO");
TOKENIZE
if (!strcmp(token, "COPY"))
outtoc.tracks[trknum].control_flags &= ~CD_FLAG_CONTROL_DIGITAL_COPY_PERMITTED;
@@ -2997,100 +3266,210 @@
}
else if (!strcmp(token, "COPY"))
{
+ if (trknum < 0)
+ return before_track("COPY");
outtoc.tracks[trknum].control_flags |= CD_FLAG_CONTROL_DIGITAL_COPY_PERMITTED;
}
else if (!strcmp(token, "PRE_EMPHASIS"))
{
+ if (trknum < 0)
+ return before_track("PRE_EMPHASIS");
outtoc.tracks[trknum].control_flags |= CD_FLAG_CONTROL_PREEMPHASIS;
}
else if (!strcmp(token, "TWO_CHANNEL_AUDIO"))
{
+ if (trknum < 0)
+ return before_track("TWO_CHANNEL_AUDIO");
outtoc.tracks[trknum].control_flags &= ~CD_FLAG_CONTROL_4CH;
}
else if (!strcmp(token, "FOUR_CHANNEL_AUDIO"))
{
+ if (trknum < 0)
+ return before_track("FOUR_CHANNEL_AUDIO");
outtoc.tracks[trknum].control_flags |= CD_FLAG_CONTROL_4CH;
}
else if ((!strcmp(token, "DATAFILE")) || (!strcmp(token, "AUDIOFILE")) || (!strcmp(token, "FILE")))
{
- int f;
+ if (trknum < 0)
+ return before_track(token);
+
+ // Discpress: as cdrdao reads them, DATAFILE "file" [#offset] [length] and AUDIOFILE (or FILE) "file"
+ // [SWAP] [#offset] start [length], where a time is MM:SS:FF or a number of bytes (DATAFILE) or
+ // samples (AUDIOFILE), and without a length the rest of the file is the track's. Also, as 0.289
+ // read it, DATAFILE "file" [#offset] start length. 0.289 read a lone time after track 1 as an
+ // offset (DATAFILE) or as a length (AUDIOFILE on track 1), a number as frames, and gave a
+ // statement without a length no data.
+ const bool datafile = !strcmp(token, "DATAFILE");
+ const uint32_t framesize = outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize;
+ toc_run run { std::string(), 0, 0, false };
+ // Discpress: as cdrdao, samples are for audio without sub-channel data (0.289 read them as bytes of
+ // frames that have it)
+ if (!datafile && outtoc.tracks[trknum].subsize != 0)
+ return sample_subcode(token);
/* found the data file for a track */
TOKENIZE
/* keep the filename */
- outinfo.track[trknum].fname.assign(path).append(token);
+ run.fname.assign(path).append(token);
- /* get either the offset or the length */
- TOKENIZE
+ // Discpress: as cdrdao, an audio file whose name ends in .wav is a WAVE file, whose samples are
+ // little-endian (0.289 read the header as audio, and the samples unswapped)
+ const bool wave = !datafile && run.fname.size() >= 4 && core_stricmp(std::string_view(run.fname).substr(run.fname.size() - 4), ".wav") == 0;
+ if (wave)
+ run.swap = true;
+ TOKENIZE
if (!strcmp(token, "SWAP"))
{
+ run.swap = !run.swap; // Discpress: the other byte order than the file's own (so big-endian in a WAVE file)
TOKENIZE
-
- outinfo.track[trknum].swap = true;
- }
- else
- {
- outinfo.track[trknum].swap = false;
}
if (token[0] == '#')
{
/* it's a decimal offset, use it */
- f = strtoul(&token[1], nullptr, 10);
+ if (!toc_bytes(&token[1], 0, 1, run.offset)) // Discpress: within bounds
+ return too_large(&token[1]);
+ TOKENIZE
}
- else if (isdigit((uint8_t)token[0]))
- {
- /* convert the time to an offset */
- f = msf_to_frames(token);
- f *= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize);
+ // Discpress: where the file's data ends. As cdrdao, a WAVE file's header is at the offset (the file's
+ // start without one), and start and length count from its samples, which end with its data chunk or
+ // with the file, whichever comes first.
+ uint64_t const filesize = get_file_size(run.fname);
+ if (filesize == 0)
+ {
+ osd_printf_error("ERROR: couldn't find bin file [%s]\n", run.fname);
+ return fail(std::errc::no_such_file_or_directory);
}
- else
+ uint64_t fileend = filesize;
+ if (wave)
{
- f = 0;
+ uint32_t wavoffs = 0;
+ uint32_t const wavlen = (run.offset < filesize) ? parse_wav_sample(run.fname, &wavoffs, run.offset) : 0;
+ if (!wavlen)
+ {
+ osd_printf_error("ERROR: couldn't read [%s] or not a valid .WAV\n", run.fname);
+ return fail(chd_file::error::INVALID_DATA);
+ }
+ run.offset = wavoffs;
+ fileend = std::min<uint64_t>(filesize, uint64_t(wavoffs) + wavlen);
}
- outinfo.track[trknum].offset = f;
-
- TOKENIZE
-
- if (isdigit((uint8_t)token[0]))
+ uint64_t times[2];
+ int ntimes = 0;
+ while (ntimes < 2 && isdigit((uint8_t)token[0]))
{
- // this could be the length or an offset from the previous field.
- f = msf_to_frames(token);
-
+ if (!toc_bytes(token, framesize, datafile ? 1 : 4, times[ntimes++]))
+ return too_large(token);
TOKENIZE
+ }
- if (isdigit((uint8_t)token[0]))
+ uint64_t length;
+ if (datafile ? (ntimes == 2) : (ntimes >= 1))
+ run.offset += times[0];
+ // Discpress: as cdrdao, a length of 0 is the rest of the file, as no length is (TocParser.g: a statement
+ // of length 0 gets TrackData::determineLength)
+ if ((ntimes == 2 || (datafile && ntimes == 1)) && times[ntimes - 1] != 0)
+ {
+ length = times[ntimes - 1];
+ }
+ else
+ {
+ length = (fileend > run.offset) ? fileend - run.offset : 0;
+ if (!datafile)
+ length &= ~uint64_t(3); // as cdrdao, whole samples
+ }
+ // Discpress: as cdrdao, data past the end of the file is an error (here, rather than a read error later)
+ if (run.offset > fileend || length > fileend - run.offset)
+ {
+ osd_printf_error("ERROR: track %d asks for %llu bytes of [%s] from byte %llu, but only %llu are there\n", trknum + 1,
+ (unsigned long long)length, run.fname, (unsigned long long)run.offset,
+ (unsigned long long)((run.offset < fileend) ? fileend - run.offset : 0));
+ return fail(chd_file::error::INVALID_DATA);
+ }
+ // Discpress: as cdrdao, a last sector that the data only partly fills is padded with zeros (0.289 dropped it)
+ run.tail = uint32_t(length % framesize);
+ run.frames = uint32_t(length / framesize) + (run.tail ? 1 : 0);
+ if (track_length(trknum) + run.frames > max_frames) // (length is at most 2^40 bytes, so this doesn't wrap)
+ {
+ osd_printf_error("ERROR: track %d is too long\n", trknum + 1);
+ return fail(chd_file::error::INVALID_DATA);
+ }
+ runs[trknum].push_back(std::move(run));
+ }
+ else if (!strcmp(token, "ZERO") || !strcmp(token, "SILENCE") || !strcmp(token, "PREGAP"))
+ {
+ // Discpress: ZERO [mode] [sub-channel mode] length, SILENCE length: zeros that no file holds;
+ // PREGAP length: as much silence, then START (0.289 ignored the three)
+ if (trknum < 0)
+ return before_track(token);
+ const bool pregap = !strcmp(token, "PREGAP"), silence = !strcmp(token, "SILENCE");
+ const uint32_t framesize = outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize;
+ if (silence && outtoc.tracks[trknum].subsize != 0)
+ return sample_subcode(token);
+ // ZERO [mode] [sub-channel mode]: cdrdao makes sectors of that mode, sized by it; a CHD's track has one
+ // mode, so it must be the track's (0.289 ignored ZERO; the engine before sized it by the track's)
+ const bool zero = !pregap && !silence;
+ bool modeword = false, subword = false;
+ TOKENIZE
+ while (token[0] && !isdigit((uint8_t)token[0]))
+ {
+ const track_info &track = outtoc.tracks[trknum];
+ track_info named = track;
+ named.datasize = 0;
+ named.subtype = CD_SUB_NONE;
+ bool same = false;
+ if (zero && !modeword && !subword && (convert_type_string_to_track_info(token, &named), named.datasize != 0))
+ same = modeword = (named.trktype == track.trktype && named.datasize == track.datasize);
+ else if (zero && !subword && (convert_subtype_string_to_track_info(token, &named), named.subtype != CD_SUB_NONE))
+ same = subword = (named.subtype == track.subtype);
+ if (!same)
{
- // it was an offset.
- f *= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize);
-
- outinfo.track[trknum].offset += f;
-
- // this is the length.
- f = msf_to_frames(token);
+ osd_printf_error("ERROR: track %d's %s has %s, which isn't the track's mode\n", trknum + 1, zero ? "ZERO" : silence ? "SILENCE" : "PREGAP", token);
+ return fail(chd_file::error::UNSUPPORTED_FORMAT);
}
+ TOKENIZE
}
- else if (trknum == 0 && outinfo.track[trknum].offset != 0)
+ if (!token[0])
{
- /* the 1st track might have a length with no offset */
- f = outinfo.track[trknum].offset / (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize);
- outinfo.track[trknum].offset = 0;
+ osd_printf_error("ERROR: track %d has a ZERO, SILENCE or PREGAP without a length\n", trknum + 1);
+ return fail(chd_file::error::INVALID_DATA);
}
- else
+ uint64_t frames;
+ uint32_t tail = 0;
+ if (strchr(token, ':') || pregap)
{
- /* guesstimate the track length? */
- f = 0;
+ frames = std::max(msf_to_frames(token), 0);
}
-
- outtoc.tracks[trknum].frames = f;
+ else
+ {
+ // a last sector that the zeros only partly fill is a sector of zeros
+ uint64_t bytes;
+ if (!toc_bytes(token, framesize, silence ? 4 : 1, bytes))
+ return too_large(token);
+ tail = uint32_t(bytes % framesize);
+ frames = bytes / framesize + (tail ? 1 : 0);
+ }
+ if (track_length(trknum) + frames > max_frames)
+ {
+ osd_printf_error("ERROR: track %d is too long\n", trknum + 1);
+ return fail(chd_file::error::INVALID_DATA);
+ }
+ runs[trknum].push_back(toc_run { std::string(), 0, uint32_t(frames), false, tail });
+ if (pregap)
+ startpos[trknum] = int(track_length(trknum));
}
else if (!strcmp(token, "TRACK"))
{
trknum++;
+ if (trknum >= int(MAX_TRACKS)) // Discpress: 0.289 wrote past its track table
+ {
+ fclose(infile);
+ osd_printf_error("ERROR: more than %d tracks\n", MAX_TRACKS);
+ return chd_file::error::INVALID_DATA;
+ }
/* next token on the line is the track type */
TOKENIZE
@@ -3117,22 +3496,128 @@
}
else if (!strcmp(token, "START"))
{
- int frames;
-
+ if (trknum < 0)
+ return before_track("START");
/* get index */
TOKENIZE
- frames = msf_to_frames(token);
- outtoc.tracks[trknum].pregap = frames;
+ // Discpress: where INDEX 01 is, from the start of the track's data (START alone: where the
+ // statements so far end)
+ startpos[trknum] = isdigit((uint8_t)token[0]) ? msf_to_frames(token) : int(track_length(trknum));
}
}
/* close the input TOC */
fclose(infile);
+ infile = nullptr;
/* store the number of tracks found */
outtoc.numtrks = trknum + 1;
outtoc.numsessions = 1;
+ // Discpress: each track's frames, pregap and where they are
+ for (int trk = 0; trk < int(outtoc.numtrks); trk++)
+ {
+ track_info &info = outtoc.tracks[trk];
+ track_input_entry &input = outinfo.track[trk];
+ std::vector<toc_run> &trackruns = runs[trk];
+ uint64_t const total = track_length(trk);
+ if (total == 0)
+ {
+ osd_printf_error("ERROR: track %d holds no data\n", trk + 1);
+ return chd_file::error::INVALID_DATA;
+ }
+ if (total > max_frames)
+ {
+ osd_printf_error("ERROR: track %d is too long\n", trk + 1);
+ return chd_file::error::INVALID_DATA;
+ }
+
+ // cdrdao joins a track's statements byte by byte, so what follows one that ends inside a sector would
+ // not start on a sector: only the last may end there
+ for (size_t n = 0; n + 1 < trackruns.size(); n++)
+ if (trackruns[n].tail != 0)
+ {
+ osd_printf_error("ERROR: track %d has data after a statement that ends inside a sector\n", trk + 1);
+ return chd_file::error::UNSUPPORTED_FORMAT;
+ }
+
+ uint32_t const start = std::max(startpos[trk], 0);
+ if (start >= total)
+ {
+ osd_printf_error("ERROR: track %d's START is at or past its end\n", trk + 1);
+ return chd_file::error::INVALID_DATA;
+ }
+ uint64_t zeros = 0;
+ for (const toc_run &run : trackruns)
+ {
+ if (!run.fname.empty())
+ break;
+ zeros += run.frames;
+ }
+ if (start > 0 && start <= zeros)
+ {
+ // zeros before START that no file holds: a pregap that isn't in the file, as a cue sheet's PREGAP
+ info.pregap = start;
+ for (uint32_t drop = start; drop > 0; )
+ {
+ uint32_t const n = std::min(drop, trackruns.front().frames);
+ trackruns.front().frames -= n;
+ drop -= n;
+ if (trackruns.front().frames == 0)
+ trackruns.erase(trackruns.begin());
+ }
+ }
+ else if (start > 0)
+ {
+ // frames before START that the track holds: a pregap in the file, as a cue sheet's INDEX 00
+ info.pregap = start;
+ info.pgtype = info.trktype;
+ info.pgdatasize = info.datasize;
+ }
+
+ // runs of a file that follow each other are one
+ uint32_t const framesize = info.datasize + info.subsize;
+ std::vector<toc_run> merged;
+ for (toc_run &run : trackruns)
+ {
+ if (run.frames == 0)
+ continue; // (its tail, 0, would cut the last frame of the run before it)
+ if (!merged.empty() && run.fname == merged.back().fname && run.swap == merged.back().swap &&
+ (run.fname.empty() || run.offset == merged.back().offset + uint64_t(merged.back().frames) * framesize))
+ {
+ merged.back().frames += run.frames;
+ merged.back().tail = run.tail;
+ }
+ else
+ merged.push_back(std::move(run));
+ }
+
+ info.frames = uint32_t(total - (info.pgdatasize ? 0 : info.pregap));
+ input.fname.clear();
+ input.offset = 0;
+ input.swap = false;
+ bool swapset = false;
+ for (const toc_run &run : merged)
+ {
+ if (run.fname.empty())
+ continue;
+ if (swapset && run.swap != input.swap)
+ {
+ osd_printf_error("ERROR: track %d has samples in both byte orders (a .wav file, or SWAP, with a raw file), which isn't supported\n", trk + 1);
+ return chd_file::error::UNSUPPORTED_FORMAT;
+ }
+ if (!swapset)
+ input.fname = run.fname;
+ input.swap = run.swap;
+ swapset = true;
+ }
+ if (merged.size() == 1 && !merged[0].fname.empty() && merged[0].offset <= UINT32_MAX && merged[0].tail == 0)
+ input.offset = uint32_t(merged[0].offset);
+ else
+ for (const toc_run &run : merged)
+ input.pieces.push_back(track_input_piece { run.fname, run.offset, run.frames, run.fname.empty() ? 0 : run.tail });
+ }
+
return std::error_condition();
}
--- a/src/lib/util/cdrom.h
+++ b/src/lib/util/cdrom.h
@@ -20,6 +20,7 @@
#include <string>
#include <string_view>
#include <system_error>
+#include <vector>
class cdrom_file {
@@ -121,16 +122,29 @@
track_info tracks[MAX_TRACKS + 1];
};
+ // Discpress: a run of a track's frames in a file, or zeros (no file name)
+ struct track_input_piece
+ {
+ std::string fname;
+ uint64_t offset;
+ uint32_t frames;
+ uint32_t tail = 0; // Discpress: when not 0, the file holds only this many bytes of the last frame, the rest is zeros
+ };
+
struct track_input_entry
{
track_input_entry() { reset(); }
- void reset() { fname.clear(); offset = 0; leadin = leadout = -1; swap = false; std::fill(std::begin(idx), std::end(idx), -1); }
+ void reset() { fname.clear(); offset = 0; leadin = leadout = -1; swap = false; std::fill(std::begin(idx), std::end(idx), -1); pieces.clear(); }
std::string fname; // filename for each track
uint32_t offset; // offset in the data file for each track
bool swap; // data needs to be byte swapped
int32_t idx[MAX_INDEX + 1];
int32_t leadin, leadout; // TODO: these should probably be their own tracks entirely
+ // Discpress: when not empty, where the track's frames are, in order, instead of fname and offset:
+ // for a track in several files (a cdrdao TOC, or a cue sheet with INDEX 00 in the previous FILE)
+ // or with frames of zeros that no file holds
+ std::vector<track_input_piece> pieces;
};
struct track_input_info
@@ -267,8 +281,6 @@
// ECC tables
static const uint8_t ecclow[256];
static const uint8_t ecchigh[256];
- static const uint16_t poffsets[ECC_P_NUM_BYTES][ECC_P_COMP];
- static const uint16_t qoffsets[ECC_Q_NUM_BYTES][ECC_Q_COMP];
/** @brief The chd. */
chd_file * chd; /* CHD file */
@@ -283,15 +295,15 @@
inline uint32_t logical_to_chd_lba(uint32_t physlba, uint32_t &tracknum) const;
static void get_info_from_type_string(const char *typestring, uint32_t *trktype, uint32_t *datasize);
- static uint8_t ecc_source_byte(const uint8_t *sector, uint32_t offset);
- static void ecc_compute_bytes(const uint8_t *sector, const uint16_t *row, int rowlen, uint8_t &val1, uint8_t &val2);
+ static void ecc_region(const uint8_t *sector, uint8_t *region); // Discpress: P and Q a word at a time
+ static void ecc_parity(const uint8_t *region, bool q, uint8_t *parity);
std::error_condition read_partial_sector(void *dest, uint32_t lbasector, uint32_t chdsector, uint32_t tracknum, uint32_t startoffs, uint32_t length, bool phys);
static std::string get_file_path(std::string &path);
static uint64_t get_file_size(std::string_view filename);
static int tokenize(const char *linebuffer, int i, int linebuffersize, char *token, int tokensize);
static int msf_to_frames(const char *token);
- static uint32_t parse_wav_sample(std::string_view filename, uint32_t *dataoffs);
+ static uint32_t parse_wav_sample(std::string_view filename, uint32_t *dataoffs, uint64_t start = 0); // Discpress: start
static uint16_t read_uint16(FILE *infile);
static uint32_t read_uint32(FILE *infile);
static uint64_t read_uint64(FILE *infile);
--- a/src/lib/util/chd.cpp
+++ b/src/lib/util/chd.cpp
@@ -28,6 +28,20 @@
#include <new>
#include <tuple>
+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+extern "C" {
+ // provided by the JavaScript host (browser build): offload hunk compression to helper workers
+ int wasm_par_enabled(uint32_t hunkbytes, uint32_t unitbytes, const uint32_t *compression, uint32_t flags);
+ void wasm_par_submit(void *item, const uint8_t *data, uint32_t length, uint32_t codecs);
+ // and decompression, for commands that read (see chd_file::wasm_read_ahead)
+ int wasm_rd_enabled(const void *chd, uint32_t hunkbytes, uint32_t unitbytes, const uint32_t *compression);
+ void wasm_rd_submit(const void *chd, uint32_t hunknum, uint32_t codec, const uint8_t *data, uint32_t length);
+ void wasm_rd_close(const void *chd);
+}
+static int s_wasm_par = 0;
+#endif
+
//**************************************************************************
// CONSTANTS
@@ -853,6 +867,17 @@
void chd_file::close()
{
+#ifdef __EMSCRIPTEN__
+ // Discpress: results from helper workers that are still coming are dropped
+ if (m_wasm_rd > 0)
+ wasm_rd_close(this);
+ m_wasm_rd = -1;
+ m_wasm_ahead.clear();
+ m_wasm_span.clear();
+ m_wasm_first = m_wasm_count = 0;
+ m_wasm_scan = m_wasm_queued = 0;
+#endif
+
// reset file characteristics
m_file.reset();
m_allow_reads = false;
@@ -1115,6 +1140,11 @@
case COMPRESSION_TYPE_2:
case COMPRESSION_TYPE_3:
{
+#ifdef __EMSCRIPTEN__
+ // Discpress: the read-ahead may have it (see wasm_read_ahead; lossless codecs only)
+ if (wasm_read_cached(hunknum, rawmap[0], dest))
+ return (util::crc16_creator::simple(dest, m_hunkbytes) != blockcrc) ? std::error_condition(error::DECOMPRESSION_ERROR) : std::error_condition();
+#endif
std::error_condition err = file_read(blockoffs, &m_compressed[0], blocklen);
if (UNEXPECTED(err))
return err;
@@ -1130,9 +1160,15 @@
case COMPRESSION_NONE:
{
+#ifdef __EMSCRIPTEN__
+ // Discpress: the read-ahead may have read it already
+ if (!wasm_read_cached(hunknum, rawmap[0], dest))
+#endif
+ {
std::error_condition err = file_read(blockoffs, dest, m_hunkbytes);
if (UNEXPECTED(err))
return err;
+ }
if (UNEXPECTED(util::crc16_creator::simple(dest, m_hunkbytes) != blockcrc))
return std::error_condition(error::DECOMPRESSION_ERROR);
return std::error_condition();
@@ -2173,6 +2209,10 @@
// for COMPRESSION_PARENT: parentbits
// the overall size is clamped later with bitbuf.flush()
int nbits_needed = (8*16) + (12 + std::max<int>({lengthbits+16, selfbits, parentbits}))*m_hunkcount;
+ // Discpress: plus the tree, at most 8 bits for each of the 16 codes. 0.289 left it out, which only
+ // matters for a few hunks: then the map overflowed, was cut short, and the CHD couldn't be read.
+ // Where 0.289's map fit, it is the same; the size of the buffer doesn't change what goes into it.
+ nbits_needed += 16 * 8;
std::vector<uint8_t> compressed(nbits_needed / 8 + 1);
bitstream_out bitbuf(&compressed[16], compressed.size() - 16);
@@ -2255,7 +2295,8 @@
// write the map header
uint32_t complen = bitbuf.flush();
- assert(!bitbuf.overflow());
+ if (bitbuf.overflow()) // Discpress: an error, not a truncated map (and a read past the buffer)
+ throw std::error_condition(error::COMPRESSION_ERROR);
put_u32be(&compressed[0], complen);
put_u48be(&compressed[4], firstoffs);
put_u16be(&compressed[10], mapcrc);
@@ -2966,6 +3007,13 @@
chd_file_compressor::~chd_file_compressor()
{
+ // Discpress: after an error, hunks are still out, and the queue frees only the work items it holds (0.289 leaked
+ // the others): let the reads finish, then release them
+ osd_work_queue_wait(m_read_queue, 30 * osd_ticks_per_second());
+ for (work_item &item : m_work_item)
+ if (item.m_osd)
+ osd_work_item_release(std::exchange(item.m_osd, nullptr));
+
// free the work queues
osd_work_queue_free(m_read_queue);
osd_work_queue_free(m_work_queue);
@@ -3022,6 +3070,10 @@
// reset write state
m_write_hunk = 0;
+
+#ifdef __EMSCRIPTEN__
+ s_wasm_par = compressed() ? wasm_par_enabled(hunk_bytes(), unit_bytes(), m_compression, m_libdeflate ? 1 : 0) : 0; // flags: 1 = libdeflate
+#endif
}
/**
@@ -3123,6 +3175,11 @@
}
else
{
+#ifdef __EMSCRIPTEN__
+ // a helper worker skipped this hunk as a probable duplicate, but it is not in the map: compress it here
+ if (item.m_compression == -2)
+ item.m_compression = item.m_codecs->find_best_compressor(item.m_data, item.m_compressed, item.m_complen, item.m_try);
+#endif
// otherwise, append it compressed and add to the self map
hunk_write_compressed(item.m_hunknum, item.m_compression, item.m_compressed, item.m_complen, item.m_hash[0].m_crc16);
m_total_out += item.m_complen;
@@ -3263,12 +3320,44 @@
// TODO: data race
if ((m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND) &&
(m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND))
- item.m_compression = item.m_codecs->find_best_compressor(item.m_data, item.m_compressed, item.m_complen);
+ item.m_compression = item.m_codecs->find_best_compressor(item.m_data, item.m_compressed, item.m_complen, item.m_try);
// mark us complete
item.m_status = WS_COMPLETE;
}
+//-------------------------------------------------
+// codec_plan_codecs - Discpress: the codec slots
+// the codec plan tries for a hunk that holds
+// content, a bit per slot
+//-------------------------------------------------
+
+uint32_t chd_file_compressor::codec_plan_codecs(hunk_content content) const noexcept
+{
+ // CD audio: cdfl; CD data: cdlz, which wins almost every data hunk (cdzl wins about one in
+ // ten by a few bytes, cdfl none); other data: every codec but flac, which wins almost none
+ // and is the slowest. Without those codecs in the list, or for a hunk that holds both
+ // kinds, every codec is tried.
+ uint32_t all = 0, flac = 0, cdlz = 0;
+ for (int slot = 0; slot < 4; slot++)
+ {
+ chd_codec_type const type = m_compression[slot];
+ if (type == CHD_CODEC_NONE)
+ continue;
+ all |= 1 << slot;
+ if ((type == CHD_CODEC_CD_FLAC) || (type == CHD_CODEC_FLAC))
+ flac |= 1 << slot;
+ if (type == CHD_CODEC_CD_LZMA)
+ cdlz |= 1 << slot;
+ }
+ uint32_t codecs = all;
+ if (content == hunk_content::AUDIO)
+ codecs = flac;
+ else if (content == hunk_content::DATA)
+ codecs = cdlz ? cdlz : (all & ~flac);
+ return codecs ? codecs : all;
+}
+
/**
* @fn void *chd_file_compressor::async_read_static(void *param, int threadid)
*
@@ -3345,6 +3434,16 @@
assert(item.m_status == WS_READING);
item.m_status = WS_QUEUED;
item.m_hunknum = hunknum;
+ item.m_try = (m_codec_plan && !m_walking_parent) ? codec_plan_codecs(read_content(curoffs, hunk_bytes())) : 0x0f;
+#ifdef __EMSCRIPTEN__
+ if (s_wasm_par && !m_walking_parent && compressed())
+ {
+ item.m_osd = nullptr;
+ item.m_codecs = m_codecs[0];
+ wasm_par_submit(&item, item.m_data, hunk_bytes(), item.m_try);
+ continue;
+ }
+#endif
item.m_osd = osd_work_item_queue(m_work_queue, m_walking_parent ? async_walk_parent_static : async_compress_hunk_static, &item, 0);
}
@@ -3512,3 +3611,179 @@
metadata_entry metaentry;
return metadata_find(AV_METADATA_TAG, 0, metaentry);
}
+
+
+#ifdef __EMSCRIPTEN__
+void chd_file_compressor::wasm_par_complete_item(void *itemp, uint32_t crc16, const uint8_t *sha1, int compression, uint32_t complen, const uint8_t *data)
+{
+ work_item &item = *reinterpret_cast<work_item *>(itemp);
+ item.m_hash[0].m_crc16 = util::crc16_t(uint16_t(crc16));
+ memcpy(item.m_hash[0].m_sha1.m_raw, sha1, sizeof(item.m_hash[0].m_sha1.m_raw));
+ item.m_compression = int8_t(compression);
+ item.m_complen = complen;
+ memcpy(item.m_compressed, data, complen);
+ item.m_status = WS_COMPLETE;
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE void wasm_par_complete(void *item, uint32_t crc16, const uint8_t *sha1, int compression, uint32_t complen, const uint8_t *data)
+{
+ chd_file_compressor::wasm_par_complete_item(item, crc16, sha1, compression, complen, data);
+}
+
+bool chd_file::wasm_read_ahead(uint32_t first, uint32_t count)
+{
+ // only compressed v5 CHDs; helper workers decompress if the page has them for these codecs
+ if (m_wasm_rd == -1)
+ m_wasm_rd = (m_file && (m_version == 5) && compressed()) ? wasm_rd_enabled(this, m_hunkbytes, m_unitbytes, m_compression) : -2;
+ if (m_wasm_rd == -2)
+ return true;
+
+ // a new window: drop the hunks before it (one read again is read the usual way)
+ if ((first != m_wasm_first) || (count != m_wasm_count))
+ {
+ m_wasm_first = first;
+ m_wasm_count = count;
+ m_wasm_scan = first;
+ for (auto it = m_wasm_ahead.begin(); it != m_wasm_ahead.end(); )
+ {
+ if (it->first < first)
+ it = m_wasm_ahead.erase(it);
+ else
+ ++it;
+ }
+ }
+
+ // the stored data of this window and the next, read in one go when it lies together in the file
+ // (every read costs far more in the browser than the bytes do): codec-compressed hunks go to the
+ // helpers, or are kept to be decompressed when read; uncompressed hunks are kept as they are
+ uint64_t const begin = std::max<uint64_t>(first, m_wasm_queued);
+ uint64_t const end = std::min<uint64_t>(uint64_t(first) + 2 * uint64_t(count), m_hunkcount);
+ if (begin < end)
+ {
+ uint64_t lo = ~uint64_t(0), hi = 0;
+ for (uint64_t hunknum = begin; hunknum < end; hunknum++)
+ {
+ uint8_t const *const rawmap = &m_rawmap[m_mapentrybytes * hunknum];
+ if (rawmap[0] > COMPRESSION_NONE)
+ continue;
+ uint64_t const blockoffs = get_u48be(&rawmap[4]);
+ uint32_t const blocklen = (rawmap[0] == COMPRESSION_NONE) ? m_hunkbytes : get_u24be(&rawmap[1]);
+ lo = std::min(lo, blockoffs);
+ hi = std::max(hi, blockoffs + blocklen);
+ }
+ uint8_t const *span = nullptr;
+ if ((lo < hi) && ((hi - lo) <= 2 * (end - begin) * m_hunkbytes))
+ {
+ try
+ {
+ m_wasm_span.resize(hi - lo);
+ if (!file_read(lo, &m_wasm_span[0], hi - lo))
+ span = &m_wasm_span[0];
+ }
+ catch (...)
+ {
+ }
+ }
+
+ for (uint64_t hunknum = begin; hunknum < end; hunknum++)
+ {
+ if (m_wasm_ahead.count(hunknum))
+ continue;
+ uint8_t const *const rawmap = &m_rawmap[m_mapentrybytes * hunknum];
+ uint8_t const type = rawmap[0];
+ uint64_t const blockoffs = get_u48be(&rawmap[4]);
+ if (type == COMPRESSION_NONE)
+ {
+ if (span)
+ m_wasm_ahead[hunknum] = wasm_ahead_hunk{ std::vector<uint8_t>(span + (blockoffs - lo), span + (blockoffs - lo) + m_hunkbytes), 4 };
+ continue;
+ }
+ if ((type > COMPRESSION_TYPE_3) || !m_decompressor[type] || m_decompressor[type]->lossy())
+ continue;
+ uint32_t const blocklen = get_u24be(&rawmap[1]);
+ uint8_t const *data = span ? (span + (blockoffs - lo)) : nullptr;
+ if (!data)
+ {
+ if ((blocklen > m_compressed.size()) || file_read(blockoffs, &m_compressed[0], blocklen))
+ continue; // read_hunk will report it
+ data = &m_compressed[0];
+ }
+ if (m_wasm_rd > 0)
+ {
+ m_wasm_ahead[hunknum];
+ wasm_rd_submit(this, hunknum, type, data, blocklen);
+ }
+ else
+ {
+ m_wasm_ahead[hunknum] = wasm_ahead_hunk{ std::vector<uint8_t>(data, data + blocklen), 3 };
+ }
+ }
+ m_wasm_queued = end;
+ }
+
+ // ready once no hunk of this window is still with a helper (m_wasm_scan: all before it are)
+ uint64_t const ready = std::min<uint64_t>(uint64_t(first) + count, m_hunkcount);
+ for ( ; m_wasm_scan < ready; m_wasm_scan++)
+ {
+ auto const found = m_wasm_ahead.find(m_wasm_scan);
+ if ((found != m_wasm_ahead.end()) && (found->second.state == 0))
+ return false;
+ }
+ return true;
+}
+
+uint8_t *chd_file::wasm_read_slot(uint32_t hunknum) noexcept
+{
+ auto const found = m_wasm_ahead.find(hunknum);
+ if ((found == m_wasm_ahead.end()) || (found->second.state != 0))
+ return nullptr; // no longer wanted
+ try
+ {
+ found->second.data.resize(m_hunkbytes);
+ }
+ catch (...)
+ {
+ return nullptr;
+ }
+ return &found->second.data[0];
+}
+
+void chd_file::wasm_read_done(uint32_t hunknum, bool ok) noexcept
+{
+ auto const found = m_wasm_ahead.find(hunknum);
+ if ((found != m_wasm_ahead.end()) && (found->second.state == 0))
+ found->second.state = (ok && (found->second.data.size() == m_hunkbytes)) ? 1 : 2;
+}
+
+// fills dest from the read-ahead if it has this hunk (decompressing kept bytes, which throws
+// like any decompression); the caller checks the CRC as usual
+bool chd_file::wasm_read_cached(uint32_t hunknum, uint8_t type, void *dest)
+{
+ auto const found = m_wasm_ahead.find(hunknum);
+ if (found == m_wasm_ahead.end())
+ return false;
+ wasm_ahead_hunk const &ahead = found->second;
+ switch (ahead.state)
+ {
+ case 1: // decompressed by a helper
+ case 4: // stored uncompressed
+ memcpy(dest, &ahead.data[0], m_hunkbytes);
+ return true;
+ case 3: // compressed bytes
+ m_decompressor[type]->decompress(&ahead.data[0], ahead.data.size(), reinterpret_cast<uint8_t *>(dest), m_hunkbytes);
+ return true;
+ default:
+ return false;
+ }
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *wasm_rd_slot(chd_file *chd, uint32_t hunknum)
+{
+ return chd->wasm_read_slot(hunknum);
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE void wasm_rd_done(chd_file *chd, uint32_t hunknum, int ok)
+{
+ chd->wasm_read_done(hunknum, ok != 0);
+}
+#endif
--- a/src/lib/util/chd.h
+++ b/src/lib/util/chd.h
@@ -22,6 +22,10 @@
#include <string>
#include <string_view>
#include <system_error>
+#ifdef __EMSCRIPTEN__
+#include <unordered_map>
+#include <vector>
+#endif
/***************************************************************************
@@ -309,6 +313,12 @@
uint64_t unit_count() const noexcept { return m_unitcount; }
bool compressed() const noexcept { return (m_compression[0] != CHD_CODEC_NONE); }
chd_codec_type compression(int index) const noexcept { return m_compression[index]; }
+
+ // Discpress: the deflate codec encodes with libdeflate instead of zlib (chdman's --libdeflate):
+ // standard deflate that every reader decodes, 2 to 3 times faster and a little smaller, but not
+ // zlib's bytes. Read when a compressor group is made (compress_begin).
+ void set_libdeflate(bool libdeflate) noexcept { m_libdeflate = libdeflate; }
+ bool libdeflate() const noexcept { return m_libdeflate; }
chd_file *parent() const noexcept { return m_parent.get(); }
bool parent_missing() const noexcept;
util::sha1_t sha1() const noexcept;
@@ -366,6 +376,16 @@
std::error_condition check_is_dvd() const noexcept;
std::error_condition check_is_av() const noexcept;
+#ifdef __EMSCRIPTEN__
+ // Discpress (browser build): reads ahead for the commands that read a whole CHD. wasm_read_ahead
+ // reads the stored data of hunks [first, first + 2 * count) in one go, hands the compressed ones
+ // to helper workers (if the page has them), and returns true once none of the first count is
+ // still with a helper; read_hunk then takes those from memory. Other hunks are read as usual.
+ bool wasm_read_ahead(uint32_t first, uint32_t count);
+ uint8_t *wasm_read_slot(uint32_t hunknum) noexcept;
+ void wasm_read_done(uint32_t hunknum, bool ok) noexcept;
+#endif
+
private:
struct metadata_entry;
struct metadata_hash;
@@ -433,6 +453,21 @@
// caching
std::vector<uint8_t> m_cache; // single-hunk cache for partial reads/writes
uint32_t m_cachehunk; // which hunk is in the cache?
+
+ bool m_libdeflate = false; // Discpress: see set_libdeflate
+
+#ifdef __EMSCRIPTEN__
+ // Discpress: hunks handed to helper workers, by hunk number (see wasm_read_ahead)
+ // state: 0 = with a helper, 1 = decompressed by it, 2 = it failed, 3 = compressed bytes, 4 = uncompressed hunk
+ struct wasm_ahead_hunk { std::vector<uint8_t> data; int state = 0; };
+ bool wasm_read_cached(uint32_t hunknum, uint8_t type, void *dest);
+ std::unordered_map<uint32_t, wasm_ahead_hunk> m_wasm_ahead;
+ std::vector<uint8_t> m_wasm_span; // this window's stored data, read in one go
+ int m_wasm_rd = -1; // -1 = not asked yet, -2 = no read-ahead, 0 = without helpers, 1 = with
+ uint32_t m_wasm_first = 0, m_wasm_count = 0; // the window last asked for
+ uint64_t m_wasm_scan = 0; // hunks of the window before this one are ready
+ uint64_t m_wasm_queued = 0; // hunks before this one were handed out (if codec-compressed)
+#endif
};
@@ -450,11 +485,28 @@
void compress_begin();
std::error_condition compress_continue(double &progress, double &ratio);
+ // Discpress: the codec plan (chdman's --codecplan). Each hunk is tried only with the codecs
+ // meant for what it holds, instead of all of them: CD audio with cdfl, CD data with cdlz, and
+ // other data with every codec but flac (codec_plan_codecs). The data and both SHA-1s are the
+ // same; the bytes differ where a codec left out would have won. Set before compress_begin.
+ void set_codec_plan(bool plan) noexcept { m_codec_plan = plan; }
+
+#ifdef __EMSCRIPTEN__
+ // browser build: completion callback for hunks compressed by helper workers
+ static void wasm_par_complete_item(void *item, uint32_t crc16, const uint8_t *sha1, int compression, uint32_t complen, const uint8_t *data);
+#endif
+
protected:
// required override: read more data
virtual uint32_t read_data(void *dest, uint64_t offset, uint32_t length) = 0;
+ // Discpress: what a range of the data holds, for the codec plan (called from the read thread)
+ enum class hunk_content { MIXED, DATA, AUDIO };
+ virtual hunk_content read_content(uint64_t offset, uint32_t length) const { return hunk_content::DATA; }
+
private:
+ uint32_t codec_plan_codecs(hunk_content content) const noexcept;
+
// hash map for looking up values
class hashmap
{
@@ -524,6 +576,7 @@
, m_complen(0)
, m_compression(0)
, m_codecs(nullptr)
+ , m_try(0x0f)
{ }
osd_work_item * m_osd; // OSD work item running on this block
@@ -537,6 +590,7 @@
uint32_t m_complen; // compressed data length
int8_t m_compression; // type of compression used
chd_compressor_group *m_codecs; // codec instance
+ uint32_t m_try; // Discpress: codec slots to try (the codec plan)
std::vector<hash_pair> m_hash; // array of hashes
};
@@ -549,6 +603,7 @@
void async_read();
// current compression status
+ bool m_codec_plan = false; // Discpress: try each hunk only with the codecs for its content
bool m_walking_parent; // are we building the parent map?
uint64_t m_total_in; // total bytes in
uint64_t m_total_out; // total bytes out
--- a/src/lib/util/chdcodec.cpp
+++ b/src/lib/util/chdcodec.cpp
@@ -20,9 +20,12 @@
#include <zlib.h>
#include <zstd.h>
+#include <libdeflate.h>
+#include <cstddef>
#include <cstring>
#include <new>
+#include <type_traits>
namespace {
@@ -39,6 +42,11 @@
// TYPE DEFINITIONS
//**************************************************************************
+// Discpress: the zlib and LZMA allocators keep each block's size in front of it; 0.289 kept it in 4 bytes, so every
+// block (zlib's and LZMA's state structures among them) was only 4-byte aligned, which C leaves undefined (UBSan
+// flags each access). A header as large as the strictest alignment keeps blocks aligned as new[] returns them.
+constexpr size_t ALLOC_HEADER = alignof(std::max_align_t);
+
// ======================> chd_zlib_allocator
// allocation helper class for zlib
@@ -80,6 +88,7 @@
// internal state
z_stream m_deflater;
chd_zlib_allocator m_allocator;
+ libdeflate_compressor * m_libdeflate = nullptr; // Discpress: libdeflate instead (chd_file::libdeflate)
};
@@ -259,6 +268,7 @@
// internal state
bool m_big_endian;
flac_encoder m_encoder;
+ std::vector<uint8_t> m_buffer_be; // Discpress: the big-endian encoding, kept for when it wins
};
@@ -376,7 +386,8 @@
}
}
- // encode the base portion
+ // encode the base portion (Discpress: it may give up once header + base reach the limit)
+ m_base_compressor.set_limit((m_limit > header_bytes) ? (m_limit - header_bytes) : 1);
uint32_t complen = m_base_compressor.compress(&m_buffer[0], frames * cdrom_file::MAX_SECTOR_DATA, &dest[header_bytes]);
if (complen >= srclen)
throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
@@ -387,7 +398,12 @@
else
put_u16be(&dest[ecc_bytes], complen);
- // encode the subcode
+ // encode the subcode (Discpress: into what is left of the hunk-sized buffer; 0.289 let it write
+ // up to frames * 96 bytes past it. A result that doesn't fit reaches the limit, so it never wins)
+ uint32_t const limit = std::min(m_limit, hunkbytes());
+ if (header_bytes + complen >= limit)
+ throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
+ m_subcode_compressor.set_limit(limit - header_bytes - complen);
return header_bytes + complen + m_subcode_compressor.compress(&m_buffer[frames * cdrom_file::MAX_SECTOR_DATA], frames * cdrom_file::MAX_SUBCODE_DATA, &dest[header_bytes + complen]);
}
@@ -735,17 +751,33 @@
// compression for this hunk
//-------------------------------------------------
-int8_t chd_compressor_group::find_best_compressor(const uint8_t *src, uint8_t *compressed, uint32_t &complen)
+int8_t chd_compressor_group::find_best_compressor(const uint8_t *src, uint8_t *compressed, uint32_t &complen, uint32_t codecs)
{
// determine best compression technique
complen = m_hunkbytes;
int8_t compression = -1;
+
+ // Discpress: the codec that won the previous hunk goes first, and every other codec only
+ // has to beat the best result so far, so it can give up as soon as it can't (set_limit).
+ // Ties still go to the lower codec number, so the choice and the data are upstream's.
+ // Only the codecs in codecs are tried: all of them unless a codec plan says otherwise.
+ int order[std::extent_v<decltype(m_compressor)>], count = 0;
+ if (m_last_best >= 0)
+ order[count++] = m_last_best;
for (int codecnum = 0; codecnum < std::size(m_compressor); codecnum++)
- if (m_compressor[codecnum])
+ if (codecnum != m_last_best)
+ order[count++] = codecnum;
+
+ for (int const codecnum : order)
+ if (m_compressor[codecnum] && ((codecs >> codecnum) & 1))
{
// attempt to compress, swallowing errors
try
{
+ // Discpress: a codec numbered below the best so far wins a tie, the others must beat it
+ bool const precedes = (compression >= 0) && (codecnum < compression);
+ m_compressor[codecnum]->set_limit(precedes ? complen + 1 : complen);
+
// if this is the best one, copy the data into the permanent buffer
uint32_t compbytes = m_compressor[codecnum]->compress(src, m_hunkbytes, &m_compress_test[0]);
#if CHDCODEC_VERIFY_COMPRESSION
@@ -772,7 +804,7 @@
}
printf(" codec%d=%d bytes \n", codecnum, compbytes);
#endif
- if (compbytes < complen)
+ if ((compbytes < complen) || ((compbytes == complen) && (compression >= 0) && (codecnum < compression)))
{
compression = codecnum;
complen = compbytes;
@@ -787,6 +819,7 @@
// if the best is none, copy it over
if (compression == -1)
memcpy(compressed, src, m_hunkbytes);
+ m_last_best = compression;
return compression;
}
@@ -852,12 +885,12 @@
{
// set the low bit of the size so we don't match next time
*ptr |= 1;
- return ptr + 1;
+ return reinterpret_cast<uint8_t *>(ptr) + ALLOC_HEADER; // Discpress: aligned (0.289: ptr + 1)
}
}
// alloc a new one and put it into the list
- auto *ptr = reinterpret_cast<uint32_t *>(new uint8_t[size + sizeof(uint32_t)]);
+ auto *ptr = reinterpret_cast<uint32_t *>(new uint8_t[size + ALLOC_HEADER]);
for (int scan = 0; scan < MAX_ZLIB_ALLOCS; scan++)
if (codec->m_allocptr[scan] == nullptr)
{
@@ -867,7 +900,7 @@
// set the low bit of the size so we don't match next time
*ptr = size | 1;
- return ptr + 1;
+ return reinterpret_cast<uint8_t *>(ptr) + ALLOC_HEADER;
}
@@ -881,7 +914,7 @@
auto *codec = reinterpret_cast<chd_zlib_allocator *>(opaque);
// find the hunk
- uint32_t *ptr = reinterpret_cast<uint32_t *>(address) - 1;
+ uint32_t *ptr = reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(address) - ALLOC_HEADER);
for (int scan = 0; scan < MAX_ZLIB_ALLOCS; scan++)
if (ptr == codec->m_allocptr[scan])
{
@@ -915,6 +948,13 @@
throw std::bad_alloc();
else if (zerr != Z_OK)
throw std::error_condition(chd_file::error::CODEC_ERROR);
+
+ // Discpress: libdeflate at its level 9, if asked for (zlib's level 9 is its best)
+ if (chd.libdeflate() && !(m_libdeflate = libdeflate_alloc_compressor(9)))
+ {
+ deflateEnd(&m_deflater);
+ throw std::bad_alloc();
+ }
}
@@ -925,6 +965,8 @@
chd_zlib_compressor::~chd_zlib_compressor()
{
deflateEnd(&m_deflater);
+ if (m_libdeflate)
+ libdeflate_free_compressor(m_libdeflate);
}
@@ -934,12 +976,24 @@
uint32_t chd_zlib_compressor::compress(const uint8_t *src, uint32_t srclen, uint8_t *dest)
{
+ // Discpress: libdeflate returns 0 when the result doesn't fit, which here means it must be
+ // smaller than the hunk and the limit, as with zlib below
+ if (m_libdeflate)
+ {
+ uint32_t const destlen = std::min(srclen, m_limit);
+ size_t const complen = (destlen > 1) ? libdeflate_deflate_compress(m_libdeflate, src, srclen, dest, destlen - 1) : 0;
+ if (!complen)
+ throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
+ return uint32_t(complen);
+ }
+
// reset the compressor
m_deflater.next_in = const_cast<Bytef *>(src);
m_deflater.avail_in = srclen;
m_deflater.total_in = 0;
+ uint32_t const destlen = std::min(srclen, m_limit); // Discpress: stop at the limit
m_deflater.next_out = dest;
- m_deflater.avail_out = srclen;
+ m_deflater.avail_out = destlen;
m_deflater.total_out = 0;
int zerr = deflateReset(&m_deflater);
if (zerr != Z_OK)
@@ -949,7 +1003,7 @@
zerr = deflate(&m_deflater, Z_FINISH);
// if we ended up with more data than we started with, return an error
- if (zerr != Z_STREAM_END || m_deflater.total_out >= srclen)
+ if (zerr != Z_STREAM_END || m_deflater.total_out >= destlen)
throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
// otherwise, return the length
@@ -1066,7 +1120,7 @@
// do it
ZSTD_inBuffer input{ src, srclen, 0 };
- ZSTD_outBuffer output = { dest, srclen, 0 };
+ ZSTD_outBuffer output = { dest, std::min(srclen, m_limit), 0 }; // Discpress: stop at the limit
while (output.pos < output.size)
{
result = ZSTD_compressStream2(m_stream, &output, &input, ZSTD_e_end);
@@ -1197,12 +1251,12 @@
{
// set the low bit of the size so we don't match next time
*ptr |= 1;
- return ptr + 1;
+ return reinterpret_cast<uint8_t *>(ptr) + ALLOC_HEADER; // Discpress: aligned (0.289: ptr + 1)
}
}
// alloc a new one and put it into the list
- auto *ptr = reinterpret_cast<uint32_t *>(new uint8_t[size + sizeof(uint32_t)]);
+ auto *ptr = reinterpret_cast<uint32_t *>(new uint8_t[size + ALLOC_HEADER]);
for (int scan = 0; scan < MAX_LZMA_ALLOCS; scan++)
if (codec->m_allocptr[scan] == nullptr)
{
@@ -1212,7 +1266,7 @@
// set the low bit of the size so we don't match next time
*ptr = size | 1;
- return ptr + 1;
+ return reinterpret_cast<uint8_t *>(ptr) + ALLOC_HEADER;
}
@@ -1229,7 +1283,7 @@
auto *const codec = static_cast<chd_lzma_allocator *>(const_cast<ISzAlloc *>(p));
// find the hunk
- uint32_t *ptr = reinterpret_cast<uint32_t *>(address) - 1;
+ uint32_t *ptr = reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(address) - ALLOC_HEADER);
for (int scan = 0; scan < MAX_LZMA_ALLOCS; scan++)
if (ptr == codec->m_allocptr[scan])
{
@@ -1284,8 +1338,8 @@
if (res != SZ_OK)
throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
- // run it
- SizeT complen = srclen;
+ // run it (Discpress: into a buffer only as big as a usable result, so it stops early)
+ SizeT complen = std::min<SizeT>(srclen, SizeT(m_limit) - 1);
res = LzmaEnc_MemEncode(encoder, dest, &complen, src, srclen, 0, nullptr, &m_allocator, &m_allocator);
if (res != SZ_OK)
throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
@@ -1419,7 +1473,7 @@
uint32_t chd_huffman_compressor::compress(const uint8_t *src, uint32_t srclen, uint8_t *dest)
{
uint32_t complen;
- if (m_encoder.encode(src, srclen, dest, srclen, complen) != HUFFERR_NONE)
+ if (m_encoder.encode(src, srclen, dest, std::min(srclen, m_limit - 1), complen) != HUFFERR_NONE) // Discpress: limit
throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
return complen;
}
@@ -1474,6 +1528,7 @@
m_encoder.set_num_channels(2);
m_encoder.set_block_size(blocksize(hunkbytes));
m_encoder.set_strip_metadata(true);
+ m_buffer_be.resize(hunkbytes);
}
@@ -1483,32 +1538,47 @@
uint32_t chd_flac_compressor::compress(const uint8_t *src, uint32_t srclen, uint8_t *dest)
{
- // reset and encode big-endian
- m_encoder.reset(dest + 1, hunkbytes() - 1);
- if (!m_encoder.encode_interleaved(reinterpret_cast<const int16_t *>(src), srclen / 4, !m_big_endian))
- throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
+ // Discpress: an encoding of more than this can't give a usable result (complen + 1 < limit),
+ // so the encoder may give up on it early; a byte order that gives up counts as too large
+ uint32_t const give_up = (m_limit >= 2) ? (m_limit - 2) : 0;
+
+ // reset and encode big-endian (Discpress: into its own buffer, so it needn't be encoded again if it wins)
+ m_encoder.reset(&m_buffer_be[0], hunkbytes() - 1);
+ m_encoder.set_give_up(give_up);
+ bool const encoded_be = m_encoder.encode_interleaved(reinterpret_cast<const int16_t *>(src), srclen / 4, !m_big_endian);
uint32_t complen_be = m_encoder.finish();
+ if (!encoded_be)
+ {
+ if (!m_encoder.gave_up())
+ throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
+ complen_be = UINT32_MAX;
+ }
// reset and encode little-endian
m_encoder.reset(dest + 1, hunkbytes() - 1);
- if (!m_encoder.encode_interleaved(reinterpret_cast<const int16_t *>(src), srclen / 4, m_big_endian))
- throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
+ m_encoder.set_give_up(give_up);
+ bool const encoded_le = m_encoder.encode_interleaved(reinterpret_cast<const int16_t *>(src), srclen / 4, m_big_endian);
uint32_t complen_le = m_encoder.finish();
+ if (!encoded_le)
+ {
+ if (!m_encoder.gave_up())
+ throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
+ complen_le = UINT32_MAX;
+ }
+ if ((complen_be == UINT32_MAX) && (complen_le == UINT32_MAX))
+ throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
- // pick the best one and add a byte
+ // pick the best one and add a byte (Discpress: the caller can't use complen + 1 >= limit either)
uint32_t complen = std::min(complen_le, complen_be);
- if (complen + 1 >= hunkbytes())
+ if (complen + 1 >= hunkbytes() || complen + 1 >= m_limit)
throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
- // if big-endian was better, re-do it
+ // if big-endian was better, use it (Discpress: copied, not encoded a third time)
dest[0] = 'L';
if (complen != complen_le)
{
dest[0] = 'B';
- m_encoder.reset(dest + 1, hunkbytes() - 1);
- if (!m_encoder.encode_interleaved(reinterpret_cast<const int16_t *>(src), srclen / 4, !m_big_endian))
- throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
- m_encoder.finish();
+ memcpy(dest + 1, &m_buffer_be[0], complen);
}
return complen + 1;
}
@@ -1642,14 +1712,16 @@
memcpy(&m_buffer[frames * cdrom_file::MAX_SECTOR_DATA + framenum * cdrom_file::MAX_SUBCODE_DATA], &src[framenum * cdrom_file::FRAME_SIZE + cdrom_file::MAX_SECTOR_DATA], cdrom_file::MAX_SUBCODE_DATA);
}
- // reset and encode the audio portion
+ // reset and encode the audio portion (Discpress: giving up once the result can't be usable)
m_encoder.reset(dest, hunkbytes());
+ m_encoder.set_give_up((m_limit >= 1) ? (m_limit - 1) : 0);
uint8_t *buffer = &m_buffer[0];
- if (!m_encoder.encode_interleaved(reinterpret_cast<int16_t *>(buffer), frames * cdrom_file::MAX_SECTOR_DATA/4, m_swap_endian))
- throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
+ bool const encoded = m_encoder.encode_interleaved(reinterpret_cast<int16_t *>(buffer), frames * cdrom_file::MAX_SECTOR_DATA/4, m_swap_endian);
// finish up
uint32_t complen = m_encoder.finish();
+ if (!encoded || complen >= m_limit)
+ throw std::error_condition(chd_file::error::COMPRESSION_ERROR);
// deflate the subcode data
m_deflater.next_in = const_cast<Bytef *>(&m_buffer[frames * cdrom_file::MAX_SECTOR_DATA]);
--- a/src/lib/util/chdcodec.h
+++ b/src/lib/util/chdcodec.h
@@ -71,6 +71,13 @@
// implementation
virtual uint32_t compress(const uint8_t *src, uint32_t srclen, uint8_t *dest) = 0;
+
+ // Discpress: a result of limit bytes or more can't be used, so a codec may give up (throw)
+ // as soon as it knows its output will be that large; results below the limit are unaffected
+ void set_limit(uint32_t limit) noexcept { m_limit = limit; }
+
+protected:
+ uint32_t m_limit = UINT32_MAX;
};
@@ -118,14 +125,15 @@
chd_compressor_group(chd_file &file, chd_codec_type compressor_list[4]);
~chd_compressor_group();
- // find the best compressor
- int8_t find_best_compressor(const uint8_t *src, uint8_t *compressed, uint32_t &complen);
+ // find the best compressor (Discpress: among the codecs whose slot bits are set in codecs)
+ int8_t find_best_compressor(const uint8_t *src, uint8_t *compressed, uint32_t &complen, uint32_t codecs = 0x0f);
private:
// internal state
uint32_t m_hunkbytes; // number of bytes in a hunk
chd_compressor::ptr m_compressor[4]; // array of active codecs
std::vector<uint8_t> m_compress_test; // test buffer for compression
+ int m_last_best = -1; // Discpress: codec that won the previous hunk
#if CHDCODEC_VERIFY_COMPRESSION
chd_decompressor::ptr m_decompressor[4]; // array of active codecs
std::vector<uint8_t> m_decompressed; // verification buffer
--- a/src/lib/util/flac.cpp
+++ b/src/lib/util/flac.cpp
@@ -20,6 +20,9 @@
#include <new>
#include <tuple>
+// Discpress: libFLAC exports this, but declares it only in its private share/private.h
+extern "C" FLAC_API FLAC__bool FLAC__stream_encoder_set_do_md5(FLAC__StreamEncoder *encoder, FLAC__bool value);
+
//**************************************************************************
// FLAC ENCODER
@@ -71,11 +74,15 @@
m_compressed_offset = 0;
m_ignore_bytes = m_strip_metadata ? 4 : 0;
m_found_audio = !m_strip_metadata;
+ m_give_up = UINT32_MAX;
+ m_gave_up = false;
// configure the encoder in a standard way
// note we do this on each reset; if we don't, results are NOT consistent!
FLAC__stream_encoder_set_verify(m_encoder, false);
-// FLAC__stream_encoder_set_do_md5(m_encoder, false);
+ // Discpress: the MD5 only ends up in the STREAMINFO header, which is rewritten at the end
+ // only when writing to a file; in memory it costs time and changes nothing
+ FLAC__stream_encoder_set_do_md5(m_encoder, m_file != nullptr);
FLAC__stream_encoder_set_compression_level(m_encoder, 8);
FLAC__stream_encoder_set_channels(m_encoder, m_channels);
FLAC__stream_encoder_set_bits_per_sample(m_encoder, 16);
@@ -192,6 +199,10 @@
uint32_t flac_encoder::finish()
{
+ // Discpress: never give up here. libFLAC encodes the last block in finish, so the work is
+ // done by the time it is written, and after a write error there it can't be reset again
+ m_give_up = UINT32_MAX;
+
// process the data and return the amount written
FLAC__stream_encoder_finish(m_encoder);
if (m_file)
@@ -229,6 +240,8 @@
m_strip_metadata = false;
m_ignore_bytes = 0;
m_found_audio = false;
+ m_give_up = UINT32_MAX;
+ m_gave_up = false;
}
@@ -273,6 +286,12 @@
}
else
{
+ // Discpress: stop once the output passes the limit the caller can use
+ if (m_compressed_offset + count > m_give_up)
+ {
+ m_gave_up = true;
+ return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;
+ }
if (m_compressed_offset + count <= m_compressed_length)
memcpy(m_compressed_start + m_compressed_offset, buffer, count);
m_compressed_offset += count;
@@ -499,9 +518,11 @@
m_uncompressed_length = num_samples;
m_uncompressed_swap = swap_endian;
- // loop until we get everything we want
+ // loop until we get everything we want (Discpress: damaged data can end the stream first, after
+ // which process_single keeps returning true without producing samples, so this looped forever)
while (m_uncompressed_offset < m_uncompressed_length)
- if (!FLAC__stream_decoder_process_single(m_decoder))
+ if (!FLAC__stream_decoder_process_single(m_decoder) ||
+ (m_uncompressed_offset < m_uncompressed_length && FLAC__stream_decoder_get_state(m_decoder) == FLAC__STREAM_DECODER_END_OF_STREAM))
return false;
return true;
}
@@ -527,9 +548,11 @@
m_uncompressed_length = num_samples;
m_uncompressed_swap = swap_endian;
- // loop until we get everything we want
+ // loop until we get everything we want (Discpress: damaged data can end the stream first, after
+ // which process_single keeps returning true without producing samples, so this looped forever)
while (m_uncompressed_offset < m_uncompressed_length)
- if (!FLAC__stream_decoder_process_single(m_decoder))
+ if (!FLAC__stream_decoder_process_single(m_decoder) ||
+ (m_uncompressed_offset < m_uncompressed_length && FLAC__stream_decoder_get_state(m_decoder) == FLAC__STREAM_DECODER_END_OF_STREAM))
return false;
return true;
}
--- a/src/lib/util/flac.h
+++ b/src/lib/util/flac.h
@@ -40,6 +40,10 @@
void set_num_channels(uint8_t num_channels) { m_channels = num_channels; }
void set_block_size(uint32_t block_size) { m_block_size = block_size; }
void set_strip_metadata(bool strip) { m_strip_metadata = strip; }
+ // Discpress: after reset, give up (encode fails) once the output would pass this many bytes;
+ // finish still completes the encoder, and gave_up tells a give-up from other failures
+ void set_give_up(uint32_t bytes) { m_give_up = bytes; }
+ bool gave_up() const { return m_gave_up; }
// getters (valid after reset)
FLAC__StreamEncoderState state() const { return FLAC__stream_encoder_get_state(m_encoder); }
@@ -83,6 +87,8 @@
bool m_strip_metadata; // strip the metadata?
uint32_t m_ignore_bytes; // how many bytes to ignore when writing
bool m_found_audio; // have we hit the audio yet?
+ uint32_t m_give_up; // Discpress: most output bytes wanted (set_give_up)
+ bool m_gave_up; // Discpress: stopped at that limit
};
--- a/src/lib/util/hashing.cpp
+++ b/src/lib/util/hashing.cpp
@@ -10,6 +10,7 @@
#include "hashing.h"
+#include "multibyte.h"
#include "strformat.h"
#include "eminline.h"
@@ -44,64 +45,34 @@
}
-inline uint32_t sha1_b(uint32_t *data, unsigned i) noexcept
-{
- uint32_t r = data[(i + 13) & 15U];
- r ^= data[(i + 8) & 15U];
- r ^= data[(i + 2) & 15U];
- r ^= data[i & 15U];
- r = rotl_32(r, 1);
- data[i & 15U] = r;
- return r;
-}
-
-inline void sha1_r0(const uint32_t *data, std::array<uint32_t, 5> &d, unsigned i) noexcept
-{
- d[i % 5] = d[i % 5] + ((d[(i + 3) % 5] & (d[(i + 2) % 5] ^ d[(i + 1) % 5])) ^ d[(i + 1) % 5]) + data[i] + 0x5a827999U + rotl_32(d[(i + 4) % 5], 5);
- d[(i + 3) % 5] = rotl_32(d[(i + 3) % 5], 30);
-}
-
-inline void sha1_r1(uint32_t *data, std::array<uint32_t, 5> &d, unsigned i) noexcept
-{
- d[i % 5] = d[i % 5] + ((d[(i + 3) % 5] & (d[(i + 2) % 5] ^ d[(i + 1) % 5])) ^ d[(i + 1) % 5])+ sha1_b(data, i) + 0x5a827999U + rotl_32(d[(i + 4) % 5], 5);
- d[(i + 3) % 5] = rotl_32(d[(i + 3) % 5], 30);
-}
-
-inline void sha1_r2(uint32_t *data, std::array<uint32_t, 5> &d, unsigned i) noexcept
-{
- d[i % 5] = d[i % 5] + (d[(i + 3) % 5] ^ d[(i + 2) % 5] ^ d[(i + 1) % 5]) + sha1_b(data, i) + 0x6ed9eba1U + rotl_32(d[(i + 4) % 5], 5);
- d[(i + 3) % 5] = rotl_32(d[(i + 3) % 5], 30);
-}
-
-inline void sha1_r3(uint32_t *data, std::array<uint32_t, 5> &d, unsigned i) noexcept
-{
- d[i % 5] = d[i % 5] + (((d[(i + 3) % 5] | d[(i + 2) % 5]) & d[(i + 1) % 5]) | (d[(i + 3) % 5] & d[(i + 2) % 5])) + sha1_b(data, i) + 0x8f1bbcdcU + rotl_32(d[(i + 4) % 5], 5);
- d[(i + 3) % 5] = rotl_32(d[(i + 3) % 5], 30);
-}
-
-inline void sha1_r4(uint32_t *data, std::array<uint32_t, 5> &d, unsigned i) noexcept
-{
- d[i % 5] = d[i % 5] + (d[(i + 3) % 5] ^ d[(i + 2) % 5] ^ d[(i + 1) % 5]) + sha1_b(data, i) + 0xca62c1d6U + rotl_32(d[(i + 4) % 5], 5);
- d[(i + 3) % 5] = rotl_32(d[(i + 3) % 5], 30);
-}
-
-inline void sha1_process(std::array<uint32_t, 5> &st, uint32_t *data) noexcept
-{
- std::array<uint32_t, 5> d = st;
- unsigned i = 0U;
- while (i < 16U)
- sha1_r0(data, d, i++);
- while (i < 20U)
- sha1_r1(data, d, i++);
- while (i < 40U)
- sha1_r2(data, d, i++);
- while (i < 60U)
- sha1_r3(data, d, i++);
- while (i < 80U)
- sha1_r4(data, d, i++);
- for (i = 0U; i < 5U; i++)
- st[i] += d[i];
-}
+// Discpress: the 80 rounds unrolled with named variables (upstream indexed an array by i % 5)
+#define SHA1_W(i) (w[(i) & 15U] = rotl_32(w[((i) + 13U) & 15U] ^ w[((i) + 8U) & 15U] ^ w[((i) + 2U) & 15U] ^ w[(i) & 15U], 1))
+#define SHA1_R0(a, b, c, d, e, i) e += ((b & (c ^ d)) ^ d) + w[i] + 0x5a827999U + rotl_32(a, 5); b = rotl_32(b, 30);
+#define SHA1_R1(a, b, c, d, e, i) e += ((b & (c ^ d)) ^ d) + SHA1_W(i) + 0x5a827999U + rotl_32(a, 5); b = rotl_32(b, 30);
+#define SHA1_R2(a, b, c, d, e, i) e += (b ^ c ^ d) + SHA1_W(i) + 0x6ed9eba1U + rotl_32(a, 5); b = rotl_32(b, 30);
+#define SHA1_R3(a, b, c, d, e, i) e += (((b | c) & d) | (b & c)) + SHA1_W(i) + 0x8f1bbcdcU + rotl_32(a, 5); b = rotl_32(b, 30);
+#define SHA1_R4(a, b, c, d, e, i) e += (b ^ c ^ d) + SHA1_W(i) + 0xca62c1d6U + rotl_32(a, 5); b = rotl_32(b, 30);
+#define SHA1_5(R, i) R(a, b, c, d, e, i) R(e, a, b, c, d, i + 1) R(d, e, a, b, c, i + 2) R(c, d, e, a, b, i + 3) R(b, c, d, e, a, i + 4)
+
+// w: the block as 16 big-endian words; used as the message schedule, so it is overwritten
+inline void sha1_process(std::array<uint32_t, 5> &st, uint32_t *w) noexcept
+{
+ uint32_t a = st[4], b = st[3], c = st[2], d = st[1], e = st[0];
+ SHA1_5(SHA1_R0, 0U) SHA1_5(SHA1_R0, 5U) SHA1_5(SHA1_R0, 10U)
+ SHA1_R0(a, b, c, d, e, 15U) SHA1_R1(e, a, b, c, d, 16U) SHA1_R1(d, e, a, b, c, 17U) SHA1_R1(c, d, e, a, b, 18U) SHA1_R1(b, c, d, e, a, 19U)
+ SHA1_5(SHA1_R2, 20U) SHA1_5(SHA1_R2, 25U) SHA1_5(SHA1_R2, 30U) SHA1_5(SHA1_R2, 35U)
+ SHA1_5(SHA1_R3, 40U) SHA1_5(SHA1_R3, 45U) SHA1_5(SHA1_R3, 50U) SHA1_5(SHA1_R3, 55U)
+ SHA1_5(SHA1_R4, 60U) SHA1_5(SHA1_R4, 65U) SHA1_5(SHA1_R4, 70U) SHA1_5(SHA1_R4, 75U)
+ st[4] += a; st[3] += b; st[2] += c; st[1] += d; st[0] += e;
+}
+
+#undef SHA1_5
+#undef SHA1_R4
+#undef SHA1_R3
+#undef SHA1_R2
+#undef SHA1_R1
+#undef SHA1_R0
+#undef SHA1_W
} // anonymous namespace
@@ -193,27 +164,30 @@
#else
constexpr unsigned swizzle = 0U;
#endif
+ auto const *const src = reinterpret_cast<const uint8_t *>(data);
uint32_t residual = (uint32_t(m_cnt) >> 3) & 63U;
m_cnt += uint64_t(length) << 3;
uint32_t offset = 0U;
- if (length >= (64U - residual))
+ if (residual)
{
- if (residual)
- {
- for (offset = 0U; (offset + residual) < 64U; offset++)
- reinterpret_cast<uint8_t *>(m_buf)[(offset + residual) ^ swizzle] = reinterpret_cast<const uint8_t *>(data)[offset];
- sha1_process(m_st, m_buf);
- }
- while ((length - offset) >= 64U)
- {
- for (residual = 0U; residual < 64U; residual++, offset++)
- reinterpret_cast<uint8_t *>(m_buf)[residual ^ swizzle] = reinterpret_cast<const uint8_t *>(data)[offset];
- sha1_process(m_st, m_buf);
- }
- residual = 0U;
+ // complete the partial block first
+ for ( ; (offset < length) && (residual < 64U); residual++, offset++)
+ reinterpret_cast<uint8_t *>(m_buf)[residual ^ swizzle] = src[offset];
+ if (residual < 64U)
+ return;
+ sha1_process(m_st, m_buf);
+ }
+
+ // Discpress: whole blocks are read straight from the input as big-endian words
+ for ( ; (length - offset) >= 64U; offset += 64U)
+ {
+ uint32_t w[16];
+ for (unsigned i = 0U; i < 16U; i++)
+ w[i] = get_u32be(&src[offset + (i << 2)]);
+ sha1_process(m_st, w);
}
- for ( ; offset < length; residual++, offset++)
- reinterpret_cast<uint8_t *>(m_buf)[residual ^ swizzle] = reinterpret_cast<const uint8_t *>(data)[offset];
+ for (residual = 0U; offset < length; residual++, offset++)
+ reinterpret_cast<uint8_t *>(m_buf)[residual ^ swizzle] = src[offset];
}
@@ -395,50 +369,45 @@
* @param length The length.
*/
-void crc16_creator::append(const void *data, uint32_t length) noexcept
+namespace {
+
+// Discpress: slice-by-8 tables for CRC-16/CCITT; t[k][x] is the CRC of byte x followed by k zero
+// bytes, so eight bytes are folded in at once (t[0] is upstream's byte-at-a-time table)
+struct crc16_tables
{
- static const uint16_t s_table[256] =
+ uint16_t t[8][256];
+
+ constexpr crc16_tables() : t()
{
- 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
- 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
- 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
- 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
- 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
- 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
- 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
- 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
- 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
- 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
- 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
- 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
- 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
- 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
- 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
- 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
- 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
- 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
- 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
- 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
- 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
- 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
- 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
- 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
- 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
- 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
- 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
- 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
- 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
- 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
- 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
- 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
- };
+ for (unsigned x = 0; x < 256; x++)
+ {
+ uint16_t crc = uint16_t(x << 8);
+ for (int bit = 0; bit < 8; bit++)
+ crc = (crc & 0x8000) ? uint16_t((crc << 1) ^ 0x1021) : uint16_t(crc << 1);
+ t[0][x] = crc;
+ }
+ for (unsigned k = 1; k < 8; k++)
+ for (unsigned x = 0; x < 256; x++)
+ t[k][x] = uint16_t((t[k - 1][x] << 8) ^ t[0][t[k - 1][x] >> 8]);
+ }
+};
+
+constexpr crc16_tables s_crc16;
+} // anonymous namespace
+
+void crc16_creator::append(const void *data, uint32_t length) noexcept
+{
const auto *src = reinterpret_cast<const uint8_t *>(data);
+ auto const &t = s_crc16.t;
// fetch the current value into a local and rip through the source data
uint16_t crc = m_accum.m_raw;
+ for ( ; length >= 8; length -= 8, src += 8)
+ crc = t[7][(crc >> 8) ^ src[0]] ^ t[6][(crc & 0xff) ^ src[1]] ^ t[5][src[2]] ^ t[4][src[3]] ^
+ t[3][src[4]] ^ t[2][src[5]] ^ t[1][src[6]] ^ t[0][src[7]];
while (length-- != 0)
- crc = (crc << 8) ^ s_table[(crc >> 8) ^ *src++];
+ crc = (crc << 8) ^ t[0][(crc >> 8) ^ *src++];
m_accum.m_raw = crc;
}
--- a/src/lib/util/unicode.cpp
+++ b/src/lib/util/unicode.cpp
@@ -16,71 +16,12 @@
#include "strconv.h"
#endif
-#include <utf8proc.h>
-
#include <codecvt>
#include <locale>
-namespace {
-
-//-------------------------------------------------
-// internal_normalize_unicode - uses utf8proc to
-// normalize unicode
-//-------------------------------------------------
-
-std::string internal_normalize_unicode(
- char const *s,
- size_t length,
- unicode_normalization_form normalization_form,
- bool fold_case,
- bool null_terminated)
-{
- // convert the normalization form
- int options;
- switch (normalization_form)
- {
- case unicode_normalization_form::C:
- options = UTF8PROC_STABLE | UTF8PROC_COMPOSE;
- break;
- case unicode_normalization_form::D:
- options = UTF8PROC_STABLE | UTF8PROC_DECOMPOSE;
- break;
- case unicode_normalization_form::KC:
- options = UTF8PROC_STABLE | UTF8PROC_COMPOSE | UTF8PROC_COMPAT;
- break;
- case unicode_normalization_form::KD:
- options = UTF8PROC_STABLE | UTF8PROC_DECOMPOSE | UTF8PROC_COMPAT;
- break;
- default:
- throw false;
- }
-
- // perform case folding?
- if (fold_case)
- options |= UTF8PROC_CASEFOLD;
-
- // use NUL terminator to determine length?
- if (null_terminated)
- options |= UTF8PROC_NULLTERM;
-
- // invoke utf8proc
- utf8proc_uint8_t *utf8proc_result(nullptr);
- utf8proc_ssize_t const utf8proc_result_length(utf8proc_map(reinterpret_cast<utf8proc_uint8_t const *>(s), length, &utf8proc_result, utf8proc_option_t(options)));
-
- // conver the result
- std::string result;
- if (utf8proc_result)
- {
- if (utf8proc_result_length > 0)
- result.assign(reinterpret_cast<char const *>(utf8proc_result), utf8proc_result_length);
- free(utf8proc_result);
- }
-
- return result;
-}
-
-} // anonymous namespace
+// Discpress: normalize_unicode, uchar_toupper and uchar_tolower (the only users of
+// utf8proc) are removed; nothing in the chdman engine calls them.
//-------------------------------------------------
@@ -482,50 +423,6 @@
}
-//-------------------------------------------------
-// normalize_unicode - uses utf8proc to normalize
-// unicode
-//-------------------------------------------------
-
-std::string normalize_unicode(const char *s, unicode_normalization_form normalization_form, bool fold_case)
-{
- return internal_normalize_unicode(s, 0, normalization_form, fold_case, true);
-}
-
-
-//-------------------------------------------------
-// normalize_unicode - uses utf8proc to normalize
-// unicode
-//-------------------------------------------------
-
-std::string normalize_unicode(std::string_view s, unicode_normalization_form normalization_form, bool fold_case)
-{
- return internal_normalize_unicode(s.data(), s.length(), normalization_form, fold_case, false);
-}
-
-
-//-------------------------------------------------
-// uchar_toupper - uses utf8proc to convert to
-// upper case
-//-------------------------------------------------
-
-char32_t uchar_toupper(char32_t ch)
-{
- return utf8proc_toupper(ch);
-}
-
-
-//-------------------------------------------------
-// uchar_tolower - uses utf8proc to convert to
-// lower case
-//-------------------------------------------------
-
-char32_t uchar_tolower(char32_t ch)
-{
- return utf8proc_tolower(ch);
-}
-
-
//-------------------------------------------------
// utf8_previous_char - return a pointer to the
// previous character in a string
--- a/src/lib/util/unicode.h
+++ b/src/lib/util/unicode.h
@@ -37,8 +37,6 @@
#define UTF8_CHAR_MAX 6
#define UTF16_CHAR_MAX 2
-enum class unicode_normalization_form { C, D, KC, KD };
-
/***************************************************************************
@@ -71,14 +69,6 @@
std::wstring wstring_from_utf8(std::string_view utf8string);
std::string utf8_from_wstring(std::wstring_view string);
-// unicode normalization
-std::string normalize_unicode(const char *s, unicode_normalization_form normalization_form, bool fold_case = false);
-std::string normalize_unicode(std::string_view s, unicode_normalization_form normalization_form, bool fold_case = false);
-
-// upper and lower case
-char32_t uchar_toupper(char32_t ch);
-char32_t uchar_tolower(char32_t ch);
-
// misc UTF-8 helpers
const char *utf8_previous_char(const char *utf8string);
bool utf8_is_valid_string(const char *utf8string);
--- a/src/tools/chdman.cpp
+++ b/src/tools/chdman.cpp
@@ -22,13 +22,17 @@
#include "strformat.h"
#include "vbiparse.h"
+#include <algorithm>
#include <array>
#include <cassert>
#include <cctype>
#include <cstdio>
#include <cstdlib>
+#include <coroutine>
#include <cstring>
#include <ctime>
+#include <exception>
+#include <filesystem>
#include <iostream>
#include <limits>
#include <memory>
@@ -41,11 +45,122 @@
#include <utility>
#include <vector>
+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+
+extern "C" int wasm_par_active(void); // browser build: hunks are being compressed by helper workers
+extern "C" int wasm_out_full(void); // Discpress: browser build: results written into a folder are waiting to be written
+#endif
+
using util::string_format;
//**************************************************************************
+// RESUMABLE COMMANDS (Discpress)
+//**************************************************************************
+
+// The commands that compress are coroutines: in the browser, compress_common pauses after each
+// step while helper workers compress hunks, and the page resumes it when their results arrive.
+// Natively nothing pauses and main runs each command straight through.
+
+class chdman_task
+{
+public:
+ struct promise_type;
+ using handle = std::coroutine_handle<promise_type>;
+
+ struct promise_type
+ {
+ std::coroutine_handle<> continuation; // the task awaiting this one, if any
+ std::exception_ptr exception;
+
+ chdman_task get_return_object() noexcept { return chdman_task(handle::from_promise(*this)); }
+ std::suspend_always initial_suspend() noexcept { return {}; }
+ auto final_suspend() noexcept
+ {
+ struct awaiter
+ {
+ bool await_ready() noexcept { return false; }
+ std::coroutine_handle<> await_suspend(handle h) noexcept
+ {
+ std::coroutine_handle<> const next = h.promise().continuation;
+ return next ? next : std::noop_coroutine();
+ }
+ void await_resume() noexcept { }
+ };
+ return awaiter();
+ }
+ void return_void() noexcept { }
+ void unhandled_exception() noexcept { exception = std::current_exception(); }
+ };
+
+ chdman_task() noexcept = default;
+ chdman_task(chdman_task &&that) noexcept : m_handle(std::exchange(that.m_handle, nullptr)) { }
+ chdman_task &operator=(chdman_task &&that) noexcept { if (this != &that) { reset(); m_handle = std::exchange(that.m_handle, nullptr); } return *this; }
+ ~chdman_task() { reset(); }
+
+ handle get() const noexcept { return m_handle; }
+ bool done() const noexcept { return !m_handle || m_handle.done(); }
+ void rethrow() const { if (m_handle && m_handle.promise().exception) std::rethrow_exception(m_handle.promise().exception); }
+ void reset() noexcept { if (m_handle) std::exchange(m_handle, nullptr).destroy(); }
+
+ // awaiting a task runs it, and continues the awaiting task when it is done
+ bool await_ready() const noexcept { return false; }
+ std::coroutine_handle<> await_suspend(std::coroutine_handle<> awaiting) noexcept
+ {
+ m_handle.promise().continuation = awaiting;
+ return m_handle;
+ }
+ void await_resume() const { rethrow(); }
+
+private:
+ explicit chdman_task(handle h) noexcept : m_handle(h) { }
+ handle m_handle;
+};
+
+// the innermost paused task, resumed by chdman_continue
+static std::coroutine_handle<> s_paused;
+
+// compress_common's pause point: lets helper workers' results arrive (browser only)
+struct compression_pause
+{
+ bool await_ready() const noexcept
+ {
+#ifdef __EMSCRIPTEN__
+ return !wasm_par_active() && !wasm_out_full(); // Discpress: also waits for a slow output folder
+#else
+ return true;
+#endif
+ }
+ void await_suspend(std::coroutine_handle<> h) noexcept { s_paused = h; }
+ void await_resume() const noexcept { }
+};
+
+// Discpress: extract and verify wait here while helper workers decompress the hunks they are about
+// to read, and the next window's (browser only; otherwise the hunks are read the usual way)
+struct read_pause
+{
+ bool await_ready() const noexcept { return false; }
+ void await_suspend(std::coroutine_handle<> h) noexcept { s_paused = h; }
+ void await_resume() const noexcept { }
+};
+
+static chdman_task read_ahead(chd_file &chd, uint64_t offset, uint64_t length)
+{
+#ifdef __EMSCRIPTEN__
+ uint32_t const first = offset / chd.hunk_bytes();
+ uint32_t const count = (offset + length + chd.hunk_bytes() - 1) / chd.hunk_bytes() - first;
+ // Discpress: also waits while the output folder is behind (results streamed into it pile up otherwise)
+ while (wasm_out_full() || !chd.wasm_read_ahead(first, count))
+ co_await read_pause();
+#endif
+ co_return;
+}
+
+
+
+//**************************************************************************
// CONSTANTS & DEFINES
//**************************************************************************
// MINGW has adopted the MSVC formatting for 64-bit ints as of GCC 4.4 and deprecated it as of GCC 9.3
@@ -64,14 +179,22 @@
// default hard disk sector size
constexpr uint32_t IDE_SECTOR_SIZE = 512;
-// temporary input buffer size
+// temporary input buffer size (Discpress: smaller in the browser, where helper workers decompress
+// the next buffer's worth of hunks while this one is used, and memory is tighter)
+#ifdef __EMSCRIPTEN__
+constexpr uint32_t TEMP_BUFFER_SIZE = 8 * 1024 * 1024;
+#else
constexpr uint32_t TEMP_BUFFER_SIZE = 32 * 1024 * 1024;
+#endif
// modes
constexpr int MODE_NORMAL = 0;
constexpr int MODE_CUEBIN = 1;
constexpr int MODE_GDI = 2;
+// Discpress: the input cue sheet, as createcd --keepcue stores it (metadata that no checksum covers)
+constexpr chd_metadata_tag CUE_SHEET_METADATA_TAG = CHD_MAKE_TAG('C','U','E','S');
+
// osd printf verbosity
constexpr bool OSD_PRINTF_VERBOSE = false;
@@ -103,6 +226,10 @@
#define OPTION_OUTPUT "output"
#define OPTION_OUTPUT_BIN "outputbin"
#define OPTION_OUTPUT_SPLITBIN "splitbin"
+#define OPTION_REDUMP "redump"
+#define OPTION_KEEPCUE "keepcue"
+#define OPTION_CODEC_PLAN "codecplan"
+#define OPTION_LIBDEFLATE "libdeflate"
#define OPTION_OUTPUT_FORCE "force"
#define OPTION_INPUT_START_BYTE "inputstartbyte"
#define OPTION_INPUT_START_HUNK "inputstarthunk"
@@ -140,15 +267,16 @@
[[noreturn]] static void report_error(int error, Format &&fmt, Params &&...args);
static void do_info(parameters_map ¶ms);
-static void do_verify(parameters_map ¶ms);
-static void do_create_raw(parameters_map ¶ms);
-static void do_create_hd(parameters_map ¶ms);
-static void do_create_cd(parameters_map ¶ms);
-static void do_create_dvd(parameters_map ¶ms);
-static void do_create_ld(parameters_map ¶ms);
-static void do_copy(parameters_map ¶ms);
-static void do_extract_raw(parameters_map ¶ms);
-static void do_extract_cd(parameters_map ¶ms);
+static chdman_task do_verify(parameters_map ¶ms);
+static chdman_task do_create_raw(parameters_map ¶ms);
+static chdman_task do_create_hd(parameters_map ¶ms);
+static chdman_task do_create_cd(parameters_map ¶ms);
+static chdman_task do_create_dvd(parameters_map ¶ms);
+static chdman_task do_create_ld(parameters_map ¶ms);
+static chdman_task do_copy(parameters_map ¶ms);
+static chdman_task do_extract_raw(parameters_map ¶ms);
+static chdman_task do_extract_dvd(parameters_map ¶ms); // Discpress
+static chdman_task do_extract_cd(parameters_map ¶ms);
static void do_extract_ld(parameters_map ¶ms);
static void do_add_metadata(parameters_map ¶ms);
static void do_del_metadata(parameters_map ¶ms);
@@ -214,12 +342,21 @@
// ======================> command_description
+// a command is a plain function, or a task when it compresses (see chdman_task)
+struct command_handler
+{
+ command_handler(void (*function)(parameters_map &)) : function(function) { }
+ command_handler(chdman_task (*task)(parameters_map &)) : task(task) { }
+ void (*function)(parameters_map &) = nullptr;
+ chdman_task (*task)(parameters_map &) = nullptr;
+};
+
struct command_description
{
const char *name;
- void (*handler)(parameters_map &);
+ command_handler handler;
const char *description;
- const char *valid_options[16];
+ const char *valid_options[20]; // Discpress: 16 in 0.289, and createhd now has 17
};
@@ -329,9 +466,15 @@
return 0;
if (offset + length > m_maxoffset)
length = m_maxoffset - offset;
- if (m_file.seek(offset, SEEK_SET)) // FIXME: better error reporting?
- return 0;
- auto const [err, actual] = read(m_file, dest, length); // FIXME: check for error return
+ // a failed or short read is an error (0.289 ignored it and compressed whatever the buffer held)
+ std::error_condition err = m_file.seek(offset, SEEK_SET);
+ std::size_t actual = 0;
+ if (!err)
+ std::tie(err, actual) = read(m_file, dest, length);
+ if (!err && (actual != length))
+ err = std::errc::io_error;
+ if (err)
+ throw err;
return actual;
}
@@ -433,6 +576,31 @@
{
}
+protected:
+ // Discpress: for the codec plan, whether a range lies in audio tracks, data tracks or both
+ virtual hunk_content read_content(uint64_t offset, uint32_t length) const
+ {
+ uint64_t const first = offset / cdrom_file::FRAME_SIZE, end = (offset + length + cdrom_file::FRAME_SIZE - 1) / cdrom_file::FRAME_SIZE;
+ bool audio = false, data = false;
+ uint64_t start = 0;
+ for (int tracknum = 0; (tracknum < m_toc.numtrks) && (start < end); tracknum++)
+ {
+ const cdrom_file::track_info &trackinfo = m_toc.tracks[tracknum];
+ uint64_t const next = start + trackinfo.frames + trackinfo.extraframes;
+ if (next > first)
+ {
+ if (trackinfo.trktype == cdrom_file::CD_TRACK_AUDIO)
+ audio = true;
+ else
+ data = true;
+ }
+ start = next;
+ }
+ return (audio && data) ? hunk_content::MIXED : audio ? hunk_content::AUDIO : hunk_content::DATA;
+ }
+
+public:
+
// read interface
virtual uint32_t read_data(void *_dest, uint64_t offset, uint32_t length)
{
@@ -455,7 +623,8 @@
if (offset >= startoffs && offset < endoffs)
{
// if we don't already have this file open, open it now
- if (!m_file || m_lastfile.compare(m_info.track[tracknum].fname)!=0)
+ // Discpress: a track in pieces opens each piece's file as it reads it
+ if (m_info.track[tracknum].pieces.empty() && (!m_file || m_lastfile.compare(m_info.track[tracknum].fname)!=0))
{
m_file.reset();
m_lastfile = m_info.track[tracknum].fname;
@@ -497,6 +666,39 @@
{
memset(dest, 0, bytesperframe);
}
+ else if (!m_info.track[tracknum].pieces.empty())
+ {
+ // Discpress: a track in pieces, whose frames add up to the track's
+ uint64_t frame = (src_frame_start - src_track_start) / bytesperframe;
+ for (const cdrom_file::track_input_piece &piece : m_info.track[tracknum].pieces)
+ {
+ if (frame >= piece.frames)
+ {
+ frame -= piece.frames;
+ continue;
+ }
+ if (!piece.fname.empty()) // else zeros
+ {
+ if (!m_file || m_lastfile.compare(piece.fname) != 0)
+ {
+ m_file.reset();
+ m_lastfile = piece.fname;
+ std::error_condition const filerr = util::core_file::open(m_lastfile, OPEN_FLAG_READ, m_file);
+ if (filerr)
+ report_error(1, "Error opening input file (%s): %s", m_lastfile, filerr.message());
+ }
+ // the last frame may be partly in the file, and zeros after that (dest is zeroed)
+ std::size_t const want = (piece.tail != 0 && frame == piece.frames - 1) ? piece.tail : bytesperframe;
+ std::error_condition err = m_file->seek(piece.offset + frame * bytesperframe, SEEK_SET);
+ std::size_t count = 0;
+ if (!err)
+ std::tie(err, count) = read(*m_file, dest, want);
+ if (err || (count != want))
+ report_error(1, "Error reading input file (%s)'", m_lastfile);
+ }
+ break;
+ }
+ }
else
{
std::error_condition err = m_file->seek(
@@ -676,6 +878,8 @@
{ OPTION_OUTPUT, "o", true, " <filename>: output file name" },
{ OPTION_OUTPUT_BIN, "ob", true, " <filename>: output file name for binary data" },
{ OPTION_OUTPUT_SPLITBIN, "sb", false, ": output one binary file per track" },
+ { OPTION_REDUMP, "rd", false, ": write a .cue as Redump does: CRLF line ends, and one binary file per track unless there is only one" },
+ { OPTION_KEEPCUE, "kc", false, ": keep the input .cue in the CHD, as metadata that leaves its checksums unchanged; extractcd --redump writes it back" },
{ OPTION_OUTPUT_FORCE, "f", false, ": force overwriting an existing file" },
{ OPTION_OUTPUT_PARENT, "op", true, " <filename>: parent file name for output CHD" },
{ OPTION_INPUT_START_BYTE, "isb", true, " <offset>: starting byte offset within the input" },
@@ -687,6 +891,8 @@
{ OPTION_HUNK_SIZE, "hs", true, " <bytes>: size of each hunk, in bytes" },
{ OPTION_UNIT_SIZE, "us", true, " <bytes>: size of each unit, in bytes" },
{ OPTION_COMPRESSION, "c", true, " <none|type1[,type2[,...]]>: which compression codecs to use (up to 4)" },
+ { OPTION_CODEC_PLAN, "cp", false, ": try each hunk only with the codecs meant for its content (CD audio: cdfl, CD data: cdlz, other data: all but flac): up to twice as fast, the same checksums, slightly larger" },
+ { OPTION_LIBDEFLATE, "ld", false, ": deflate (zlib, cdzl) with libdeflate: 2 to 3 times faster and a little smaller; the same checksums, and any reader decodes it" },
{ OPTION_IDENT, "id", true, " <filename>: name of ident file to provide CHS information" },
{ OPTION_CHS, "chs", true, " <cylinders,heads,sectors>: specifies CHS geometry directly" },
{ OPTION_SECTOR_SIZE, "ss", true, " <bytes>: size of each hard disk sector" },
@@ -734,7 +940,9 @@
OPTION_HUNK_SIZE,
OPTION_UNIT_SIZE,
OPTION_COMPRESSION,
- OPTION_NUMPROCESSORS
+ OPTION_NUMPROCESSORS,
+ OPTION_CODEC_PLAN,
+ OPTION_LIBDEFLATE
}
},
@@ -755,7 +963,9 @@
OPTION_CHS,
OPTION_SIZE,
OPTION_SECTOR_SIZE,
- OPTION_NUMPROCESSORS
+ OPTION_NUMPROCESSORS,
+ OPTION_CODEC_PLAN,
+ OPTION_LIBDEFLATE
}
},
@@ -767,7 +977,10 @@
REQUIRED OPTION_INPUT,
OPTION_HUNK_SIZE,
OPTION_COMPRESSION,
- OPTION_NUMPROCESSORS
+ OPTION_NUMPROCESSORS,
+ OPTION_KEEPCUE,
+ OPTION_CODEC_PLAN,
+ OPTION_LIBDEFLATE
}
},
{ COMMAND_CREATE_DVD, do_create_dvd, ": create a DVD CHD from the input file",
@@ -782,7 +995,9 @@
OPTION_INPUT_LENGTH_HUNKS,
OPTION_HUNK_SIZE,
OPTION_COMPRESSION,
- OPTION_NUMPROCESSORS
+ OPTION_NUMPROCESSORS,
+ OPTION_CODEC_PLAN,
+ OPTION_LIBDEFLATE
}
},
@@ -831,13 +1046,14 @@
REQUIRED OPTION_OUTPUT,
OPTION_OUTPUT_BIN,
OPTION_OUTPUT_SPLITBIN,
+ OPTION_REDUMP,
OPTION_OUTPUT_FORCE,
REQUIRED OPTION_INPUT,
OPTION_INPUT_PARENT,
}
},
- { COMMAND_EXTRACT_DVD, do_extract_raw, ": extract DVD file from a CHD input file",
+ { COMMAND_EXTRACT_DVD, do_extract_dvd, ": extract DVD file from a CHD input file",
{
REQUIRED OPTION_OUTPUT,
OPTION_OUTPUT_FORCE,
@@ -1285,6 +1501,15 @@
static void check_existing_output_file(const parameters_map ¶ms, std::string_view filename)
{
+ // Discpress: never write over an input (upstream, copy -i x -o x -f destroys x)
+ for (const char *option : { OPTION_INPUT, OPTION_INPUT_PARENT, OPTION_OUTPUT_PARENT })
+ {
+ const auto input = params.find(option);
+ std::error_code ec;
+ if ((input != params.end()) && std::filesystem::equivalent(std::filesystem::path(filename), std::filesystem::path(*input->second), ec))
+ report_error(1, "Error: output file (%s) is also an input file", filename);
+ }
+
if (params.find(OPTION_OUTPUT_FORCE) == params.end())
{
util::core_file::ptr file;
@@ -1498,7 +1723,7 @@
// compress_common - standard compression loop
//-------------------------------------------------
-static void compress_common(chd_file_compressor &chd)
+static chdman_task compress_common(chd_file_compressor &chd)
{
// begin compressing
chd.compress_begin();
@@ -1507,10 +1732,13 @@
double complete, ratio;
std::error_condition err;
while ((err = chd.compress_continue(complete, ratio)) == chd_file::error::WALKING_PARENT || err == chd_file::error::COMPRESSING)
+ {
if (err == chd_file::error::WALKING_PARENT)
progress(false, "Examining parent, %.1f%% complete... \r", 100.0 * complete);
else
progress(false, "Compressing, %.1f%% complete... (ratio=%.1f%%) \r", 100.0 * complete, 100.0 * ratio);
+ co_await compression_pause();
+ }
// handle errors
if (err)
@@ -1522,11 +1750,36 @@
//-------------------------------------------------
+// toc_writer - writes a TOC file (Discpress: with
+// CRLF line ends on any platform for --redump)
+//-------------------------------------------------
+
+struct toc_writer
+{
+ util::core_file &file;
+ bool crlf;
+
+ template <typename Format, typename... Params> void printf(Format &&fmt, Params &&...args)
+ {
+ std::string text = util::string_format(std::forward<Format>(fmt), std::forward<Params>(args)...);
+ if (!crlf)
+ {
+ file.puts(text);
+ return;
+ }
+ for (size_t pos = 0; (pos = text.find('\n', pos)) != std::string::npos; pos += 2)
+ text.insert(pos, 1, '\r');
+ auto const [err, written] = write(file, text.data(), text.size()); // as puts, errors show up when the file is closed
+ }
+};
+
+
+//-------------------------------------------------
// output_track_metadata - output track metadata
// to a CUE file
//-------------------------------------------------
-void output_track_metadata(int mode, util::core_file &file, int tracknum, const cdrom_file::track_info &info, const std::string &filename, uint32_t frameoffs, uint64_t outputoffs)
+void output_track_metadata(int mode, toc_writer &file, int tracknum, const cdrom_file::track_info &info, const std::string &filename, uint32_t frameoffs, uint64_t outputoffs)
{
if (mode == MODE_GDI)
{
@@ -1610,7 +1863,9 @@
}
// output pregap
- if (info.pregap > 0)
+ // Discpress: only a pregap that isn't in the file is zeros; one in the file is its data before START
+ // (0.289 wrote ZERO for both, which reads back as zeros followed by the pregap's data)
+ if (info.pregap > 0 && info.pgdatasize == 0)
file.printf("ZERO %s %s\n", modesubmode, msf_string_from_frames(info.pregap));
if (outputoffs == 0)
@@ -1781,7 +2036,7 @@
// do_verify - validate the SHA-1 on a CHD
//-------------------------------------------------
-static void do_verify(parameters_map ¶ms)
+static chdman_task do_verify(parameters_map ¶ms)
{
bool fix_sha1 = params.find(OPTION_FIX) != params.end();
// parse out input files
@@ -1820,6 +2075,7 @@
// determine how much to read
uint32_t bytes_to_read = (std::min<uint64_t>)(buffer.size(), input_chd.logical_bytes() - offset);
+ co_await read_ahead(input_chd, offset, bytes_to_read);
std::error_condition err = input_chd.read_bytes(offset, &buffer[0], bytes_to_read);
if (err)
report_error(1, "Error reading CHD file (%s): %s", *input_chd_str->second, err.message());
@@ -1830,7 +2086,8 @@
}
util::sha1_t computed_sha1 = rawsha1.finish();
- // finish up
+ // finish up (Discpress: a mismatch is a failure unless --fix corrected it; upstream exits 0)
+ bool mismatch = false;
if (raw_sha1 != computed_sha1)
{
util::stream_format(std::cerr, "Error: Raw SHA1 in header = %s\n", raw_sha1.as_string());
@@ -1844,6 +2101,8 @@
report_error(1, "Error updating SHA1: %s", err.message());
util::stream_format(std::cout, "SHA1 updated to correct value in input CHD\n");
}
+ else
+ mismatch = true;
}
else
{
@@ -1868,9 +2127,13 @@
report_error(1, "Error updating SHA1: %s", err.message());
util::stream_format(std::cout, "SHA1 updated to correct value in input CHD\n");
}
+ else
+ mismatch = true;
}
}
}
+ if (mismatch)
+ report_error(1, "Verification failed");
}
@@ -1879,7 +2142,7 @@
// image from a raw file
//-------------------------------------------------
-static void do_create_raw(parameters_map ¶ms)
+static chdman_task do_create_raw(parameters_map ¶ms)
{
// process input file
auto [input_file, input_file_str] = open_input_file(params);
@@ -1894,6 +2157,8 @@
if (unit_size_str != params.end())
{
unit_size = parse_number(unit_size_str->second->c_str());
+ if (unit_size == 0)
+ report_error(1, "Invalid unit size: %s", *unit_size_str->second);
if (output_parent.opened() && (output_parent.unit_bytes() != unit_size))
report_error(1, "Specified unit size %u bytes does not match output parent CHD unit size %u bytes", unit_size, output_parent.unit_bytes());
}
@@ -1937,13 +2202,15 @@
// create the new CHD
auto chd = std::make_unique<chd_rawfile_compressor>(*input_file, input_start, input_end);
create_output_chd(*chd, *output_chd_str, input_end - input_start, hunk_size, unit_size, compression, output_parent);
+ chd->set_codec_plan(params.find(OPTION_CODEC_PLAN) != params.end());
+ chd->set_libdeflate(params.find(OPTION_LIBDEFLATE) != params.end());
// if we have a parent, copy forward all the metadata
if (output_parent.opened())
chd->clone_all_metadata(output_parent);
// compress it generically
- compress_common(*chd);
+ co_await compress_common(*chd);
}
catch (...)
{
@@ -1959,7 +2226,7 @@
// disk image from a raw file
//-------------------------------------------------
-static void do_create_hd(parameters_map ¶ms)
+static chdman_task do_create_hd(parameters_map ¶ms)
{
// process input file
auto [input_file, input_file_str] = open_input_file(params);
@@ -2008,6 +2275,8 @@
if (template_str != params.end())
report_error(1, "Sector size cannot be specified separately when a template is specified");
sector_size = parse_number(sectorsize_str->second->c_str());
+ if (sector_size == 0)
+ report_error(1, "Invalid sector size: %s", *sectorsize_str->second);
}
if (output_parent.opened() && (output_parent.unit_bytes() != sector_size))
report_error(1, "Sector size %u bytes does not match output parent CHD sector size %u bytes", sector_size, output_parent.unit_bytes());
@@ -2129,6 +2398,8 @@
else
chd.reset(new chd_zero_compressor(input_start, input_end));
create_output_chd(*chd, *output_chd_str, uint64_t(totalsectors) * sector_size, hunk_size, sector_size, compression, output_parent);
+ chd->set_codec_plan(params.find(OPTION_CODEC_PLAN) != params.end());
+ chd->set_libdeflate(params.find(OPTION_LIBDEFLATE) != params.end());
// add the standard hard disk metadata
std::string metadata = string_format(HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sector_size);
@@ -2147,7 +2418,7 @@
// compress it generically
if (input_file)
- compress_common(*chd);
+ co_await compress_common(*chd);
}
catch (...)
{
@@ -2163,7 +2434,7 @@
// image from a raw file
//-------------------------------------------------
-static void do_create_cd(parameters_map ¶ms)
+static chdman_task do_create_cd(parameters_map ¶ms)
{
// process input file
cdrom_file::track_input_info track_info;
@@ -2174,12 +2445,39 @@
std::error_condition err = cdrom_file::parse_toc(*input_file_str->second, toc, track_info);
if (err)
report_error(1, "Error parsing input file (%s: %s)\n", *input_file_str->second, err.message());
+ // Discpress: with no tracks, 0.289 went on and never finished
+ if (toc.numtrks == 0)
+ report_error(1, "Error parsing input file (%s): no tracks found\n", *input_file_str->second);
+ }
+
+ // Discpress: --keepcue, the cue sheet as it is, for extractcd --redump to write back
+ std::vector<uint8_t> cue_sheet;
+ if (params.find(OPTION_KEEPCUE) != params.end())
+ {
+ if (!core_filename_ends_with(*input_file_str->second, ".cue"))
+ report_error(1, "--%s needs a .cue input file", OPTION_KEEPCUE);
+ std::error_condition const err = util::core_file::load(*input_file_str->second, cue_sheet);
+ if (err)
+ report_error(1, "Unable to read the cue sheet (%s): %s", *input_file_str->second, err.message());
+ if (cue_sheet.size() > 1024 * 1024)
+ report_error(1, "The cue sheet (%s) is too large to keep", *input_file_str->second);
}
// process output CHD
chd_file output_parent;
const auto output_chd_str = parse_output_chd_parameters(params, output_parent);
+ // Discpress: nor over a track file the descriptor names (0.289 truncated it, then deleted it)
+ for (int tracknum = 0; tracknum < toc.numtrks; tracknum++)
+ {
+ std::error_code ec;
+ if (std::filesystem::equivalent(std::filesystem::path(*output_chd_str), std::filesystem::path(track_info.track[tracknum].fname), ec))
+ report_error(1, "Error: output file (%s) is also an input file", *output_chd_str);
+ for (const cdrom_file::track_input_piece &piece : track_info.track[tracknum].pieces)
+ if (!piece.fname.empty() && std::filesystem::equivalent(std::filesystem::path(*output_chd_str), std::filesystem::path(piece.fname), ec))
+ report_error(1, "Error: output file (%s) is also an input file", *output_chd_str);
+ }
+
// process hunk size
const uint32_t hunk_size = parse_hunk_size(params, output_parent, cdrom_file::FRAME_SIZE, cdrom_file::FRAMES_PER_HUNK * cdrom_file::FRAME_SIZE);
if (output_parent.opened() && (output_parent.unit_bytes() != cdrom_file::FRAME_SIZE))
@@ -2203,6 +2501,9 @@
origtotalsectors += trackinfo.frames;
totalsectors += trackinfo.frames + trackinfo.extraframes;
}
+ // Discpress: tracks without data (such as a cdrdao TOC without track lengths) left 0.289 compressing forever
+ if (totalsectors == 0)
+ report_error(1, "Error parsing input file (%s): the tracks hold no data\n", *input_file_str->second);
// print some info
util::stream_format(std::cout, "Output CHD: %s\n", *output_chd_str);
@@ -2220,14 +2521,24 @@
// create the new CD
auto chd = std::make_unique<chd_cd_compressor>(toc, track_info);
create_output_chd(*chd, *output_chd_str, uint64_t(totalsectors) * cdrom_file::FRAME_SIZE, hunk_size, cdrom_file::FRAME_SIZE, compression, output_parent);
+ chd->set_codec_plan(params.find(OPTION_CODEC_PLAN) != params.end());
+ chd->set_libdeflate(params.find(OPTION_LIBDEFLATE) != params.end());
// add the standard CD metadata; we do this even if we have a parent because it might be different
const std::error_condition err = cdrom_file::write_metadata(chd.get(), toc);
if (err)
report_error(1, "Error adding CD metadata: %s", err.message());
+ // Discpress: then the kept cue sheet, without CHD_MDFLAGS_CHECKSUM: neither SHA-1 changes
+ if (!cue_sheet.empty())
+ {
+ const std::error_condition cueerr = chd->write_metadata(CUE_SHEET_METADATA_TAG, 0, cue_sheet, 0);
+ if (cueerr)
+ report_error(1, "Error adding the cue sheet: %s", cueerr.message());
+ }
+
// compress it generically
- compress_common(*chd);
+ co_await compress_common(*chd);
}
catch (...)
{
@@ -2243,7 +2554,7 @@
// image from a raw file
//-------------------------------------------------
-static void do_create_dvd(parameters_map ¶ms)
+static chdman_task do_create_dvd(parameters_map ¶ms)
{
// process input file
auto [input_file, input_file_str] = open_input_file(params);
@@ -2288,6 +2599,8 @@
// create the new DVD
auto chd = std::make_unique<chd_rawfile_compressor>(*input_file, input_start, input_end);
create_output_chd(*chd, *output_chd_str, input_end - input_start, hunk_size, 2048, compression, output_parent);
+ chd->set_codec_plan(params.find(OPTION_CODEC_PLAN) != params.end());
+ chd->set_libdeflate(params.find(OPTION_LIBDEFLATE) != params.end());
// add the standard DVD type tag
const std::error_condition err = chd->write_metadata(DVD_METADATA_TAG, 0, "");
@@ -2295,7 +2608,7 @@
report_error(1, "Error adding DVD metadata: %s", err.message());
// compress it generically
- compress_common(*chd);
+ co_await compress_common(*chd);
}
catch (...)
{
@@ -2311,7 +2624,7 @@
// input AVI file and metadata
//-------------------------------------------------
-static void do_create_ld(parameters_map ¶ms)
+static chdman_task do_create_ld(parameters_map ¶ms)
{
// process input file
avi_file::ptr input_file;
@@ -2399,7 +2712,7 @@
report_error(1, "Error adding AV metadata: %s\n", err.message());
// create the compressor and then run it generically
- compress_common(*chd);
+ co_await compress_common(*chd);
// write the final LD metadata
if (info.height == 524/2 || info.height == 624/2)
@@ -2456,7 +2769,7 @@
// another CHD
//-------------------------------------------------
-static void do_copy(parameters_map ¶ms)
+static chdman_task do_copy(parameters_map ¶ms)
{
// parse out input files
chd_file input_parent_chd;
@@ -2547,7 +2860,7 @@
}
// compress it generically
- compress_common(*chd);
+ co_await compress_common(*chd);
}
catch (...)
{
@@ -2563,7 +2876,7 @@
// CHD image
//-------------------------------------------------
-static void do_extract_raw(parameters_map ¶ms)
+static chdman_task do_extract_raw(parameters_map ¶ms)
{
// parse out input files
chd_file input_parent_chd;
@@ -2604,6 +2917,7 @@
// determine how much to read
uint32_t bytes_to_read = (std::min<uint64_t>)(buffer.size(), input_end - offset);
+ co_await read_ahead(input_chd, offset, bytes_to_read);
std::error_condition err = input_chd.read_bytes(offset, &buffer[0], bytes_to_read);
if (err)
report_error(1, "Error reading CHD file (%s): %s", *params.find(OPTION_INPUT)->second, err.message());
@@ -2639,15 +2953,138 @@
// CHD image
//-------------------------------------------------
-static void do_extract_cd(parameters_map ¶ms)
+//-------------------------------------------------
+// Discpress: kept_cue_sheet - the cue sheet createcd
+// --keepcue stored, to write instead of a new one,
+// if its files are the ones extractcd --redump
+// writes: one BINARY file per track, or one file
+// and track for a single-track disc. Its FILE names
+// become theirs, in order (unless already the same);
+// anything else stays as it is, with LF line ends
+// (the writer turns them into CRLF). Empty if the
+// layout differs.
+//-------------------------------------------------
+
+static std::string kept_cue_sheet(const std::vector<uint8_t> &sheet, const std::vector<std::string> &track_filenames)
+{
+ std::vector<std::string> files;
+ for (size_t i = 0; i < track_filenames.size(); i++)
+ if (i == 0 || track_filenames[i] != track_filenames[i - 1])
+ files.emplace_back(core_filename_extract_base(track_filenames[i]));
+
+ // an upper-case keyword at `at`, in any case and followed by a space or the end: skips it and the spaces after it
+ auto const keyword = [] (std::string_view line, size_t &at, std::string_view word)
+ {
+ size_t const end = at + word.size();
+ if (line.size() < end || (line.size() > end && !isspace(uint8_t(line[end]))))
+ return false;
+ for (size_t i = 0; i < word.size(); i++)
+ if (std::toupper(uint8_t(line[at + i])) != word[i])
+ return false;
+ at = end;
+ while (at < line.size() && isspace(uint8_t(line[at])))
+ at++;
+ return true;
+ };
+
+ std::string const text(sheet.begin(), sheet.end());
+ std::string out;
+ size_t file = 0, tracks = 0, file_tracks = 0;
+ for (size_t pos = 0; pos < text.size(); )
+ {
+ size_t const nl = text.find('\n', pos);
+ std::string line = text.substr(pos, ((nl == std::string::npos) ? text.size() : nl) - pos);
+ pos = (nl == std::string::npos) ? text.size() : (nl + 1);
+ if (!line.empty() && line.back() == '\r')
+ line.pop_back();
+
+ // the keyword, after spaces (and the byte order mark some sheets start with)
+ size_t at = (out.empty() && line.compare(0, 3, "\xef\xbb\xbf") == 0) ? 3 : 0;
+ while (at < line.size() && isspace(uint8_t(line[at])))
+ at++;
+ if (keyword(line, at, "FILE"))
+ {
+ if ((file > 0 && file_tracks != 1) || file >= files.size())
+ return std::string();
+ size_t const name_start = at;
+ std::string name;
+ if (at < line.size() && line[at] == '"')
+ {
+ size_t const close = line.find('"', at + 1);
+ if (close == std::string::npos)
+ return std::string();
+ name = line.substr(at + 1, close - at - 1);
+ at = close + 1;
+ }
+ else
+ {
+ while (at < line.size() && !isspace(uint8_t(line[at])))
+ name += line[at++];
+ }
+ size_t type = at;
+ while (type < line.size() && isspace(uint8_t(line[type])))
+ type++;
+ if (!keyword(line, type, "BINARY") || type != line.size())
+ return std::string();
+ if (name != files[file])
+ line = line.substr(0, name_start) + "\"" + files[file] + "\"" + line.substr(at);
+ file++;
+ file_tracks = 0;
+ }
+ else if (keyword(line, at, "TRACK"))
+ {
+ file_tracks++;
+ tracks++;
+ }
+ out += line;
+ if (nl != std::string::npos)
+ out += '\n';
+ }
+ if (file != files.size() || file_tracks != 1 || tracks != track_filenames.size())
+ return std::string();
+ return out;
+}
+
+
+// Discpress: a frame of a CD that can't be read; cdrom_file only says that it failed, so the frame
+// is read again for the reason (such as a damaged hunk), as the other extract commands report it
+[[noreturn]] static void report_cd_read_error(chd_file &input_chd, const std::string &input_name, uint64_t offset)
+{
+ std::vector<uint8_t> frame(cdrom_file::FRAME_SIZE);
+ std::error_condition const err = input_chd.read_bytes(offset, &frame[0], cdrom_file::FRAME_SIZE);
+ report_error(1, "Error reading CHD file (%s) at offset %d: %s", input_name, offset, err ? err.message() : std::string("read error"));
+}
+
+//-------------------------------------------------
+// do_extract_dvd - Discpress: extractraw, but not
+// of a CD (0.289 wrote its frames, subcode and
+// all, as an .iso)
+//-------------------------------------------------
+
+static chdman_task do_extract_dvd(parameters_map ¶ms)
+{
+ {
+ chd_file input_parent_chd;
+ chd_file input_chd;
+ parse_input_chd_parameters(params, input_chd, input_parent_chd);
+ if (!input_chd.check_is_cd() || !input_chd.check_is_gd())
+ report_error(1, "Input CHD is a CD-ROM or GD-ROM; extract it with extractcd, or its frames with extractraw");
+ }
+ co_await do_extract_raw(params);
+}
+
+
+static chdman_task do_extract_cd(parameters_map ¶ms)
{
// parse out input files
chd_file input_parent_chd;
chd_file input_chd;
parse_input_chd_parameters(params, input_chd, input_parent_chd);
- // further process input file
- cdrom_file *cdrom = new cdrom_file(&input_chd);
+ // further process input file (Discpress: cdrom_file throws nullptr for a CHD that is not a CD)
+ if (input_chd.check_is_cd() && input_chd.check_is_gd())
+ report_error(1, "Input CHD is not a CD-ROM or GD-ROM; extract it with extractdvd, extracthd or extractraw");
+ const auto cdrom = std::make_unique<cdrom_file>(&input_chd); // Discpress: freed (0.289: new, never deleted)
const cdrom_file::toc &toc = cdrom->get_toc();
// verify output file doesn't exist
@@ -2671,8 +3108,19 @@
if (chop != std::string::npos)
default_name.erase(chop, default_name.size());
+ // Discpress: --redump, Redump's layout for a .cue (CRLF; one bin per track, unless there is only one)
+ const bool redump = params.find(OPTION_REDUMP) != params.end();
+ if (redump && mode != MODE_CUEBIN)
+ report_error(1, "--%s needs a .cue output file", OPTION_REDUMP);
+
+ // Discpress: a cdrdao TOC can't describe a GD-ROM: it has no high-density area, and the padding before LBA 45000
+ // is in no file. 0.289 wrote a TOC whose lengths its files don't hold, which reads back as an error (or, from
+ // -sb, as a CD of other data).
+ if (cdrom->is_gdrom() && mode == MODE_NORMAL)
+ report_error(1, "A GD-ROM can't be written as a TOC (it has no high-density area); extract it to a .gdi, or a .cue");
+
// GDIs will always output as split bin
- bool is_splitbin = mode == MODE_GDI || params.find(OPTION_OUTPUT_SPLITBIN) != params.end();
+ bool is_splitbin = mode == MODE_GDI || params.find(OPTION_OUTPUT_SPLITBIN) != params.end() || (redump && toc.numtrks > 1);
if (!is_splitbin && cdrom->is_gdrom() && mode == MODE_CUEBIN)
{
// GD-ROM cue/bin is in Redump format which should always be split by tracks
@@ -2730,6 +3178,7 @@
std::error_condition filerr = util::core_file::open(*output_file_str->second, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_NO_BOM, output_toc_file);
if (filerr)
report_error(1, "Unable to open file (%s): %s", *output_file_str->second, filerr.message());
+ toc_writer toc_out{ *output_toc_file, redump };
uint64_t total_bytes = 0;
for (int tracknum = 0; tracknum < toc.numtrks; tracknum++)
@@ -2807,10 +3256,24 @@
track_filenames.push_back(filename_formatted);
}
+ // Discpress: with --redump, the cue sheet createcd --keepcue stored, if it fits these files
+ std::string kept_cue;
+ if (redump)
+ {
+ std::vector<uint8_t> sheet;
+ if (!input_chd.read_metadata(CUE_SHEET_METADATA_TAG, 0, sheet))
+ kept_cue = kept_cue_sheet(sheet, track_filenames);
+ if (!kept_cue.empty())
+ {
+ util::stream_format(std::cout, "Cue sheet: the one kept when the CHD was made\n");
+ toc_out.printf("%s", kept_cue);
+ }
+ }
+
// GDI must start with the # of tracks
if (mode == MODE_GDI)
{
- output_toc_file->printf("%d\n", toc.numtrks);
+ toc_out.printf("%d\n", toc.numtrks);
}
else if (mode == MODE_NORMAL)
{
@@ -2842,11 +3305,11 @@
}
if (mode2)
- output_toc_file->printf("CD_ROM_XA\n\n\n");
+ toc_out.printf("CD_ROM_XA\n\n\n");
else if (cdda && !mode1)
- output_toc_file->printf("CD_DA\n\n\n");
+ toc_out.printf("CD_DA\n\n\n");
else
- output_toc_file->printf("CD_ROM\n\n\n");
+ toc_out.printf("CD_ROM\n\n\n");
}
if (cdrom->is_gdrom() && mode == MODE_CUEBIN)
@@ -2855,7 +3318,9 @@
cdrom_file::toc *trackinfo = (cdrom_file::toc*)&toc;
// TOSEC GDI-based CHDs have the padframes field set to non-0 where the pregaps for the next track would be
- const bool has_physical_pregap = trackinfo->tracks[0].padframes == 0;
+ // Discpress: --redump writes Redump's layout, each track's pregap in its own file, whatever the CHD was made
+ // from; the CHD made from it then holds the same data, the gaps of a GDI as data of the tracks before them
+ const bool has_physical_pregap = trackinfo->tracks[0].padframes == 0 || redump;
for (int tracknum = 1; tracknum < toc.numtrks; tracknum++)
{
@@ -2880,6 +3345,10 @@
// "type 3" where the high-density area is just two data tracks
// there shouldn't be any pregap in the padframes from the previous track in this case, and the full 3s pregap is baked into the previous track
// Only known to be used by Shenmue II JP's discs 2, 3, 4 and Virtua Fighter History & VF4
+ // Discpress: only then; otherwise the padding is the pregap, as for other tracks (0.289 put
+ // INDEX 01 the padding's length past the frames it wrote before it)
+ if (toc.tracks[tracknum-1].padframes != 0 || toc.tracks[tracknum-1].frames < 225)
+ continue;
trackinfo->tracks[tracknum-1].padframes += 225;
trackinfo->tracks[tracknum].pregap += 225;
@@ -2894,8 +3363,9 @@
// It's currently not possible to format it as expected without hacky code because the 150 pregap for the last track
// is sandwiched between these 75 frames and the actual track data.
// The 75 frames seems to normally be 0s so this should be ok for now until a use case is found.
- trackinfo->tracks[tracknum-1].frames -= 75;
- trackinfo->tracks[tracknum].pregap += 75;
+ // Discpress: they stay at the end of the previous track's file, and the pregap is the padding
+ // after them, so createcd makes the same CHD again. 0.289 left them out of every file, and
+ // read them back as padding.
}
}
}
@@ -2905,6 +3375,14 @@
if (tracknum + 1 >= toc.numtrks && toc.tracks[tracknum].trktype != cdrom_file::CD_TRACK_AUDIO)
curextra += 75; // 00:01:00, special case when last track is data
+ // Discpress: a previous track too short to hold the pregap keeps its frames, padding and all, in its
+ // own file (0.289 read the pregap from before the track's start)
+ if (toc.tracks[tracknum-1].frames < uint32_t(curextra))
+ {
+ trackinfo->tracks[tracknum-1].padframes = 0;
+ continue;
+ }
+
trackinfo->tracks[tracknum-1].padframes = curextra;
trackinfo->tracks[tracknum].pregap += curextra;
@@ -2920,6 +3398,7 @@
uint64_t outputoffs = 0;
uint32_t discoffs = 0;
std::vector<uint8_t> buffer;
+ uint64_t ahead_start = 0, ahead_end = 0; // Discpress: the window read_ahead last covered
for (int tracknum = 0; tracknum < toc.numtrks; tracknum++)
{
@@ -2942,17 +3421,18 @@
output_bin_filenames.push_back(trackbin_name);
}
- if (cdrom->is_gdrom() && mode == MODE_CUEBIN)
+ if (cdrom->is_gdrom() && mode == MODE_CUEBIN && kept_cue.empty())
{
if (tracknum == 0)
- output_toc_file->printf("REM SINGLE-DENSITY AREA\n");
+ toc_out.printf("REM SINGLE-DENSITY AREA\n");
else if (toc.tracks[tracknum].physframeofs == 45000)
- output_toc_file->printf("REM HIGH-DENSITY AREA\n");
+ toc_out.printf("REM HIGH-DENSITY AREA\n");
}
- // output the metadata about the track to the TOC file
+ // output the metadata about the track to the TOC file (Discpress: unless the kept cue sheet was written)
const cdrom_file::track_info &trackinfo = toc.tracks[tracknum];
- output_track_metadata(mode, *output_toc_file, tracknum, trackinfo, std::string(core_filename_extract_base(trackbin_name)), discoffs, outputoffs);
+ if (kept_cue.empty())
+ output_track_metadata(mode, toc_out, tracknum, trackinfo, std::string(core_filename_extract_base(trackbin_name)), discoffs, outputoffs);
// If this is bin/cue output and the CHD contains subdata, warn the user and don't include
// the subdata size in the buffer calculation.
@@ -2987,8 +3467,18 @@
frameofs = frame - trackinfo.splitframes;
}
- // read the data
- cdrom->read_data(cdrom->get_track_start_phys(trk) + frameofs, &buffer[bufferoffs], toc.tracks[trk].trktype, true);
+ // Discpress: helper workers decompress a buffer's worth of hunks ahead (browser only)
+ uint64_t const chdoffs = uint64_t(toc.tracks[trk].chdframeofs + frameofs) * cdrom_file::FRAME_SIZE;
+ if ((chdoffs < ahead_start) || (chdoffs >= ahead_end))
+ {
+ co_await read_ahead(input_chd, chdoffs, TEMP_BUFFER_SIZE);
+ ahead_start = chdoffs;
+ ahead_end = chdoffs + TEMP_BUFFER_SIZE;
+ }
+
+ // read the data (Discpress: a read error is an error; 0.289 wrote whatever the buffer held and exited 0)
+ if (!cdrom->read_data(cdrom->get_track_start_phys(trk) + frameofs, &buffer[bufferoffs], toc.tracks[trk].trktype, true))
+ report_cd_read_error(input_chd, *params.find(OPTION_INPUT)->second, chdoffs);
// for CDRWin and GDI audio tracks must be reversed
// in the case of GDI and CHD version < 5 we assuming source CHD image is GDROM so audio tracks is already reversed
@@ -3005,7 +3495,8 @@
// read the subcode data
if (toc.tracks[trk].subtype != cdrom_file::CD_SUB_NONE && (mode == MODE_NORMAL))
{
- cdrom->read_subcode(cdrom->get_track_start_phys(trk) + frameofs, &buffer[bufferoffs], true);
+ if (!cdrom->read_subcode(cdrom->get_track_start_phys(trk) + frameofs, &buffer[bufferoffs], true)) // Discpress: likewise
+ report_cd_read_error(input_chd, *params.find(OPTION_INPUT)->second, chdoffs);
bufferoffs += toc.tracks[trk].subsize;
}
@@ -3387,7 +3878,8 @@
{
// delete the output file
output_file.reset();
- osd_file::remove(*output_file_str->second);
+ if (output_file_str != params.end())
+ osd_file::remove(*output_file_str->second);
throw;
}
}
@@ -3425,14 +3917,43 @@
// main - entry point
//-------------------------------------------------
-int CLIB_DECL main(int argc, char *argv[])
+namespace {
+
+// what a command needs until it finishes, since it may pause (see chdman_task)
+struct chdman_run
{
- const std::vector<std::string> args = osd_get_command_line(argc, argv);
chdman_osd_output osdoutput;
+ parameters_map parameters;
+ chdman_task task;
+
+ // Discpress: the options' values are allocated as the command line is read (0.289 never freed them); the
+ // command, which refers to them, goes first
+ ~chdman_run() { task.reset(); for (auto ¶m : parameters) delete param.second; }
+};
+
+std::unique_ptr<chdman_run> s_run;
+constexpr int CHDMAN_PAUSED = -1;
+
+chdman_task run_function(void (*function)(parameters_map &), parameters_map ¶ms)
+{
+ function(params);
+ co_return;
+}
+
+} // anonymous namespace
+
+
+// parses the command line and prepares the command: returns CHDMAN_PAUSED when it is ready to
+// run (chdman_continue), otherwise the exit code
+static int chdman_parse(int argc, char *argv[])
+{
+ const std::vector<std::string> args = osd_get_command_line(argc, argv);
- // print the header
+ // print the header (Discpress: on stderr when stdout carries data, i.e. dumpmeta without an output file)
extern const char build_version[];
- util::stream_format(std::cout, "chdman - MAME Compressed Hunks of Data (CHD) manager %s\n", build_version);
+ const bool data_on_stdout = (args.size() >= 2) && (args[1] == COMMAND_DUMP_METADATA)
+ && std::none_of(args.begin(), args.end(), [] (const std::string &arg) { return (arg == "-o") || (arg == "--" OPTION_OUTPUT); });
+ util::stream_format(data_on_stdout ? std::cerr : std::cout, "chdman - MAME Compressed Hunks of Data (CHD) manager %s\n", build_version);
// handle help specially
if (args.size() < 2)
@@ -3459,7 +3980,7 @@
return print_help(args[0], desc);
// otherwise, verify the parameters
- parameters_map parameters;
+ parameters_map ¶meters = s_run->parameters;
while (argnum < args.size())
{
// should be an option name
@@ -3519,30 +4040,86 @@
return print_help(args[0], desc, "Required parameters missing");
}
- // all clear, run the command
- try
- {
- (*s_command.handler)(parameters);
- return 0;
- }
- catch (std::error_condition const &err)
- {
- util::stream_format(std::cerr, "CHD error occurred (main): %s\n", err.message());
- return 1;
- }
- catch (fatal_error &err)
- {
- util::stream_format(std::cerr, "Fatal error occurred: %d\n", err.error());
- return err.error();
- }
- catch (std::exception& ex)
- {
- util::stream_format(std::cerr, "Unhandled exception: %s\n", ex.what());
- return 1;
- }
+ // all clear, prepare the command
+ s_run->task = s_command.handler.task ? s_command.handler.task(parameters) : run_function(s_command.handler.function, parameters);
+ return CHDMAN_PAUSED;
}
}
// print generic help if nothing found
return print_help(args[0]);
}
+
+
+static int chdman_start(int argc, char *argv[])
+{
+ s_run = std::make_unique<chdman_run>();
+ int const result = chdman_parse(argc, argv);
+ if (result != CHDMAN_PAUSED)
+ s_run.reset();
+ return result;
+}
+
+
+// runs the command until it finishes (returns the exit code) or pauses (returns CHDMAN_PAUSED)
+static int chdman_continue()
+{
+ std::coroutine_handle<> const next = s_paused ? std::exchange(s_paused, nullptr) : s_run->task.get();
+ next.resume();
+ if (!s_run->task.done())
+ return CHDMAN_PAUSED;
+
+ int result = 0;
+ try
+ {
+ s_run->task.rethrow();
+ }
+ catch (std::error_condition const &err)
+ {
+ util::stream_format(std::cerr, "CHD error occurred (main): %s\n", err.message());
+ result = 1;
+ }
+ catch (fatal_error &err)
+ {
+ util::stream_format(std::cerr, "Fatal error occurred: %d\n", err.error());
+ result = err.error();
+ }
+ catch (std::exception& ex)
+ {
+ util::stream_format(std::cerr, "Unhandled exception: %s\n", ex.what());
+ result = 1;
+ }
+ catch (...)
+ {
+ // Discpress: upstream terminates here (e.g. cdrom_file throws nullptr)
+ util::stream_format(std::cerr, "Unhandled exception\n");
+ result = 1;
+ }
+ s_run.reset();
+ return result;
+}
+
+
+int CLIB_DECL main(int argc, char *argv[])
+{
+ int result = chdman_start(argc, argv);
+ while (result == CHDMAN_PAUSED)
+ result = chdman_continue();
+ return result;
+}
+
+
+#ifdef __EMSCRIPTEN__
+// browser build: the page starts a command with chdman_begin and calls chdman_resume while they
+// return -1 (paused while helper workers compress); anything else is the exit code
+extern "C" EMSCRIPTEN_KEEPALIVE int chdman_begin(int argc, char *argv[])
+{
+ int const result = chdman_start(argc, argv);
+ return (result == CHDMAN_PAUSED) ? chdman_continue() : result;
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE int chdman_resume()
+{
+ return chdman_continue();
+}
+#endif
--- /dev/null
+++ wasm_helper.cpp (new file)
+// Browser build helper: lets a secondary WebAssembly instance (running in a
+// helper Web Worker) compress CHD hunks exactly the way chd_file_compressor
+// would, so the main chdman instance can spread compression over many cores,
+// and decompress them for extract and verify.
+#include "chd.h"
+#include "chdcodec.h"
+#include "hashing.h"
+#include "ioprocsvec.h"
+
+#include <emscripten.h>
+
+#include <cstring>
+#include <memory>
+#include <unordered_set>
+#include <vector>
+
+namespace {
+std::vector<uint8_t> s_store;
+std::unique_ptr<chd_file> s_chd;
+std::unique_ptr<chd_compressor_group> s_group;
+std::vector<uint8_t> s_in, s_out;
+uint32_t s_hunkbytes = 0;
+std::unordered_set<uint64_t> s_seen; // hashes of hunks this helper already compressed
+}
+
+// flags: 1 = the deflate codec encodes with libdeflate, as the job's CHD does (chd_file::set_libdeflate)
+extern "C" EMSCRIPTEN_KEEPALIVE int wasm_helper_init(uint32_t hunkbytes, uint32_t unitbytes, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3, uint32_t flags)
+{
+ try
+ {
+ chd_codec_type comp[4] = { c0, c1, c2, c3 };
+ s_group.reset();
+ s_chd.reset();
+ s_store.clear();
+ s_chd = std::make_unique<chd_file>();
+ util::random_read_write::ptr file = std::make_unique<util::vector_read_write_adapter<uint8_t>>(s_store);
+ std::error_condition err = s_chd->create(std::move(file), uint64_t(hunkbytes), hunkbytes, unitbytes, comp);
+ if (err)
+ return -1;
+ s_chd->set_libdeflate(flags & 1);
+ s_group = std::make_unique<chd_compressor_group>(*s_chd, comp);
+ s_hunkbytes = hunkbytes;
+ s_seen.clear();
+ s_in.assign(hunkbytes, 0);
+ s_out.assign(hunkbytes, 0);
+ return 0;
+ }
+ catch (...)
+ {
+ return -2;
+ }
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *wasm_helper_inbuf() { return s_in.data(); }
+extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *wasm_helper_outbuf() { return s_out.data(); }
+
+// result[0] = compressed length, result[1] = crc16; sha1out receives 20 bytes.
+// codecs: the codec slots to try, a bit each (the codec plan; 15 = all).
+// returns the codec index (-1 = stored uncompressed)
+extern "C" EMSCRIPTEN_KEEPALIVE int wasm_helper_compress(uint32_t *result, uint8_t *sha1out, uint32_t codecs)
+{
+ result[1] = uint16_t(util::crc16_creator::simple(s_in.data(), s_hunkbytes));
+ util::sha1_t const sha1 = util::sha1_creator::simple(s_in.data(), s_hunkbytes);
+ std::memcpy(sha1out, sha1.m_raw, sizeof(sha1.m_raw));
+
+ // a repeat of a hunk this helper already handled (earlier in the file) will be
+ // stored as a "copy from self" by the writer, so don't waste time compressing it;
+ // returns -2 and the writer compresses it itself if it turns out not to be a copy
+ uint64_t key;
+ std::memcpy(&key, sha1.m_raw, sizeof(key));
+ key ^= uint64_t(result[1]) << 48;
+ if (!s_seen.insert(key).second)
+ {
+ result[0] = 0;
+ return -2;
+ }
+ if (s_seen.size() > 262144)
+ s_seen.clear();
+
+ uint32_t complen = s_hunkbytes;
+ int8_t const compression = s_group->find_best_compressor(s_in.data(), s_out.data(), complen, codecs);
+ result[0] = complen;
+ return compression;
+}
+
+
+// ---------------------------------------------------------------------------
+// Decompression for extract and verify: the job worker hands over a hunk's
+// compressed bytes and its codec slot, and gets the hunk back
+// (chd_file::wasm_read_ahead). The job worker still checks each hunk's CRC.
+// ---------------------------------------------------------------------------
+
+namespace {
+std::vector<uint8_t> d_store;
+std::unique_ptr<chd_file> d_chd;
+chd_decompressor::ptr d_codec[4];
+std::vector<uint8_t> d_in, d_out;
+uint32_t d_hunkbytes = 0;
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE int wasm_helper_dinit(uint32_t hunkbytes, uint32_t unitbytes, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3)
+{
+ try
+ {
+ // codecs are made for a CHD: a scratch one with the same parameters
+ chd_codec_type comp[4] = { c0, c1, c2, c3 };
+ for (auto &codec : d_codec)
+ codec.reset();
+ d_chd.reset();
+ d_store.clear();
+ d_chd = std::make_unique<chd_file>();
+ util::random_read_write::ptr file = std::make_unique<util::vector_read_write_adapter<uint8_t>>(d_store);
+ std::error_condition err = d_chd->create(std::move(file), uint64_t(hunkbytes), hunkbytes, unitbytes, comp);
+ if (err)
+ return -1;
+ for (int i = 0; i < 4; i++)
+ {
+ if (comp[i] && !(d_codec[i] = chd_codec_list::new_decompressor(comp[i], *d_chd)))
+ return -1;
+ }
+ d_hunkbytes = hunkbytes;
+ d_in.assign(hunkbytes, 0);
+ d_out.assign(hunkbytes, 0);
+ return 0;
+ }
+ catch (...)
+ {
+ return -2;
+ }
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *wasm_helper_dinbuf() { return d_in.data(); }
+extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *wasm_helper_doutbuf() { return d_out.data(); }
+
+// decompresses length bytes from the input buffer with codec slot 0-3; returns 0, or -1 on failure
+extern "C" EMSCRIPTEN_KEEPALIVE int wasm_helper_decompress(uint32_t codec, uint32_t length)
+{
+ if ((codec > 3) || !d_codec[codec] || (length > d_in.size()))
+ return -1;
+ try
+ {
+ d_codec[codec]->decompress(d_in.data(), length, d_out.data(), d_hunkbytes);
+ return 0;
+ }
+ catch (...)
+ {
+ return -1;
+ }
+}
+
+
+// ---------------------------------------------------------------------------
+// Raw deflate, for CSO images (compressed ISOs) that the job worker presents to
+// chdman as the ISO itself: returns the bytes written to dst, or -1.
+// ---------------------------------------------------------------------------
+#include <zlib.h>
+
+namespace {
+z_stream i_stream;
+bool i_ready = false;
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE int wasm_inflate_raw(const uint8_t *src, uint32_t srclen, uint8_t *dst, uint32_t dstlen)
+{
+ if (!i_ready)
+ {
+ std::memset(&i_stream, 0, sizeof(i_stream));
+ if (inflateInit2(&i_stream, -MAX_WBITS) != Z_OK)
+ return -1;
+ i_ready = true;
+ }
+ else if (inflateReset(&i_stream) != Z_OK)
+ return -1;
+ i_stream.next_in = const_cast<Bytef *>(src);
+ i_stream.avail_in = srclen;
+ i_stream.next_out = dst;
+ i_stream.avail_out = dstlen;
+ int const err = inflate(&i_stream, Z_FINISH);
+ if ((err != Z_STREAM_END) && !((err == Z_BUF_ERROR || err == Z_OK) && (i_stream.avail_out == 0)))
+ return -1;
+ return int(dstlen - i_stream.avail_out);
+}
+
+
+// ---------------------------------------------------------------------------
+// Probe API: read sectors from an existing CHD so the page can identify the
+// game (system, serial, title) without extracting it.
+// ---------------------------------------------------------------------------
+#include "cdrom.h"
+
+namespace {
+std::unique_ptr<chd_file> p_chd;
+std::unique_ptr<chd_file> p_parent; // the parent CHD a child CHD needs, if it has one
+std::unique_ptr<cdrom_file> p_cd;
+}
+
+// returns 1 = CD/GD-ROM, 2 = other (DVD, HD, raw), -1 = cannot open, -2 = needs parent
+// parent: the path of the child's parent CHD, or null
+extern "C" EMSCRIPTEN_KEEPALIVE int wasm_probe_open(const char *path, const char *parent)
+{
+ p_cd.reset();
+ p_chd.reset();
+ p_parent.reset();
+ try
+ {
+ if (parent && *parent)
+ {
+ p_parent = std::make_unique<chd_file>();
+ if (p_parent->open(parent))
+ p_parent.reset();
+ }
+ p_chd = std::make_unique<chd_file>();
+ std::error_condition err = p_chd->open(path, false, p_parent.get());
+ if (err == chd_file::error::REQUIRES_PARENT)
+ return -2;
+ if (err)
+ return -1;
+ if (!p_chd->check_is_cd() || !p_chd->check_is_gd())
+ {
+ p_cd = std::make_unique<cdrom_file>(p_chd.get());
+ return 1;
+ }
+ return 2;
+ }
+ catch (...)
+ {
+ p_cd.reset();
+ return p_chd && p_chd->opened() ? 2 : -1;
+ }
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE int wasm_probe_tracks()
+{
+ return p_cd ? p_cd->get_last_track() : 0;
+}
+
+// out[0]=type, out[1]=frames, out[2]=pregap, out[3]=datasize, out[4]=gdrom flag
+extern "C" EMSCRIPTEN_KEEPALIVE int wasm_probe_track_info(int track, uint32_t *out)
+{
+ if (!p_cd || track < 0 || track >= p_cd->get_last_track())
+ return -1;
+ const cdrom_file::track_info &t = p_cd->get_toc().tracks[track];
+ out[0] = t.trktype;
+ out[1] = t.frames;
+ out[2] = t.pregap;
+ out[3] = t.datasize;
+ out[4] = p_cd->is_gdrom() ? 1 : 0;
+ return 0;
+}
+
+// reads 2048 bytes of user data: sector lba of a CD track (counted from index 1),
+// or byte offset lba*2048 for DVD/raw CHDs (track ignored)
+extern "C" EMSCRIPTEN_KEEPALIVE int wasm_probe_read(int track, uint32_t lba, uint8_t *out)
+{
+ try
+ {
+ if (p_cd)
+ {
+ if (track < 0 || track >= p_cd->get_last_track())
+ return -1;
+ // the sector as stored, then its 2048 bytes of user data: asking read_data for MODE1 only
+ // converts from Mode 1 tracks, and gave shifted bytes or nothing for MODE2/2048 and MODE2/2336
+ static const int skip[] = { 0, 16, 8, 0, 0, 8, 24 }; // by CD_TRACK_* type, up to MODE2_RAW
+ uint32_t type = p_cd->get_track_type(track);
+ if (type > cdrom_file::CD_TRACK_MODE2_RAW || type == cdrom_file::CD_TRACK_MODE2_FORM2)
+ return -1;
+ uint8_t sector[2352];
+ if (!p_cd->read_data(p_cd->get_track_start(track) + lba, sector, cdrom_file::CD_TRACK_RAW_DONTCARE))
+ return -1;
+ memcpy(out, sector + skip[type], 2048);
+ return 0;
+ }
+ if (!p_chd)
+ return -1;
+ uint64_t offset = uint64_t(lba) * 2048;
+ if (offset + 2048 > p_chd->logical_bytes())
+ return -1;
+ return p_chd->read_bytes(offset, out, 2048) ? -1 : 0;
+ }
+ catch (...)
+ {
+ return -1;
+ }
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE double wasm_probe_logical()
+{
+ return p_chd ? double(p_chd->logical_bytes()) : 0;
+}
--- /dev/null
+++ ecm.cpp (new file)
+// ECM images (the Error Code Modeler format of Neill Corlett's ecm tools, .ecm): CD images without
+// the parts of each sector that can be computed back from its data. The job worker presents the
+// image inside to chdman (EcmStore in app/worker.js): a scan of the chunk headers maps the image's
+// bytes to the file's, and windows of the image are rebuilt here as chdman reads them.
+//
+// The format: "ECM\0", chunks, a header with count 0 that ends them, and the EDC of the whole image
+// (4 bytes, little-endian). A chunk is a header, a type and a count, then its stored bytes. The
+// header's first byte holds the type (bits 0-1) and the low 5 bits of count - 1 (bits 2-6); while
+// bit 7 is set another byte follows with the next 7 bits. The types:
+// 0: count bytes, stored as they are
+// 1: count Mode 1 sectors (2,352 bytes), stored as the 3-byte address and the 2,048 bytes of data;
+// sync, mode, EDC, the 8 zero bytes and ECC are computed
+// 2: count Mode 2 Form 1 sectors without their sync and header (2,336 bytes), stored as the 4-byte
+// subheader (its copy is the same) and the 2,048 bytes of data; EDC and ECC are computed, the
+// ECC with a zero header as the standard has it for Mode 2
+// 3: count Mode 2 Form 2 sectors, likewise (2,336 bytes), stored as the subheader and 2,324
+// bytes of data; the EDC is computed
+// A raw image's Mode 2 sectors are 16 bytes of sync and header stored as they are, then a type 2
+// or 3 sector.
+
+#include "cdrom.h"
+
+#include <cstdint>
+#include <cstring>
+
+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+#else
+#define EMSCRIPTEN_KEEPALIVE
+#endif
+
+namespace {
+
+// EDC: a CRC-32 with the polynomial x^32 + x^31 + x^16 + x^15 + x^4 + x^3 + x + 1 (0xd8018001
+// reflected), starting from 0 and not inverted. The ECC is the engine's (cdrom_file::ecc_generate).
+uint32_t edc_table[8][256];
+bool tables_ready = false;
+
+void make_tables()
+{
+ for (int i = 0; i < 256; i++)
+ {
+ uint32_t e = i;
+ for (int k = 0; k < 8; k++)
+ e = (e >> 1) ^ ((e & 1) ? 0xd8018001u : 0);
+ edc_table[0][i] = e;
+ }
+ for (int t = 1; t < 8; t++)
+ for (int i = 0; i < 256; i++)
+ edc_table[t][i] = (edc_table[t - 1][i] >> 8) ^ edc_table[0][edc_table[t - 1][i] & 0xff];
+ tables_ready = true;
+}
+
+uint32_t edc_update(uint32_t edc, uint8_t const *p, uint32_t n)
+{
+ for (; n >= 8; p += 8, n -= 8) // eight bytes at a time ("slicing-by-8")
+ {
+ uint32_t const x = edc ^ (uint32_t(p[0]) | (uint32_t(p[1]) << 8) | (uint32_t(p[2]) << 16) | (uint32_t(p[3]) << 24));
+ uint32_t const y = uint32_t(p[4]) | (uint32_t(p[5]) << 8) | (uint32_t(p[6]) << 16) | (uint32_t(p[7]) << 24);
+ edc = edc_table[7][x & 0xff] ^ edc_table[6][(x >> 8) & 0xff] ^ edc_table[5][(x >> 16) & 0xff] ^ edc_table[4][x >> 24] ^
+ edc_table[3][y & 0xff] ^ edc_table[2][(y >> 8) & 0xff] ^ edc_table[1][(y >> 16) & 0xff] ^ edc_table[0][y >> 24];
+ }
+ for (; n; p++, n--)
+ edc = (edc >> 8) ^ edc_table[0][(edc ^ *p) & 0xff];
+ return edc;
+}
+
+inline void put_le32(uint8_t *p, uint32_t v)
+{
+ p[0] = uint8_t(v);
+ p[1] = uint8_t(v >> 8);
+ p[2] = uint8_t(v >> 16);
+ p[3] = uint8_t(v >> 24);
+}
+
+uint8_t const sync_pattern[12] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
+uint32_t const stored_size[4] = { 1, 3 + 2048, 4 + 2048, 4 + 2324 };
+uint32_t const image_size[4] = { 1, 2352, 2336, 2336 };
+
+// one sector of type 1, 2 or 3 at out, from its stored bytes at in
+void rebuild_sector(uint32_t type, uint8_t const *in, uint8_t *out)
+{
+ if (type == 1)
+ {
+ std::memcpy(out, sync_pattern, 12);
+ std::memcpy(out + 12, in, 3);
+ out[15] = 1;
+ std::memcpy(out + 16, in + 3, 2048);
+ put_le32(out + 0x810, edc_update(0, out, 0x810));
+ std::memset(out + 0x814, 0, 8);
+ cdrom_file::ecc_generate(out);
+ }
+ else
+ {
+ // in a whole sector, for the ECC: mode 2, whose header counts as zeros
+ uint8_t frame[2352] = { };
+ uint8_t *const s = frame + 16;
+ uint32_t const data = type == 2 ? 2048 : 2324;
+ std::memcpy(s, in, 4);
+ std::memcpy(s + 4, in, 4);
+ std::memcpy(s + 8, in + 4, data);
+ put_le32(s + 8 + data, edc_update(0, s, 8 + data));
+ if (type == 2)
+ {
+ frame[15] = 2;
+ cdrom_file::ecc_generate(frame);
+ }
+ std::memcpy(out, s, 2336);
+ }
+}
+
+} // anonymous namespace
+
+
+// Rebuilds out_len bytes of the image at out from the stored bytes at in (in_len of them),
+// starting inside a chunk (its type and the sectors, or bytes, of it left) or, with left 0, at a
+// header. The windows the job worker asks for start and end on sector boundaries, except inside
+// a chunk of type 0. Returns the stored bytes used, or -1 if they are not a valid continuation.
+extern "C" EMSCRIPTEN_KEEPALIVE int wasm_ecm_decode(uint8_t const *in, uint32_t in_len, uint8_t *out, uint32_t out_len, uint32_t type, uint32_t left)
+{
+ if (!tables_ready)
+ make_tables();
+ uint32_t i = 0, o = 0;
+ while (o < out_len)
+ {
+ if (!left)
+ {
+ if (i >= in_len)
+ return -1;
+ uint32_t c = in[i++];
+ uint64_t n = (c >> 2) & 0x1f;
+ int bits = 5;
+ type = c & 3;
+ while (c & 0x80)
+ {
+ if (i >= in_len || bits > 26)
+ return -1;
+ c = in[i++];
+ n |= uint64_t(c & 0x7f) << bits;
+ bits += 7;
+ }
+ if (n >= 0xffffffffu) // the end of the chunks (or a count too large): not inside an image
+ return -1;
+ left = uint32_t(n) + 1;
+ }
+ if (type > 3)
+ return -1;
+ if (type == 0)
+ {
+ uint32_t const k = left < out_len - o ? left : out_len - o;
+ if (in_len - i < k)
+ return -1;
+ std::memcpy(out + o, in + i, k);
+ i += k;
+ o += k;
+ left -= k;
+ }
+ else
+ {
+ if (out_len - o < image_size[type] || in_len - i < stored_size[type])
+ return -1;
+ rebuild_sector(type, in + i, out + o);
+ i += stored_size[type];
+ o += image_size[type];
+ left--;
+ }
+ }
+ return int(i);
+}
+
+// the EDC of the image so far, continued over n more bytes (the file's last 4 bytes hold it for the whole image)
+extern "C" EMSCRIPTEN_KEEPALIVE uint32_t wasm_ecm_edc(uint32_t edc, uint8_t const *p, uint32_t n)
+{
+ if (!tables_ready)
+ make_tables();
+ return edc_update(edc, p, n);
+}
--- /dev/null
+++ par_lib.js (new file, Emscripten JS library)
+addToLibrary({
+ // flags: 1 = the deflate codec encodes with libdeflate (--libdeflate)
+ wasm_par_enabled: function (hunkbytes, unitbytes, comp, flags) {
+ if (!Module['parSetup']) return 0;
+ var p = comp >>> 2;
+ var list = [HEAPU32[p], HEAPU32[p + 1], HEAPU32[p + 2], HEAPU32[p + 3]];
+ return Module['parSetup'](hunkbytes >>> 0, unitbytes >>> 0, list, flags >>> 0) ? 1 : 0;
+ },
+ // codecs: the codec slots to try for this hunk, a bit each (the codec plan; 15 = all)
+ wasm_par_submit: function (item, data, length, codecs) {
+ Module['parSubmit'](item >>> 0, data >>> 0, length >>> 0, codecs >>> 0);
+ },
+ // while helper workers compress, chdman pauses after each compression step
+ // (chdman_begin/chdman_resume return -1) so that their results can arrive
+ wasm_par_active: function () {
+ return Module['parActive'] ? 1 : 0;
+ },
+ // results streamed into a folder the page hasn't written yet are over the limit: chdman pauses
+ // (at the same points) until the page catches up
+ wasm_out_full: function () {
+ return Module['outFull'] && Module['outFull']() ? 1 : 0;
+ },
+ // extract and verify: helper workers decompress the hunks chdman is about to read
+ // (chd_file::wasm_read_ahead); results come back through _wasm_rd_slot/_wasm_rd_done
+ wasm_rd_enabled: function (chd, hunkbytes, unitbytes, comp) {
+ if (!Module['rdSetup']) return 0;
+ var p = comp >>> 2;
+ var list = [HEAPU32[p], HEAPU32[p + 1], HEAPU32[p + 2], HEAPU32[p + 3]];
+ return Module['rdSetup'](chd >>> 0, hunkbytes >>> 0, unitbytes >>> 0, list) ? 1 : 0;
+ },
+ wasm_rd_submit: function (chd, hunk, codec, data, length) {
+ Module['rdSubmit'](chd >>> 0, hunk >>> 0, codec, data >>> 0, length >>> 0);
+ },
+ wasm_rd_close: function (chd) {
+ if (Module['rdClose']) Module['rdClose'](chd >>> 0);
+ },
+});
--- /dev/null
+++ engine/libm/flac_libm.c (new file, from Arm optimized-routines)
+/*
+ * cosf and log for libFLAC, identical in every Discpress build (see README.md).
+ *
+ * From Arm's optimized-routines, commit 47597821aaa52e9c055caf1ecf8f3aecfd751cd9
+ * (math/cosf.c, math/sincosf.h, math/sincosf_data.c, math/log.c, math/log_data.c),
+ * the code glibc's cosf and log are built from.
+ * Copyright (c) 2018-2024, Arm Limited.
+ * SPDX-License-Identifier: MIT OR Apache-2.0 WITH LLVM-exception (used here under MIT, see LICENSE)
+ *
+ * Changes: one file, renamed functions, the configuration fixed (TOINT_INTRINSICS 0, a
+ * 128-entry log table), unused paths removed, and in log the multiply-adds that GCC fuses when
+ * glibc is built for x86-64 with FMA computed as fused, without an fma instruction (see
+ * discpress_log). Build with -ffp-contract=off.
+ */
+
+#include "flac_libm.h"
+
+#include <math.h>
+#include <stdint.h>
+#include <string.h>
+
+static inline uint32_t asuint(float f) { uint32_t i; memcpy(&i, &f, sizeof(i)); return i; }
+static inline uint64_t asuint64(double f) { uint64_t i; memcpy(&i, &f, sizeof(i)); return i; }
+static inline double asdouble(uint64_t i) { double f; memcpy(&f, &i, sizeof(f)); return f; }
+
+/* fma(a, b, c), exactly, from plain operations: a*b = ph + pl exactly (Veltkamp's split, Dekker's
+ product), c + ph = th + tl exactly (TwoSum), and th + (tl + pl) with the inner sum rounded to odd
+ (Boldo and Melquiond, "Emulation of a FMA and correctly rounded sums: proved algorithms using
+ rounding to odd", IEEE Trans. Computers 57(4), 2008). Exact for the operands of log below: no
+ overflow in the split, no underflow in pl. Wasm has no fma instruction, and musl's fma() takes
+ about 20 ns. */
+static inline double
+fma_exact (double a, double b, double c)
+{
+ const double split = 0x1p27 + 1.0;
+ double t = split * a, ah = t - (t - a), al = a - ah;
+ t = split * b;
+ double bh = t - (t - b), bl = b - bh;
+ double ph = a * b, pl = ((ah * bh - ph) + ah * bl + al * bh) + al * bl;
+ double th = c + ph, v = th - c, tl = (c - (th - v)) + (ph - v);
+ double s = tl + pl, sv = s - tl, e = (tl - (s - sv)) + (pl - sv);
+ if (e != 0)
+ {
+ uint64_t u = asuint64 (s);
+ /* s is even: take the other neighbour of the exact sum, which is odd */
+ if (!(u & 1))
+ s = asdouble ((e > 0) == (s > 0) ? u + 1 : u - 1);
+ }
+ return th + s;
+}
+
+
+/* ---------------------------------------------------------------- cosf */
+
+/* 2PI * 2^-64. */
+static const double pi63 = 0x1.921FB54442D18p-62;
+/* PI / 4. */
+static const float pio4f = 0x1.921FB6p-1f;
+
+/* The constants and polynomials for sine and cosine. */
+typedef struct
+{
+ double sign[4]; /* Sign of sine in quadrants 0..3. */
+ double hpi_inv; /* 2 / PI * 2^24. */
+ double hpi; /* PI / 2. */
+ double c0, c1, c2, c3, c4; /* Cosine polynomial. */
+ double s1, s2, s3; /* Sine polynomial. */
+} sincos_t;
+
+/* The 2nd entry computes -cos (x) rather than cos (x) to get negation for free. */
+static const sincos_t sincosf_table[2] =
+{
+ {
+ { 1.0, -1.0, -1.0, 1.0 },
+ 0x1.45F306DC9C883p+23,
+ 0x1.921FB54442D18p0,
+ 0x1p0,
+ -0x1.ffffffd0c621cp-2,
+ 0x1.55553e1068f19p-5,
+ -0x1.6c087e89a359dp-10,
+ 0x1.99343027bf8c3p-16,
+ -0x1.555545995a603p-3,
+ 0x1.1107605230bc4p-7,
+ -0x1.994eb3774cf24p-13
+ },
+ {
+ { 1.0, -1.0, -1.0, 1.0 },
+ 0x1.45F306DC9C883p+23,
+ 0x1.921FB54442D18p0,
+ -0x1p0,
+ 0x1.ffffffd0c621cp-2,
+ -0x1.55553e1068f19p-5,
+ 0x1.6c087e89a359dp-10,
+ -0x1.99343027bf8c3p-16,
+ -0x1.555545995a603p-3,
+ 0x1.1107605230bc4p-7,
+ -0x1.994eb3774cf24p-13
+ }
+};
+
+/* Table with 4/PI to 192 bit precision. To avoid unaligned accesses
+ only 8 new bits are added per entry, making the table 4 times larger. */
+static const uint32_t inv_pio4[24] =
+{
+ 0xa2, 0xa2f9, 0xa2f983, 0xa2f9836e,
+ 0xf9836e4e, 0x836e4e44, 0x6e4e4415, 0x4e441529,
+ 0x441529fc, 0x1529fc27, 0x29fc2757, 0xfc2757d1,
+ 0x2757d1f5, 0x57d1f534, 0xd1f534dd, 0xf534ddc0,
+ 0x34ddc0db, 0xddc0db62, 0xc0db6295, 0xdb629599,
+ 0x6295993c, 0x95993c43, 0x993c4390, 0x3c439041
+};
+
+/* Top 12 bits of the float representation with the sign bit cleared. */
+static inline uint32_t
+abstop12 (float x)
+{
+ return (asuint (x) >> 20) & 0x7ff;
+}
+
+/* Return the sine of inputs X and X2 (X squared) using the polynomial P.
+ N is the quadrant, and if odd the cosine polynomial is used. */
+static inline float
+sinf_poly (double x, double x2, const sincos_t *p, int n)
+{
+ double x3, x4, x6, x7, s, c, c1, c2, s1;
+
+ if ((n & 1) == 0)
+ {
+ x3 = x * x2;
+ s1 = p->s2 + x2 * p->s3;
+
+ x7 = x3 * x2;
+ s = x + x3 * p->s1;
+
+ return s + x7 * s1;
+ }
+ else
+ {
+ x4 = x2 * x2;
+ c2 = p->c3 + x2 * p->c4;
+ c1 = p->c0 + x2 * p->c1;
+
+ x6 = x4 * x2;
+ c = c1 + x4 * p->c2;
+
+ return c + x6 * c2;
+ }
+}
+
+/* Fast range reduction using single multiply-subtract. Return the modulo of
+ X as a value between -PI/4 and PI/4 and store the quadrant in NP.
+ The values for PI/2 and 2/PI are accessed via P. Since PI/2 as a double
+ is accurate to 55 bits and the worst-case cancellation happens at 6 * PI/4,
+ the result is accurate for |X| <= 120.0. */
+static inline double
+reduce_fast (double x, const sincos_t *p, int *np)
+{
+ double r;
+ /* Use scaled float to int conversion with explicit rounding.
+ hpi_inv is prescaled by 2^24 so the quadrant ends up in bits 24..31.
+ This avoids inaccuracies introduced by truncating negative values. */
+ r = x * p->hpi_inv;
+ int n = ((int32_t)r + 0x800000) >> 24;
+ *np = n;
+ return x - n * p->hpi;
+}
+
+/* Reduce the range of XI to a multiple of PI/2 using fast integer arithmetic.
+ XI is a reinterpreted float and must be >= 2.0f (the sign bit is ignored).
+ Return the modulo between -PI/4 and PI/4 and store the quadrant in NP.
+ Reduction uses a table of 4/PI with 192 bits of precision. A 32x96->128 bit
+ multiply computes the exact 2.62-bit fixed-point modulo. Since the result
+ can have at most 29 leading zeros after the binary point, the double
+ precision result is accurate to 33 bits. */
+static inline double
+reduce_large (uint32_t xi, int *np)
+{
+ const uint32_t *arr = &inv_pio4[(xi >> 26) & 15];
+ int shift = (xi >> 23) & 7;
+ uint64_t n, res0, res1, res2;
+
+ xi = (xi & 0xffffff) | 0x800000;
+ xi <<= shift;
+
+ res0 = xi * arr[0];
+ res1 = (uint64_t)xi * arr[4];
+ res2 = (uint64_t)xi * arr[8];
+ res0 = (res2 >> 32) | (res0 << 32);
+ res0 += res1;
+
+ n = (res0 + (1ULL << 61)) >> 62;
+ res0 -= n << 62;
+ double x = (int64_t)res0;
+ *np = n;
+ return x * pi63;
+}
+
+/* Fast cosf implementation. Worst-case ULP is 0.5607, maximum relative
+ error is 0.5303 * 2^-23. A single-step range reduction is used for
+ small values. Large inputs have their range reduced using fast integer
+ arithmetic. */
+float
+discpress_cosf (float y)
+{
+ double x = y;
+ double s;
+ int n;
+ const sincos_t *p = &sincosf_table[0];
+
+ if (abstop12 (y) < abstop12 (pio4f))
+ {
+ double x2 = x * x;
+
+ if (abstop12 (y) < abstop12 (0x1p-12f))
+ return 1.0f;
+
+ return sinf_poly (x, x2, p, 1);
+ }
+ else if (abstop12 (y) < abstop12 (120.0f))
+ {
+ x = reduce_fast (x, p, &n);
+
+ /* Setup the signs for sin and cos. */
+ s = p->sign[n & 3];
+
+ if (n & 2)
+ p = &sincosf_table[1];
+
+ return sinf_poly (x * s, x * x, p, n ^ 1);
+ }
+ else if (abstop12 (y) < abstop12 (INFINITY))
+ {
+ uint32_t xi = asuint (y);
+ int sign = xi >> 31;
+
+ x = reduce_large (xi, &n);
+
+ /* Setup signs for sin and cos - include original sign. */
+ s = p->sign[(n + sign) & 3];
+
+ if ((n + sign) & 2)
+ p = &sincosf_table[1];
+
+ return sinf_poly (x * s, x * x, p, n ^ 1);
+ }
+ else
+ return (y - y) / (y - y);
+}
+
+
+/* ---------------------------------------------------------------- log */
+
+#define LOG_TABLE_BITS 7
+#define LOG_POLY_ORDER 6
+#define LOG_POLY1_ORDER 12
+#define N (1 << LOG_TABLE_BITS)
+#define OFF 0x3fe6000000000000
+
+static const struct
+{
+ double ln2hi;
+ double ln2lo;
+ double poly[LOG_POLY_ORDER - 1]; /* First coefficient is 1. */
+ double poly1[LOG_POLY1_ORDER - 1];
+ struct {double invc, logc;} tab[N];
+} log_data = {
+.ln2hi = 0x1.62e42fefa3800p-1,
+.ln2lo = 0x1.ef35793c76730p-45,
+.poly1 = {
+-0x1p-1,
+0x1.5555555555577p-2,
+-0x1.ffffffffffdcbp-3,
+0x1.999999995dd0cp-3,
+-0x1.55555556745a7p-3,
+0x1.24924a344de3p-3,
+-0x1.fffffa4423d65p-4,
+0x1.c7184282ad6cap-4,
+-0x1.999eb43b068ffp-4,
+0x1.78182f7afd085p-4,
+-0x1.5521375d145cdp-4,
+},
+.poly = {
+-0x1.0000000000001p-1,
+0x1.555555551305bp-2,
+-0x1.fffffffeb459p-3,
+0x1.999b324f10111p-3,
+-0x1.55575e506c89fp-3,
+},
+.tab = {
+{0x1.734f0c3e0de9fp+0, -0x1.7cc7f79e69000p-2},
+{0x1.713786a2ce91fp+0, -0x1.76feec20d0000p-2},
+{0x1.6f26008fab5a0p+0, -0x1.713e31351e000p-2},
+{0x1.6d1a61f138c7dp+0, -0x1.6b85b38287800p-2},
+{0x1.6b1490bc5b4d1p+0, -0x1.65d5590807800p-2},
+{0x1.69147332f0cbap+0, -0x1.602d076180000p-2},
+{0x1.6719f18224223p+0, -0x1.5a8ca86909000p-2},
+{0x1.6524f99a51ed9p+0, -0x1.54f4356035000p-2},
+{0x1.63356aa8f24c4p+0, -0x1.4f637c36b4000p-2},
+{0x1.614b36b9ddc14p+0, -0x1.49da7fda85000p-2},
+{0x1.5f66452c65c4cp+0, -0x1.445923989a800p-2},
+{0x1.5d867b5912c4fp+0, -0x1.3edf439b0b800p-2},
+{0x1.5babccb5b90dep+0, -0x1.396ce448f7000p-2},
+{0x1.59d61f2d91a78p+0, -0x1.3401e17bda000p-2},
+{0x1.5805612465687p+0, -0x1.2e9e2ef468000p-2},
+{0x1.56397cee76bd3p+0, -0x1.2941b3830e000p-2},
+{0x1.54725e2a77f93p+0, -0x1.23ec58cda8800p-2},
+{0x1.52aff42064583p+0, -0x1.1e9e129279000p-2},
+{0x1.50f22dbb2bddfp+0, -0x1.1956d2b48f800p-2},
+{0x1.4f38f4734ded7p+0, -0x1.141679ab9f800p-2},
+{0x1.4d843cfde2840p+0, -0x1.0edd094ef9800p-2},
+{0x1.4bd3ec078a3c8p+0, -0x1.09aa518db1000p-2},
+{0x1.4a27fc3e0258ap+0, -0x1.047e65263b800p-2},
+{0x1.4880524d48434p+0, -0x1.feb224586f000p-3},
+{0x1.46dce1b192d0bp+0, -0x1.f474a7517b000p-3},
+{0x1.453d9d3391854p+0, -0x1.ea4443d103000p-3},
+{0x1.43a2744b4845ap+0, -0x1.e020d44e9b000p-3},
+{0x1.420b54115f8fbp+0, -0x1.d60a22977f000p-3},
+{0x1.40782da3ef4b1p+0, -0x1.cc00104959000p-3},
+{0x1.3ee8f5d57fe8fp+0, -0x1.c202956891000p-3},
+{0x1.3d5d9a00b4ce9p+0, -0x1.b81178d811000p-3},
+{0x1.3bd60c010c12bp+0, -0x1.ae2c9ccd3d000p-3},
+{0x1.3a5242b75dab8p+0, -0x1.a45402e129000p-3},
+{0x1.38d22cd9fd002p+0, -0x1.9a877681df000p-3},
+{0x1.3755bc5847a1cp+0, -0x1.90c6d69483000p-3},
+{0x1.35dce49ad36e2p+0, -0x1.87120a645c000p-3},
+{0x1.34679984dd440p+0, -0x1.7d68fb4143000p-3},
+{0x1.32f5cceffcb24p+0, -0x1.73cb83c627000p-3},
+{0x1.3187775a10d49p+0, -0x1.6a39a9b376000p-3},
+{0x1.301c8373e3990p+0, -0x1.60b3154b7a000p-3},
+{0x1.2eb4ebb95f841p+0, -0x1.5737d76243000p-3},
+{0x1.2d50a0219a9d1p+0, -0x1.4dc7b8fc23000p-3},
+{0x1.2bef9a8b7fd2ap+0, -0x1.4462c51d20000p-3},
+{0x1.2a91c7a0c1babp+0, -0x1.3b08abc830000p-3},
+{0x1.293726014b530p+0, -0x1.31b996b490000p-3},
+{0x1.27dfa5757a1f5p+0, -0x1.2875490a44000p-3},
+{0x1.268b39b1d3bbfp+0, -0x1.1f3b9f879a000p-3},
+{0x1.2539d838ff5bdp+0, -0x1.160c8252ca000p-3},
+{0x1.23eb7aac9083bp+0, -0x1.0ce7f57f72000p-3},
+{0x1.22a012ba940b6p+0, -0x1.03cdc49fea000p-3},
+{0x1.2157996cc4132p+0, -0x1.f57bdbc4b8000p-4},
+{0x1.201201dd2fc9bp+0, -0x1.e370896404000p-4},
+{0x1.1ecf4494d480bp+0, -0x1.d17983ef94000p-4},
+{0x1.1d8f5528f6569p+0, -0x1.bf9674ed8a000p-4},
+{0x1.1c52311577e7cp+0, -0x1.adc79202f6000p-4},
+{0x1.1b17c74cb26e9p+0, -0x1.9c0c3e7288000p-4},
+{0x1.19e010c2c1ab6p+0, -0x1.8a646b372c000p-4},
+{0x1.18ab07bb670bdp+0, -0x1.78d01b3ac0000p-4},
+{0x1.1778a25efbcb6p+0, -0x1.674f145380000p-4},
+{0x1.1648d354c31dap+0, -0x1.55e0e6d878000p-4},
+{0x1.151b990275fddp+0, -0x1.4485cdea1e000p-4},
+{0x1.13f0ea432d24cp+0, -0x1.333d94d6aa000p-4},
+{0x1.12c8b7210f9dap+0, -0x1.22079f8c56000p-4},
+{0x1.11a3028ecb531p+0, -0x1.10e4698622000p-4},
+{0x1.107fbda8434afp+0, -0x1.ffa6c6ad20000p-5},
+{0x1.0f5ee0f4e6bb3p+0, -0x1.dda8d4a774000p-5},
+{0x1.0e4065d2a9fcep+0, -0x1.bbcece4850000p-5},
+{0x1.0d244632ca521p+0, -0x1.9a1894012c000p-5},
+{0x1.0c0a77ce2981ap+0, -0x1.788583302c000p-5},
+{0x1.0af2f83c636d1p+0, -0x1.5715e67d68000p-5},
+{0x1.09ddb98a01339p+0, -0x1.35c8a49658000p-5},
+{0x1.08cabaf52e7dfp+0, -0x1.149e364154000p-5},
+{0x1.07b9f2f4e28fbp+0, -0x1.e72c082eb8000p-6},
+{0x1.06ab58c358f19p+0, -0x1.a55f152528000p-6},
+{0x1.059eea5ecf92cp+0, -0x1.63d62cf818000p-6},
+{0x1.04949cdd12c90p+0, -0x1.228fb8caa0000p-6},
+{0x1.038c6c6f0ada9p+0, -0x1.c317b20f90000p-7},
+{0x1.02865137932a9p+0, -0x1.419355daa0000p-7},
+{0x1.0182427ea7348p+0, -0x1.81203c2ec0000p-8},
+{0x1.008040614b195p+0, -0x1.0040979240000p-9},
+{0x1.fe01ff726fa1ap-1, 0x1.feff384900000p-9},
+{0x1.fa11cc261ea74p-1, 0x1.7dc41353d0000p-7},
+{0x1.f6310b081992ep-1, 0x1.3cea3c4c28000p-6},
+{0x1.f25f63ceeadcdp-1, 0x1.b9fc114890000p-6},
+{0x1.ee9c8039113e7p-1, 0x1.1b0d8ce110000p-5},
+{0x1.eae8078cbb1abp-1, 0x1.58a5bd001c000p-5},
+{0x1.e741aa29d0c9bp-1, 0x1.95c8340d88000p-5},
+{0x1.e3a91830a99b5p-1, 0x1.d276aef578000p-5},
+{0x1.e01e009609a56p-1, 0x1.07598e598c000p-4},
+{0x1.dca01e577bb98p-1, 0x1.253f5e30d2000p-4},
+{0x1.d92f20b7c9103p-1, 0x1.42edd8b380000p-4},
+{0x1.d5cac66fb5ccep-1, 0x1.606598757c000p-4},
+{0x1.d272caa5ede9dp-1, 0x1.7da76356a0000p-4},
+{0x1.cf26e3e6b2ccdp-1, 0x1.9ab434e1c6000p-4},
+{0x1.cbe6da2a77902p-1, 0x1.b78c7bb0d6000p-4},
+{0x1.c8b266d37086dp-1, 0x1.d431332e72000p-4},
+{0x1.c5894bd5d5804p-1, 0x1.f0a3171de6000p-4},
+{0x1.c26b533bb9f8cp-1, 0x1.067152b914000p-3},
+{0x1.bf583eeece73fp-1, 0x1.147858292b000p-3},
+{0x1.bc4fd75db96c1p-1, 0x1.2266ecdca3000p-3},
+{0x1.b951e0c864a28p-1, 0x1.303d7a6c55000p-3},
+{0x1.b65e2c5ef3e2cp-1, 0x1.3dfc33c331000p-3},
+{0x1.b374867c9888bp-1, 0x1.4ba366b7a8000p-3},
+{0x1.b094b211d304ap-1, 0x1.5933928d1f000p-3},
+{0x1.adbe885f2ef7ep-1, 0x1.66acd2418f000p-3},
+{0x1.aaf1d31603da2p-1, 0x1.740f8ec669000p-3},
+{0x1.a82e63fd358a7p-1, 0x1.815c0f51af000p-3},
+{0x1.a5740ef09738bp-1, 0x1.8e92954f68000p-3},
+{0x1.a2c2a90ab4b27p-1, 0x1.9bb3602f84000p-3},
+{0x1.a01a01393f2d1p-1, 0x1.a8bed1c2c0000p-3},
+{0x1.9d79f24db3c1bp-1, 0x1.b5b515c01d000p-3},
+{0x1.9ae2505c7b190p-1, 0x1.c2967ccbcc000p-3},
+{0x1.9852ef297ce2fp-1, 0x1.cf635d5486000p-3},
+{0x1.95cbaeea44b75p-1, 0x1.dc1bd3446c000p-3},
+{0x1.934c69de74838p-1, 0x1.e8c01b8cfe000p-3},
+{0x1.90d4f2f6752e6p-1, 0x1.f5509c0179000p-3},
+{0x1.8e6528effd79dp-1, 0x1.00e6c121fb800p-2},
+{0x1.8bfce9fcc007cp-1, 0x1.071b80e93d000p-2},
+{0x1.899c0dabec30ep-1, 0x1.0d46b9e867000p-2},
+{0x1.87427aa2317fbp-1, 0x1.13687334bd000p-2},
+{0x1.84f00acb39a08p-1, 0x1.1980d67234800p-2},
+{0x1.82a49e8653e55p-1, 0x1.1f8ffe0cc8000p-2},
+{0x1.8060195f40260p-1, 0x1.2595fd7636800p-2},
+{0x1.7e22563e0a329p-1, 0x1.2b9300914a800p-2},
+{0x1.7beb377dcb5adp-1, 0x1.3187210436000p-2},
+{0x1.79baa679725c2p-1, 0x1.377266dec1800p-2},
+{0x1.77907f2170657p-1, 0x1.3d54ffbaf3000p-2},
+{0x1.756cadbd6130cp-1, 0x1.432eee32fe000p-2},
+},
+};
+
+#define T log_data.tab
+#define B log_data.poly1
+#define A log_data.poly
+#define Ln2hi log_data.ln2hi
+#define Ln2lo log_data.ln2lo
+
+/* Top 16 bits of a double. */
+static inline uint32_t
+top16 (double x)
+{
+ return asuint64 (x) >> 48;
+}
+
+/* Worst-case error: 0.519 ULP (0.520 without fma). The fused multiply-adds are where GCC fuses
+ the upstream expressions (quoted before each block) when glibc is built for x86-64 with FMA,
+ which makes this glibc's log on such a CPU, on any machine.
+
+ Emulating each of them would make log 12 times slower than musl's in wasm, so the main path
+ computes the fused result another way: r and w exactly, as below, and the rest unfused, which
+ moves y by less than 0x1p-62 (by at most 0x1p-63.5, from the operands' ranges; 0x1p-69 seen).
+ When that can't change the final rounding, the result is the fused one; otherwise, about once
+ in 2,500 calls, the fused operations are emulated. */
+double
+discpress_log (double x)
+{
+ double w, z, r, r2, r3, y, invc, logc, kd, hi, lo;
+ uint64_t ix, iz, tmp;
+ uint32_t top;
+ int k, i;
+
+ ix = asuint64 (x);
+ top = top16 (x);
+#define LO asuint64 (1.0 - 0x1p-4)
+#define HI asuint64 (1.0 + 0x1.09p-4)
+ if (ix - LO < HI - LO)
+ {
+ /* Handle close to 1.0 inputs separately. */
+ /* Fix sign of zero with downward rounding when x==1. */
+ if (ix == asuint64 (1.0))
+ return 0;
+ r = x - 1.0;
+ r2 = r * r;
+ r3 = r * r2;
+ /* y = r3 * (B[1] + r * B[2] + r2 * B[3]
+ + r3 * (B[4] + r * B[5] + r2 * B[6]
+ + r3 * (B[7] + r * B[8] + r2 * B[9] + r3 * B[10]))); */
+ double const c1 = fma_exact (B[3], r2, fma_exact (B[2], r, B[1]));
+ double const c2 = fma_exact (B[6], r2, fma_exact (B[5], r, B[4]));
+ double const c3 = fma_exact (B[10], r3, fma_exact (B[9], r2, fma_exact (B[8], r, B[7])));
+ double const c = fma_exact (fma_exact (c3, r3, c2), r3, c1);
+ /* Worst-case error is around 0.507 ULP.
+ w = r * 0x1p27;
+ double rhi = r + w - w;
+ double rlo = r - rhi;
+ w = rhi * rhi * B[0]; (B[0] == -0.5)
+ hi = r + w;
+ lo = r - hi + w;
+ lo += B[0] * rlo * (rhi + r);
+ y += lo;
+ y += hi; */
+ double const rhi = fma_exact (-r, 0x1p27, fma_exact (r, 0x1p27, r));
+ double const rlo = r - rhi;
+ double const rhi2 = rhi * rhi;
+ hi = fma_exact (rhi2, B[0], r);
+ lo = fma_exact (rhi2, B[0], r - hi);
+ lo = fma_exact (B[0] * rlo, r + rhi, lo);
+ y = fma_exact (c, r3, lo);
+ return hi + y;
+ }
+ if (top - 0x0010 >= 0x7ff0 - 0x0010)
+ {
+ /* x < 0x1p-1022 or inf or nan. */
+ if (ix * 2 == 0)
+ return -1.0 / 0.0;
+ if (ix == asuint64 (INFINITY)) /* log(inf) == inf. */
+ return x;
+ if ((top & 0x8000) || (top & 0x7ff0) == 0x7ff0)
+ return (x - x) / (x - x);
+ /* x is subnormal, normalize it. */
+ ix = asuint64 (x * 0x1p52);
+ ix -= 52ULL << 52;
+ }
+
+ /* x = 2^k z; where z is in range [OFF,2*OFF) and exact.
+ The range is split into N subintervals.
+ The ith subinterval contains z and c is near its center. */
+ tmp = ix - OFF;
+ i = (tmp >> (52 - LOG_TABLE_BITS)) % N;
+ k = (int64_t) tmp >> 52; /* arithmetic shift */
+ iz = ix - (tmp & 0xfffULL << 52);
+ invc = T[i].invc;
+ logc = T[i].logc;
+ z = asdouble (iz);
+
+ /* log(x) = log1p(z/c-1) + log(c) + k*Ln2. */
+ /* r ~= z/c - 1, |r| < 1/(2*N). */
+ /* rounding error: 0x1p-55/N. */
+ /* r = fma (z, invc, -1.0): z*invc = ph + pl exactly, ph is within 2^-7 of 1 so ph - 1.0 is
+ exact, and (ph - 1.0) + pl then rounds once, as the fused operation does. */
+ {
+ const double split = 0x1p27 + 1.0;
+ double t = split * z, zh = t - (t - z), zl = z - zh;
+ t = split * invc;
+ double ih = t - (t - invc), il = invc - ih;
+ double ph = z * invc, pl = ((zh * ih - ph) + zh * il + zl * ih) + zl * il;
+ r = (ph - 1.0) + pl;
+ }
+ kd = (double) k;
+
+ /* hi + lo = r + log(c) + k*Ln2.
+ w = kd * Ln2hi + logc;
+ hi = w + r;
+ lo = w - hi + r + kd * Ln2lo; */
+ /* w = fma (Ln2hi, kd, logc): the table makes kd*Ln2hi + logc exact, so nothing rounds. */
+ w = Ln2hi * kd + logc;
+ hi = w + r;
+ double const t = w - hi + r;
+
+ /* log(x) = lo + (log1p(r) - r) + hi.
+ y = lo + r2 * A[0] + r * r2 * (A[1] + r * A[2] + r2 * (A[3] + r * A[4])) + hi; */
+ r2 = r * r; /* rounding error: 0x1p-54/N^2. */
+ double const rr2 = r * r2;
+ {
+ /* the rest unfused: |r| < 2^-7, and |lo| < 2^-33 as |k| < 1100, bound y's change */
+ double const q1 = A[4] * r + A[3], q2 = A[2] * r + A[1];
+ double const q3 = q1 * r2 + q2;
+ double const q4 = A[0] * r2 + (Ln2lo * kd + t);
+ double const yu = rr2 * q3 + q4;
+ double const zu = yu + hi;
+ double const e = (hi - zu) + yu; /* exact, as |hi| > 0.06 > |yu| */
+ /* half the gap to zu's neighbours (a quarter of an ulp towards zero at a power of 2) */
+ uint64_t const zb = asuint64 (zu);
+ double const ulp = asdouble ((zb & 0x7ff0000000000000ULL) - (52ULL << 52));
+ double const half = (zb & 0x000fffffffffffffULL) ? 0.5 * ulp : 0.25 * ulp;
+ if (fabs (e) + 0x1p-62 < half)
+ return zu;
+ }
+ lo = fma_exact (Ln2lo, kd, t);
+ y = fma_exact (rr2, fma_exact (fma_exact (A[4], r, A[3]), r2, fma_exact (A[2], r, A[1])), fma_exact (A[0], r2, lo));
+ return y + hi;
+}