lollipop版本分开,有些新特性android5.0和6.0不支持
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package me.ag2s.umdlib.domain;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import me.ag2s.umdlib.tool.WrapOutputStream;
|
||||
|
||||
public class UmdBook {
|
||||
|
||||
public int getNum() {
|
||||
return num;
|
||||
}
|
||||
|
||||
public void setNum(int num) {
|
||||
this.num = num;
|
||||
}
|
||||
|
||||
private int num;
|
||||
|
||||
|
||||
/**
|
||||
* Header Part of UMD book
|
||||
*/
|
||||
private UmdHeader header = new UmdHeader();
|
||||
/**
|
||||
* Detail chapters Part of UMD book
|
||||
* (include Titles & Contents of each chapter)
|
||||
*/
|
||||
private UmdChapters chapters = new UmdChapters();
|
||||
|
||||
/**
|
||||
* Cover Part of UMD book (for example, and JPEG file)
|
||||
*/
|
||||
private UmdCover cover = new UmdCover();
|
||||
|
||||
/**
|
||||
* End Part of UMD book
|
||||
*/
|
||||
private UmdEnd end = new UmdEnd();
|
||||
|
||||
/**
|
||||
* Build the UMD file.
|
||||
*
|
||||
* @param os OutputStream
|
||||
* @throws IOException
|
||||
*/
|
||||
public void buildUmd(OutputStream os) throws IOException {
|
||||
WrapOutputStream wos = new WrapOutputStream(os);
|
||||
|
||||
header.buildHeader(wos);
|
||||
chapters.buildChapters(wos);
|
||||
cover.buildCover(wos);
|
||||
end.buildEnd(wos);
|
||||
}
|
||||
|
||||
public UmdHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public void setHeader(UmdHeader header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
public UmdChapters getChapters() {
|
||||
return chapters;
|
||||
}
|
||||
|
||||
public void setChapters(UmdChapters chapters) {
|
||||
this.chapters = chapters;
|
||||
}
|
||||
|
||||
public UmdCover getCover() {
|
||||
return cover;
|
||||
}
|
||||
|
||||
public void setCover(UmdCover cover) {
|
||||
this.cover = cover;
|
||||
}
|
||||
|
||||
public UmdEnd getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(UmdEnd end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package me.ag2s.umdlib.domain;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
|
||||
import me.ag2s.umdlib.tool.UmdUtils;
|
||||
import me.ag2s.umdlib.tool.WrapOutputStream;
|
||||
|
||||
/**
|
||||
* It includes all titles and contents of each chapter in the UMD file.
|
||||
* And the content has been compressed by zlib.
|
||||
*
|
||||
* @author Ray Liang (liangguanhui@qq.com)
|
||||
* 2009-12-20
|
||||
*/
|
||||
public class UmdChapters {
|
||||
|
||||
private static final int DEFAULT_CHUNK_INIT_SIZE = 32768;
|
||||
private int TotalContentLen;
|
||||
|
||||
public List<byte[]> getTitles() {
|
||||
return titles;
|
||||
}
|
||||
|
||||
private final List<byte[]> titles = new ArrayList<>();
|
||||
public List<Integer> contentLengths = new ArrayList<>();
|
||||
public ByteArrayOutputStream contents = new ByteArrayOutputStream();
|
||||
|
||||
public void addTitle(String s) {
|
||||
titles.add(UmdUtils.stringToUnicodeBytes(s));
|
||||
}
|
||||
|
||||
public void addTitle(byte[] s) {
|
||||
titles.add(s);
|
||||
}
|
||||
|
||||
public void addContentLength(Integer integer) {
|
||||
contentLengths.add(integer);
|
||||
}
|
||||
|
||||
public int getContentLength(int index) {
|
||||
return contentLengths.get(index);
|
||||
}
|
||||
|
||||
public byte[] getContent(int index) {
|
||||
int st = contentLengths.get(index);
|
||||
byte[] b = contents.toByteArray();
|
||||
int end = index + 1 < contentLengths.size() ? contentLengths.get(index + 1) : getTotalContentLen();
|
||||
System.out.println("总长度:" + contents.size());
|
||||
System.out.println("起始值:" + st);
|
||||
System.out.println("结束值:" + end);
|
||||
byte[] bAr = new byte[end - st];
|
||||
System.arraycopy(b, st, bAr, 0, bAr.length);
|
||||
return bAr;
|
||||
|
||||
}
|
||||
|
||||
public String getContentString(int index) {
|
||||
return UmdUtils.unicodeBytesToString(getContent(index)).replace((char) 0x2029, '\n');
|
||||
|
||||
}
|
||||
|
||||
public String getTitle(int index) {
|
||||
return UmdUtils.unicodeBytesToString(titles.get(index));
|
||||
}
|
||||
|
||||
|
||||
public void buildChapters(WrapOutputStream wos) throws IOException {
|
||||
writeChaptersHead(wos);
|
||||
writeChaptersContentOffset(wos);
|
||||
writeChaptersTitles(wos);
|
||||
writeChaptersChunks(wos);
|
||||
}
|
||||
|
||||
private void writeChaptersHead(WrapOutputStream wos) throws IOException {
|
||||
wos.writeBytes('#', 0x0b, 0, 0, 0x09);
|
||||
wos.writeInt(contents.size());
|
||||
}
|
||||
|
||||
private void writeChaptersContentOffset(WrapOutputStream wos) throws IOException {
|
||||
wos.writeBytes('#', 0x83, 0, 0, 0x09);
|
||||
byte[] rb = UmdUtils.genRandomBytes(4);
|
||||
wos.writeBytes(rb); //random numbers
|
||||
wos.write('$');
|
||||
wos.writeBytes(rb); //random numbers
|
||||
|
||||
wos.writeInt(contentLengths.size() * 4 + 9); // about the count of chapters
|
||||
int offset = 0;
|
||||
for (Integer n : contentLengths) {
|
||||
wos.writeInt(offset);
|
||||
offset += n;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeChaptersTitles(WrapOutputStream wos) throws IOException {
|
||||
wos.writeBytes('#', 0x84, 0, 0x01, 0x09);
|
||||
byte[] rb = UmdUtils.genRandomBytes(4);
|
||||
wos.writeBytes(rb); //random numbers
|
||||
wos.write('$');
|
||||
wos.writeBytes(rb); //random numbers
|
||||
|
||||
int totalTitlesLen = 0;
|
||||
for (byte[] t : titles) {
|
||||
totalTitlesLen += t.length;
|
||||
}
|
||||
|
||||
// about the length of the titles
|
||||
wos.writeInt(totalTitlesLen + titles.size() + 9);
|
||||
|
||||
for (byte[] t : titles) {
|
||||
wos.writeByte(t.length);
|
||||
wos.write(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeChaptersChunks(WrapOutputStream wos) throws IOException {
|
||||
byte[] allContents = contents.toByteArray();
|
||||
|
||||
byte[] zero16 = new byte[16];
|
||||
Arrays.fill(zero16, 0, zero16.length, (byte) 0);
|
||||
|
||||
// write each package of content
|
||||
int startPos = 0;
|
||||
int len = 0;
|
||||
int left = 0;
|
||||
int chunkCnt = 0;
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(DEFAULT_CHUNK_INIT_SIZE + 256);
|
||||
List<byte[]> chunkRbList = new ArrayList<>();
|
||||
|
||||
while (startPos < allContents.length) {
|
||||
left = allContents.length - startPos;
|
||||
len = Math.min(DEFAULT_CHUNK_INIT_SIZE, left);
|
||||
|
||||
bos.reset();
|
||||
DeflaterOutputStream zos = new DeflaterOutputStream(bos);
|
||||
zos.write(allContents, startPos, len);
|
||||
zos.close();
|
||||
byte[] chunk = bos.toByteArray();
|
||||
|
||||
byte[] rb = UmdUtils.genRandomBytes(4);
|
||||
wos.writeByte('$');
|
||||
wos.writeBytes(rb); // 4 random
|
||||
chunkRbList.add(rb);
|
||||
wos.writeInt(chunk.length + 9);
|
||||
wos.write(chunk);
|
||||
|
||||
// end of each chunk
|
||||
wos.writeBytes('#', 0xF1, 0, 0, 0x15);
|
||||
wos.write(zero16);
|
||||
|
||||
startPos += len;
|
||||
chunkCnt++;
|
||||
}
|
||||
|
||||
// end of all chunks
|
||||
wos.writeBytes('#', 0x81, 0, 0x01, 0x09);
|
||||
wos.writeBytes(0, 0, 0, 0); //random numbers
|
||||
wos.write('$');
|
||||
wos.writeBytes(0, 0, 0, 0); //random numbers
|
||||
wos.writeInt(chunkCnt * 4 + 9);
|
||||
for (int i = chunkCnt - 1; i >= 0; i--) {
|
||||
// random. They are as the same as random numbers in the begin of each chunk
|
||||
// use desc order to output these random
|
||||
wos.writeBytes(chunkRbList.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void addChapter(String title, String content) {
|
||||
titles.add(UmdUtils.stringToUnicodeBytes(title));
|
||||
byte[] b = UmdUtils.stringToUnicodeBytes(content);
|
||||
contentLengths.add(b.length);
|
||||
try {
|
||||
contents.write(b);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void addFile(File f, String title) throws IOException {
|
||||
byte[] temp = UmdUtils.readFile(f);
|
||||
String s = new String(temp);
|
||||
addChapter(title, s);
|
||||
}
|
||||
|
||||
public void addFile(File f) throws IOException {
|
||||
String s = f.getName();
|
||||
int idx = s.lastIndexOf('.');
|
||||
if (idx >= 0) {
|
||||
s = s.substring(0, idx);
|
||||
}
|
||||
addFile(f, s);
|
||||
}
|
||||
|
||||
public void clearChapters() {
|
||||
titles.clear();
|
||||
contentLengths.clear();
|
||||
contents.reset();
|
||||
}
|
||||
|
||||
public int getTotalContentLen() {
|
||||
return TotalContentLen;
|
||||
}
|
||||
|
||||
public void setTotalContentLen(int totalContentLen) {
|
||||
TotalContentLen = totalContentLen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package me.ag2s.umdlib.domain;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import me.ag2s.umdlib.tool.UmdUtils;
|
||||
import me.ag2s.umdlib.tool.WrapOutputStream;
|
||||
|
||||
|
||||
/**
|
||||
* This is the cover part of the UMD file.
|
||||
* <p>
|
||||
* NOTICE: if the "coverData" is empty, it will be skipped when building UMD file.
|
||||
* </P>
|
||||
* There are 3 ways to load the image data:
|
||||
* <ol>
|
||||
* <li>new constructor function of UmdCover.</li>
|
||||
* <li>use UmdCover.load function.</li>
|
||||
* <li>use UmdCover.initDefaultCover, it will generate a simple image with text.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Ray Liang (liangguanhui@qq.com)
|
||||
* 2009-12-20
|
||||
*/
|
||||
public class UmdCover {
|
||||
|
||||
private static final int DEFAULT_COVER_WIDTH = 120;
|
||||
private static final int DEFAULT_COVER_HEIGHT = 160;
|
||||
|
||||
private byte[] coverData;
|
||||
|
||||
public UmdCover() {
|
||||
}
|
||||
|
||||
public UmdCover(byte[] coverData) {
|
||||
this.coverData = coverData;
|
||||
}
|
||||
|
||||
public void load(File f) throws IOException {
|
||||
this.coverData = UmdUtils.readFile(f);
|
||||
}
|
||||
|
||||
public void load(String fileName) throws IOException {
|
||||
load(new File(fileName));
|
||||
}
|
||||
|
||||
public void initDefaultCover(String title) throws IOException {
|
||||
// BufferedImage img = new BufferedImage(DEFAULT_COVER_WIDTH, DEFAULT_COVER_HEIGHT, BufferedImage.TYPE_INT_RGB);
|
||||
// Graphics g = img.getGraphics();
|
||||
// g.setColor(Color.BLACK);
|
||||
// g.fillRect(0, 0, img.getWidth(), img.getHeight());
|
||||
// g.setColor(Color.WHITE);
|
||||
// g.setFont(new Font("����", Font.PLAIN, 12));
|
||||
//
|
||||
// FontMetrics fm = g.getFontMetrics();
|
||||
// int ascent = fm.getAscent();
|
||||
// int descent = fm.getDescent();
|
||||
// int strWidth = fm.stringWidth(title);
|
||||
// int x = (img.getWidth() - strWidth) / 2;
|
||||
// int y = (img.getHeight() - ascent - descent) / 2;
|
||||
// g.drawString(title, x, y);
|
||||
// g.dispose();
|
||||
//
|
||||
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
//
|
||||
// JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(baos);
|
||||
// JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(img);
|
||||
// param.setQuality(0.5f, false);
|
||||
// encoder.setJPEGEncodeParam(param);
|
||||
// encoder.encode(img);
|
||||
//
|
||||
// coverData = baos.toByteArray();
|
||||
}
|
||||
|
||||
public void buildCover(WrapOutputStream wos) throws IOException {
|
||||
if (coverData == null || coverData.length == 0) {
|
||||
return;
|
||||
}
|
||||
wos.writeBytes('#', 0x82, 0, 0x01, 0x0A, 0x01);
|
||||
byte[] rb = UmdUtils.genRandomBytes(4);
|
||||
wos.writeBytes(rb); //random numbers
|
||||
wos.write('$');
|
||||
wos.writeBytes(rb); //random numbers
|
||||
wos.writeInt(coverData.length + 9);
|
||||
wos.write(coverData);
|
||||
}
|
||||
|
||||
public byte[] getCoverData() {
|
||||
return coverData;
|
||||
}
|
||||
|
||||
public void setCoverData(byte[] coverData) {
|
||||
this.coverData = coverData;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package me.ag2s.umdlib.domain;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import me.ag2s.umdlib.tool.WrapOutputStream;
|
||||
|
||||
/**
|
||||
* End part of UMD book, nothing to be special
|
||||
*
|
||||
* @author Ray Liang (liangguanhui@qq.com)
|
||||
* 2009-12-20
|
||||
*/
|
||||
public class UmdEnd {
|
||||
|
||||
public void buildEnd(WrapOutputStream wos) throws IOException {
|
||||
wos.writeBytes('#', 0x0C, 0, 0x01, 0x09);
|
||||
wos.writeInt(wos.getWritten() + 4);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package me.ag2s.umdlib.domain;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import me.ag2s.umdlib.tool.UmdUtils;
|
||||
import me.ag2s.umdlib.tool.WrapOutputStream;
|
||||
|
||||
/**
|
||||
* Header of UMD file.
|
||||
* It includes a lot of properties of header.
|
||||
* All the properties are String type.
|
||||
*
|
||||
* @author Ray Liang (liangguanhui@qq.com)
|
||||
* 2009-12-20
|
||||
*/
|
||||
public class UmdHeader {
|
||||
public byte getUmdType() {
|
||||
return umdType;
|
||||
}
|
||||
|
||||
public void setUmdType(byte umdType) {
|
||||
this.umdType = umdType;
|
||||
}
|
||||
|
||||
private byte umdType;
|
||||
private String title;
|
||||
|
||||
private String author;
|
||||
|
||||
private String year;
|
||||
|
||||
private String month;
|
||||
|
||||
private String day;
|
||||
|
||||
private String bookType;
|
||||
|
||||
private String bookMan;
|
||||
|
||||
private String shopKeeper;
|
||||
private final static byte B_type_umd = (byte) 0x01;
|
||||
private final static byte B_type_title = (byte) 0x02;
|
||||
private final static byte B_type_author = (byte) 0x03;
|
||||
private final static byte B_type_year = (byte) 0x04;
|
||||
private final static byte B_type_month = (byte) 0x05;
|
||||
private final static byte B_type_day = (byte) 0x06;
|
||||
private final static byte B_type_bookType = (byte) 0x07;
|
||||
private final static byte B_type_bookMan = (byte) 0x08;
|
||||
private final static byte B_type_shopKeeper = (byte) 0x09;
|
||||
|
||||
public void buildHeader(WrapOutputStream wos) throws IOException {
|
||||
wos.writeBytes(0x89, 0x9b, 0x9a, 0xde); // UMD file type flags
|
||||
wos.writeByte('#');
|
||||
wos.writeBytes(0x01, 0x00, 0x00, 0x08); // Unknown
|
||||
wos.writeByte(0x01); //0x01 is text type; while 0x02 is Image type.
|
||||
wos.writeBytes(UmdUtils.genRandomBytes(2)); //random number
|
||||
|
||||
// start properties output
|
||||
buildType(wos, B_type_title, getTitle());
|
||||
buildType(wos, B_type_author, getAuthor());
|
||||
buildType(wos, B_type_year, getYear());
|
||||
buildType(wos, B_type_month, getMonth());
|
||||
buildType(wos, B_type_day, getDay());
|
||||
buildType(wos, B_type_bookType, getBookType());
|
||||
buildType(wos, B_type_bookMan, getBookMan());
|
||||
buildType(wos, B_type_shopKeeper, getShopKeeper());
|
||||
}
|
||||
|
||||
public void buildType(WrapOutputStream wos, byte type, String content) throws IOException {
|
||||
if (content == null || content.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
wos.writeBytes('#', type, 0, 0);
|
||||
|
||||
byte[] temp = UmdUtils.stringToUnicodeBytes(content);
|
||||
wos.writeByte(temp.length + 5);
|
||||
wos.write(temp);
|
||||
}
|
||||
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getBookMan() {
|
||||
return bookMan;
|
||||
}
|
||||
|
||||
public void setBookMan(String bookMan) {
|
||||
this.bookMan = bookMan;
|
||||
}
|
||||
|
||||
public String getShopKeeper() {
|
||||
return shopKeeper;
|
||||
}
|
||||
|
||||
public void setShopKeeper(String shopKeeper) {
|
||||
this.shopKeeper = shopKeeper;
|
||||
}
|
||||
|
||||
public String getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public void setYear(String year) {
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public String getMonth() {
|
||||
return month;
|
||||
}
|
||||
|
||||
public void setMonth(String month) {
|
||||
this.month = month;
|
||||
}
|
||||
|
||||
public String getDay() {
|
||||
return day;
|
||||
}
|
||||
|
||||
public void setDay(String day) {
|
||||
this.day = day;
|
||||
}
|
||||
|
||||
public String getBookType() {
|
||||
return bookType;
|
||||
}
|
||||
|
||||
public void setBookType(String bookType) {
|
||||
this.bookType = bookType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return "UmdHeader{" +
|
||||
"umdType=" + umdType +
|
||||
", title='" + title + '\'' +
|
||||
", author='" + author + '\'' +
|
||||
", year='" + year + '\'' +
|
||||
", month='" + month + '\'' +
|
||||
", day='" + day + '\'' +
|
||||
", bookType='" + bookType + '\'' +
|
||||
", bookMan='" + bookMan + '\'' +
|
||||
", shopKeeper='" + shopKeeper + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package me.ag2s.umdlib.tool;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class StreamReader {
|
||||
private final InputStream is;
|
||||
|
||||
public long getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(long offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
private long offset;
|
||||
private long size;
|
||||
|
||||
private void incCount(int value) {
|
||||
int temp = (int) (offset + value);
|
||||
if (temp < 0) {
|
||||
temp = Integer.MAX_VALUE;
|
||||
}
|
||||
offset = temp;
|
||||
}
|
||||
|
||||
public StreamReader(InputStream inputStream) throws IOException {
|
||||
this.is = inputStream;
|
||||
//this.size=inputStream.getChannel().size();
|
||||
}
|
||||
|
||||
public short readUint8() throws IOException {
|
||||
byte[] b = new byte[1];
|
||||
is.read(b);
|
||||
incCount(1);
|
||||
return (short) ((b[0] & 0xFF));
|
||||
|
||||
}
|
||||
|
||||
public byte readByte() throws IOException {
|
||||
byte[] b = new byte[1];
|
||||
is.read(b);
|
||||
incCount(1);
|
||||
return b[0];
|
||||
}
|
||||
|
||||
public byte[] readBytes(int len) throws IOException {
|
||||
if (len < 1) {
|
||||
System.out.println(len);
|
||||
throw new IllegalArgumentException("Length must > 0: " + len);
|
||||
}
|
||||
byte[] b = new byte[len];
|
||||
is.read(b);
|
||||
incCount(len);
|
||||
return b;
|
||||
}
|
||||
|
||||
public String readHex(int len) throws IOException {
|
||||
if (len < 1) {
|
||||
System.out.println(len);
|
||||
throw new IllegalArgumentException("Length must > 0: " + len);
|
||||
}
|
||||
byte[] b = new byte[len];
|
||||
is.read(b);
|
||||
incCount(len);
|
||||
return UmdUtils.toHex(b);
|
||||
}
|
||||
|
||||
public short readShort() throws IOException {
|
||||
byte[] b = new byte[2];
|
||||
is.read(b);
|
||||
incCount(2);
|
||||
short x = (short) (((b[0] & 0xFF) << 8) | ((b[1] & 0xFF) << 0));
|
||||
return x;
|
||||
}
|
||||
|
||||
public short readShortLe() throws IOException {
|
||||
byte[] b = new byte[2];
|
||||
is.read(b);
|
||||
incCount(2);
|
||||
short x = (short) (((b[1] & 0xFF) << 8) | ((b[0] & 0xFF) << 0));
|
||||
return x;
|
||||
}
|
||||
|
||||
public int readInt() throws IOException {
|
||||
byte[] b = new byte[4];
|
||||
is.read(b);
|
||||
incCount(4);
|
||||
int x = ((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) |
|
||||
((b[2] & 0xFF) << 8) | ((b[3] & 0xFF) << 0);
|
||||
return x;
|
||||
}
|
||||
|
||||
public int readIntLe() throws IOException {
|
||||
byte[] b = new byte[4];
|
||||
is.read(b);
|
||||
incCount(4);
|
||||
int x = ((b[3] & 0xFF) << 24) | ((b[2] & 0xFF) << 16) |
|
||||
((b[1] & 0xFF) << 8) | ((b[0] & 0xFF) << 0);
|
||||
return x;
|
||||
}
|
||||
|
||||
public void skip(int len) throws IOException {
|
||||
readBytes(len);
|
||||
}
|
||||
|
||||
|
||||
public byte[] read(byte[] b) throws IOException {
|
||||
is.read(b);
|
||||
incCount(b.length);
|
||||
return b;
|
||||
}
|
||||
|
||||
public byte[] read(byte[] b, int off, int len) throws IOException {
|
||||
is.read(b, off, len);
|
||||
incCount(len);
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
|
||||
package me.ag2s.umdlib.tool;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
|
||||
public class UmdUtils {
|
||||
|
||||
private static final int EOF = -1;
|
||||
private static final int BUFFER_SIZE = 8 * 1024;
|
||||
|
||||
|
||||
/**
|
||||
* 将字符串编码成Unicode形式的byte[]
|
||||
*
|
||||
* @param s 要编码的字符串
|
||||
* @return 编码好的byte[]
|
||||
*/
|
||||
public static byte[] stringToUnicodeBytes(String s) {
|
||||
if (s == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
|
||||
int len = s.length();
|
||||
byte[] ret = new byte[len * 2];
|
||||
int a, b, c;
|
||||
for (int i = 0; i < len; i++) {
|
||||
c = s.charAt(i);
|
||||
a = c >> 8;
|
||||
b = c & 0xFF;
|
||||
if (a < 0) {
|
||||
a += 0xFF;
|
||||
}
|
||||
if (b < 0) {
|
||||
b += 0xFF;
|
||||
}
|
||||
ret[i * 2] = (byte) b;
|
||||
ret[i * 2 + 1] = (byte) a;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将编码成Unicode形式的byte[]解码成原始字符串
|
||||
*
|
||||
* @param bytes 编码成Unicode形式的byte[]
|
||||
* @return 原始字符串
|
||||
*/
|
||||
public static String unicodeBytesToString(byte[] bytes) {
|
||||
char[] s = new char[bytes.length / 2];
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int a, b, c;
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
a = bytes[i * 2 + 1];
|
||||
b = bytes[i * 2];
|
||||
c = (a & 0xff) << 8 | (b & 0xff);
|
||||
if (c < 0) {
|
||||
c += 0xffff;
|
||||
}
|
||||
char[] c1 = Character.toChars(c);
|
||||
sb.append(c1);
|
||||
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将byte[]转化成Hex形式
|
||||
*
|
||||
* @param bArr byte[]
|
||||
* @return 目标HEX字符串
|
||||
*/
|
||||
public static String toHex(byte[] bArr) {
|
||||
StringBuilder sb = new StringBuilder(bArr.length);
|
||||
String sTmp;
|
||||
|
||||
for (int i = 0; i < bArr.length; i++) {
|
||||
sTmp = Integer.toHexString(0xFF & bArr[i]);
|
||||
if (sTmp.length() < 2)
|
||||
sb.append(0);
|
||||
sb.append(sTmp.toUpperCase());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压缩zip的byte[]
|
||||
*
|
||||
* @param compress zippered byte[]
|
||||
* @return decompressed byte[]
|
||||
* @throws Exception 解码时失败时
|
||||
*/
|
||||
public static byte[] decompress(byte[] compress) throws Exception {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(compress);
|
||||
InflaterInputStream iis = new InflaterInputStream(bais);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
int c = 0;
|
||||
byte[] buf = new byte[BUFFER_SIZE];
|
||||
while (true) {
|
||||
c = iis.read(buf);
|
||||
|
||||
if (c == EOF)
|
||||
break;
|
||||
baos.write(buf, 0, c);
|
||||
}
|
||||
baos.flush();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
public static void saveFile(File f, byte[] content) throws IOException {
|
||||
try (FileOutputStream fos = new FileOutputStream(f)) {
|
||||
BufferedOutputStream bos = new BufferedOutputStream(fos);
|
||||
bos.write(content);
|
||||
bos.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readFile(File f) throws IOException {
|
||||
try (FileInputStream fis = new FileInputStream(f)) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
BufferedInputStream bis = new BufferedInputStream(fis);
|
||||
int ch;
|
||||
while ((ch = bis.read()) >= 0) {
|
||||
baos.write(ch);
|
||||
}
|
||||
baos.flush();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static final Random random = new Random();
|
||||
|
||||
public static byte[] genRandomBytes(int len) {
|
||||
if (len <= 0) {
|
||||
throw new IllegalArgumentException("Length must > 0: " + len);
|
||||
}
|
||||
byte[] ret = new byte[len];
|
||||
for (int i = 0; i < ret.length; i++) {
|
||||
ret[i] = (byte) random.nextInt(256);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package me.ag2s.umdlib.tool;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class WrapOutputStream extends OutputStream {
|
||||
|
||||
private final OutputStream os;
|
||||
private int written;
|
||||
|
||||
public WrapOutputStream(OutputStream os) {
|
||||
this.os = os;
|
||||
}
|
||||
|
||||
private void incCount(int value) {
|
||||
int temp = written + value;
|
||||
if (temp < 0) {
|
||||
temp = Integer.MAX_VALUE;
|
||||
}
|
||||
written = temp;
|
||||
}
|
||||
|
||||
// it is different from the writeInt of DataOutputStream
|
||||
public void writeInt(int v) throws IOException {
|
||||
os.write((v >>> 0) & 0xFF);
|
||||
os.write((v >>> 8) & 0xFF);
|
||||
os.write((v >>> 16) & 0xFF);
|
||||
os.write((v >>> 24) & 0xFF);
|
||||
incCount(4);
|
||||
}
|
||||
|
||||
public void writeByte(byte b) throws IOException {
|
||||
write(b);
|
||||
}
|
||||
|
||||
public void writeByte(int n) throws IOException {
|
||||
write(n);
|
||||
}
|
||||
|
||||
public void writeBytes(byte... bytes) throws IOException {
|
||||
write(bytes);
|
||||
}
|
||||
|
||||
public void writeBytes(int... vals) throws IOException {
|
||||
for (int v : vals) {
|
||||
write(v);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
os.write(b, off, len);
|
||||
incCount(len);
|
||||
}
|
||||
|
||||
public void write(byte[] b) throws IOException {
|
||||
os.write(b);
|
||||
incCount(b.length);
|
||||
}
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
os.write(b);
|
||||
incCount(1);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
public void close() throws IOException {
|
||||
os.close();
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
os.flush();
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
return os.equals(obj);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return os.hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return os.toString();
|
||||
}
|
||||
|
||||
public int getWritten() {
|
||||
return written;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package me.ag2s.umdlib.umd;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import me.ag2s.umdlib.domain.UmdBook;
|
||||
import me.ag2s.umdlib.domain.UmdCover;
|
||||
import me.ag2s.umdlib.domain.UmdHeader;
|
||||
import me.ag2s.umdlib.tool.StreamReader;
|
||||
import me.ag2s.umdlib.tool.UmdUtils;
|
||||
|
||||
/**
|
||||
* UMD格式的电子书解析
|
||||
* 格式规范参考:
|
||||
* http://blog.sina.com.cn/s/blog_7c8dc2d501018o5d.html
|
||||
* http://blog.sina.com.cn/s/blog_7c8dc2d501018o5l.html
|
||||
*/
|
||||
public class UmdReader {
|
||||
UmdBook book;
|
||||
InputStream inputStream;
|
||||
int _AdditionalCheckNumber;
|
||||
int _TotalContentLen;
|
||||
boolean end = false;
|
||||
|
||||
|
||||
public synchronized UmdBook read(InputStream inputStream) throws Exception {
|
||||
|
||||
book = new UmdBook();
|
||||
this.inputStream = inputStream;
|
||||
StreamReader reader = new StreamReader(inputStream);
|
||||
UmdHeader umdHeader = new UmdHeader();
|
||||
book.setHeader(umdHeader);
|
||||
if (reader.readIntLe() != 0xde9a9b89) {
|
||||
throw new IOException("Wrong header");
|
||||
}
|
||||
short num1 = -1;
|
||||
byte ch = reader.readByte();
|
||||
while (ch == 35) {
|
||||
//int num2=reader.readByte();
|
||||
short segType = reader.readShortLe();
|
||||
byte segFlag = reader.readByte();
|
||||
short len = (short) (reader.readUint8() - 5);
|
||||
|
||||
System.out.println("块标识:" + segType);
|
||||
//short length1 = reader.readByte();
|
||||
readSection(segType, segFlag, len, reader, umdHeader);
|
||||
|
||||
if ((int) segType == 241 || (int) segType == 10) {
|
||||
segType = num1;
|
||||
}
|
||||
for (ch = reader.readByte(); ch == 36; ch = reader.readByte()) {
|
||||
//int num3 = reader.readByte();
|
||||
System.out.println(ch);
|
||||
int additionalCheckNumber = reader.readIntLe();
|
||||
int length2 = (reader.readIntLe() - 9);
|
||||
readAdditionalSection(segType, additionalCheckNumber, length2, reader);
|
||||
}
|
||||
num1 = segType;
|
||||
|
||||
}
|
||||
System.out.println(book.getHeader().toString());
|
||||
return book;
|
||||
|
||||
}
|
||||
|
||||
private void readAdditionalSection(short segType, int additionalCheckNumber, int length, StreamReader reader) throws Exception {
|
||||
switch (segType) {
|
||||
case 14:
|
||||
//this._TotalImageList.Add((object) Image.FromStream((Stream) new MemoryStream(reader.ReadBytes((int) length))));
|
||||
break;
|
||||
case 15:
|
||||
//this._TotalImageList.Add((object) Image.FromStream((Stream) new MemoryStream(reader.ReadBytes((int) length))));
|
||||
break;
|
||||
case 129:
|
||||
reader.readBytes(length);
|
||||
break;
|
||||
case 130:
|
||||
//byte[] covers = reader.readBytes(length);
|
||||
book.setCover(new UmdCover(reader.readBytes(length)));
|
||||
//this._Book.Cover = BitmapImage.FromStream((Stream) new MemoryStream(reader.ReadBytes((int) length)));
|
||||
break;
|
||||
case 131:
|
||||
System.out.println(length / 4);
|
||||
book.setNum(length / 4);
|
||||
for (int i = 0; i < length / 4; ++i) {
|
||||
book.getChapters().addContentLength(reader.readIntLe());
|
||||
}
|
||||
break;
|
||||
case 132:
|
||||
//System.out.println(length/4);
|
||||
System.out.println(_AdditionalCheckNumber);
|
||||
System.out.println(additionalCheckNumber);
|
||||
if (this._AdditionalCheckNumber != additionalCheckNumber) {
|
||||
System.out.println(length);
|
||||
book.getChapters().contents.write(UmdUtils.decompress(reader.readBytes(length)));
|
||||
book.getChapters().contents.flush();
|
||||
break;
|
||||
} else {
|
||||
for (int i = 0; i < book.getNum(); i++) {
|
||||
short len = reader.readUint8();
|
||||
byte[] title = reader.readBytes(len);
|
||||
//System.out.println(UmdUtils.unicodeBytesToString(title));
|
||||
book.getChapters().addTitle(title);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
default:
|
||||
/*Console.WriteLine("未知内容");
|
||||
Console.WriteLine("Seg Type = " + (object) segType);
|
||||
Console.WriteLine("Seg Len = " + (object) length);
|
||||
Console.WriteLine("content = " + (object) reader.ReadBytes((int) length));*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void readSection(short segType, byte segFlag, short length, StreamReader reader, UmdHeader header) throws IOException {
|
||||
switch (segType) {
|
||||
case 1://umd文件头 DCTS_CMD_ID_VERSION
|
||||
header.setUmdType(reader.readByte());
|
||||
reader.readBytes(2);//Random 2
|
||||
System.out.println("UMD文件类型:" + header.getUmdType());
|
||||
break;
|
||||
case 2://文件标题 DCTS_CMD_ID_TITLE
|
||||
header.setTitle(UmdUtils.unicodeBytesToString(reader.readBytes(length)));
|
||||
System.out.println("文件标题:" + header.getTitle());
|
||||
break;
|
||||
case 3://作者
|
||||
header.setAuthor(UmdUtils.unicodeBytesToString(reader.readBytes(length)));
|
||||
System.out.println("作者:" + header.getAuthor());
|
||||
break;
|
||||
case 4://年
|
||||
header.setYear(UmdUtils.unicodeBytesToString(reader.readBytes(length)));
|
||||
System.out.println("年:" + header.getYear());
|
||||
break;
|
||||
case 5://月
|
||||
header.setMonth(UmdUtils.unicodeBytesToString(reader.readBytes(length)));
|
||||
System.out.println("月:" + header.getMonth());
|
||||
break;
|
||||
case 6://日
|
||||
header.setDay(UmdUtils.unicodeBytesToString(reader.readBytes(length)));
|
||||
System.out.println("日:" + header.getDay());
|
||||
break;
|
||||
case 7://小说类型
|
||||
header.setBookType(UmdUtils.unicodeBytesToString(reader.readBytes(length)));
|
||||
System.out.println("小说类型:" + header.getBookType());
|
||||
break;
|
||||
case 8://出版商
|
||||
header.setBookMan(UmdUtils.unicodeBytesToString(reader.readBytes(length)));
|
||||
System.out.println("出版商:" + header.getBookMan());
|
||||
break;
|
||||
case 9:// 零售商
|
||||
header.setShopKeeper(UmdUtils.unicodeBytesToString(reader.readBytes(length)));
|
||||
System.out.println("零售商:" + header.getShopKeeper());
|
||||
break;
|
||||
case 10://CONTENT ID
|
||||
System.out.println("CONTENT ID:" + reader.readHex(length));
|
||||
break;
|
||||
case 11:
|
||||
//内容长度 DCTS_CMD_ID_FILE_LENGTH
|
||||
_TotalContentLen = reader.readIntLe();
|
||||
book.getChapters().setTotalContentLen(_TotalContentLen);
|
||||
System.out.println("内容长度:" + _TotalContentLen);
|
||||
break;
|
||||
case 12://UMD文件结束
|
||||
end = true;
|
||||
int num2 = reader.readIntLe();
|
||||
System.out.println("整个文件长度" + num2);
|
||||
break;
|
||||
case 13:
|
||||
break;
|
||||
case 14:
|
||||
int num3 = reader.readByte();
|
||||
break;
|
||||
case 15:
|
||||
reader.readBytes(length);
|
||||
break;
|
||||
case 129://正文
|
||||
case 131://章节偏移
|
||||
_AdditionalCheckNumber = reader.readIntLe();
|
||||
System.out.println("章节偏移:" + _AdditionalCheckNumber);
|
||||
break;
|
||||
case 132://章节标题,正文
|
||||
_AdditionalCheckNumber = reader.readIntLe();
|
||||
System.out.println("章节标题,正文:" + _AdditionalCheckNumber);
|
||||
break;
|
||||
case 130://封面(jpg)
|
||||
int num4 = reader.readByte();
|
||||
_AdditionalCheckNumber = reader.readIntLe();
|
||||
break;
|
||||
case 135://页面偏移(Page Offset)
|
||||
reader.readUint8();//fontSize 一字节 字体大小
|
||||
reader.readUint8();//screenWidth 屏幕宽度
|
||||
reader.readBytes(4);//BlockRandom 指向一个页面偏移数据块
|
||||
break;
|
||||
case 240://CDS KEY
|
||||
break;
|
||||
case 241://许可证(LICENCE KEY)
|
||||
//System.out.println("整个文件长度" + length);
|
||||
System.out.println("许可证(LICENCE KEY):" + reader.readHex(16));
|
||||
break;
|
||||
default:
|
||||
if (length > 0) {
|
||||
byte[] numArray = reader.readBytes(length);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return "UmdReader{" +
|
||||
"book=" + book +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user