Display and edit metadata of MOBI or AZW3 file
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

526 lines
13 KiB

package mobimeta;
import java.io.*;
import java.util.*;
public class MobiHeader
{
private byte[] compression = { 0, 0 };
private byte[] unused0 = { 0, 0 };
private byte[] textLength = { 0, 0, 0, 0 };
private byte[] recordCount = { 0, 0 };
private byte[] recordSize = { 0, 0 };
private byte[] encryptionType = { 0, 0 };
private byte[] unused1 = { 0, 0 };
private byte[] identifier = { 0, 0, 0, 0 };
private byte[] headerLength = { 0, 0, 0, 0 }; // from offset 0x10
private byte[] mobiType = { 0, 0, 0, 0 };
private byte[] textEncoding = { 0, 0, 0, 0 };
private byte[] uniqueID = { 0, 0, 0, 0 };
private byte[] fileVersion = { 0, 0, 0, 0 };
private byte[] orthographicIndex = { 0, 0, 0, 0 };
private byte[] inflectionIndex = { 0, 0, 0, 0 };
private byte[] indexNames = { 0, 0, 0, 0 };
private byte[] indexKeys = { 0, 0, 0, 0 };
private byte[] extraIndex0 = { 0, 0, 0, 0 };
private byte[] extraIndex1 = { 0, 0, 0, 0 };
private byte[] extraIndex2 = { 0, 0, 0, 0 };
private byte[] extraIndex3 = { 0, 0, 0, 0 };
private byte[] extraIndex4 = { 0, 0, 0, 0 };
private byte[] extraIndex5 = { 0, 0, 0, 0 };
private byte[] firstNonBookIndex = { 0, 0, 0, 0 };
private byte[] fullNameOffset = { 0, 0, 0, 0 };
private byte[] fullNameLength = { 0, 0, 0, 0 };
private byte[] locale = { 0, 0, 0, 0 };
private byte[] inputLanguage = { 0, 0, 0, 0 };
private byte[] outputLanguage = { 0, 0, 0, 0 };
private byte[] minVersion = { 0, 0, 0, 0 };
private byte[] firstImageIndex = { 0, 0, 0, 0 };
private byte[] huffmanRecordOffset = { 0, 0, 0, 0 };
private byte[] huffmanRecordCount = { 0, 0, 0, 0 };
private byte[] huffmanTableOffset = { 0, 0, 0, 0 };
private byte[] huffmanTableLength = { 0, 0, 0, 0 };
private byte[] exthFlags = { 0, 0, 0, 0 };
private byte[] restOfMobiHeader = null;
private EXTHHeader exthHeader = null;
private byte[] remainder = null;
// end of useful data
private byte[] fullName = null;
private String characterEncoding = null;
public MobiHeader(InputStream in, long mobiHeaderSize) throws IOException
{
MobiCommon.logMessage("*** MobiHeader ***");
MobiCommon.logMessage("compression");
StreamUtils.readByteArray(in, compression);
StreamUtils.readByteArray(in, unused0);
StreamUtils.readByteArray(in, textLength);
StreamUtils.readByteArray(in, recordCount);
StreamUtils.readByteArray(in, recordSize);
MobiCommon.logMessage("encryptionType");
StreamUtils.readByteArray(in, encryptionType);
StreamUtils.readByteArray(in, unused1);
StreamUtils.readByteArray(in, identifier);
if (MobiCommon.debug)
{
MobiCommon.logMessage("identifier: "
+ StreamUtils.byteArrayToString(identifier));
}
if ((identifier[0] != 77)
||
(identifier[1] != 79)
||
(identifier[2] != 66)
||
(identifier[3] != 73))
{
throw new IOException("Did not get expected MOBI identifier");
}
// this value will determine the size of restOfMobiHeader[]
//
StreamUtils.readByteArray(in, headerLength);
int headLen = StreamUtils.byteArrayToInt(headerLength);
restOfMobiHeader = new byte[headLen + 16 - 132];
if (MobiCommon.debug)
{
MobiCommon.logMessage("headerLength: " + headLen);
}
StreamUtils.readByteArray(in, mobiType);
if (MobiCommon.debug)
{
MobiCommon.logMessage("mobiType: "
+ StreamUtils.byteArrayToInt(mobiType));
}
StreamUtils.readByteArray(in, textEncoding);
switch (StreamUtils.byteArrayToInt(textEncoding))
{
case 1252:
characterEncoding = "Cp1252";
break;
case 65001:
characterEncoding = "UTF-8";
break;
default:
characterEncoding = null;
break;
}
MobiCommon.logMessage("text encoding: " + characterEncoding);
StreamUtils.readByteArray(in, uniqueID);
StreamUtils.readByteArray(in, fileVersion);
StreamUtils.readByteArray(in, orthographicIndex);
StreamUtils.readByteArray(in, inflectionIndex);
StreamUtils.readByteArray(in, indexNames);
StreamUtils.readByteArray(in, indexKeys);
StreamUtils.readByteArray(in, extraIndex0);
StreamUtils.readByteArray(in, extraIndex1);
StreamUtils.readByteArray(in, extraIndex2);
StreamUtils.readByteArray(in, extraIndex3);
StreamUtils.readByteArray(in, extraIndex4);
StreamUtils.readByteArray(in, extraIndex5);
StreamUtils.readByteArray(in, firstNonBookIndex);
StreamUtils.readByteArray(in, fullNameOffset);
if (MobiCommon.debug)
{
MobiCommon.logMessage("full name offset: "
+ StreamUtils.byteArrayToInt(fullNameOffset));
}
StreamUtils.readByteArray(in, fullNameLength);
int fullNameLen = StreamUtils.byteArrayToInt(fullNameLength);
MobiCommon.logMessage("full name length: " + fullNameLen);
StreamUtils.readByteArray(in, locale);
StreamUtils.readByteArray(in, inputLanguage);
StreamUtils.readByteArray(in, outputLanguage);
StreamUtils.readByteArray(in, minVersion);
StreamUtils.readByteArray(in, firstImageIndex);
StreamUtils.readByteArray(in, huffmanRecordOffset);
StreamUtils.readByteArray(in, huffmanRecordCount);
StreamUtils.readByteArray(in, huffmanTableOffset);
StreamUtils.readByteArray(in, huffmanTableLength);
StreamUtils.readByteArray(in, exthFlags);
if (MobiCommon.debug)
{
MobiCommon.logMessage("exthFlags: "
+ StreamUtils.byteArrayToInt(exthFlags));
}
boolean exthExists = ((StreamUtils.byteArrayToInt(exthFlags) & 0x40)
!= 0);
MobiCommon.logMessage("exthExists: " + exthExists);
StreamUtils.readByteArray(in, restOfMobiHeader);
if (exthExists)
{
exthHeader = new EXTHHeader(in);
}
int currentOffset = 132 + restOfMobiHeader.length + exthHeaderSize();
remainder = new byte[(int)(mobiHeaderSize - currentOffset)];
StreamUtils.readByteArray(in, remainder);
int fullNameIndexInRemainder
= StreamUtils.byteArrayToInt(fullNameOffset) - currentOffset;
fullName = new byte[fullNameLen];
MobiCommon.logMessage("fullNameIndexInRemainder: "
+ fullNameIndexInRemainder);
MobiCommon.logMessage("fullNameLen: " + fullNameLen);
if ((fullNameIndexInRemainder >= 0)
&&
(fullNameIndexInRemainder < remainder.length)
&&
((fullNameIndexInRemainder + fullNameLen) <= remainder.length)
&&
(fullNameLen > 0))
{
System.arraycopy(remainder,
fullNameIndexInRemainder,
fullName,
0,
fullNameLen);
}
if (MobiCommon.debug)
{
MobiCommon.logMessage("full name: "
+ StreamUtils.byteArrayToString(fullName));
}
}
public String getCharacterEncoding()
{
return characterEncoding;
}
public String getFullName()
{
return StreamUtils.byteArrayToString(fullName, characterEncoding);
}
public void setFullName(String s)
{
byte[] fullBytes = StreamUtils.stringToByteArray(s, characterEncoding);
int len = fullBytes.length;
StreamUtils.intToByteArray(len, fullNameLength);
// the string must be terminated by 2 null bytes
// then this must end in a 4-byte boundary
//
int padding = (len + 2) % 4;
if (padding != 0) padding = 4 - padding;
padding += 2;
byte[] buffer = new byte[len + padding];
System.arraycopy(fullBytes, 0, buffer, 0, len);
for (int i=len; i<buffer.length; i++) buffer[i] = 0;
fullName = buffer;
}
public int getLocale()
{
return StreamUtils.byteArrayToInt(locale);
}
public void setLocale(int localeInt)
{
StreamUtils.intToByteArray(localeInt, locale);
}
public int getInputLanguage()
{
return StreamUtils.byteArrayToInt(inputLanguage);
}
public void setInputLanguage(int input)
{
StreamUtils.intToByteArray(input, inputLanguage);
}
public int getOutputLanguage()
{
return StreamUtils.byteArrayToInt(outputLanguage);
}
public void setOutputLanguage(int output)
{
StreamUtils.intToByteArray(output, outputLanguage);
}
public List<EXTHRecord> getEXTHRecords()
{
return (exthHeader == null) ? (new LinkedList<EXTHRecord>())
: exthHeader.getRecordList();
}
public void setEXTHRecords(List<EXTHRecord> list)
{
int flag = StreamUtils.byteArrayToInt(exthFlags) & 0xffffbf;
if ((list == null) || (list.size() == 0))
{
exthHeader = null;
StreamUtils.intToByteArray(flag, exthFlags);
}
else
{
if (exthHeader == null)
exthHeader = new EXTHHeader(list);
else
exthHeader.setRecordList(list);
StreamUtils.intToByteArray(flag | 0x40, exthFlags);
}
}
public void pack()
{
if (!MobiCommon.safeMode)
{
// dump existing remainder, set to fullName
remainder = new byte[fullName.length];
System.arraycopy(fullName, 0, remainder, 0, remainder.length);
// adjust fullNameOffset
StreamUtils.intToByteArray(132 + restOfMobiHeader.length
+ exthHeaderSize(), fullNameOffset);
}
}
public int size()
{
return 132 + restOfMobiHeader.length + exthHeaderSize() + remainder.length;
}
public String getCompression()
{
int comp = StreamUtils.byteArrayToInt(compression);
switch (comp)
{
case 1:
return "None";
case 2:
return "PalmDOC";
case 17480:
return "HUFF/CDIC";
default:
return "Unknown (" + comp + ")";
}
}
public long getTextLength()
{
return StreamUtils.byteArrayToLong(textLength);
}
public int getRecordCount()
{
return StreamUtils.byteArrayToInt(recordCount);
}
public int getRecordSize()
{
return StreamUtils.byteArrayToInt(recordSize);
}
public String getEncryptionType()
{
int enc = StreamUtils.byteArrayToInt(encryptionType);
switch (enc)
{
case 0: return "None";
case 1: return "Old Mobipocket";
case 2: return "Mobipocket";
default: return "Unknown (" + enc + ")";
}
}
public long getHeaderLength()
{
return StreamUtils.byteArrayToLong(headerLength);
}
public String getMobiType()
{
long type = StreamUtils.byteArrayToLong(mobiType);
if (type == 2)
return "Mobipocket Book";
else if (type == 3)
return "PalmDoc Book";
else if (type == 4)
return "Audio";
else if (type == 257)
return "News";
else if (type == 258)
return "News Feed";
else if (type == 259)
return "News Magazine";
else if (type == 513)
return "PICS";
else if (type == 514)
return "WORD";
else if (type == 515)
return "XLS";
else if (type == 516)
return "PPT";
else if (type == 517)
return "TEXT";
else if (type == 518)
return "HTML";
else
return "Unknown (" + type + ")";
}
public long getUniqueID()
{
return StreamUtils.byteArrayToLong(uniqueID);
}
public long getFileVersion()
{
return StreamUtils.byteArrayToLong(fileVersion);
}
public long getOrthographicIndex()
{
return StreamUtils.byteArrayToLong(orthographicIndex);
}
public long getInflectionIndex()
{
return StreamUtils.byteArrayToLong(inflectionIndex);
}
public long getIndexNames()
{
return StreamUtils.byteArrayToLong(indexNames);
}
public long getIndexKeys()
{
return StreamUtils.byteArrayToLong(indexKeys);
}
public long getExtraIndex0()
{
return StreamUtils.byteArrayToLong(extraIndex0);
}
public long getExtraIndex1()
{
return StreamUtils.byteArrayToLong(extraIndex1);
}
public long getExtraIndex2()
{
return StreamUtils.byteArrayToLong(extraIndex2);
}
public long getExtraIndex3()
{
return StreamUtils.byteArrayToLong(extraIndex3);
}
public long getExtraIndex4()
{
return StreamUtils.byteArrayToLong(extraIndex4);
}
public long getExtraIndex5()
{
return StreamUtils.byteArrayToLong(extraIndex5);
}
public long getFirstNonBookIndex()
{
return StreamUtils.byteArrayToLong(firstNonBookIndex);
}
public long getFullNameOffset()
{
return StreamUtils.byteArrayToLong(fullNameOffset);
}
public long getFullNameLength()
{
return StreamUtils.byteArrayToLong(fullNameLength);
}
public long getMinVersion()
{
return StreamUtils.byteArrayToLong(minVersion);
}
public long getHuffmanRecordOffset()
{
return StreamUtils.byteArrayToLong(huffmanRecordOffset);
}
public long getHuffmanRecordCount()
{
return StreamUtils.byteArrayToLong(huffmanRecordCount);
}
public long getHuffmanTableOffset()
{
return StreamUtils.byteArrayToLong(huffmanTableOffset);
}
public long getHuffmanTableLength()
{
return StreamUtils.byteArrayToLong(huffmanTableLength);
}
private int exthHeaderSize()
{
return (exthHeader == null)?0:exthHeader.size();
}
public void write(OutputStream out) throws IOException
{
out.write(compression);
out.write(unused0);
out.write(textLength);
out.write(recordCount);
out.write(recordSize);
out.write(encryptionType);
out.write(unused1);
out.write(identifier);
out.write(headerLength);
out.write(mobiType);
out.write(textEncoding);
out.write(uniqueID);
out.write(fileVersion);
out.write(orthographicIndex);
out.write(inflectionIndex);
out.write(indexNames);
out.write(indexKeys);
out.write(extraIndex0);
out.write(extraIndex1);
out.write(extraIndex2);
out.write(extraIndex3);
out.write(extraIndex4);
out.write(extraIndex5);
out.write(firstNonBookIndex);
out.write(fullNameOffset);
out.write(fullNameLength);
out.write(locale);
out.write(inputLanguage);
out.write(outputLanguage);
out.write(minVersion);
out.write(firstImageIndex);
out.write(huffmanRecordOffset);
out.write(huffmanRecordCount);
out.write(huffmanTableOffset);
out.write(huffmanTableLength);
out.write(exthFlags);
out.write(restOfMobiHeader);
if (exthHeader != null) exthHeader.write(out);
out.write(remainder);
}
}