GCC Code Coverage Report


Directory: src/
File: src/mpq-mpqe.c
Date: 2026-10-06 13:47:41
Exec Total Coverage
Lines: 96 96 100.0%
Functions: 4 4 100.0%
Branches: 17 18 94.4%

Line Branch Exec Source
1 /*
2 * mpq-mpqe.c -- private MPQE cryptographic helper implementations.
3 *
4 * Copyright (c) 2026-2026 Maik Broemme <mbroemme@libmpq.org>
5 *
6 * This file is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
10 *
11 * This file is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this file; if not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "mpq-endian.h"
25 #include "mpq-mpqe.h"
26
27 #include <string.h>
28
29 /* Clear key material without allowing the compiler to elide the writes. */
30 void
31 133300 libmpq__mpqe_clear(void *buffer, size_t size)
32 {
33 133300 volatile uint8_t *bytes = buffer;
34
35
2/2
✓ Branch 0 taken 25088704 times.
✓ Branch 1 taken 133300 times.
25222004 while (size-- != 0)
36 25088704 *bytes++ = 0;
37 133300 }
38
39 /* Rotate a 32-bit word left; MPQE uses this as part of its per-chunk shuffle. */
40 static uint32_t
41 13353600 rol32(uint32_t value, unsigned count)
42 {
43 13353600 return (value << count) | (value >> (32U - count));
44 }
45
46 /* Derive the MPQE working key from the installer authentication-code bytes. */
47 int32_t
48 479 libmpq__mpqe_key(
49 uint8_t key[LIBMPQ_MPQE_CHUNK_SIZE], const uint8_t *auth_code, size_t auth_code_size
50 )
51 {
52 static const char template_key[] =
53 "expand 32-byte k000000000000000000000000000000000000000000000000";
54 static const uint8_t source_words[8] = { 3, 7, 2, 6, 1, 5, 0, 4 };
55 static const uint8_t target_words[8] = { 0, 2, 3, 5, 6, 8, 9, 11 };
56 uint8_t derived_key[LIBMPQ_MPQE_CHUNK_SIZE];
57 size_t i;
58
59
5/6
✓ Branch 0 taken 479 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 476 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 471 times.
479 if (key == NULL || auth_code == NULL || auth_code_size < LIBMPQ_MPQE_AUTH_CODE_MINIMUM)
60 8 return LIBMPQ_ERROR_DECRYPT;
61 471 memcpy(derived_key, template_key, sizeof(derived_key));
62
2/2
✓ Branch 0 taken 3768 times.
✓ Branch 1 taken 471 times.
4239 for (i = 0; i < sizeof(source_words); ++i) {
63 3768 uint32_t value = libmpq__load_le32(auth_code + source_words[i] * sizeof(uint32_t));
64
65 3768 libmpq__store_le32(derived_key + (4U + target_words[i]) * sizeof(uint32_t), value);
66 }
67 471 memcpy(key, derived_key, sizeof(derived_key));
68 471 libmpq__mpqe_clear(derived_key, sizeof(derived_key));
69 471 return 0;
70 }
71
72 /* Decrypt one zero-padded MPQE chunk in place using its absolute stream position. */
73 void
74 41730 libmpq__mpqe_transform_chunk(
75 uint8_t chunk[LIBMPQ_MPQE_CHUNK_SIZE], const uint8_t key[LIBMPQ_MPQE_CHUNK_SIZE],
76 uint64_t offset
77 )
78 {
79 uint32_t shuffled[16];
80 uint32_t key_mirror[16];
81 uint32_t mirror[16];
82 41730 uint64_t chunk_number = offset / LIBMPQ_MPQE_CHUNK_SIZE;
83 unsigned round;
84 unsigned i;
85
86
2/2
✓ Branch 0 taken 667680 times.
✓ Branch 1 taken 41730 times.
709410 for (i = 0; i < 16; ++i)
87 667680 key_mirror[i] = libmpq__load_le32(key + i * sizeof(uint32_t));
88 41730 key_mirror[5] = (uint32_t)(chunk_number >> 32);
89 41730 key_mirror[8] = (uint32_t)chunk_number;
90 41730 shuffled[14] = key_mirror[0];
91 41730 shuffled[12] = key_mirror[1];
92 41730 shuffled[5] = key_mirror[2];
93 41730 shuffled[15] = key_mirror[3];
94 41730 shuffled[10] = key_mirror[4];
95 41730 shuffled[7] = key_mirror[5];
96 41730 shuffled[11] = key_mirror[6];
97 41730 shuffled[9] = key_mirror[7];
98 41730 shuffled[3] = key_mirror[8];
99 41730 shuffled[6] = key_mirror[9];
100 41730 shuffled[8] = key_mirror[10];
101 41730 shuffled[13] = key_mirror[11];
102 41730 shuffled[2] = key_mirror[12];
103 41730 shuffled[4] = key_mirror[13];
104 41730 shuffled[1] = key_mirror[14];
105 41730 shuffled[0] = key_mirror[15];
106
2/2
✓ Branch 0 taken 417300 times.
✓ Branch 1 taken 41730 times.
459030 for (round = 0; round < 20; round += 2) {
107 417300 shuffled[10] ^= rol32(shuffled[14] + shuffled[2], 7);
108 417300 shuffled[3] ^= rol32(shuffled[10] + shuffled[14], 9);
109 417300 shuffled[2] ^= rol32(shuffled[3] + shuffled[10], 13);
110 417300 shuffled[14] ^= rol32(shuffled[2] + shuffled[3], 18);
111 417300 shuffled[7] ^= rol32(shuffled[12] + shuffled[4], 7);
112 417300 shuffled[6] ^= rol32(shuffled[7] + shuffled[12], 9);
113 417300 shuffled[4] ^= rol32(shuffled[6] + shuffled[7], 13);
114 417300 shuffled[12] ^= rol32(shuffled[4] + shuffled[6], 18);
115 417300 shuffled[11] ^= rol32(shuffled[5] + shuffled[1], 7);
116 417300 shuffled[8] ^= rol32(shuffled[11] + shuffled[5], 9);
117 417300 shuffled[1] ^= rol32(shuffled[8] + shuffled[11], 13);
118 417300 shuffled[5] ^= rol32(shuffled[1] + shuffled[8], 18);
119 417300 shuffled[9] ^= rol32(shuffled[15] + shuffled[0], 7);
120 417300 shuffled[13] ^= rol32(shuffled[9] + shuffled[15], 9);
121 417300 shuffled[0] ^= rol32(shuffled[13] + shuffled[9], 13);
122 417300 shuffled[15] ^= rol32(shuffled[0] + shuffled[13], 18);
123 417300 shuffled[4] ^= rol32(shuffled[14] + shuffled[9], 7);
124 417300 shuffled[8] ^= rol32(shuffled[4] + shuffled[14], 9);
125 417300 shuffled[9] ^= rol32(shuffled[8] + shuffled[4], 13);
126 417300 shuffled[14] ^= rol32(shuffled[9] + shuffled[8], 18);
127 417300 shuffled[1] ^= rol32(shuffled[12] + shuffled[10], 7);
128 417300 shuffled[13] ^= rol32(shuffled[1] + shuffled[12], 9);
129 417300 shuffled[10] ^= rol32(shuffled[13] + shuffled[1], 13);
130 417300 shuffled[12] ^= rol32(shuffled[10] + shuffled[13], 18);
131 417300 shuffled[0] ^= rol32(shuffled[5] + shuffled[7], 7);
132 417300 shuffled[3] ^= rol32(shuffled[0] + shuffled[5], 9);
133 417300 shuffled[7] ^= rol32(shuffled[3] + shuffled[0], 13);
134 417300 shuffled[5] ^= rol32(shuffled[7] + shuffled[3], 18);
135 417300 shuffled[2] ^= rol32(shuffled[15] + shuffled[11], 7);
136 417300 shuffled[6] ^= rol32(shuffled[2] + shuffled[15], 9);
137 417300 shuffled[11] ^= rol32(shuffled[6] + shuffled[2], 13);
138 417300 shuffled[15] ^= rol32(shuffled[11] + shuffled[6], 18);
139 }
140
2/2
✓ Branch 0 taken 667680 times.
✓ Branch 1 taken 41730 times.
709410 for (i = 0; i < 16; ++i)
141 667680 mirror[i] = libmpq__load_le32(chunk + i * sizeof(uint32_t));
142 41730 mirror[0] ^= shuffled[14] + key_mirror[0];
143 41730 mirror[1] ^= shuffled[4] + key_mirror[13];
144 41730 mirror[2] ^= shuffled[8] + key_mirror[10];
145 41730 mirror[3] ^= shuffled[9] + key_mirror[7];
146 41730 mirror[4] ^= shuffled[10] + key_mirror[4];
147 41730 mirror[5] ^= shuffled[12] + key_mirror[1];
148 41730 mirror[6] ^= shuffled[1] + key_mirror[14];
149 41730 mirror[7] ^= shuffled[13] + key_mirror[11];
150 41730 mirror[8] ^= shuffled[3] + key_mirror[8];
151 41730 mirror[9] ^= shuffled[7] + key_mirror[5];
152 41730 mirror[10] ^= shuffled[5] + key_mirror[2];
153 41730 mirror[11] ^= shuffled[0] + key_mirror[15];
154 41730 mirror[12] ^= shuffled[2] + key_mirror[12];
155 41730 mirror[13] ^= shuffled[6] + key_mirror[9];
156 41730 mirror[14] ^= shuffled[11] + key_mirror[6];
157 41730 mirror[15] ^= shuffled[15] + key_mirror[3];
158
2/2
✓ Branch 0 taken 667680 times.
✓ Branch 1 taken 41730 times.
709410 for (i = 0; i < 16; ++i)
159 667680 libmpq__store_le32(chunk + i * sizeof(uint32_t), mirror[i]);
160 41730 libmpq__mpqe_clear(shuffled, sizeof(shuffled));
161 41730 libmpq__mpqe_clear(key_mirror, sizeof(key_mirror));
162 41730 libmpq__mpqe_clear(mirror, sizeof(mirror));
163 41730 }
164