commit
7b35a0dc84
@ -0,0 +1,138 @@ |
||||
package io.legado.app.model.localBook |
||||
|
||||
import android.net.Uri |
||||
import android.util.Log |
||||
import io.legado.app.data.entities.Book |
||||
import io.legado.app.data.entities.BookChapter |
||||
|
||||
import io.legado.app.utils.FileUtils |
||||
import io.legado.app.utils.MD5Utils |
||||
import io.legado.app.utils.externalFilesDir |
||||
import io.legado.app.utils.isContentScheme |
||||
import me.ag2s.umdlib.domain.UmdBook |
||||
import me.ag2s.umdlib.umd.UmdReader |
||||
import splitties.init.appCtx |
||||
import java.io.File |
||||
import java.io.InputStream |
||||
import java.util.ArrayList |
||||
|
||||
class UmdFile(var book: Book) { |
||||
companion object { |
||||
private var uFile: UmdFile? = null |
||||
|
||||
@Synchronized |
||||
private fun getUFile(book: Book): UmdFile { |
||||
|
||||
if (uFile == null || uFile?.book?.bookUrl != book.bookUrl) { |
||||
uFile = UmdFile(book) |
||||
return uFile!! |
||||
} |
||||
uFile?.book = book |
||||
return uFile!! |
||||
} |
||||
|
||||
@Synchronized |
||||
fun getChapterList(book: Book): ArrayList<BookChapter> { |
||||
return getUFile(book).getChapterList() |
||||
} |
||||
|
||||
@Synchronized |
||||
fun getContent(book: Book, chapter: BookChapter): String? { |
||||
return getUFile(book).getContent(chapter) |
||||
} |
||||
|
||||
@Synchronized |
||||
fun getImage( |
||||
book: Book, |
||||
href: String |
||||
): InputStream? { |
||||
return getUFile(book).getImage(href) |
||||
} |
||||
|
||||
|
||||
@Synchronized |
||||
fun upBookInfo(book: Book) { |
||||
return getUFile(book).upBookInfo() |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
private var umdBook: UmdBook? = null |
||||
get() { |
||||
if (field != null) { |
||||
return field |
||||
} |
||||
field = readUmd() |
||||
return field |
||||
} |
||||
|
||||
|
||||
|
||||
init { |
||||
try { |
||||
umdBook?.let { |
||||
if (book.coverUrl.isNullOrEmpty()) { |
||||
book.coverUrl = FileUtils.getPath( |
||||
appCtx.externalFilesDir, |
||||
"covers", |
||||
"${MD5Utils.md5Encode16(book.bookUrl)}.jpg" |
||||
) |
||||
} |
||||
if (!File(book.coverUrl!!).exists()) { |
||||
FileUtils.writeBytes(book.coverUrl!!,it.cover.coverData) |
||||
|
||||
} |
||||
} |
||||
} catch (e: Exception) { |
||||
e.printStackTrace() |
||||
} |
||||
} |
||||
private fun readUmd(): UmdBook? { |
||||
val input= if (book.bookUrl.isContentScheme()) { |
||||
val uri = Uri.parse(book.bookUrl) |
||||
appCtx.contentResolver.openInputStream(uri) |
||||
} else { |
||||
File(book.bookUrl).inputStream() |
||||
} |
||||
return UmdReader().read(input) |
||||
} |
||||
|
||||
private fun upBookInfo() { |
||||
if(umdBook==null){ |
||||
uFile = null |
||||
book.intro = "书籍导入异常" |
||||
}else{ |
||||
val hd= umdBook!!.header |
||||
book.name=hd.title; |
||||
book.author=hd.author; |
||||
book.kind=hd.bookType; |
||||
} |
||||
} |
||||
private fun getContent(chapter: BookChapter): String? { |
||||
return umdBook?.chapters?.getContentString(chapter.index) |
||||
} |
||||
|
||||
private fun getChapterList(): ArrayList<BookChapter> { |
||||
val chapterList = ArrayList<BookChapter>() |
||||
umdBook?.chapters?.titles?.forEachIndexed { index, _ -> |
||||
val title = umdBook!!.chapters.getTitle(index) |
||||
val chapter = BookChapter() |
||||
chapter.title=title; |
||||
chapter.index = index |
||||
chapter.bookUrl = book.bookUrl |
||||
chapter.url = index.toString(); |
||||
Log.d("UMD",chapter.url) |
||||
chapterList.add(chapter) |
||||
} |
||||
book.latestChapterTitle = chapterList.lastOrNull()?.title |
||||
book.totalChapterNum = chapterList.size |
||||
return chapterList |
||||
} |
||||
|
||||
private fun getImage(href: String): InputStream? { |
||||
TODO("Not yet implemented") |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,81 @@ |
||||
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 |
||||
* @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,207 @@ |
||||
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 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<byte[]>(); |
||||
|
||||
while(startPos < allContents.length) { |
||||
left = allContents.length - startPos; |
||||
len = DEFAULT_CHUNK_INIT_SIZE < left ? 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,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,162 @@ |
||||
package me.ag2s.umdlib.domain; |
||||
|
||||
|
||||
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 |
||||
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,124 @@ |
||||
package me.ag2s.umdlib.tool; |
||||
|
||||
import java.io.FileInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
|
||||
public class StreamReader { |
||||
private 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,159 @@ |
||||
|
||||
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 { |
||||
FileOutputStream fos = new FileOutputStream(f); |
||||
try { |
||||
BufferedOutputStream bos = new BufferedOutputStream(fos); |
||||
bos.write(content); |
||||
bos.flush(); |
||||
} finally { |
||||
fos.close(); |
||||
} |
||||
} |
||||
|
||||
public static byte[] readFile(File f) throws IOException { |
||||
FileInputStream fis = new FileInputStream(f); |
||||
try { |
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
||||
BufferedInputStream bis = new BufferedInputStream(fis); |
||||
int ch; |
||||
while ((ch = bis.read()) >= 0) { |
||||
baos.write(ch); |
||||
} |
||||
baos.flush(); |
||||
return baos.toByteArray(); |
||||
} finally { |
||||
fis.close(); |
||||
} |
||||
} |
||||
|
||||
private static 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 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,222 @@ |
||||
package me.ag2s.umdlib.umd; |
||||
|
||||
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 = (int) 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 = (int) 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 |
||||
public String toString() { |
||||
return "UmdReader{" + |
||||
"book=" + book + |
||||
'}'; |
||||
} |
||||
} |
Loading…
Reference in new issue