]> FriiDump Source - friidump.git/blob - libfriidump/xbox_portable.c
Harden GCC-4243N A102 HLDS E7 profile
[friidump.git] / libfriidump / xbox_portable.c
1 #include "xbox_portable.h"
2 #ifndef XBOX_PORTABLE_NO_DISC_WRAPPER
3 #include "disc.h"
4 #endif
5 #include "xbox_region.h"
6
7 #include <ctype.h>
8 #include <stdlib.h>
9 #include <string.h>
10
11 #define XBOX_SECTOR_SIZE 2048U
12 #define XBOX_XDFS_MAGIC "MICROSOFT*XBOX*MEDIA"
13 #define XBOX_XDFS_MAGIC_SIZE 20U
14 #define XBOX_XDFS_STANDARD_VOLUME_LBA 32U
15 #define XBOX_XDFS_LEGACY_VOLUME_LBA 306112U
16 #define XBOX_MAX_ROOT_DIRECTORY_SECTORS 4096U
17 #define XBOX_XBE_CERTIFICATE_MIN_SIZE 0x0B0U
18
19 static uint16_t xbox_le16(const unsigned char *p)
20 {
21     return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
22 }
23
24 static uint32_t xbox_le32(const unsigned char *p)
25 {
26     return (uint32_t)p[0] |
27            ((uint32_t)p[1] << 8) |
28            ((uint32_t)p[2] << 16) |
29            ((uint32_t)p[3] << 24);
30 }
31
32 static int xbox_ascii_case_equal(const unsigned char *value,
33                                  size_t value_length,
34                                  const char *expected)
35 {
36     size_t index;
37     size_t expected_length;
38
39     if (!value || !expected)
40         return 0;
41
42     expected_length = strlen(expected);
43     if (value_length != expected_length)
44         return 0;
45
46     for (index = 0; index < value_length; ++index) {
47         if (tolower((unsigned char)value[index]) !=
48             tolower((unsigned char)expected[index]))
49             return 0;
50     }
51
52     return 1;
53 }
54
55 static size_t xbox_utf8_append(char *output,
56                                size_t output_size,
57                                size_t position,
58                                uint32_t codepoint)
59 {
60     if (!output || output_size == 0 || position >= output_size)
61         return position;
62
63     if (codepoint <= 0x7FU) {
64         if (position + 1 >= output_size)
65             return position;
66         output[position++] = (char)codepoint;
67     } else if (codepoint <= 0x7FFU) {
68         if (position + 2 >= output_size)
69             return position;
70         output[position++] = (char)(0xC0U | (codepoint >> 6));
71         output[position++] = (char)(0x80U | (codepoint & 0x3FU));
72     } else {
73         if (position + 3 >= output_size)
74             return position;
75         output[position++] = (char)(0xE0U | (codepoint >> 12));
76         output[position++] = (char)(0x80U | ((codepoint >> 6) & 0x3FU));
77         output[position++] = (char)(0x80U | (codepoint & 0x3FU));
78     }
79
80     output[position] = '\0';
81     return position;
82 }
83
84 static void xbox_decode_title(const unsigned char *certificate,
85                               size_t certificate_size,
86                               char *output,
87                               size_t output_size)
88 {
89     size_t index;
90     size_t position;
91
92     if (!output || output_size == 0)
93         return;
94
95     output[0] = '\0';
96     if (!certificate || certificate_size < 0x00CU + 80U)
97         return;
98
99     position = 0;
100     for (index = 0; index < 40U; ++index) {
101         uint16_t codepoint = xbox_le16(certificate + 0x00CU + (index * 2U));
102         if (codepoint == 0)
103             break;
104         if (codepoint >= 0xD800U && codepoint <= 0xDFFFU)
105             codepoint = (uint16_t)'?';
106         position = xbox_utf8_append(output,
107                                     output_size,
108                                     position,
109                                     codepoint);
110     }
111 }
112
113 static int xbox_parse_media_id(const unsigned char *dmi,
114                                size_t dmi_size,
115                                char *output,
116                                size_t output_size)
117 {
118     size_t index;
119     size_t position;
120
121     if (!output || output_size == 0)
122         return 0;
123
124     output[0] = '\0';
125     if (!dmi || dmi_size <= 8U)
126         return 0;
127
128     position = 0;
129     for (index = 8U; index < dmi_size && index < 24U; ++index) {
130         unsigned char c = dmi[index];
131         if (isalnum(c)) {
132             if (position + 1U >= output_size)
133                 break;
134             output[position++] = (char)c;
135         }
136     }
137     output[position] = '\0';
138
139     return position >= 4U;
140 }
141
142 static int xbox_find_default_xbe(xbox_portable_read10_func read10,
143                                  void *context,
144                                  uint32_t root_lba,
145                                  uint32_t root_size,
146                                  uint32_t *xbe_lba)
147 {
148     unsigned char sector[XBOX_SECTOR_SIZE];
149     uint32_t sectors;
150     uint32_t sector_index;
151
152     if (!read10 || !xbe_lba || root_size == 0)
153         return 0;
154
155     sectors = (root_size + XBOX_SECTOR_SIZE - 1U) / XBOX_SECTOR_SIZE;
156     if (sectors > XBOX_MAX_ROOT_DIRECTORY_SECTORS)
157         sectors = XBOX_MAX_ROOT_DIRECTORY_SECTORS;
158
159     for (sector_index = 0; sector_index < sectors; ++sector_index) {
160         size_t offset = 0;
161         if (read10(context,
162                    root_lba + sector_index,
163                    1,
164                    sector,
165                    sizeof(sector)) < 0)
166             return 0;
167
168         while (offset + 14U <= sizeof(sector)) {
169             uint16_t left_entry;
170             uint8_t name_length;
171             uint32_t start_lba;
172             size_t next_offset;
173
174             left_entry = xbox_le16(sector + offset);
175             name_length = sector[offset + 13U];
176             if (left_entry == 0xFFFFU || name_length == 0U ||
177                 name_length == 0xFFU)
178                 break;
179             if (offset + 14U + name_length > sizeof(sector))
180                 break;
181
182             start_lba = xbox_le32(sector + offset + 4U);
183             if (xbox_ascii_case_equal(sector + offset + 14U,
184                                       name_length,
185                                       "default.xbe")) {
186                 *xbe_lba = start_lba;
187                 return 1;
188             }
189
190             next_offset = (14U + (size_t)name_length + 3U) & ~((size_t)3U);
191             if (next_offset == 0)
192                 break;
193             offset += next_offset;
194         }
195     }
196
197     return 0;
198 }
199
200 static int xbox_parse_xbe_certificate(xbox_portable_read10_func read10,
201                                       void *context,
202                                       uint32_t xbe_lba,
203                                       xbox_portable_metadata *metadata)
204 {
205     unsigned char header[XBOX_SECTOR_SIZE];
206     unsigned char certificate[XBOX_SECTOR_SIZE * 2U];
207     uint32_t base_va;
208     uint32_t certificate_va;
209     uint32_t file_offset;
210     uint32_t certificate_lba;
211     size_t inner_offset;
212     uint32_t sectors_to_read;
213     const unsigned char *cert;
214
215     if (read10(context, xbe_lba, 1, header, sizeof(header)) < 0)
216         return 0;
217     if (xbox_le32(header) != 0x48454258U)
218         return 0;
219
220     base_va = xbox_le32(header + 0x104U);
221     certificate_va = xbox_le32(header + 0x118U);
222     if (certificate_va < base_va)
223         return 0;
224
225     file_offset = certificate_va - base_va;
226     certificate_lba = xbe_lba + (file_offset / XBOX_SECTOR_SIZE);
227     inner_offset = file_offset % XBOX_SECTOR_SIZE;
228     sectors_to_read =
229         (inner_offset + XBOX_XBE_CERTIFICATE_MIN_SIZE > XBOX_SECTOR_SIZE) ? 2U : 1U;
230
231     memset(certificate, 0, sizeof(certificate));
232     if (read10(context,
233                certificate_lba,
234                sectors_to_read,
235                certificate,
236                sizeof(certificate)) < 0)
237         return 0;
238
239     cert = certificate + inner_offset;
240     metadata->title_id = xbox_le32(cert + 0x008U);
241     metadata->allowed_media = xbox_le32(cert + 0x09CU);
242     metadata->game_region = xbox_le32(cert + 0x0A0U);
243     metadata->game_ratings = xbox_le32(cert + 0x0A4U);
244     metadata->disc_number = xbox_le32(cert + 0x0A8U);
245     metadata->version = xbox_le32(cert + 0x0ACU);
246     metadata->have_game_region = 1;
247     xbox_decode_title(cert,
248                       XBOX_XBE_CERTIFICATE_MIN_SIZE,
249                       metadata->title,
250                       sizeof(metadata->title));
251     xbox_region_format(metadata->game_region,
252                        metadata->region,
253                        sizeof(metadata->region));
254
255     return 1;
256 }
257
258 void xbox_portable_metadata_init(xbox_portable_metadata *metadata)
259 {
260     if (metadata)
261         memset(metadata, 0, sizeof(*metadata));
262 }
263
264 int xbox_portable_read_metadata(xbox_portable_read10_func read10,
265                                 xbox_portable_read_structure_func read_structure,
266                                 void *context,
267                                 xbox_portable_metadata *metadata)
268 {
269     static const uint32_t volume_candidates[] = {
270         XBOX_XDFS_STANDARD_VOLUME_LBA,
271         XBOX_XDFS_LEGACY_VOLUME_LBA
272     };
273     unsigned char volume[XBOX_SECTOR_SIZE];
274     unsigned char dmi[XBOX_SECTOR_SIZE];
275     uint32_t root_lba = 0;
276     uint32_t root_size = 0;
277     uint32_t xbe_lba = 0;
278     size_t candidate_index;
279
280     if (!read10 || !metadata)
281         return 0;
282
283     xbox_portable_metadata_init(metadata);
284
285     for (candidate_index = 0;
286          candidate_index < sizeof(volume_candidates) / sizeof(volume_candidates[0]);
287          ++candidate_index) {
288         memset(volume, 0, sizeof(volume));
289         if (read10(context,
290                    volume_candidates[candidate_index],
291                    1,
292                    volume,
293                    sizeof(volume)) < 0)
294             continue;
295         if (memcmp(volume, XBOX_XDFS_MAGIC, XBOX_XDFS_MAGIC_SIZE) != 0)
296             continue;
297
298         metadata->xdfs_volume_lba = volume_candidates[candidate_index];
299         root_lba = xbox_le32(volume + 0x14U);
300         root_size = xbox_le32(volume + 0x18U);
301         break;
302     }
303
304     if (root_lba == 0 || root_size == 0)
305         return 0;
306     if (!xbox_find_default_xbe(read10,
307                                context,
308                                root_lba,
309                                root_size,
310                                &xbe_lba))
311         return 0;
312     if (!xbox_parse_xbe_certificate(read10,
313                                     context,
314                                     xbe_lba,
315                                     metadata))
316         return 0;
317
318     if (read_structure) {
319         memset(dmi, 0, sizeof(dmi));
320         if (read_structure(context,
321                            0x04U,
322                            0x00U,
323                            dmi,
324                            sizeof(dmi)) >= 0)
325             xbox_parse_media_id(dmi,
326                                 sizeof(dmi),
327                                 metadata->media_id,
328                                 sizeof(metadata->media_id));
329     }
330
331     metadata->valid = 1;
332     return 1;
333 }
334
335 #ifndef XBOX_PORTABLE_NO_DISC_WRAPPER
336 static int xbox_disc_read10(void *context,
337                             uint32_t lba,
338                             uint32_t sectors,
339                             unsigned char *buffer,
340                             size_t buffer_size)
341 {
342     return disc_xbox_read_10((disc *)context,
343                              lba,
344                              sectors,
345                              buffer,
346                              buffer_size);
347 }
348
349 static int xbox_disc_read_structure(void *context,
350                                     uint8_t format,
351                                     uint8_t layer,
352                                     unsigned char *buffer,
353                                     size_t buffer_size)
354 {
355     return disc_xbox_read_dvd_structure((disc *)context,
356                                         format,
357                                         layer,
358                                         buffer,
359                                         buffer_size);
360 }
361
362 int xbox_portable_read_metadata_from_disc(struct disc_s *disc_value,
363                                           xbox_portable_metadata *metadata)
364 {
365     if (!disc_value)
366         return 0;
367
368     return xbox_portable_read_metadata(xbox_disc_read10,
369                                        xbox_disc_read_structure,
370                                        disc_value,
371                                        metadata);
372 }
373 #endif