/** * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * muCommander 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.commons.file.archive.iso; import com.mucommander.commons.file.AbstractFile; import com.mucommander.commons.io.RandomAccessInputStream; import java.io.IOException; import java.util.Vector; /** * @see <a href="http://en.wikipedia.org/wiki/NRG_(file_format)">NRG file format on Wikipedia</a> * @author Xavier Martin */ class NrgParser extends IsoParser { static Vector<IsoArchiveEntry> getEntries(byte[] buffer, AbstractFile file, RandomAccessInputStream rais) throws Exception { int sectSize = IsoUtil.MODE1_2048; // sector shift : 0 most of the time long sector_offset = 0; // bytes : depend if there's earlier track we discard long shiftOffset = 0; int len = rais.available(); int tracksMode[] = new int[255]; long tracksOffset[] = new long[255]; long tracksStart[] = new long[255]; long tracksEnd[] = new long[255]; int tracks = 0; for (int i = 7; i <= 11; i += 4) { long offset = -1; rais.seek(len - i); if (rais.read(buffer, 0, (i == 7) ? 4 : 12) == -1) throw new IOException("unable to read tail of nrg file"); if (buffer[0] == 'N' && buffer[1] == 'E' && buffer[2] == 'R' && buffer[3] == '5') // v2 footer offset = IsoUtil.toDword(buffer, 8); else if (buffer[0] == 'N' && buffer[1] == 'E' && buffer[2] == 'R' && buffer[3] == 'O') // v1 footer offset = IsoUtil.toDword(buffer, 4); if (offset == -1) continue; // read chunks boolean end = false; for (int j = 0; j < 255 && !end; j++) { long clen; rais.seek(offset); if (rais.read(buffer, 0, 8) == -1) throw new IOException("unable to read chunk in tail of nrg file"); if (buffer[0] == 'E' && buffer[1] == 'N' && buffer[2] == 'D' && buffer[3] == '!') end = true; else if (buffer[0] == 'E' && buffer[1] == 'T' && buffer[2] == 'N') { clen = IsoUtil.toDword(buffer, 4); boolean ETN2 = buffer[3] == '2'; if (rais.read(buffer, 0, (int) clen) == -1) throw new IOException("unable to read chunk in tail of nrg file"); for (int z = 0; z < clen; z += ETN2 ? 32 : 20) { tracksOffset[tracks] = IsoUtil.toDword(buffer, ETN2 ? 4 : 0); tracksMode[tracks] = IsoUtil.toDword(buffer, ETN2 ? 16 : 8); tracks++; } end = true; } else if (buffer[0] == 'D' && buffer[1] == 'A' && buffer[2] == 'O') { boolean DAOX = buffer[3] == 'X'; clen = IsoUtil.toDword(buffer, 4); offset += 8 + clen; // skip endian if (rais.read(buffer, 0, 4) == -1) throw new IOException("unable to skip endian in DAO chunk"); if (rais.read(buffer, 0, (int) clen) == -1) throw new IOException("unable to read DAO chunk"); int first = buffer[16]; int cur = first - 1; for (int z = 18; z < clen - 4; z += DAOX ? 42 : 30) { tracksMode[cur] = buffer[z + 14]; tracksOffset[cur] = IsoUtil.toDword(buffer, DAOX ? z + 30 : 22); tracksEnd[cur] = IsoUtil.toDword(buffer, DAOX ? z + 38 : 26) - 1; cur++; } tracks = cur; rais.seek(offset); // TODO CUES } else if (buffer[0] == 'C' && buffer[1] == 'U' && buffer[2] == 'E' && buffer[3] == 'X') { clen = IsoUtil.toDword(buffer, 4); offset += 8 + clen; if (rais.read(buffer, 0, (int) clen) == -1) throw new IOException("unable to read CUEX chunk"); for (int z = 0; z < clen; z += 8) { //long toc = IsoUtil.toDword(buffer, z); long tstart = IsoUtil.toDword(buffer, z + 4); if (buffer[z + 2] == 0 || (buffer[z + 1] & 0xff) == 0xAA) { // skip toc/pregap ? } else { tracksStart[tracks] = tstart; tracks++; } } rais.seek(offset); } else { // skip irrelevant chunk clen = IsoUtil.toDword(buffer, 4); offset += 8 + clen; rais.seek(offset); } } boolean audioOnly = true; for (int k = 0; k < tracks; k++) { shiftOffset = tracksOffset[k]; sector_offset = tracksStart[k]; switch (tracksMode[k]) { case 0: sectSize = IsoUtil.MODE1_2048; audioOnly = false; break; case 3: sectSize = IsoUtil.MODE2_2336; audioOnly = false; break; case 7: // audio 2352 - 2352 sectSize = IsoUtil.MODE2_2352; break; case 16: // TODO find a sample image w/ subchannel data // audio 2448 - 2352 break; default: throw new Exception("unhandled mode " + tracksMode[k]); } } // fun : handle audio disc :) if (audioOnly) { Vector<IsoArchiveEntry> entries = new Vector<IsoArchiveEntry>(); for (int k = 0; k < tracks; k++) { entries.add( new IsoArchiveEntry( file.getName() + ".TRACK" + (k + 1) + ".wav", false, file.getDate(), tracksEnd[k] - tracksOffset[k] + IsoUtil.WAV_header, // adding wav header 0, sectSize, tracksOffset[k], true) ); } return entries; } } return getEntries(buffer, rais, sectSize, sector_offset, shiftOffset); } }