/* * Copyright (C) 2007 Steve Ratcliffe * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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. * * * Author: Steve Ratcliffe * Create date: 23-Sep-2007 */ package uk.me.parabola.tdbfmt; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.zip.CRC32; import java.util.zip.CheckedOutputStream; import uk.me.parabola.imgfmt.app.Area; import uk.me.parabola.io.EndOfFileException; import uk.me.parabola.io.StructuredInputStream; import uk.me.parabola.log.Logger; /** * The TDB file. See the package documentation for more details. * * @author Steve Ratcliffe */ public class TdbFile { private static final Logger log = Logger.getLogger(TdbFile.class); public static final int TDB_V407 = 407; // The version number of the TDB format private int tdbVersion; // The blocks that go to make up the file. private HeaderBlock headerBlock; private CopyrightBlock copyrightBlock = new CopyrightBlock(); private OverviewMapBlock overviewMapBlock; private final List<DetailMapBlock> detailBlocks = new ArrayList<>(); private final RBlock rblock = new RBlock(); private final TBlock tblock = new TBlock(); private String overviewDescription; private int codePage; public TdbFile() { } public TdbFile(int tdbVersion) { this.tdbVersion = tdbVersion; } /** * Read in a TDB file from the disk. * * @param name The file name to load. * @return A TdbFile instance. * @throws IOException For problems reading the file. */ public static TdbFile read(String name) throws IOException { TdbFile tdb = new TdbFile(); try (InputStream is = new BufferedInputStream(new FileInputStream(name))) { tdb.load(is); } return tdb; } public void setProductInfo(int familyId, int productId, short productVersion, String seriesName, String familyName, String overviewDescription, byte enableProfile) { headerBlock = new HeaderBlock(tdbVersion); headerBlock.setFamilyId((short) familyId); headerBlock.setProductId((short) productId); headerBlock.setProductVersion(productVersion); headerBlock.setSeriesName(seriesName); headerBlock.setFamilyName(familyName); headerBlock.setEnableProfile(enableProfile); this.overviewDescription = overviewDescription; } public void setCodePage(int codePage) { this.codePage = codePage; headerBlock.setCodePage(codePage); } /** * Add a copyright segment to the file. * @param msg The message to add. */ public void addCopyright(String msg) { if (msg.isEmpty()) return; CopyrightSegment seg = new CopyrightSegment(CopyrightSegment.CODE_COPYRIGHT_TEXT_STRING, 3, msg); copyrightBlock.addSegment(seg); } /** * Set the overview information. Basically the overall size of the map * set. * @param bounds The bounds for the map. */ public void setOverview(Area bounds, String number) { overviewMapBlock = new OverviewMapBlock(); overviewMapBlock.setArea(bounds); overviewMapBlock.setMapName(number); overviewMapBlock.setDescription(overviewDescription); } /** * Add a detail block. This describes and names one of the maps in the * map set. * @param detail The detail to add. */ public void addDetail(DetailMapBlock detail) { detailBlocks.add(detail); } public void write(String name) throws IOException { if (headerBlock == null || overviewMapBlock == null) throw new IOException("Attempting to write file without being fully set up"); try (CheckedOutputStream stream = new CheckedOutputStream( new BufferedOutputStream(new FileOutputStream(name)), new CRC32())) { headerBlock.writeTo(stream, codePage); copyrightBlock.writeTo(stream, codePage); if (tdbVersion >= TDB_V407) { rblock.writeTo(stream, codePage); } overviewMapBlock.writeTo(stream, codePage); for (DetailMapBlock detail : detailBlocks) { detail.writeTo(stream, codePage); } if (tdbVersion >= TDB_V407) { tblock.setSum(stream.getChecksum().getValue()); tblock.writeTo(stream, codePage); } } } /** * Load from the given file name. * * @param ds The stream to read from. * @throws IOException For problems reading the file. */ private void load(InputStream ds) throws IOException { boolean eof = false; while (!eof) { try { readBlock(ds); } catch (EndOfFileException ignore) { eof = true; } } } /** * The file is divided into blocks. This reads a single block. * * @param is The input stream. * @throws IOException For problems reading the file. */ private void readBlock(InputStream is) throws IOException { int blockType = is.read(); if (blockType == -1) throw new EndOfFileException(); int blockLength = readBlockLength(is); if (blockLength == -1) throw new EndOfFileException(); byte[] body = new byte[blockLength]; int n = is.read(body); if (n < 0) throw new IOException("failed to read block"); StructuredInputStream ds = new StructuredInputStream(new ByteArrayInputStream(body)); switch (blockType) { case HeaderBlock.BLOCK_ID: headerBlock = new HeaderBlock(ds); log.info("header block seen", headerBlock); break; case CopyrightBlock.BLOCK_ID: log.info("copyright block"); copyrightBlock = new CopyrightBlock(ds); break; case OverviewMapBlock.BLOCK_ID: overviewMapBlock = new OverviewMapBlock(ds); log.info("overview block", overviewMapBlock); break; case DetailMapBlock.BLOCK_ID: DetailMapBlock db = new DetailMapBlock(ds); log.info("detail block", db); detailBlocks.add(db); break; default: log.warn("Unknown block in tdb file"); break; } } private int readBlockLength(InputStream is) throws IOException { int b1 = is.read(); if (b1 < 0) return -1; int b2 = is.read(); if (b2 < 0) return -1; return ((b2 & 0xff) << 8) | (b1 & 0xff); } public int getTdbVersion() { return headerBlock.getTdbVersion(); } }