VirtualBox

source: vbox/trunk/src/VBox/Storage/CUE.cpp@ 86186

Last change on this file since 86186 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 64.5 KB
Line 
1/* $Id: CUE.cpp 82968 2020-02-04 10:35:17Z vboxsync $ */
2/** @file
3 * CUE - CUE/BIN Disk image, Core Code.
4 */
5
6/*
7 * Copyright (C) 2017-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_VD_CUE
23#include <VBox/vd-plugin.h>
24#include <VBox/err.h>
25
26#include <VBox/log.h>
27#include <VBox/scsiinline.h>
28#include <iprt/assert.h>
29#include <iprt/asm.h>
30#include <iprt/alloc.h>
31#include <iprt/cdefs.h>
32#include <iprt/ctype.h>
33#include <iprt/path.h>
34#include <iprt/string.h>
35
36#include "VDBackends.h"
37#include "VDBackendsInline.h"
38
39
40/*********************************************************************************************************************************
41* Constants And Macros, Structures and Typedefs *
42*********************************************************************************************************************************/
43
44/**
45 * CUE descriptor file token type.
46 */
47typedef enum CUETOKENTYPE
48{
49 /** Invalid token type. */
50 CUETOKENTYPE_INVALID = 0,
51 /** Reserved keyword. */
52 CUETOKENTYPE_KEYWORD,
53 /** String token. */
54 CUETOKENTYPE_STRING,
55 /** Unsigned integer. */
56 CUETOKENTYPE_INTEGER_UNSIGNED,
57 /** MSF (mm:ss:ff) location token. */
58 CUETOKENTYPE_MSF,
59 /** Error token (unexpected character found). */
60 CUETOKENTYPE_ERROR,
61 /** End of stream token. */
62 CUETOKENTYPE_EOS
63} CUETOKENTYPE;
64
65/**
66 * CUE reservered keyword type.
67 */
68typedef enum CUEKEYWORD
69{
70 /** Invalid keyword. */
71 CUEKEYWORD_INVALID = 0,
72 /** FILE. */
73 CUEKEYWORD_FILE,
74 /** BINARY */
75 CUEKEYWORD_BINARY,
76 /** MOTOROLA */
77 CUEKEYWORD_MOTOROLA,
78 /** WAVE */
79 CUEKEYWORD_WAVE,
80 /** MP3 */
81 CUEKEYWORD_MP3,
82 /** AIFF */
83 CUEKEYWORD_AIFF,
84 /** CATALOG */
85 CUEKEYWORD_CATALOG,
86 CUEKEYWORD_CDTEXTFILE,
87 CUEKEYWORD_FLAGS,
88 CUEKEYWORD_INDEX,
89 CUEKEYWORD_ISRC,
90 CUEKEYWORD_PERFORMER,
91 CUEKEYWORD_POSTGAP,
92 CUEKEYWORD_PREGAP,
93 CUEKEYWORD_SONGWRITER,
94 CUEKEYWORD_TITLE,
95 CUEKEYWORD_TRACK,
96 CUEKEYWORD_MODE1_2048,
97 CUEKEYWORD_MODE1_2352,
98 CUEKEYWORD_AUDIO,
99 CUEKEYWORD_REM
100} CUEKEYWORD;
101
102/**
103 * CUE sheet token.
104 */
105typedef struct CUETOKEN
106{
107 /** The token type. */
108 CUETOKENTYPE enmType;
109 /** Token type dependent data. */
110 union
111 {
112 /** Keyword token. */
113 struct
114 {
115 /** The keyword enumerator. */
116 CUEKEYWORD enmKeyword;
117 } Keyword;
118 /** String token (without quotation marks). */
119 struct
120 {
121 /** Pointer to the start of the string. */
122 const char *psz;
123 /** Number of characters for the string excluding the null terminator. */
124 size_t cch;
125 } String;
126 /** Integer token. */
127 struct
128 {
129 /** Numerical constant. */
130 uint64_t u64;
131 } Int;
132 /** MSF location token. */
133 struct
134 {
135 /** Minute part. */
136 uint8_t u8Minute;
137 /** Second part. */
138 uint8_t u8Second;
139 /** Frame part. */
140 uint8_t u8Frame;
141 } Msf;
142 } Type;
143} CUETOKEN;
144/** Pointer to a CUE sheet token. */
145typedef CUETOKEN *PCUETOKEN;
146/** Pointer to a const CUE sheet token. */
147typedef const CUETOKEN *PCCUETOKEN;
148
149/**
150 * CUE tokenizer state.
151 */
152typedef struct CUETOKENIZER
153{
154 /** Char buffer to read from. */
155 const char *pszInput;
156 /** Token 1. */
157 CUETOKEN Token1;
158 /** Token 2. */
159 CUETOKEN Token2;
160 /** Pointer to the current active token. */
161 PCUETOKEN pTokenCurr;
162 /** The next token in the input stream (used for peeking). */
163 PCUETOKEN pTokenNext;
164} CUETOKENIZER;
165/** Pointer to a CUE tokenizer state. */
166typedef CUETOKENIZER *PCUETOKENIZER;
167
168/**
169 * CUE keyword entry.
170 */
171typedef struct CUEKEYWORDDESC
172{
173 /** Keyword string. */
174 const char *pszKeyword;
175 /** Size of the string in characters without zero terminator. */
176 size_t cchKeyword;
177 /** Keyword type. */
178 CUEKEYWORD enmKeyword;
179} CUEKEYWORDDESC;
180/** Pointer to a CUE keyword entry. */
181typedef CUEKEYWORDDESC *PCUEKEYWORDDESC;
182/** Pointer to a const CUE keyword entry. */
183typedef const CUEKEYWORDDESC *PCCUEKEYWORDDESC;
184
185/**
186 * CUE image data structure.
187 */
188typedef struct CUEIMAGE
189{
190 /** Image name. */
191 const char *pszFilename;
192 /** Storage handle. */
193 PVDIOSTORAGE pStorage;
194 /** The backing file containing the actual data. */
195 char *pszDataFilename;
196 /** Storage handle for the backing file. */
197 PVDIOSTORAGE pStorageData;
198
199 /** Pointer to the per-disk VD interface list. */
200 PVDINTERFACE pVDIfsDisk;
201 /** Pointer to the per-image VD interface list. */
202 PVDINTERFACE pVDIfsImage;
203 /** Error interface. */
204 PVDINTERFACEERROR pIfError;
205 /** I/O interface. */
206 PVDINTERFACEIOINT pIfIo;
207
208 /** Open flags passed by VD layer. */
209 unsigned uOpenFlags;
210 /** Image flags defined during creation or determined during open. */
211 unsigned uImageFlags;
212 /** Maximum number of tracks the region list can hold. */
213 uint32_t cTracksMax;
214 /** Pointer to our internal region list. */
215 PVDREGIONLIST pRegionList;
216 /** Flag whether the backing file is little (BINARY) or big (MOTOROLA) endian. */
217 bool fLittleEndian;
218} CUEIMAGE, *PCUEIMAGE;
219
220
221/*********************************************************************************************************************************
222* Static Variables *
223*********************************************************************************************************************************/
224
225/** NULL-terminated array of supported file extensions. */
226static const VDFILEEXTENSION s_aCueFileExtensions[] =
227{
228 {"cue", VDTYPE_OPTICAL_DISC},
229 {NULL, VDTYPE_INVALID}
230};
231
232/**
233 * Known keywords.
234 */
235static const CUEKEYWORDDESC g_aCueKeywords[] =
236{
237 {RT_STR_TUPLE("FILE"), CUEKEYWORD_FILE},
238 {RT_STR_TUPLE("BINARY"), CUEKEYWORD_BINARY},
239 {RT_STR_TUPLE("WAVE"), CUEKEYWORD_WAVE},
240 {RT_STR_TUPLE("MP3"), CUEKEYWORD_MP3},
241 {RT_STR_TUPLE("AIFF"), CUEKEYWORD_AIFF},
242 {RT_STR_TUPLE("CATALOG"), CUEKEYWORD_CATALOG},
243 {RT_STR_TUPLE("CDTEXTFILE"), CUEKEYWORD_CDTEXTFILE},
244 {RT_STR_TUPLE("FLAGS"), CUEKEYWORD_FLAGS},
245 {RT_STR_TUPLE("INDEX"), CUEKEYWORD_INDEX},
246 {RT_STR_TUPLE("ISRC"), CUEKEYWORD_ISRC},
247 {RT_STR_TUPLE("PERFORMER"), CUEKEYWORD_PERFORMER},
248 {RT_STR_TUPLE("POSTGAP"), CUEKEYWORD_POSTGAP},
249 {RT_STR_TUPLE("PREGAP"), CUEKEYWORD_PREGAP},
250 {RT_STR_TUPLE("SONGWRITER"), CUEKEYWORD_SONGWRITER},
251 {RT_STR_TUPLE("TITLE"), CUEKEYWORD_TITLE},
252 {RT_STR_TUPLE("TRACK"), CUEKEYWORD_TRACK},
253 {RT_STR_TUPLE("MODE1/2048"), CUEKEYWORD_MODE1_2048},
254 {RT_STR_TUPLE("MODE1/2352"), CUEKEYWORD_MODE1_2352},
255 {RT_STR_TUPLE("AUDIO"), CUEKEYWORD_AUDIO},
256 {RT_STR_TUPLE("REM"), CUEKEYWORD_REM}
257};
258
259
260/*********************************************************************************************************************************
261* Internal Functions *
262*********************************************************************************************************************************/
263
264
265/**
266 * Converts a MSF formatted address value read from the given buffer
267 * to an LBA number. MSF 00:00:00 equals LBA 0.
268 *
269 * @returns The LBA number.
270 * @param pbBuf The buffer to read the MSF formatted address
271 * from.
272 */
273DECLINLINE(uint32_t) cueMSF2LBA(const uint8_t *pbBuf)
274{
275 return (pbBuf[0] * 60 + pbBuf[1]) * 75 + pbBuf[2];
276}
277
278/**
279 * Ensures that the region list can hold up to the given number of tracks.
280 *
281 * @returns VBox status code.
282 * @param pThis The CUE image state.
283 * @param cTracksMax Maximum number of tracks.
284 */
285static int cueEnsureRegionListSize(PCUEIMAGE pThis, uint32_t cTracksMax)
286{
287 int rc = VINF_SUCCESS;
288
289 if (pThis->cTracksMax < cTracksMax)
290 {
291 PVDREGIONLIST pRegionListNew = (PVDREGIONLIST)RTMemRealloc(pThis->pRegionList,
292 RT_UOFFSETOF_DYN(VDREGIONLIST, aRegions[cTracksMax]));
293 if (pRegionListNew)
294 {
295 /* Init all the new allocated tracks. */
296 for (uint32_t i = pThis->cTracksMax; i < cTracksMax; i++)
297 pRegionListNew->aRegions[i].offRegion = UINT64_MAX;
298 pThis->pRegionList = pRegionListNew;
299 pThis->cTracksMax = cTracksMax;
300 }
301 else
302 rc = VERR_NO_MEMORY;
303 }
304
305 return rc;
306}
307
308/**
309 * Returns whether the tokenizer reached the end of the stream.
310 *
311 * @returns true if the tokenizer reached the end of stream marker
312 * false otherwise.
313 * @param pTokenizer The tokenizer state.
314 */
315DECLINLINE(bool) cueTokenizerIsEos(PCUETOKENIZER pTokenizer)
316{
317 return *pTokenizer->pszInput == '\0';
318}
319
320/**
321 * Skip one character in the input stream.
322 *
323 * @returns nothing.
324 * @param pTokenizer The tokenizer state.
325 */
326DECLINLINE(void) cueTokenizerSkipCh(PCUETOKENIZER pTokenizer)
327{
328 /* Never ever go past EOS. */
329 if (!cueTokenizerIsEos(pTokenizer))
330 pTokenizer->pszInput++;
331}
332
333/**
334 * Returns the next char in the input buffer without advancing it.
335 *
336 * @returns Next character in the input buffer.
337 * @param pTokenizer The tokenizer state.
338 */
339DECLINLINE(char) cueTokenizerPeekCh(PCUETOKENIZER pTokenizer)
340{
341 return cueTokenizerIsEos(pTokenizer)
342 ? '\0'
343 : *(pTokenizer->pszInput + 1);
344}
345
346/**
347 * Returns the next character in the input buffer advancing the internal
348 * position.
349 *
350 * @returns Next character in the stream.
351 * @param pTokenizer The tokenizer state.
352 */
353DECLINLINE(char) cueTokenizerGetCh(PCUETOKENIZER pTokenizer)
354{
355 char ch;
356
357 if (cueTokenizerIsEos(pTokenizer))
358 ch = '\0';
359 else
360 ch = *pTokenizer->pszInput;
361
362 return ch;
363}
364
365/**
366 * Sets a new line for the tokenizer.
367 *
368 * @returns nothing.
369 * @param pTokenizer The tokenizer state.
370 * @param cSkip How many characters to skip.
371 */
372DECLINLINE(void) cueTokenizerNewLine(PCUETOKENIZER pTokenizer, unsigned cSkip)
373{
374 pTokenizer->pszInput += cSkip;
375}
376
377/**
378 * Checks whether the current position in the input stream is a new line
379 * and skips it.
380 *
381 * @returns Flag whether there was a new line at the current position
382 * in the input buffer.
383 * @param pTokenizer The tokenizer state.
384 */
385DECLINLINE(bool) cueTokenizerIsSkipNewLine(PCUETOKENIZER pTokenizer)
386{
387 bool fNewline = true;
388
389 if ( cueTokenizerGetCh(pTokenizer) == '\r'
390 && cueTokenizerPeekCh(pTokenizer) == '\n')
391 cueTokenizerNewLine(pTokenizer, 2);
392 else if (cueTokenizerGetCh(pTokenizer) == '\n')
393 cueTokenizerNewLine(pTokenizer, 1);
394 else
395 fNewline = false;
396
397 return fNewline;
398}
399
400/**
401 * Skip all whitespace starting from the current input buffer position.
402 * Skips all present comments too.
403 *
404 * @returns nothing.
405 * @param pTokenizer The tokenizer state.
406 */
407DECLINLINE(void) cueTokenizerSkipWhitespace(PCUETOKENIZER pTokenizer)
408{
409 while (!cueTokenizerIsEos(pTokenizer))
410 {
411 while ( cueTokenizerGetCh(pTokenizer) == ' '
412 || cueTokenizerGetCh(pTokenizer) == '\t')
413 cueTokenizerSkipCh(pTokenizer);
414
415 if ( !cueTokenizerIsEos(pTokenizer)
416 && !cueTokenizerIsSkipNewLine(pTokenizer))
417 break; /* Skipped everything, next is some real content. */
418 }
419}
420
421/**
422 * Skips a multi line comment.
423 *
424 * @returns nothing.
425 * @param pTokenizer The tokenizer state.
426 */
427DECLINLINE(void) cueTokenizerSkipComment(PCUETOKENIZER pTokenizer)
428{
429 while ( !cueTokenizerIsEos(pTokenizer)
430 && !cueTokenizerIsSkipNewLine(pTokenizer))
431 cueTokenizerSkipCh(pTokenizer);
432 cueTokenizerSkipWhitespace(pTokenizer);
433}
434
435/**
436 * Get an identifier token from the tokenizer.
437 *
438 * @returns nothing.
439 * @param pTokenizer The tokenizer state.
440 * @param pToken The uninitialized token.
441 */
442static void cueTokenizerGetKeyword(PCUETOKENIZER pTokenizer, PCUETOKEN pToken)
443{
444 char ch;
445 unsigned cchKeyword = 0;
446 bool fIsKeyword = false;
447 bool fIsComment;
448 const char *pszKeyword;
449
450 Assert(RT_C_IS_ALPHA(*pTokenizer->pszInput));
451
452 do
453 {
454 fIsComment = false;
455 pszKeyword = pTokenizer->pszInput;
456
457 do
458 {
459 cchKeyword++;
460 cueTokenizerSkipCh(pTokenizer);
461 ch = cueTokenizerGetCh(pTokenizer);
462 }
463 while (RT_C_IS_ALNUM(ch) || ch == '_' || ch == '/' || ch == '.');
464
465 /* Check whether we got a keyword or a string constant. */
466 for (unsigned i = 0; i < RT_ELEMENTS(g_aCueKeywords); i++)
467 {
468 if (!RTStrNCmp(g_aCueKeywords[i].pszKeyword, pszKeyword, RT_MIN(cchKeyword, g_aCueKeywords[i].cchKeyword)))
469 {
470 if (g_aCueKeywords[i].enmKeyword == CUEKEYWORD_REM)
471 {
472 /* The REM keyword is handled here as it indicates a comment which we just skip. */
473 cueTokenizerSkipComment(pTokenizer);
474 fIsComment = true;
475 }
476 else
477 {
478 fIsKeyword = true;
479 pToken->enmType = CUETOKENTYPE_KEYWORD;
480 pToken->Type.Keyword.enmKeyword = g_aCueKeywords[i].enmKeyword;
481 }
482 break;
483 }
484 }
485 } while (fIsComment);
486
487 /* Make it a string. */
488 if (ch == '\0')
489 pToken->enmType = CUETOKENTYPE_EOS;
490 else if (!fIsKeyword)
491 {
492 pToken->enmType = CUETOKENTYPE_STRING;
493 pToken->Type.String.psz = pszKeyword;
494 pToken->Type.String.cch = cchKeyword;
495 }
496}
497
498/**
499 * Get an integer value or MSF location indicator from the tokenizer.
500 *
501 * @returns nothing.
502 * @param pTokenizer The tokenizer state.
503 * @param pToken The uninitialized token.
504 */
505static void cueTokenizerGetIntegerOrMsf(PCUETOKENIZER pTokenizer, PCUETOKEN pToken)
506{
507 char szInt[20 + 1]; /* Maximum which fits into an unsigned 64bit integer + zero terminator. */
508 unsigned cchInt = 0;
509 bool fMsf = false;
510 char ch = cueTokenizerGetCh(pTokenizer);
511
512 Assert(RT_C_IS_DIGIT(ch));
513 RT_ZERO(szInt);
514
515 /* Go through the characters and check for the : mark which denotes MSF location indicator. */
516 do
517 {
518 szInt[cchInt++] = ch;
519 cueTokenizerSkipCh(pTokenizer);
520 ch = cueTokenizerGetCh(pTokenizer);
521 if (ch == ':')
522 fMsf = true;
523 }
524 while ( (RT_C_IS_DIGIT(ch) || ch == ':')
525 && cchInt < sizeof(szInt));
526
527 if (cchInt < sizeof(szInt) - 1)
528 {
529 if (fMsf)
530 {
531 /* Check that the format matches our expectations (mm:ss:ff). */
532 if ( cchInt == 8 && szInt[2] == ':' && szInt[5] == ':')
533 {
534 /* Parse the single fields. */
535 szInt[2] = '\0';
536 szInt[5] = '\0';
537
538 int rc = RTStrToUInt8Full(&szInt[0], 10, &pToken->Type.Msf.u8Minute);
539 if (RT_SUCCESS(rc))
540 rc = RTStrToUInt8Full(&szInt[3], 10, &pToken->Type.Msf.u8Second);
541 if (RT_SUCCESS(rc))
542 rc = RTStrToUInt8Full(&szInt[6], 10, &pToken->Type.Msf.u8Frame);
543 if (RT_SUCCESS(rc))
544 pToken->enmType = CUETOKENTYPE_MSF;
545 else
546 pToken->enmType = CUETOKENTYPE_ERROR;
547 }
548 else
549 pToken->enmType = CUETOKENTYPE_ERROR;
550 }
551 else
552 {
553 pToken->enmType = CUETOKENTYPE_INTEGER_UNSIGNED;
554 int rc = RTStrToUInt64Full(&szInt[0], 10, &pToken->Type.Int.u64);
555 if (RT_FAILURE(rc))
556 pToken->enmType = CUETOKENTYPE_ERROR;
557 }
558 }
559 else
560 pToken->enmType = CUETOKENTYPE_ERROR;
561}
562
563/**
564 * Parses a string constant.
565 *
566 * @returns nothing.
567 * @param pTokenizer The tokenizer state.
568 * @param pToken The uninitialized token.
569 *
570 * @remarks: No escape sequences allowed at this time.
571 */
572static void cueTokenizerGetStringConst(PCUETOKENIZER pTokenizer, PCUETOKEN pToken)
573{
574 unsigned cchStr = 0;
575
576 Assert(cueTokenizerGetCh(pTokenizer) == '\"');
577 cueTokenizerSkipCh(pTokenizer); /* Skip " */
578
579 pToken->enmType = CUETOKENTYPE_STRING;
580 pToken->Type.String.psz = pTokenizer->pszInput;
581
582 while ( !cueTokenizerIsEos(pTokenizer)
583 && cueTokenizerGetCh(pTokenizer) != '\"')
584 {
585 cchStr++;
586 cueTokenizerSkipCh(pTokenizer);
587 }
588
589 /* End of stream without a closing quote is an error. */
590 if (RT_UNLIKELY(cueTokenizerIsEos(pTokenizer)))
591 pToken->enmType = CUETOKENTYPE_ERROR;
592 else
593 {
594 cueTokenizerSkipCh(pTokenizer); /* Skip closing " */
595 pToken->Type.String.cch = cchStr;
596 }
597}
598
599/**
600 * Get the end of stream token.
601 *
602 * @returns nothing.
603 * @param pTokenizer The tokenizer state.
604 * @param pToken The uninitialized token.
605 */
606static void cueTokenizerGetEos(PCUETOKENIZER pTokenizer, PCUETOKEN pToken)
607{
608 Assert(cueTokenizerGetCh(pTokenizer) == '\0'); RT_NOREF(pTokenizer);
609
610 pToken->enmType = CUETOKENTYPE_EOS;
611}
612
613/**
614 * Read the next token from the tokenizer stream.
615 *
616 * @returns nothing.
617 * @param pTokenizer The tokenizer to read from.
618 * @param pToken Uninitialized token to fill the token data into.
619 */
620static void cueTokenizerReadNextToken(PCUETOKENIZER pTokenizer, PCUETOKEN pToken)
621{
622 /* Skip all eventually existing whitespace, newlines and comments first. */
623 cueTokenizerSkipWhitespace(pTokenizer);
624
625 char ch = cueTokenizerGetCh(pTokenizer);
626 if (RT_C_IS_ALPHA(ch))
627 cueTokenizerGetKeyword(pTokenizer, pToken);
628 else if (RT_C_IS_DIGIT(ch))
629 cueTokenizerGetIntegerOrMsf(pTokenizer, pToken);
630 else if (ch == '\"')
631 cueTokenizerGetStringConst(pTokenizer, pToken);
632 else if (ch == '\0')
633 cueTokenizerGetEos(pTokenizer, pToken);
634 else
635 pToken->enmType = CUETOKENTYPE_ERROR;
636}
637
638/**
639 * Create a new tokenizer.
640 *
641 * @returns Pointer to the new tokenizer state on success.
642 * NULL if out of memory.
643 * @param pszInput The input to create the tokenizer for.
644 */
645static PCUETOKENIZER cueTokenizerCreate(const char *pszInput)
646{
647 PCUETOKENIZER pTokenizer = (PCUETOKENIZER)RTMemAllocZ(sizeof(CUETOKENIZER));
648 if (pTokenizer)
649 {
650 pTokenizer->pszInput = pszInput;
651 pTokenizer->pTokenCurr = &pTokenizer->Token1;
652 pTokenizer->pTokenNext = &pTokenizer->Token2;
653 /* Fill the tokenizer with two first tokens. */
654 cueTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenCurr);
655 cueTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
656 }
657
658 return pTokenizer;
659}
660
661/**
662 * Get the current token in the input stream.
663 *
664 * @returns Pointer to the next token in the stream.
665 * @param pTokenizer The tokenizer to destroy.
666 */
667DECLINLINE(PCCUETOKEN) cueTokenizerGetToken(PCUETOKENIZER pTokenizer)
668{
669 return pTokenizer->pTokenCurr;
670}
671
672/**
673 * Get the class of the current token.
674 *
675 * @returns Class of the current token.
676 * @param pTokenizer The tokenizer state.
677 */
678DECLINLINE(CUETOKENTYPE) cueTokenizerGetTokenType(PCUETOKENIZER pTokenizer)
679{
680 return pTokenizer->pTokenCurr->enmType;
681}
682
683/**
684 * Consume the current token advancing to the next in the stream.
685 *
686 * @returns nothing.
687 * @param pTokenizer The tokenizer state.
688 */
689static void cueTokenizerConsume(PCUETOKENIZER pTokenizer)
690{
691 PCUETOKEN pTokenTmp = pTokenizer->pTokenCurr;
692
693 /* Switch next token to current token and read in the next token. */
694 pTokenizer->pTokenCurr = pTokenizer->pTokenNext;
695 pTokenizer->pTokenNext = pTokenTmp;
696 cueTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
697}
698
699/**
700 * Check whether the next token in the input stream is a keyword and matches the given
701 * keyword.
702 *
703 * @returns true if the token matched.
704 * false otherwise.
705 * @param pTokenizer The tokenizer state.
706 * @param enmKeyword The keyword to check against.
707 */
708static bool cueTokenizerIsKeywordEqual(PCUETOKENIZER pTokenizer, CUEKEYWORD enmKeyword)
709{
710 PCCUETOKEN pToken = cueTokenizerGetToken(pTokenizer);
711
712 if ( pToken->enmType == CUETOKENTYPE_KEYWORD
713 && pToken->Type.Keyword.enmKeyword == enmKeyword)
714 return true;
715
716 return false;
717}
718
719/**
720 * Check whether the next token in the input stream is a keyword and matches the given
721 * keyword and skips it.
722 *
723 * @returns true if the token matched and was skipped.
724 * false otherwise.
725 * @param pTokenizer The tokenizer state.
726 * @param enmKeyword The keyword to check against.
727 */
728static bool cueTokenizerSkipIfIsKeywordEqual(PCUETOKENIZER pTokenizer, CUEKEYWORD enmKeyword)
729{
730 bool fEqual = cueTokenizerIsKeywordEqual(pTokenizer, enmKeyword);
731 if (fEqual)
732 cueTokenizerConsume(pTokenizer);
733
734 return fEqual;
735}
736
737/**
738 * Duplicates the string of the current token and consumes it.
739 *
740 * @returns VBox status code.
741 * @param pTokenizer The tokenizer state.
742 * @param ppszStr Where to store the pointer to the duplicated string on success.
743 * Free with RTStrFree().
744 */
745static int cueTokenizerConsumeStringDup(PCUETOKENIZER pTokenizer, char **ppszStr)
746{
747 int rc = VINF_SUCCESS;
748 Assert(cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_STRING);
749
750 *ppszStr = RTStrDupN(pTokenizer->pTokenCurr->Type.String.psz,
751 pTokenizer->pTokenCurr->Type.String.cch);
752 if (!*ppszStr)
753 rc = VERR_NO_STR_MEMORY;
754
755 cueTokenizerConsume(pTokenizer);
756 return rc;
757}
758
759/**
760 * Consumes an integer token returning the value.
761 *
762 * @returns Integer value in the token.
763 * @param pTokenizer The tokenizer state.
764 */
765static uint64_t cueTokenizerConsumeInteger(PCUETOKENIZER pTokenizer)
766{
767 uint64_t u64 = 0;
768 Assert(cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_INTEGER_UNSIGNED);
769
770 u64 = pTokenizer->pTokenCurr->Type.Int.u64;
771 cueTokenizerConsume(pTokenizer);
772 return u64;
773}
774
775/**
776 * Parses and skips the remaining string part of a directive.
777 *
778 * @returns VBox status code.
779 * @param pThis The CUE image state.
780 * @param pTokenizer The tokenizer state.
781 * @param pszDirective The directive we skip the string part for.
782 */
783static int cueParseAndSkipStringRemainder(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer,
784 const char *pszDirective)
785{
786 int rc = VINF_SUCCESS;
787
788 if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_STRING)
789 cueTokenizerConsume(pTokenizer);
790 else
791 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
792 N_("CUE: Error parsing '%s', expected string for %s directive"), pThis->pszFilename,
793 pszDirective);
794
795 return rc;
796}
797
798/**
799 * Parses and skips the remaining MSF part of a directive.
800 *
801 * @returns VBox status code.
802 * @param pThis The CUE image state.
803 * @param pTokenizer The tokenizer state.
804 * @param pszDirective The directive we skip the string part for.
805 */
806static int cueParseAndSkipMsfRemainder(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer,
807 const char *pszDirective)
808{
809 int rc = VINF_SUCCESS;
810
811 if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_MSF)
812 cueTokenizerConsume(pTokenizer);
813 else
814 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
815 N_("CUE: Error parsing '%s', expected MSF location for %s directive"), pThis->pszFilename,
816 pszDirective);
817
818 return rc;
819}
820
821/**
822 * Parses the remainder of a INDEX directive.
823 *
824 * @returns VBox status code.
825 * @param pThis The CUE image state.
826 * @param pTokenizer The tokenizer state.
827 * @param pu8Index Where to store the parsed index number on success.
828 * @param pu64Lba Where to store the parsed positional information on success.
829 */
830static int cueParseIndex(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer,
831 uint8_t *pu8Index, uint64_t *pu64Lba)
832{
833 int rc = VINF_SUCCESS;
834
835 /*
836 * The index consists of the index number and positional information in MSF format.
837 */
838 if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_INTEGER_UNSIGNED)
839 {
840 uint64_t u64Index = cueTokenizerConsumeInteger(pTokenizer);
841 if (u64Index <= 99)
842 {
843 /* Parse the position. */
844 if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_MSF)
845 {
846 PCCUETOKEN pToken = cueTokenizerGetToken(pTokenizer);
847 uint8_t abMsf[3];
848 abMsf[0] = pToken->Type.Msf.u8Minute;
849 abMsf[1] = pToken->Type.Msf.u8Second;
850 abMsf[2] = pToken->Type.Msf.u8Frame;
851
852 *pu8Index = (uint8_t)u64Index;
853 *pu64Lba = cueMSF2LBA(&abMsf[0]);
854 cueTokenizerConsume(pTokenizer);
855 }
856 else
857 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
858 N_("CUE: Error parsing '%s', expected MSF location"), pThis->pszFilename);
859 }
860 else
861 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
862 N_("CUE: Error parsing '%s', index number must be between 01 and 99"), pThis->pszFilename);
863 }
864 else
865 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
866 N_("CUE: Error parsing '%s', expected index number after INDEX directive"), pThis->pszFilename);
867
868 return rc;
869}
870
871/**
872 * Parses the things coming below a TRACK directive.
873 *
874 * @returns VBox status code.
875 * @param pThis The CUE image state.
876 * @param pTokenizer The tokenizer state.
877 * @param pu64LbaStart Where to store the starting LBA for this track on success.
878 */
879static int cueParseTrackNesting(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer, uint64_t *pu64LbaStart)
880{
881 int rc = VINF_SUCCESS;
882 bool fSeenInitialIndex = false;
883
884 do
885 {
886 if ( cueTokenizerIsKeywordEqual(pTokenizer, CUEKEYWORD_TRACK)
887 || cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_EOS)
888 break;
889
890 if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_KEYWORD)
891 {
892 if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_TITLE))
893 rc = cueParseAndSkipStringRemainder(pThis, pTokenizer, "TITLE");
894 else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_PERFORMER))
895 rc = cueParseAndSkipStringRemainder(pThis, pTokenizer, "PERFORMER");
896 else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_PREGAP))
897 rc = cueParseAndSkipMsfRemainder(pThis, pTokenizer, "PREGAP");
898 else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_POSTGAP))
899 rc = cueParseAndSkipMsfRemainder(pThis, pTokenizer, "POSTGAP");
900 else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_INDEX))
901 {
902 uint8_t u8Index = 0;
903 uint64_t u64Lba = 0;
904 rc = cueParseIndex(pThis, pTokenizer, &u8Index, &u64Lba);
905 if ( RT_SUCCESS(rc)
906 && u8Index == 1)
907 {
908 if (!fSeenInitialIndex)
909 {
910 fSeenInitialIndex = true;
911 *pu64LbaStart = u64Lba;
912 }
913 else
914 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
915 N_("CUE: Error parsing '%s', multiple INDEX 01 directives"), pThis->pszFilename);
916 }
917 }
918 else
919 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
920 N_("CUE: Error parsing '%s', unexpected directive for TRACK found"), pThis->pszFilename);
921 }
922 else
923 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
924 N_("CUE: Error parsing '%s', expected a CUE sheet keyword"), pThis->pszFilename);
925 }
926 while (RT_SUCCESS(rc));
927
928 if ( RT_SUCCESS(rc)
929 && !fSeenInitialIndex)
930 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
931 N_("CUE: Error parsing '%s', no initial INDEX directive for this track"), pThis->pszFilename);
932
933 return rc;
934}
935
936/**
937 * Parses the remainder of a TRACK directive.
938 *
939 * @returns VBox status code.
940 * @param pThis The CUE image state.
941 * @param pTokenizer The tokenizer state.
942 */
943static int cueParseTrack(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer)
944{
945 int rc = VINF_SUCCESS;
946
947 /*
948 * A track consists of the track number and data type followed by a list of indexes
949 * and other metadata like title and performer we don't care about.
950 */
951 if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_INTEGER_UNSIGNED)
952 {
953 uint64_t u64Track = cueTokenizerConsumeInteger(pTokenizer);
954 if (u64Track <= 99)
955 {
956 /* Parse the data mode. */
957 if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_KEYWORD)
958 {
959 CUEKEYWORD enmDataMode = pTokenizer->pTokenCurr->Type.Keyword.enmKeyword;
960 if ( cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_AUDIO)
961 || cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_MODE1_2048)
962 || cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_MODE1_2352))
963 {
964 /*
965 * Parse everything coming below the track (index points, etc.), we only need to find
966 * the starting point.
967 */
968 uint64_t uLbaStart = 0;
969 rc = cueParseTrackNesting(pThis, pTokenizer, &uLbaStart);
970 if (RT_SUCCESS(rc))
971 {
972 /* Create a new region for this track. */
973 RT_NOREF1(enmDataMode);
974 rc = cueEnsureRegionListSize(pThis, u64Track);
975 if (RT_SUCCESS(rc))
976 {
977 PVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[u64Track - 1];
978 pRegion->offRegion = uLbaStart;
979 if (enmDataMode == CUEKEYWORD_MODE1_2352)
980 {
981 pRegion->cbBlock = 2352;
982 pRegion->enmDataForm = VDREGIONDATAFORM_MODE1_2352;
983 }
984 else if (enmDataMode == CUEKEYWORD_AUDIO)
985 {
986 pRegion->cbBlock = 2352;
987 pRegion->enmDataForm = VDREGIONDATAFORM_CDDA;
988 }
989 else
990 {
991 pRegion->cbBlock = 2048;
992 pRegion->enmDataForm = VDREGIONDATAFORM_MODE1_2048;
993 }
994 pRegion->enmMetadataForm = VDREGIONMETADATAFORM_NONE;
995 pRegion->cbData = pRegion->cbBlock;
996 pRegion->cbMetadata = 0;
997 }
998 else
999 rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
1000 N_("CUE: Failed to allocate memory for the track list for '%s'"),
1001 pThis->pszFilename);
1002 }
1003 }
1004 else
1005 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
1006 N_("CUE: Error parsing '%s', the data mode is not supported"), pThis->pszFilename);
1007 }
1008 else
1009 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
1010 N_("CUE: Error parsing '%s', expected data mode"), pThis->pszFilename);
1011 }
1012 else
1013 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
1014 N_("CUE: Error parsing '%s', track number must be between 01 and 99"), pThis->pszFilename);
1015 }
1016 else
1017 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
1018 N_("CUE: Error parsing '%s', expected track number after TRACK directive"), pThis->pszFilename);
1019
1020 return rc;
1021}
1022
1023/**
1024 * Parses a list of tracks which must come after a FILE directive.
1025 *
1026 * @returns VBox status code.
1027 * @param pThis The CUE image state.
1028 * @param pTokenizer The tokenizer state.
1029 */
1030static int cueParseTrackList(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer)
1031{
1032 int rc = VINF_SUCCESS;
1033
1034 while ( RT_SUCCESS(rc)
1035 && cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_TRACK))
1036 rc = cueParseTrack(pThis, pTokenizer);
1037
1038 return rc;
1039}
1040
1041/**
1042 * Parses the remainder of a FILE directive.
1043 *
1044 * @returns VBox status code.
1045 * @param pThis The CUE image state.
1046 * @param pTokenizer The tokenizer state.
1047 */
1048static int cueParseFile(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer)
1049{
1050 int rc = VINF_SUCCESS;
1051
1052 /* First must come a string constant followed by a keyword giving the file type. */
1053 if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_STRING)
1054 {
1055 rc = cueTokenizerConsumeStringDup(pTokenizer, &pThis->pszDataFilename);
1056 if (RT_SUCCESS(rc))
1057 {
1058 if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_KEYWORD)
1059 {
1060 if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_BINARY))
1061 {
1062 pThis->fLittleEndian = true;
1063 rc = cueParseTrackList(pThis, pTokenizer);
1064 }
1065 else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_MOTOROLA))
1066 {
1067 pThis->fLittleEndian = false;
1068 rc = cueParseTrackList(pThis, pTokenizer);
1069 }
1070 else
1071 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
1072 N_("CUE: Error parsing '%s', the file type is not supported (only BINARY)"), pThis->pszFilename);
1073 }
1074 else
1075 rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
1076 N_("CUE: Error parsing '%s', expected file type"), pThis->pszFilename);
1077 }
1078 else
1079 rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
1080 N_("CUE: Error parsing '%s', failed to allocate memory for filename"), pThis->pszFilename);
1081 }
1082 else
1083 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
1084 N_("CUE: Error parsing '%s', expected filename after FILE directive"), pThis->pszFilename);
1085
1086 return rc;
1087}
1088
1089/**
1090 * Parses the keyword in the given tokenizer.
1091 *
1092 * @returns VBox status code.
1093 * @param pThis The CUE image state.
1094 * @param pTokenizer The tokenizer state.
1095 */
1096static int cueParseKeyword(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer)
1097{
1098 int rc = VINF_SUCCESS;
1099
1100 if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_FILE))
1101 rc = cueParseFile(pThis, pTokenizer);
1102 else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_TITLE))
1103 rc = cueParseAndSkipStringRemainder(pThis, pTokenizer, "TITLE");
1104 else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_PERFORMER))
1105 rc = cueParseAndSkipStringRemainder(pThis, pTokenizer, "PERFORMER");
1106 else if (cueTokenizerSkipIfIsKeywordEqual(pTokenizer, CUEKEYWORD_SONGWRITER))
1107 rc = cueParseAndSkipStringRemainder(pThis, pTokenizer, "SONGWRITER");
1108 else /* Skip all other keywords we don't need/support. */
1109 cueTokenizerConsume(pTokenizer);
1110
1111 return rc;
1112}
1113
1114
1115/**
1116 * Parses the CUE sheet from the given tokenizer.
1117 *
1118 * @returns VBox status code.
1119 * @param pThis The CUE image state.
1120 * @param pTokenizer The tokenizer state.
1121 */
1122static int cueParseFromTokenizer(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer)
1123{
1124 int rc = VINF_SUCCESS;
1125
1126 LogFlowFunc(("pThis=%p\n", pThis));
1127
1128 /* We don't support multiple FILE directives for now. */
1129 if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_KEYWORD)
1130 rc = cueParseKeyword(pThis, pTokenizer);
1131 else
1132 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
1133 N_("CUE: Error parsing '%s', expected a keyword"), pThis->pszFilename);
1134
1135 if ( RT_SUCCESS(rc)
1136 && cueTokenizerGetTokenType(pTokenizer) != CUETOKENTYPE_EOS)
1137 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
1138 N_("CUE: Error parsing '%s', expected end of stream"), pThis->pszFilename);
1139
1140 LogFlowFunc(("returns rc=%Rrc\n", rc));
1141 return rc;
1142}
1143
1144/**
1145 * Finalizes the track list of the image.
1146 *
1147 * @returns VBox status code.
1148 * @param pThis The CUE image state.
1149 * @param cbImage Size of the image data in bytes.
1150 */
1151static int cueTrackListFinalize(PCUEIMAGE pThis, uint64_t cbImage)
1152{
1153 int rc = VINF_SUCCESS;
1154
1155 if ( pThis->cTracksMax == 0
1156 || pThis->pRegionList->aRegions[0].offRegion == UINT64_MAX)
1157 return vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
1158 N_("CUE: Error parsing '%s', detected empty track list"), pThis->pszFilename);
1159
1160 /*
1161 * Fixup the track list to contain the proper sizes now that we parsed all tracks,
1162 * check also that there are no gaps in the list.
1163 */
1164 uint32_t cTracks = 1;
1165 uint64_t offDisk = 0;
1166 for (uint32_t i = 1; i < pThis->cTracksMax; i++)
1167 {
1168 PVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[i];
1169 PVDREGIONDESC pRegionPrev = &pThis->pRegionList->aRegions[i - 1];
1170 if (pRegion->offRegion != UINT64_MAX)
1171 {
1172 cTracks++;
1173 uint64_t cBlocks = pRegion->offRegion - (pRegionPrev->offRegion / pRegionPrev->cbBlock);
1174 pRegionPrev->cRegionBlocksOrBytes = pRegionPrev->cbBlock * cBlocks;
1175 offDisk += pRegionPrev->cRegionBlocksOrBytes;
1176
1177 if (cbImage < pRegionPrev->cRegionBlocksOrBytes)
1178 return vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
1179 N_("CUE: Error parsing '%s', image file is too small for track list"),
1180 pThis->pszFilename);
1181
1182 cbImage -= pRegionPrev->cRegionBlocksOrBytes;
1183 pRegion->offRegion = offDisk;
1184 }
1185 else
1186 break;
1187 }
1188
1189 /* Fixup last track. */
1190 PVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[cTracks - 1];
1191 pRegion->cRegionBlocksOrBytes = cbImage;
1192
1193 pThis->pRegionList->cRegions = cTracks;
1194 pThis->pRegionList->fFlags = 0;
1195
1196 /* Check that there are no gaps in the track list. */
1197 for (uint32_t i = cTracks; cTracks < pThis->cTracksMax; i++)
1198 {
1199 pRegion = &pThis->pRegionList->aRegions[i];
1200 if (pRegion->offRegion != UINT64_MAX)
1201 {
1202 rc = vdIfError(pThis->pIfError, VERR_NOT_SUPPORTED, RT_SRC_POS,
1203 N_("CUE: Error parsing '%s', detected gaps in the track list"), pThis->pszFilename);
1204 break;
1205 }
1206 }
1207
1208 return rc;
1209}
1210
1211/**
1212 * Internal. Free all allocated space for representing an image except pThis,
1213 * and optionally delete the image from disk.
1214 */
1215static int cueFreeImage(PCUEIMAGE pThis, bool fDelete)
1216{
1217 int rc = VINF_SUCCESS;
1218
1219 /* Freeing a never allocated image (e.g. because the open failed) is
1220 * not signalled as an error. After all nothing bad happens. */
1221 if (pThis)
1222 {
1223 if (pThis->pStorage)
1224 {
1225 rc = vdIfIoIntFileClose(pThis->pIfIo, pThis->pStorage);
1226 pThis->pStorage = NULL;
1227 }
1228
1229 if (pThis->pStorageData)
1230 {
1231 rc = vdIfIoIntFileClose(pThis->pIfIo, pThis->pStorageData);
1232 pThis->pStorageData = NULL;
1233 }
1234
1235 if (pThis->pRegionList)
1236 {
1237 RTMemFree(pThis->pRegionList);
1238 pThis->pRegionList = NULL;
1239 }
1240
1241 if (pThis->pszDataFilename)
1242 {
1243 RTStrFree(pThis->pszDataFilename);
1244 pThis->pszDataFilename = NULL;
1245 }
1246
1247 if (fDelete && pThis->pszFilename)
1248 vdIfIoIntFileDelete(pThis->pIfIo, pThis->pszFilename);
1249 }
1250
1251 LogFlowFunc(("returns %Rrc\n", rc));
1252 return rc;
1253}
1254
1255/**
1256 * Internal: Open an image, constructing all necessary data structures.
1257 */
1258static int cueOpenImage(PCUEIMAGE pThis, unsigned uOpenFlags)
1259{
1260 pThis->uOpenFlags = uOpenFlags;
1261
1262 pThis->pIfError = VDIfErrorGet(pThis->pVDIfsDisk);
1263 pThis->pIfIo = VDIfIoIntGet(pThis->pVDIfsImage);
1264 AssertPtrReturn(pThis->pIfIo, VERR_INVALID_PARAMETER);
1265
1266 /* Open the image. */
1267 int rc = vdIfIoIntFileOpen(pThis->pIfIo, pThis->pszFilename,
1268 VDOpenFlagsToFileOpenFlags(uOpenFlags,
1269 false /* fCreate */),
1270 &pThis->pStorage);
1271 if (RT_SUCCESS(rc))
1272 {
1273 uint64_t cbFile;
1274 /* The descriptor file shouldn't be huge, so limit ourselfs to 16KB for now. */
1275 rc = vdIfIoIntFileGetSize(pThis->pIfIo, pThis->pStorage, &cbFile);
1276 if ( RT_SUCCESS(rc)
1277 && cbFile <= _16K - 1)
1278 {
1279 char szInput[_16K];
1280 RT_ZERO(szInput);
1281
1282 rc = vdIfIoIntFileReadSync(pThis->pIfIo, pThis->pStorage, 0,
1283 &szInput, cbFile);
1284 if (RT_SUCCESS(rc))
1285 {
1286 RTStrPurgeEncoding(&szInput[0]);
1287 PCUETOKENIZER pTokenizer = cueTokenizerCreate(&szInput[0]);
1288 if (pTokenizer)
1289 {
1290 rc = cueParseFromTokenizer(pThis, pTokenizer);
1291 RTMemFree(pTokenizer);
1292 if (RT_SUCCESS(rc))
1293 {
1294 /* Open the backing file. */
1295 char szBackingFile[RTPATH_MAX];
1296 rc = RTStrCopy(&szBackingFile[0], sizeof(szBackingFile), pThis->pszFilename);
1297 if (RT_SUCCESS(rc))
1298 {
1299 RTPathStripFilename(&szBackingFile[0]);
1300 rc = RTPathAppend(&szBackingFile[0], sizeof(szBackingFile), pThis->pszDataFilename);
1301 if (RT_SUCCESS(rc))
1302 {
1303 rc = vdIfIoIntFileOpen(pThis->pIfIo, szBackingFile,
1304 VDOpenFlagsToFileOpenFlags(uOpenFlags,
1305 false /* fCreate */),
1306 &pThis->pStorageData);
1307 if (RT_SUCCESS(rc))
1308 {
1309 rc = vdIfIoIntFileGetSize(pThis->pIfIo, pThis->pStorageData, &cbFile);
1310 if (RT_SUCCESS(rc))
1311 rc = cueTrackListFinalize(pThis, cbFile);
1312 else
1313 rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
1314 N_("CUE: Unable to query size of backing file '%s'"),
1315 szBackingFile);
1316 }
1317 else
1318 rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
1319 N_("CUE: Unable to open backing file '%s'"),
1320 szBackingFile);
1321 }
1322 else
1323 rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
1324 N_("CUE: Error constructing backing filename from '%s'"),
1325 pThis->pszFilename);
1326 }
1327 else
1328 rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS,
1329 N_("CUE: Error constructing backing filename from '%s'"),
1330 pThis->pszFilename);
1331 }
1332 }
1333 }
1334 else
1335 rc = vdIfError(pThis->pIfError, rc, RT_SRC_POS, N_("CUE: Error reading '%s'"), pThis->pszFilename);
1336 }
1337 else if (RT_SUCCESS(rc))
1338 rc = vdIfError(pThis->pIfError, VERR_VD_INVALID_SIZE,
1339 RT_SRC_POS, N_("CUE: The descriptor file '%s' is too huge (%llu vs %llu)"),
1340 pThis->pszFilename, cbFile, _16K - 1);
1341 }
1342 /* else: Do NOT signal an appropriate error here, as the VD layer has the
1343 * choice of retrying the open if it failed. */
1344
1345 if (RT_FAILURE(rc))
1346 cueFreeImage(pThis, false);
1347 return rc;
1348}
1349
1350/**
1351 * Converts the data form enumeration to a string.
1352 *
1353 * @returns String name of the given data form.
1354 * @param enmDataForm The data form.
1355 */
1356static const char *cueRegionDataFormStringify(VDREGIONDATAFORM enmDataForm)
1357{
1358 switch (enmDataForm)
1359 {
1360 #define DATAFORM2STR(tag) case VDREGIONDATAFORM_##tag: return #tag
1361
1362 DATAFORM2STR(INVALID);
1363 DATAFORM2STR(RAW);
1364 DATAFORM2STR(CDDA);
1365 DATAFORM2STR(CDDA_PAUSE);
1366 DATAFORM2STR(MODE1_2048);
1367 DATAFORM2STR(MODE1_2352);
1368 DATAFORM2STR(MODE1_0);
1369 DATAFORM2STR(XA_2336);
1370 DATAFORM2STR(XA_2352);
1371 DATAFORM2STR(XA_0);
1372 DATAFORM2STR(MODE2_2336);
1373 DATAFORM2STR(MODE2_2352);
1374 DATAFORM2STR(MODE2_0);
1375
1376 #undef DATAFORM2STR
1377
1378 default:
1379 {
1380 AssertMsgFailed(("Unknown data form %d! forgot to add it to the switch?\n", enmDataForm));
1381 return "UNKNOWN!";
1382 }
1383 }
1384}
1385
1386/**
1387 * Converts the data form enumeration to a string.
1388 *
1389 * @returns String name of the given data form.
1390 * @param enmMetadataForm The metadata form.
1391 */
1392static const char *cueRegionMetadataFormStringify(VDREGIONMETADATAFORM enmMetadataForm)
1393{
1394 switch (enmMetadataForm)
1395 {
1396 #define METADATAFORM2STR(tag) case VDREGIONMETADATAFORM_##tag: return #tag
1397
1398 METADATAFORM2STR(INVALID);
1399 METADATAFORM2STR(RAW);
1400 METADATAFORM2STR(NONE);
1401
1402 #undef METADATAFORM2STR
1403
1404 default:
1405 {
1406 AssertMsgFailed(("Unknown metadata form %d! forgot to add it to the switch?\n", enmMetadataForm));
1407 return "UNKNOWN!";
1408 }
1409 }
1410}
1411
1412/**
1413 * Returns the region containing the given offset.
1414 *
1415 * @returns Pointer to the region or NULL if not found.
1416 * @param pThis The CUE image state.
1417 * @param uOffset The offset to look for.
1418 */
1419static PCVDREGIONDESC cueRegionQueryByOffset(PCUEIMAGE pThis, uint64_t uOffset)
1420{
1421 for (uint32_t i = 0; i < pThis->pRegionList->cRegions; i++)
1422 {
1423 PCVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[i];
1424 if ( pRegion->offRegion <= uOffset
1425 && pRegion->offRegion + pRegion->cRegionBlocksOrBytes > uOffset)
1426 return pRegion;
1427 }
1428
1429 return NULL;
1430}
1431
1432/** @copydoc VDIMAGEBACKEND::pfnProbe */
1433static DECLCALLBACK(int) cueProbe(const char *pszFilename, PVDINTERFACE pVDIfsDisk,
1434 PVDINTERFACE pVDIfsImage, VDTYPE enmDesiredType, VDTYPE *penmType)
1435{
1436 RT_NOREF(pVDIfsDisk, enmDesiredType);
1437 LogFlowFunc(("pszFilename=\"%s\" pVDIfsDisk=%#p pVDIfsImage=%#p\n", pszFilename, pVDIfsDisk, pVDIfsImage));
1438 int rc = VINF_SUCCESS;
1439
1440 AssertReturn((VALID_PTR(pszFilename) && *pszFilename), VERR_INVALID_PARAMETER);
1441
1442 PCUEIMAGE pThis = (PCUEIMAGE)RTMemAllocZ(sizeof(CUEIMAGE));
1443 if (RT_LIKELY(pThis))
1444 {
1445 pThis->pszFilename = pszFilename;
1446 pThis->pStorage = NULL;
1447 pThis->pVDIfsDisk = pVDIfsDisk;
1448 pThis->pVDIfsImage = pVDIfsImage;
1449
1450 rc = cueOpenImage(pThis, VD_OPEN_FLAGS_INFO | VD_OPEN_FLAGS_READONLY);
1451 cueFreeImage(pThis, false);
1452 RTMemFree(pThis);
1453
1454 if (RT_SUCCESS(rc))
1455 *penmType = VDTYPE_OPTICAL_DISC;
1456 else
1457 rc = VERR_VD_GEN_INVALID_HEADER;
1458 }
1459 else
1460 rc = VERR_NO_MEMORY;
1461
1462 LogFlowFunc(("returns %Rrc\n", rc));
1463 return rc;
1464}
1465
1466/** @copydoc VDIMAGEBACKEND::pfnOpen */
1467static DECLCALLBACK(int) cueOpen(const char *pszFilename, unsigned uOpenFlags,
1468 PVDINTERFACE pVDIfsDisk, PVDINTERFACE pVDIfsImage,
1469 VDTYPE enmType, void **ppBackendData)
1470{
1471 LogFlowFunc(("pszFilename=\"%s\" uOpenFlags=%#x pVDIfsDisk=%#p pVDIfsImage=%#p enmType=%u ppBackendData=%#p\n",
1472 pszFilename, uOpenFlags, pVDIfsDisk, pVDIfsImage, enmType, ppBackendData));
1473 int rc;
1474 PCUEIMAGE pThis;
1475
1476 /* Check open flags. All valid flags are supported. */
1477 AssertReturn(!(uOpenFlags & ~VD_OPEN_FLAGS_MASK), VERR_INVALID_PARAMETER);
1478 AssertReturn((VALID_PTR(pszFilename) && *pszFilename), VERR_INVALID_PARAMETER);
1479 AssertReturn(enmType == VDTYPE_OPTICAL_DISC, VERR_NOT_SUPPORTED);
1480
1481 pThis = (PCUEIMAGE)RTMemAllocZ(sizeof(CUEIMAGE));
1482 if (RT_LIKELY(pThis))
1483 {
1484 pThis->pszFilename = pszFilename;
1485 pThis->pStorage = NULL;
1486 pThis->pVDIfsDisk = pVDIfsDisk;
1487 pThis->pVDIfsImage = pVDIfsImage;
1488
1489 rc = cueOpenImage(pThis, uOpenFlags);
1490 if (RT_SUCCESS(rc))
1491 *ppBackendData = pThis;
1492 else
1493 RTMemFree(pThis);
1494 }
1495 else
1496 rc = VERR_NO_MEMORY;
1497
1498 LogFlowFunc(("returns %Rrc (pBackendData=%#p)\n", rc, *ppBackendData));
1499 return rc;
1500}
1501
1502/** @copydoc VDIMAGEBACKEND::pfnClose */
1503static DECLCALLBACK(int) cueClose(void *pBackendData, bool fDelete)
1504{
1505 LogFlowFunc(("pBackendData=%#p fDelete=%d\n", pBackendData, fDelete));
1506 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1507 int rc = cueFreeImage(pThis, fDelete);
1508 RTMemFree(pThis);
1509
1510 LogFlowFunc(("returns %Rrc\n", rc));
1511 return rc;
1512}
1513
1514/** @copydoc VDIMAGEBACKEND::pfnRead */
1515static DECLCALLBACK(int) cueRead(void *pBackendData, uint64_t uOffset, size_t cbToRead,
1516 PVDIOCTX pIoCtx, size_t *pcbActuallyRead)
1517{
1518 LogFlowFunc(("pBackendData=%#p uOffset=%llu cbToRead=%zu pIoCtx=%#p pcbActuallyRead=%#p\n",
1519 pBackendData, uOffset, cbToRead, pIoCtx, pcbActuallyRead));
1520 int rc = VINF_SUCCESS;
1521 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1522
1523 /* Get the region */
1524 PCVDREGIONDESC pRegion = cueRegionQueryByOffset(pThis, uOffset);
1525 if (pRegion)
1526 {
1527 /* Clip read size to remain in the region (not necessary I think). */
1528 uint64_t offRead = uOffset - pRegion->offRegion;
1529
1530 cbToRead = RT_MIN(cbToRead, pRegion->cRegionBlocksOrBytes - offRead);
1531 Assert(!(cbToRead % pRegion->cbBlock));
1532
1533 /* Need to convert audio data samples to big endian. */
1534 if ( pRegion->enmDataForm == VDREGIONDATAFORM_CDDA
1535 && pThis->fLittleEndian)
1536 {
1537 *pcbActuallyRead = cbToRead;
1538
1539 while (cbToRead)
1540 {
1541 RTSGSEG Segment;
1542 unsigned cSegments = 1;
1543 size_t cbSeg = 0;
1544
1545 cbSeg = vdIfIoIntIoCtxSegArrayCreate(pThis->pIfIo, pIoCtx, &Segment,
1546 &cSegments, cbToRead);
1547
1548 rc = vdIfIoIntFileReadSync(pThis->pIfIo, pThis->pStorageData, uOffset, Segment.pvSeg, cbSeg);
1549 if (RT_FAILURE(rc))
1550 break;
1551
1552 uint16_t *pu16Buf = (uint16_t *)Segment.pvSeg;
1553 for (uint32_t i = 0; i < cbSeg / sizeof(uint16_t); i++)
1554 {
1555 *pu16Buf = RT_BSWAP_U16(*pu16Buf);
1556 pu16Buf++;
1557 }
1558
1559 cbToRead -= RT_MIN(cbToRead, cbSeg);
1560 uOffset += cbSeg;
1561 }
1562 }
1563 else
1564 {
1565 rc = vdIfIoIntFileReadUser(pThis->pIfIo, pThis->pStorageData, uOffset,
1566 pIoCtx, cbToRead);
1567 if (RT_SUCCESS(rc))
1568 *pcbActuallyRead = cbToRead;
1569 }
1570 }
1571 else
1572 rc = VERR_INVALID_PARAMETER;
1573
1574 return rc;
1575}
1576
1577/** @copydoc VDIMAGEBACKEND::pfnWrite */
1578static DECLCALLBACK(int) cueWrite(void *pBackendData, uint64_t uOffset, size_t cbToWrite,
1579 PVDIOCTX pIoCtx, size_t *pcbWriteProcess, size_t *pcbPreRead,
1580 size_t *pcbPostRead, unsigned fWrite)
1581{
1582 RT_NOREF7(uOffset, cbToWrite, pIoCtx, pcbWriteProcess, pcbPreRead, pcbPostRead, fWrite);
1583 LogFlowFunc(("pBackendData=%#p uOffset=%llu pIoCtx=%#p cbToWrite=%zu pcbWriteProcess=%#p pcbPreRead=%#p pcbPostRead=%#p\n",
1584 pBackendData, uOffset, pIoCtx, cbToWrite, pcbWriteProcess, pcbPreRead, pcbPostRead));
1585 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1586 int rc;
1587
1588 AssertPtr(pThis);
1589
1590 if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY)
1591 rc = VERR_VD_IMAGE_READ_ONLY;
1592 else
1593 rc = VERR_NOT_SUPPORTED;
1594
1595 LogFlowFunc(("returns %Rrc\n", rc));
1596 return rc;
1597}
1598
1599/** @copydoc VDIMAGEBACKEND::pfnFlush */
1600static DECLCALLBACK(int) cueFlush(void *pBackendData, PVDIOCTX pIoCtx)
1601{
1602 RT_NOREF2(pBackendData, pIoCtx);
1603
1604 return VINF_SUCCESS;
1605}
1606
1607/** @copydoc VDIMAGEBACKEND::pfnGetVersion */
1608static DECLCALLBACK(unsigned) cueGetVersion(void *pBackendData)
1609{
1610 LogFlowFunc(("pBackendData=%#p\n", pBackendData));
1611 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1612
1613 AssertPtrReturn(pThis, 0);
1614
1615 return 1;
1616}
1617
1618/** @copydoc VDIMAGEBACKEND::pfnGetFileSize */
1619static DECLCALLBACK(uint64_t) cueGetFileSize(void *pBackendData)
1620{
1621 LogFlowFunc(("pBackendData=%#p\n", pBackendData));
1622 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1623
1624 AssertPtrReturn(pThis, 0);
1625
1626 uint64_t cbFile = 0;
1627 if (pThis->pStorage)
1628 {
1629 int rc = vdIfIoIntFileGetSize(pThis->pIfIo, pThis->pStorageData, &cbFile);
1630 if (RT_FAILURE(rc))
1631 cbFile = 0; /* Make sure it is 0 */
1632 }
1633
1634 LogFlowFunc(("returns %lld\n", cbFile));
1635 return cbFile;
1636}
1637
1638/** @copydoc VDIMAGEBACKEND::pfnGetPCHSGeometry */
1639static DECLCALLBACK(int) cueGetPCHSGeometry(void *pBackendData,
1640 PVDGEOMETRY pPCHSGeometry)
1641{
1642 RT_NOREF1(pPCHSGeometry);
1643 LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p\n", pBackendData, pPCHSGeometry));
1644 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1645 int rc = VINF_SUCCESS;
1646
1647 AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
1648
1649 rc = VERR_NOT_SUPPORTED;
1650
1651 LogFlowFunc(("returns %Rrc (PCHS=%u/%u/%u)\n", rc, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
1652 return rc;
1653}
1654
1655/** @copydoc VDIMAGEBACKEND::pfnSetPCHSGeometry */
1656static DECLCALLBACK(int) cueSetPCHSGeometry(void *pBackendData,
1657 PCVDGEOMETRY pPCHSGeometry)
1658{
1659 RT_NOREF1(pPCHSGeometry);
1660 LogFlowFunc(("pBackendData=%#p pPCHSGeometry=%#p PCHS=%u/%u/%u\n",
1661 pBackendData, pPCHSGeometry, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
1662 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1663 int rc = VINF_SUCCESS;
1664
1665 AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
1666
1667 if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY)
1668 rc = VERR_VD_IMAGE_READ_ONLY;
1669 else
1670 rc = VERR_NOT_SUPPORTED;
1671
1672 LogFlowFunc(("returns %Rrc\n", rc));
1673 return rc;
1674}
1675
1676/** @copydoc VDIMAGEBACKEND::pfnGetLCHSGeometry */
1677static DECLCALLBACK(int) cueGetLCHSGeometry(void *pBackendData,
1678 PVDGEOMETRY pLCHSGeometry)
1679{
1680 RT_NOREF1(pLCHSGeometry);
1681 LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p\n", pBackendData, pLCHSGeometry));
1682 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1683 int rc = VINF_SUCCESS;
1684
1685 AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
1686
1687 rc = VERR_NOT_SUPPORTED;
1688
1689 LogFlowFunc(("returns %Rrc (LCHS=%u/%u/%u)\n", rc, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
1690 return rc;
1691}
1692
1693/** @copydoc VDIMAGEBACKEND::pfnSetLCHSGeometry */
1694static DECLCALLBACK(int) cueSetLCHSGeometry(void *pBackendData,
1695 PCVDGEOMETRY pLCHSGeometry)
1696{
1697 RT_NOREF1(pLCHSGeometry);
1698 LogFlowFunc(("pBackendData=%#p pLCHSGeometry=%#p LCHS=%u/%u/%u\n",
1699 pBackendData, pLCHSGeometry, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
1700 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1701 int rc = VINF_SUCCESS;
1702
1703 AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
1704
1705 if (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY)
1706 rc = VERR_VD_IMAGE_READ_ONLY;
1707 else
1708 rc = VERR_NOT_SUPPORTED;
1709
1710 LogFlowFunc(("returns %Rrc\n", rc));
1711 return rc;
1712}
1713
1714/** @copydoc VDIMAGEBACKEND::pfnQueryRegions */
1715static DECLCALLBACK(int) cueQueryRegions(void *pBackendData, PCVDREGIONLIST *ppRegionList)
1716{
1717 LogFlowFunc(("pBackendData=%#p ppRegionList=%#p\n", pBackendData, ppRegionList));
1718 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1719
1720 AssertPtrReturn(pThis, VERR_VD_NOT_OPENED);
1721
1722 *ppRegionList = pThis->pRegionList;
1723 LogFlowFunc(("returns %Rrc\n", VINF_SUCCESS));
1724 return VINF_SUCCESS;
1725}
1726
1727/** @copydoc VDIMAGEBACKEND::pfnRegionListRelease */
1728static DECLCALLBACK(void) cueRegionListRelease(void *pBackendData, PCVDREGIONLIST pRegionList)
1729{
1730 RT_NOREF1(pRegionList);
1731 LogFlowFunc(("pBackendData=%#p pRegionList=%#p\n", pBackendData, pRegionList));
1732 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1733 AssertPtr(pThis); RT_NOREF(pThis);
1734
1735 /* Nothing to do here. */
1736}
1737
1738/** @copydoc VDIMAGEBACKEND::pfnGetImageFlags */
1739static DECLCALLBACK(unsigned) cueGetImageFlags(void *pBackendData)
1740{
1741 LogFlowFunc(("pBackendData=%#p\n", pBackendData));
1742 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1743
1744 AssertPtrReturn(pThis, 0);
1745
1746 LogFlowFunc(("returns %#x\n", pThis->uImageFlags));
1747 return pThis->uImageFlags;
1748}
1749
1750/** @copydoc VDIMAGEBACKEND::pfnGetOpenFlags */
1751static DECLCALLBACK(unsigned) cueGetOpenFlags(void *pBackendData)
1752{
1753 LogFlowFunc(("pBackendData=%#p\n", pBackendData));
1754 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1755
1756 AssertPtrReturn(pThis, 0);
1757
1758 LogFlowFunc(("returns %#x\n", pThis->uOpenFlags));
1759 return pThis->uOpenFlags;
1760}
1761
1762/** @copydoc VDIMAGEBACKEND::pfnSetOpenFlags */
1763static DECLCALLBACK(int) cueSetOpenFlags(void *pBackendData, unsigned uOpenFlags)
1764{
1765 LogFlowFunc(("pBackendData=%#p\n uOpenFlags=%#x", pBackendData, uOpenFlags));
1766 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1767 int rc = VINF_SUCCESS;
1768
1769 /* Image must be opened and the new flags must be valid. */
1770 if (!pThis || (uOpenFlags & ~( VD_OPEN_FLAGS_READONLY | VD_OPEN_FLAGS_INFO
1771 | VD_OPEN_FLAGS_ASYNC_IO | VD_OPEN_FLAGS_SHAREABLE
1772 | VD_OPEN_FLAGS_SEQUENTIAL | VD_OPEN_FLAGS_SKIP_CONSISTENCY_CHECKS)))
1773 rc = VERR_INVALID_PARAMETER;
1774 else
1775 {
1776 /* Implement this operation via reopening the image. */
1777 rc = cueFreeImage(pThis, false);
1778 if (RT_SUCCESS(rc))
1779 rc = cueOpenImage(pThis, uOpenFlags);
1780 }
1781
1782 LogFlowFunc(("returns %Rrc\n", rc));
1783 return rc;
1784}
1785
1786/** @copydoc VDIMAGEBACKEND::pfnGetComment */
1787VD_BACKEND_CALLBACK_GET_COMMENT_DEF_NOT_SUPPORTED(cueGetComment);
1788
1789/** @copydoc VDIMAGEBACKEND::pfnSetComment */
1790VD_BACKEND_CALLBACK_SET_COMMENT_DEF_NOT_SUPPORTED(cueSetComment, PCUEIMAGE);
1791
1792/** @copydoc VDIMAGEBACKEND::pfnGetUuid */
1793VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(cueGetUuid);
1794
1795/** @copydoc VDIMAGEBACKEND::pfnSetUuid */
1796VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(cueSetUuid, PCUEIMAGE);
1797
1798/** @copydoc VDIMAGEBACKEND::pfnGetModificationUuid */
1799VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(cueGetModificationUuid);
1800
1801/** @copydoc VDIMAGEBACKEND::pfnSetModificationUuid */
1802VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(cueSetModificationUuid, PCUEIMAGE);
1803
1804/** @copydoc VDIMAGEBACKEND::pfnGetParentUuid */
1805VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(cueGetParentUuid);
1806
1807/** @copydoc VDIMAGEBACKEND::pfnSetParentUuid */
1808VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(cueSetParentUuid, PCUEIMAGE);
1809
1810/** @copydoc VDIMAGEBACKEND::pfnGetParentModificationUuid */
1811VD_BACKEND_CALLBACK_GET_UUID_DEF_NOT_SUPPORTED(cueGetParentModificationUuid);
1812
1813/** @copydoc VDIMAGEBACKEND::pfnSetParentModificationUuid */
1814VD_BACKEND_CALLBACK_SET_UUID_DEF_NOT_SUPPORTED(cueSetParentModificationUuid, PCUEIMAGE);
1815
1816/** @copydoc VDIMAGEBACKEND::pfnDump */
1817static DECLCALLBACK(void) cueDump(void *pBackendData)
1818{
1819 PCUEIMAGE pThis = (PCUEIMAGE)pBackendData;
1820
1821 AssertPtrReturnVoid(pThis);
1822 vdIfErrorMessage(pThis->pIfError, "Dumping CUE image \"%s\" mode=%s uOpenFlags=%X File=%#p\n",
1823 pThis->pszFilename,
1824 (pThis->uOpenFlags & VD_OPEN_FLAGS_READONLY) ? "r/o" : "r/w",
1825 pThis->uOpenFlags,
1826 pThis->pStorage);
1827 vdIfErrorMessage(pThis->pIfError, "Backing File \"%s\" File=%#p\n",
1828 pThis->pszDataFilename, pThis->pStorageData);
1829 vdIfErrorMessage(pThis->pIfError, "Number of tracks: %u\n", pThis->pRegionList->cRegions);
1830 for (uint32_t i = 0; i < pThis->pRegionList->cRegions; i++)
1831 {
1832 PCVDREGIONDESC pRegion = &pThis->pRegionList->aRegions[i];
1833
1834 vdIfErrorMessage(pThis->pIfError, "------------------------ Track %u ------------------------\n", i);
1835 vdIfErrorMessage(pThis->pIfError, "Start=%llu Size=%llu BlockSize=%llu DataSize=%llu MetadataSize=%llu\n",
1836 pRegion->offRegion, pRegion->cRegionBlocksOrBytes, pRegion->cbBlock, pRegion->cbData,
1837 pRegion->cbMetadata);
1838 vdIfErrorMessage(pThis->pIfError, "DataForm=%s MetadataForm=%s\n",
1839 cueRegionDataFormStringify(pRegion->enmDataForm),
1840 cueRegionMetadataFormStringify(pRegion->enmMetadataForm));
1841 }
1842}
1843
1844
1845
1846const VDIMAGEBACKEND g_CueBackend =
1847{
1848 /* u32Version */
1849 VD_IMGBACKEND_VERSION,
1850 /* pszBackendName */
1851 "CUE",
1852 /* uBackendCaps */
1853 VD_CAP_FILE | VD_CAP_VFS,
1854 /* paFileExtensions */
1855 s_aCueFileExtensions,
1856 /* paConfigInfo */
1857 NULL,
1858 /* pfnProbe */
1859 cueProbe,
1860 /* pfnOpen */
1861 cueOpen,
1862 /* pfnCreate */
1863 NULL,
1864 /* pfnRename */
1865 NULL,
1866 /* pfnClose */
1867 cueClose,
1868 /* pfnRead */
1869 cueRead,
1870 /* pfnWrite */
1871 cueWrite,
1872 /* pfnFlush */
1873 cueFlush,
1874 /* pfnDiscard */
1875 NULL,
1876 /* pfnGetVersion */
1877 cueGetVersion,
1878 /* pfnGetFileSize */
1879 cueGetFileSize,
1880 /* pfnGetPCHSGeometry */
1881 cueGetPCHSGeometry,
1882 /* pfnSetPCHSGeometry */
1883 cueSetPCHSGeometry,
1884 /* pfnGetLCHSGeometry */
1885 cueGetLCHSGeometry,
1886 /* pfnSetLCHSGeometry */
1887 cueSetLCHSGeometry,
1888 /* pfnQueryRegions */
1889 cueQueryRegions,
1890 /* pfnRegionListRelease */
1891 cueRegionListRelease,
1892 /* pfnGetImageFlags */
1893 cueGetImageFlags,
1894 /* pfnGetOpenFlags */
1895 cueGetOpenFlags,
1896 /* pfnSetOpenFlags */
1897 cueSetOpenFlags,
1898 /* pfnGetComment */
1899 cueGetComment,
1900 /* pfnSetComment */
1901 cueSetComment,
1902 /* pfnGetUuid */
1903 cueGetUuid,
1904 /* pfnSetUuid */
1905 cueSetUuid,
1906 /* pfnGetModificationUuid */
1907 cueGetModificationUuid,
1908 /* pfnSetModificationUuid */
1909 cueSetModificationUuid,
1910 /* pfnGetParentUuid */
1911 cueGetParentUuid,
1912 /* pfnSetParentUuid */
1913 cueSetParentUuid,
1914 /* pfnGetParentModificationUuid */
1915 cueGetParentModificationUuid,
1916 /* pfnSetParentModificationUuid */
1917 cueSetParentModificationUuid,
1918 /* pfnDump */
1919 cueDump,
1920 /* pfnGetTimestamp */
1921 NULL,
1922 /* pfnGetParentTimestamp */
1923 NULL,
1924 /* pfnSetParentTimestamp */
1925 NULL,
1926 /* pfnGetParentFilename */
1927 NULL,
1928 /* pfnSetParentFilename */
1929 NULL,
1930 /* pfnComposeLocation */
1931 genericFileComposeLocation,
1932 /* pfnComposeName */
1933 genericFileComposeName,
1934 /* pfnCompact */
1935 NULL,
1936 /* pfnResize */
1937 NULL,
1938 /* pfnRepair */
1939 NULL,
1940 /* pfnTraverseMetadata */
1941 NULL,
1942 /* u32VersionEnd */
1943 VD_IMGBACKEND_VERSION
1944};
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette