File: parser.c

package info (click to toggle)
libmirage 3.2.10-1
  • links: PTS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,540 kB
  • sloc: ansic: 25,454; xml: 108; makefile: 11
file content (408 lines) | stat: -rw-r--r-- 16,896 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
/*
 *  libMirage: hard-disk image parser: parser
 *  Copyright (C) 2013-2014 Henrik Stokseth
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "image-harddisk.h"

#define __debug__ "HD-Parser"


/**********************************************************************\
 *                          Private structure                         *
\**********************************************************************/
struct _MirageParserHdPrivate
{
    MirageDisc *disc;

    gint track_mode;
    gint track_sectsize;

    gboolean needs_padding;
};


static gboolean mirage_parser_hd_is_file_valid (MirageParserHd *self, MirageStream *stream, GError **error)
{
    gsize file_length;

    /* Get stream length */
    if (!mirage_stream_seek(stream, 0, G_SEEK_END, error)) {
        return FALSE;
    }
    file_length = mirage_stream_tell(stream);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: verifying file size...\n", __debug__);

    /* Make sure the file is large enough to contain APM or GPT headers and a small partition table and a MDB */
    if (file_length < 3*512) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: parser cannot handle given image: file too small!\n", __debug__);
        g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_CANNOT_HANDLE, Q_("Parser cannot handle given image: file too small!"));
        return FALSE;
    }

    /* Hybrid ISO images are best handled by the ISO parser, so we reject those here */
    if (file_length >= 17 * 2048) {
        guint8 buf[8];

        if (!mirage_stream_seek(stream, 16 * 2048, G_SEEK_SET, NULL)) {
    	    MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to seek to ISO signature!\n", __debug__);
            g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to seek to ISO signature!"));
            return FALSE;
        }

        if (mirage_stream_read(stream, &buf, sizeof(buf), NULL) != sizeof(buf)) {
            MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to read ISO signature!\n", __debug__);
            g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to read ISO signature!"));
            return FALSE;
        }

        if (!memcmp(buf, mirage_pattern_cd001, sizeof(mirage_pattern_cd001))
         || !memcmp(buf, mirage_pattern_bea01, sizeof(mirage_pattern_bea01))) {
            MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: Image is an ISO image - deferring!\n", __debug__);
            g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_CANNOT_HANDLE, Q_("Image is an ISO image - deferring!"));
            return FALSE;
        }
    }

    /* Checking if image has a valid Driver Descriptor Map (DDM) */
    driver_descriptor_map_t ddm;

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: checking if image has a valid Driver Descriptor Map (DDM)...\n", __debug__);

    if (!mirage_stream_seek(stream, 0, G_SEEK_SET, NULL)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to seek to beginning of file!\n", __debug__);
        g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to seek to beginning of file!"));
        return FALSE;
    }

    if (mirage_stream_read(stream, &ddm, sizeof(driver_descriptor_map_t), NULL) != sizeof(driver_descriptor_map_t)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to read driver descriptor map!\n", __debug__);
        g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to read driver descriptor map!"));
        return FALSE;
    }

    mirage_ddm_block_fix_endian (&ddm);

    if (!memcmp(&ddm.signature, "ER", 2)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: Image has a valid Driver Descriptor Map!\n", __debug__);

        mirage_print_ddm_block(MIRAGE_CONTEXTUAL(self), &ddm);

        /* Check if image has valid Apple Partition Map (APM) entries */
        apm_entry_t pme;

        for (guint p = 0;; p++) {
            if (!mirage_stream_seek(stream, ddm.block_size * (p + 1), G_SEEK_SET, NULL)) {
                MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to seek to beginning of APM entries!\n", __debug__);
                g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to seek to beginning of APM entries!"));
                return FALSE;
            }

            if (mirage_stream_read(stream, &pme, sizeof(apm_entry_t), NULL) != sizeof(apm_entry_t)) {
                MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to read APM entry!\n", __debug__);
                g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to read APM entry!"));
                return FALSE;
            }

            mirage_apm_entry_block_fix_endian (&pme);

            if (!memcmp(&pme.signature, "PM", 2)) {
                mirage_print_apm_entry_block(MIRAGE_CONTEXTUAL(self), &pme);
                if (p + 1 >= pme.map_entries) break; /* Last APM entry */
            } else {
                break; /* Invalid Partition Map entry */
            }
        }

        /* Apply padding if needed */
        if (ddm.block_size == 512 || ddm.block_size == 1024) {
            self->priv->needs_padding = file_length % 2048;
        } else if (ddm.block_size == 2048) {
            self->priv->needs_padding = FALSE;
        } else {
            MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: parser cannot map this sector size!\n", __debug__);
            g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_PARSER_ERROR, Q_("Parser cannot map this sector size!"));
            return FALSE;
        }
        self->priv->track_sectsize = 2048;
        self->priv->track_mode = MIRAGE_SECTOR_MODE1;

        return TRUE;
    }

    /* Checking if image has a valid GPT header */
    gpt_header_t gpt_header;

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: checking if image has a valid GPT header...\n", __debug__);

    if (!mirage_stream_seek(stream, 512, G_SEEK_SET, NULL)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to seek to GPT header!\n", __debug__);
        g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to seek to GPT header!"));
        return FALSE;
    }

    if (mirage_stream_read(stream, &gpt_header, sizeof(gpt_header_t), NULL) != sizeof(gpt_header_t)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to read GPT header!\n", __debug__);
        g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to read GPT header!"));
        return FALSE;
    }

    mirage_gpt_header_fix_endian (&gpt_header);

    if (!memcmp(&gpt_header.signature, "EFI PART", 8)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: Image has a valid GPT header!\n", __debug__);

        mirage_print_gpt_header(MIRAGE_CONTEXTUAL(self), &gpt_header);

        /* Check if image has valid GPT entries */
        gpt_entry_t gpt_entry;

        for (guint p = 0; p < gpt_header.gpt_entries; p++) {
            if (!mirage_stream_seek(stream, (512 * gpt_header.lba_gpt_table) + (gpt_header.gpt_entry_size * p), G_SEEK_SET, NULL)) {
                MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to seek to beginning of GPT entries!\n", __debug__);
                g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to seek to beginning of GPT entries!"));
                return FALSE;
            }

            if (mirage_stream_read(stream, &gpt_entry, sizeof(gpt_entry_t), NULL) != sizeof(gpt_entry_t)) {
                MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to read GPT entry!\n", __debug__);
                g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to read GPT entry!"));
                return FALSE;
            }

            mirage_gpt_entry_fix_endian (&gpt_entry);

            if (gpt_entry.type.as_int[0] == 0 && gpt_entry.type.as_int[1] == 0) {
                continue; /* Unused partition entry */
            } else {
                mirage_print_gpt_entry(MIRAGE_CONTEXTUAL(self), &gpt_entry);
            }
        }

        /* Apply padding if needed */
        self->priv->needs_padding = file_length % 2048;

        self->priv->track_sectsize = 2048;
        self->priv->track_mode = MIRAGE_SECTOR_MODE1;

        return TRUE;
    }

    /* Macintosh HFS/HFS+ image check */
    guint8  mac_buf[512];
    guint16 mac_sectsize = 512;
    guint32 mac_dev_sectors = file_length / mac_sectsize;

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: checking if image is a HFS/HFS+ image...\n", __debug__);

    if (!mirage_stream_seek(stream, 2 * 512, G_SEEK_SET, NULL)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to seek to Master Directory Block (MDB)!\n", __debug__);
        g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to seek to Master Directory Block (MDB)!"));
        return FALSE;
    }

    if (mirage_stream_read(stream, mac_buf, sizeof(mac_buf), NULL) != sizeof(mac_buf)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to read Master Directory Block (MDB)!\n", __debug__);
        g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_IMAGE_FILE_ERROR, Q_("Failed to read Master Directory Block (MDB)!"));
        return FALSE;
    }

    if (!memcmp(mac_buf, "BD", 2) || !memcmp(mac_buf, "H+", 2) || !memcmp(mac_buf, "HX", 2)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: HFS/HFS+ signature found!\n", __debug__);

        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: image is a HFS/HFS+ image; %d sectors, sector size: %d\n", __debug__, mac_dev_sectors, mac_sectsize);

        self->priv->needs_padding = file_length % 2048;
        self->priv->track_sectsize = 2048;
        self->priv->track_mode = MIRAGE_SECTOR_MODE1;

        return TRUE;
    }

    /* Nope, can't load the file */
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: parser cannot handle given image!\n", __debug__);
    g_set_error(error, MIRAGE_ERROR, MIRAGE_ERROR_CANNOT_HANDLE, Q_("Parser cannot handle given image!"));
    return FALSE;
}

static gboolean mirage_parser_hd_load_track (MirageParserHd *self, MirageStream *stream, GError **error)
{
    MirageSession *session;
    MirageTrack *track;
    MirageFragment *fragment;

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: loading track...\n", __debug__);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: track mode: %d\n", __debug__, self->priv->track_mode);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: sector size: %d\n", __debug__, self->priv->track_sectsize);
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: padding sector needed: %d\n", __debug__, self->priv->needs_padding);

    /* Create data fragment */
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: creating data fragment\n", __debug__);
    fragment = g_object_new(MIRAGE_TYPE_FRAGMENT, NULL);

    mirage_fragment_main_data_set_stream(fragment, stream);
    mirage_fragment_main_data_set_size(fragment, self->priv->track_sectsize);
    mirage_fragment_main_data_set_format(fragment, MIRAGE_MAIN_DATA_FORMAT_DATA);

    /* Use whole file */
    if (!mirage_fragment_use_the_rest_of_file(fragment, error)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to use the rest of file!\n", __debug__);
        g_object_unref(fragment);
        return FALSE;
    }

    /* Add one sector to cover otherwise truncated data */
    if (self->priv->needs_padding) {
        gint cur_length = mirage_fragment_get_length(fragment);
        mirage_fragment_set_length(fragment, cur_length + 1);
    }

    /* Add track */
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: adding track\n", __debug__);

    session = mirage_disc_get_session_by_index(self->priv->disc, -1, NULL);

    track = g_object_new(MIRAGE_TYPE_TRACK, NULL);
    mirage_session_add_track_by_index(session, -1, track);
    g_object_unref(session);

    /* Set track mode */
    mirage_track_set_sector_type(track, self->priv->track_mode);

    /* Add fragment to track */
    mirage_track_add_fragment(track, -1, fragment);

    g_object_unref(fragment);
    g_object_unref(track);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: finished loading track\n", __debug__);

    return TRUE;
}


/**********************************************************************\
 *                MirageParser methods implementation                *
\**********************************************************************/
static MirageDisc *mirage_parser_hd_load_image (MirageParser *_self, MirageStream **streams, GError **error)
{
    MirageParserHd *self = MIRAGE_PARSER_HD(_self);
    const gchar *hd_filename;
    MirageStream *stream;
    gboolean succeeded = TRUE;

    /* Check if file can be loaded */
    stream = g_object_ref(streams[0]);
    hd_filename = mirage_stream_get_filename(stream);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: checking if parser can handle given image...\n", __debug__);
    if (!mirage_parser_hd_is_file_valid(self, stream, error)) {
        g_object_unref(stream);
        return FALSE;
    }
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_IMAGE_ID, "%s: parser can handle given image!\n", __debug__);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: parsing the image...\n", __debug__);

    /* Create disc */
    self->priv->disc = g_object_new(MIRAGE_TYPE_DISC, NULL);
    mirage_object_set_parent(MIRAGE_OBJECT(self->priv->disc), self);

    /* Set filenames */
    mirage_disc_set_filename(self->priv->disc, hd_filename);

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: Hard-disk filename: %s\n", __debug__, hd_filename);

    /* Session: one session (with one tracks) */
    MirageSession *session = g_object_new(MIRAGE_TYPE_SESSION, NULL);
    mirage_disc_add_session_by_index(self->priv->disc, 0, session);

    /* Hard-disk image parser assumes single-track image, so we're dealing with regular CD-ROM session */
    mirage_session_set_session_type(session, MIRAGE_SESSION_CDROM);
    g_object_unref(session);

    /* Load track */
    if (!mirage_parser_hd_load_track(self, stream, error)) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_WARNING, "%s: failed to load track!\n", __debug__);
        succeeded = FALSE;
        goto end;
    }

    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "\n");
    MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: finishing the layout\n", __debug__);

    /* Set medium type to harddisk */
    mirage_disc_set_medium_type(self->priv->disc, MIRAGE_MEDIUM_HDD);

end:
    g_object_unref(stream);

    /* Return disc */
    if (succeeded) {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: parsing completed successfully\n\n", __debug__);
        return self->priv->disc;
    } else {
        MIRAGE_DEBUG(self, MIRAGE_DEBUG_PARSER, "%s: parsing failed!\n\n", __debug__);
        g_object_unref(self->priv->disc);
        return NULL;
    }
}


/**********************************************************************\
 *                             Object init                            *
\**********************************************************************/
G_DEFINE_DYNAMIC_TYPE_EXTENDED(MirageParserHd,
                               mirage_parser_hd,
                               MIRAGE_TYPE_PARSER,
                               0,
                               G_ADD_PRIVATE_DYNAMIC(MirageParserHd))

void mirage_parser_hd_type_register (GTypeModule *type_module)
{
    return mirage_parser_hd_register_type(type_module);
}


static void mirage_parser_hd_init (MirageParserHd *self)
{
    self->priv = mirage_parser_hd_get_instance_private(self);

    mirage_parser_generate_info(MIRAGE_PARSER(self),
        "PARSER-HD",
        Q_("Hard-disk Image Parser"),
        3,
        Q_("Apple Disk image (*.cdr)"), "application/x-apple-diskimage",
        Q_("Apple Disk image (*.smi)"), "application/x-apple-diskimage",
        Q_("Apple Disk image (*.img)"), "application/x-apple-diskimage"
    );

    self->priv->needs_padding = FALSE;
}

static void mirage_parser_hd_class_init (MirageParserHdClass *klass)
{
    MirageParserClass *parser_class = MIRAGE_PARSER_CLASS(klass);

    parser_class->load_image = mirage_parser_hd_load_image;
}

static void mirage_parser_hd_class_finalize (MirageParserHdClass *klass G_GNUC_UNUSED)
{
}