/* DiskImageFDR.java (c) 2008-2015 Edward Swartz All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html */ package v9t9.common.files; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class DiskImageFDR extends TIFDR { public static final int FDRSIZE = 256; /** char filenam[10];// filename, padded with spaces u8 res10[2]; // reserved u8 flags; // filetype flags u8 recspersec; // # records per sector, 256/reclen for FIXED, 255/(reclen+1) for VAR, 0 for program u16 secsused; // [big-endian]: # sectors in file u8 byteoffs; // last byte used in file (0 = no last empty sector) u8 reclen; // record length, 0 for program u16 numrecs; // [little-endian]: # records for FIXED file, # sectors for VARIABLE file, 0 for program u8 rec20[8]; // reserved u8 dcpb[192]; // sector layout of file, in >UM >SN >OF == >NUM >OFS format u8 comment[36]; // used for comments */ public DiskImageFDR() { super(FDRSIZE); } public static DiskImageFDR readFDR(File file) throws IOException, InvalidFDRException { DiskImageFDR fdr = new DiskImageFDR(); FileInputStream stream = new FileInputStream(file); try { stream.read(fdr.filename); stream.read(fdr.res10); fdr.flags = stream.read(); fdr.recspersec = stream.read(); fdr.secsused = (stream.read() << 8) | stream.read(); fdr.byteoffs = stream.read(); fdr.reclen = stream.read(); fdr.numrecs = stream.read() | (stream.read() << 8); stream.read(fdr.rec20); stream.read(fdr.dcpb); stream.read(fdr.comment); } finally { stream.close(); } fdr.validate(file); return fdr; } public static DiskImageFDR createFDR(byte[] data, int offset) { DiskImageFDR fdr = new DiskImageFDR(); System.arraycopy(data, offset, fdr.filename, 0, fdr.filename.length); System.arraycopy(data, offset + 0xA, fdr.res10, 0, fdr.res10.length); fdr.flags = data[offset + 0xc] & 0xff; fdr.recspersec = data[offset + 0xd] & 0xff; fdr.secsused = ((data[offset + 0xe] & 0xff) << 8) | (data[offset + 0xf] & 0xff); fdr.byteoffs = data[offset + 0x10] & 0xff; fdr.reclen = data[offset + 0x11] & 0xff; fdr.numrecs = (data[offset + 0x12] & 0xff) | ((data[offset + 0x13] & 0xff) << 8); System.arraycopy(data, offset + 0x14, fdr.rec20, 0, fdr.rec20.length); System.arraycopy(data, offset + 0x1C, fdr.dcpb, 0, fdr.dcpb.length); System.arraycopy(data, offset + 0xDC, fdr.comment, 0, fdr.comment.length); return fdr; } /** * Set the filename */ public void setFileName(String name) throws IOException { if (name.length() > 10) throw new IOException("Name too long: " + name); for (int i = 0; i < filename.length; i++) { char ch = ' '; if (i < name.length()) ch = name.charAt(i); filename[i] = (byte) ch; } } public byte[] toBytes() { ByteArrayOutputStream os = new ByteArrayOutputStream(256); try { os.write(filename); os.write(res10); os.write(flags); os.write(recspersec); os.write(secsused >> 8); os.write(secsused & 0xff); os.write(byteoffs); os.write(reclen); os.write(numrecs & 0xff); os.write(numrecs >> 8); os.write(rec20); os.write(dcpb); os.write(comment); os.close(); } catch (IOException e) { throw new IllegalStateException(e); } return os.toByteArray(); } public String getFileName() { StringBuilder builder = new StringBuilder(); int len = 0; for (int i = 0; i < filename.length; i++) { char ch = (char) filename[i]; if (ch != ' ') len = i; builder.append(ch); } builder.setLength(len + 1); return builder.toString(); } /* (non-Javadoc) * @see v9t9.common.files.FDR#fetchContentSectors() */ @Override protected int[] fetchContentSectors() { int[] secs = new int[getSectorsUsed()]; // decode cluster list: // // >1C-FF Cluster list >UM >SN >OF == >NUM >OFS int clusOffs = 0; int secIdx = 0; while (secIdx < secs.length && clusOffs < dcpb.length) { int um = dcpb[clusOffs++] & 0xff; int sn = dcpb[clusOffs++] & 0xff; int of = dcpb[clusOffs++] & 0xff; if ((um | sn | of) == 0) break; int num = um | ((sn & 0xf) << 8); int ofs = (of << 4) | ((sn >> 4) & 0xf); while (secIdx <= ofs && secIdx < secs.length) { secs[secIdx++] = num++; } } while (secIdx < secs.length) { secs[secIdx++] = -1; } return secs; } /* (non-Javadoc) * @see v9t9.common.files.FDR#setContentSectors(int[]) */ @Override public void setContentSectors(int[] secs) throws IOException { int clusOffs = 0; int startSec = -1; int prevSec = -1; int ofs = 0; for (int i = 0; i < secs.length; i++) { if (startSec < 0) { startSec = secs[i]; } else if (prevSec + 1 == secs[i]) { // ok } else { clusOffs = addContentRange(clusOffs, startSec, ofs); startSec = secs[i]; } prevSec = secs[i]; ofs++; } if (prevSec >= 0) { clusOffs = addContentRange(clusOffs, startSec, ofs); } } /** * @param clusOffs * @param startSec * @param i * @return */ protected int addContentRange(int clusOffs, int num, int ofs) throws IOException { // encode cluster list: // // >1C-FF Cluster list >UM >SN >OF == >NUM >OFS if (clusOffs + 3 > dcpb.length) throw new IOException("cannot encode sectors for file; disk is too fragmented: " + this); int um = num & 0xff; dcpb[clusOffs++] = (byte) um; int sn = ((ofs << 4) & 0xf0) | ((num >> 8) & 0xf); dcpb[clusOffs++] = (byte) sn; int of = (ofs >> 4); dcpb[clusOffs++] = (byte) of; return clusOffs; } /** * @param cnt * @throws IOException * */ public void allocateSectors(IDiskImage image, int cnt) throws IOException { for (int s = 0; s < cnt; s++) { int sec = image.allocateSector(32); addContentSector(sec); setSectorsUsed(getSectorsUsed() + 1); } } /** * @param sec * @throws IOException */ public void addContentSector(int sec) throws IOException { int[] secs = getContentSectors(); List<Integer> secList = new ArrayList<Integer>(secs.length); for (int s : secs) { secList.add(s); } if (secList.contains(sec)) { throw new IOException("sector " + sec + " already allocated to file: " + this); } int idx = secList.indexOf(-1); if (idx >= 0) secList.set(idx, sec); else secList.add(sec); int[] newSecs = new int[secList.size()]; for (int i = 0; i < newSecs.length; i++) newSecs[i] = secList.get(i); setContentSectors(newSecs); } /* (non-Javadoc) * @see v9t9.common.files.IFDRInfo#getComment() */ @Override public String getComment() { String str = new String(comment).trim(); if (str.length() > 0) return str; return null; } }