[转]umd文件结构深度解剖(附手机umd电子书生成算法类|PHP版)

记得刚开始准备做小说下载站时(UMD格式的电子书应该在Nokia机上是相当的流行吧),研究UMD文件,在网上搜不到UMD文件结构说明.费了好大的劲,用反编译工具才找到相关信息.可惜对C#不了解,只能摸到点皮毛,差点就放弃了,后来一个偶然的机会看到2lin兄和Mark兄的结构文章,振奋人心啊,于是对着打开标准umd文件的16进制码和2位仁兄的结构分析,用偶熟悉的php,做了一个umd生成类,对umd文件编码。解码应该也很简单的一个求逆运算,就不啰嗦了。

相关关键字节含义:

0×23,也就是字符’#’,这个字符在Umd中被用来作为功能块的分割符。

1.已知的#块(类型也就是#后面的16进制数字)
0×01–文件开始
0×02–标题
0×03–作者
0×04–年
0×05–月
0×06–日
0×07–小说类型
0×08–出版商
0×09–零售商
0×0b–内容长度
0×83–章节偏移
0×84–章节标题,正文
0×81–正文写入完毕
0×82–封面
0×87–PageOffset
0×0c–文件结束

2. 整数编码为littleEndian, 也就是低字节在前,高字节在后,相应的,所有的文本也都是Unicode16 LittleEndian编码的
3. 章节数据块(0×84)后面的第一个数据块是所有章节的标题,按照以下规则排列:
[第1章标题文本的字节长度(1byte)][第1章标题unicode文本][第2章标题文本的字节长度(1byte)][第2章标题unicode文本]…
4. 章节数据块(0×84)后面的第二个数据块及以后的数据块是正文文本数据,是用标准zlib算法压缩的
5. 似乎每个数据块的字节大小都在18K以内
6. 似乎正文中的换行(\r\n)都被替换成了unicode段分隔符\?,不知道是否跟制作工具有关
7. 封面图片的数据是未压缩的,也就是说直接把数据段复制出来保存成一个jpg文件就可以了

umd文件简介(C#):

注:来自2lin@ 爱林-博客
UMD首先会在文件头写入一个
UINT类型 值为 0xde9a9b89 可能是用于识别版本类别什么的.
然后的格式大概如下
#
short 1 //文件信息
byte 0
byte 8 //这个值用是用来定义后面长度的. 实际长度为 值-5
byte 2 //这里1为普通书 2为漫画书
short random1.Next(0×401, 0×7fff) % 0xffff //PGKSeed

#
short 2 //文件标题
byte 0
byte * //标题长度=*-5
byte[] * //写入标题

#
short 3 //作者名称
byte 0
byte * //作者名称长度=*-5
byte[] * //写入作者名称

接下来的是可选的其格式和上面的一样
#4 //年 #5 //月 #6 //日 #7 //书的类别 #8 //出版人 #9 //出售人

写入文章长度
#
short 11
byte 0
byte 9
int * //长度
写入章节数
#
short 0×83
byte 1
byte 9
uint 0×3000 + random1.Next(0xfff); //这个值用来关联0×83
$
uint * //这个值就是上面关联0×83随机产生的值
uint 9 + (章节长度 * 4) //章节长度
byte[] * 写入每章的偏移值

写入章节标题
#
short 0×84
byte 1
byte 9
uint 0×4000 + random1.Next(0xfff); //这个值用来关联0×84
$
uint * //这个值就是上面关联0×84随机产生的值
uint 9 + 所有标题相加的长度
byte[] * 写入所有章节标题

写入压缩后的内容
$
uint random1.Next(1, 0xfffffff) * -1
uint 9+压缩后的长度
byte[] * //写入压缩后的内容

在压缩的时候 有可能把文章分成了很几段 所以 前面写压缩内容也许会接着再写一次 并且在中间随机写入
#
short 10
byte 0
byte 9
int CID //标识用的

写入结束
#
short 0xf1
byte 0
byte 0×15
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
最后还要写封面数据 代号 # 0×81 这里就不多讲了.

16进制分析:

来自:mark的博客

UMD文件有三种格式类型,一种叫纯文本格式,一种叫漫画&写真集格式,以及连环画(文字+图画).
文本格式中的文字流是用ZLIB进行压缩的,今天我们就先来了解一下文本格式的UMD文件吧.

文本格式类弄的UMD文件的组成格式如下:

1.首先是文件头,大部分文件都是靠文件头来区分文件格式的吧,Umd也不例外,Umd的文件头是0xde9a9b89,写到文件上前四位分别应该是0×89,0×9b,0×9a,0xde,这个大家理解起来应该没什么问题吧,以下的类似。(如果不是此格式,即不为UMD文件)

2.第5到9个字节为:0×23 0×01 0×00 0×08 0×01 (注:0×23,也就是字符’#’,这个字符在Umd中被用来作为功能块的分割符。)

3.第10个字节为:0×01/0×02.注0×01代表文本格式的UMD文件,0×02代表动漫格式的UMD文件
4.接下来2个字节的随机数.没有任何意义,可以扔掉.(呵呵,记住目前是第12个字节了)
5.第13个字节为:0X23(必须的)
6.解析接下来的2个字节0X02 0X00.即为数据类型.目前数据类型为2.代表的意思是以下的数据代表文件的TITLE.
那下面让我们观注如何解析TITLE吧.(呵呵.目前好像是第16个字节了吧)
7.第17个字节0X00(必须的)
8.解析第18个字节值.该字节的组成是:TITLE的长度*2 + 5.所以你要得到TITLE的长度必须要减五.
另外TITLE的长度为什么要*2,因为UMD是用UNICODE编码文件数据的.
9.注意现在就不能按多少个标准字节记数了,因为文件不一样,TITLE不一样.长度也不一样了.
那就继续看吧.再读TITLE长度个字节,就得到了TITLE的数据.
10.TITLE数据读完后,接下来1个字节是:0X23也就是’#'字符(必须的)
11.解析接下来的2个字节0X03 0X00.即为数据类型.目前数据类型为3.代表的意思是以下的数据代表文件的Author.
12.接下来1个字节是0X00(必须的)
13.接下来解析1个字节,该字节的组成Author的长度*2 + 5.所以你要得到Author的长度必须要减五.

注意!!!大家会发现TITLE和Author的解析过程是一样的,哈哈.你非常厉害.确实解析是一样的.即然这样我就不再重复费话了.因为下面涉及到的解析都是这个流程.
14.下面会解析到year = 4,mouth = 5,day = 6,gender = 7,p lisher = 8,Vendor = 9.OK解析完成以上的数据后UMD的基本信息你已经得到了.

15.紧接着的第1个字节:0X23 也就是’#'(大家会发现,UMD是用#来进行数据隔离的)
16.解析2个字节:0×0B 0×00 数据类型为11
17.接下来2个字节:0X00 0X09(必须的)
18.接下来4个字节:代表内容长度.
19.内容长度解析完成,用分隔符’#’.所以接下1个字节是0X23
20.接下来2个字节代表数据类型.0X83章节偏移量.
21.接下来2个字节:0X01 0X09
22.接下来4个字节:代表一个随机数,目前看来是起同步作用的.
23.接下来1个字节:0X36 也就是’$'$了.哈哈.
24.接下来4个字节:也是随机数.但是和22的随机数一样
25.接下来4个字节:代表偏移量的长度*4 + 9.所以偏移量的长度为:你解析出来的(len – 9)/4.
26.接下来偏移量长度个字节:每个字节代表:每节章节的偏移地址.
27.偏移量数据块解析完成了.接下来又是数据分隔符’#’ 0X23
28.接下来2个字节:数据类型0X84 .章节标题
29.接下来2个字节:0X01 0X09(必须的)
30.接下来4个字节:随机数
31.接下来1个字节:$
32.接下来4个字节:随机数.二次随机数要相等
33.接下来4个字节:代表 (标题长度*2 + 1) + 9
34.接下来取得每个标题的数据.
分析一下:为了取得每个标题的数据,如果有三个标题显然要取三次.OK.
那如何取呢?
我们先来解释第一个标题是如何取的.
接下来1个字节:标题的长度*2 = count.
接下来count个字节:就是标题的内容数据.
其他的标题同样的方法.接着取即可.
那标题取完后,接下来的数据会是什么呢?
想必现在应该章节类的数据了吧.好那让我们继续看吧!
35.接下来1个字节:$
36.接下来4个字节:随机数
37.接下来4个字节:数据流的长度 + 9 = count
38.接下来数据流长度个字节就是数据了.(注意目前的注意是ZLIB压缩的数据)
接下来UMD做了安全处理.生成三个随机数.如果随机数有二个相同.处理一些数据.如果不相同就不处理.
39.让我们看看相等的情况吧.下面的数据可能会有下面二种情况的组合出现.
(1)
接下来1个字节:’#'分隔符
接下来2个字节数据类型:0XF1 0X00
接下来2个字节:0X00 0X15
接下来16个字节空数据
(2)
接下来1个字节:’#'分隔符
接下来2个字节数据类型:0X0A 0X00
接下来2个字节:0X00 0X09
接下来4个字节:随机数
40.接下来1个字节:’#'分隔符
41.接下来2个字节:数据类型 0X81 0X00
42.接下来2个字节:0X01 0X09
43.接下来4个字节:随机数
44.接下来1个字节:$
45.接下来4个字节:随机数
46.接下来4个字节: (页面数*4 + 9) = count
47.接下来页面数*4个字节.
48.接下来1个字节:’#'分隔符
49.接下来2个字节:0X82 0X00数据类型//封面图
50.接下来3个字节:0X01 0X0A 0X01
51.接下来4个字节:随机数
52.接下来1个字节:$
53.接下来4个字节:随机数
54.接下来4个字节:封面长度 + 9
55.接下来封面长度个字节

56.接下来1个字节:’#'分隔符
57.接下来2个字节:0X0C 0X00数据类型
58.接下来2个字节:0X0C 0X00数据类型//结束吧!!!
59.接下来2个字节:0X01 0X09
60.接下来4个字节:整个文件长度//
到此为此我们的UMD文件解析完成.

php生成umd文件类源码(尚未做验证):

<?php
/**
+------------------------------------------------------------------------------
* UMD编码,文本转umd文件,测试可用在支持umd的阅读器上
+------------------------------------------------------------------------------
* @HXPHP Framwork
* @Author ieliwb
* @Copyright (c) www.ieliwb.com
+------------------------------------------------------------------------------
*/
class UMD
{
public $bookinfo = array
(
"id"         =>         0,
"title"     =>         "umd book",
"author"     =>         "unknow",
"year"         =>         "0",
"month"     =>         "0",
"day"         =>         "0",
"sort"         =>         "default",
"publisher" =>         "ChinaPub",
"seller"     =>         "DIY_GENERATED",
"cover"     =>         ""
);
public $chapters = array();
public $chaptercount = 0;
public $articlelen = 0;
public $chaptitlelen = 0;
public $charset = "GBK";
public $handle;
function __construct()
{
$this->bookinfo['year'] = date("Y");
$this->bookinfo['month'] = date("n");
$this->bookinfo['day'] = date("j");
}
/**
* 设置书籍编码
*
* @param String $charset
*/
function setCharset($charset)
{
$this->charset = $charset;
}
/**
* 设置添加书籍头信息
*
* @param Array $bookinfo
*/
function addBookInfo($bookinfo = array())
{
foreach($this->bookinfo as $key => $value)
{
if(isset($bookinfo[$key]))
{
$this->bookinfo[$key] = $bookinfo[$key];
}
if(($key != "id") && ($this->charset != "UCS"))
{
$this->bookinfo[$key] = iconv($this->charset,"UCS-2LE//IGNORE",$this->bookinfo[$key]);
}
}
}
/**
* 设置添加章节
*
* @param String $c_title
* @param String $c_content
*/
function addChapter($c_title,$c_content)
{
if ( $this->charset != "UCS" )
{
$c_title = iconv($this->charset,"UCS-2LE//IGNORE",$c_title);
$c_content = iconv($this->charset,"UCS-2LE//IGNORE",str_replace("\r","",$c_content));
}
$this->chapters[$this->chaptercount] = array
(
"title" => $c_title,
"content" => $c_content
);
++$this->chaptercount;
$this->chaptitlelen += strlen($c_title);
$this->articlelen += strlen($c_content);
}
/**
* 写入简介及其他相关信息
*
* @param String $string
* @param Int $node
* @return String
*/
function makeInfo($string,$node)
{
$data  = chr(35).chr($node).chr(0).chr(0);
$data .= $this->dec2hex(strlen($string) + 5,1);
$data .= $string;
return $data;
}
/**
* 十进制转十六进制
*
* @param String $string
* @param Int $length
* @return String
*/
function dec2hex($string,$length)
{
$data = "";
$length *= 2;
$c_string = substr(sprintf("%0".$length."s",dechex($string)),0 - $length);
for ($i = 0;$i < $length;$i += 2)
{
$data = chr(hexdec(substr($c_string,$i,2))).$data;
}
return $data;
}
/**
* 写入章节偏移量
*
* @param Int $fontSize
* @param Int $screenWidth
* @param Int $PID
*/
function writePageOffset($fontSize,$screenWidth,$PID)
{
$h = mt_rand(28672,32767);
$content_len = $this->articlelen + $this->chaptercount * 2;
$data = pack('H*',"2387");
$data .= pack('n',$PID);
$data .= chr(0x0B);
$data .= chr($fontSize).chr($screenWidth);
$data .= $this->dec2hex($h,4);
$data .= chr(36);
$data .= $this->dec2hex($h,4);
$random = 17;
$data .= $this->dec2hex($random,4);
$random = 0;
$data .= $this->dec2hex($random,4);
$data .= $this->dec2hex($content_len,4);
//$data .= $this->dec2hex(floor($content_len / 2),4);
fwrite($this->handle,$data,strlen($data));
unset($data);
}
/**
* 编译生成UMD
*
* @param String $filename
* @return Boolean
*/
function makeUmd($filename)
{
$this->handle = fopen($filename,"wb");
if(!$this->handle)
{
return false;
}
flock($this->handle,LOCK_EX);
$data  = "";
$data .= pack('H*',"899B9ADE");                                //头 umd文件标志
$data .= pack('H*',"230100000801");                            //0x01--文件开始
$data .= $this->dec2hex(mt_rand(1025,32767),2);
$data .= $this->makeInfo($this->bookinfo['title'],2);        //0x02--标题
$data .= $this->makeInfo($this->bookinfo['author'],3);        //0x03--作者
$data .= $this->makeInfo($this->bookinfo['year'],4);        //0x04--年
$data .= $this->makeInfo($this->bookinfo['month'],5);        //0x05--月
$data .= $this->makeInfo($this->bookinfo['day'],6);            //0x06--日
$data .= $this->makeInfo($this->bookinfo['sort'],7);        //0x07--小说类型
$data .= $this->makeInfo($this->bookinfo['publisher'],8);    //0x08--出版商
$data .= $this->makeInfo($this->bookinfo['seller'],9);        //0x09--零售商
fwrite($this->handle,$data,strlen($data));
//0x0b--内容长度
$data = "";
$data .= pack('H*',"230B000009");
$data .= $this->dec2hex($this->articlelen + $this->chaptercount * 2,4);
//0x83--章节偏移 写入章节数
$data .= pack('H*',"2383000109");
$random = mt_rand(12288,16383);
$data .= $this->dec2hex($random,4);
$data .= pack('H*',"24");
$data .= $this->dec2hex($random,4);
$random = $this->chaptercount * 4 + 9;
$data .= $this->dec2hex($random,4);
$chapteroffset = 0;
foreach($this->chapters as $key => $value)
{
$data .= $this->dec2hex($chapteroffset,4);
$chapteroffset += strlen($value['content']) + 2;
}
//0x84--章节标题,正文
$data .= pack('H*',"2384000109");
$random = mt_rand(16384,20479);
$data .= $this->dec2hex($random,4);
$data .= pack('H*',"24");
$data .= $this->dec2hex($random,4);
$random = 9 + $this->chaptitlelen + $this->chaptercount;
$data .= $this->dec2hex($random,4);
foreach($this->chapters as $key => $value)
{
$random = strlen($value['title']);
$data .= $this->dec2hex($random,1);
$data .= $value['title'];
}
fwrite($this->handle,$data,strlen($data));
$ss  = 0;
$oo = 32768;
$chapstr = "";
foreach($this->chapters as $key => $value)
{
$chapstr .= $value['content'].chr(41).chr(32);
}
$chap_len = strlen($chapstr);
$maximum = ceil($chap_len / $oo);
$num_1 = mt_rand(0,$maximum - 1);
$num_2 = mt_rand(0,$maximum - 1);
$aa = array();
for($i = 0;$i < $maximum;++$i)
{
$data = "";
$data .= chr(36);
$numrand = mt_rand(4.02653e+009,4.29497e+009);
$aa[$i] = $numrand;
$data .= $this->dec2hex($numrand,4);
$c_chapstr = substr($chapstr,$ss,$oo);
$ss += $oo ;
$z_chapstr = gzcompress($c_chapstr);
$random = 9 + strlen($z_chapstr);
$data .= $this->dec2hex($random,4);
$data .= $z_chapstr ;
if($i == $num_1)
{
$data .= pack('H*',"23F100001500000000000000000000000000000000");
}
if ($i == $num_2)
{
$data .= pack('H*',"230A000009");
$data .= $this->dec2hex($this->bookinfo['id'] + 268435456,4);
}
fwrite($this->handle,$data,strlen($data));
}
//0x81--正文写入完毕
$data = "";
$data .= pack('H*',"2381000109");
$random = mt_rand(8192,12287);
$data .= $this->dec2hex($random,4);
$data .= chr(36);
$data .= $this->dec2hex($random,4);
$random = 9 + $maximum * 4;
$data .= $this->dec2hex($random,4);
for($i = 0;$i < $maximum;++$i)
{
$data .= $this->dec2hex($aa[$i],4);
}
fwrite($this->handle,$data,strlen($data));
//0x82--封面
$data = "";
if(!empty($this->bookinfo['cover']) || is_file($this->bookinfo['cover']))
{
$data .= pack('H*',"238200011001");
$random = mt_rand(4096,8191);
$data .= $this->dec2hex($random,4);
$data .= chr(36);
$data .= $this->dec2hex($random,4);
$coverstream = file_get_contents($this->bookinfo['cover']);
$random = strlen($coverstream) + 9;
$data .= $this->dec2hex($random,4);
$data .= $coverstream;
fwrite($this->handle,$data,strlen($data));
$data = "";
}
//0x87--PageOffset
$this->writePageOffset(0x10,0xD0,0x01);
$this->writePageOffset(0x10,0xB0,0x01);
$this->writePageOffset(0x0C,0xD0,0x01);
$this->writePageOffset(0x0C,0xB0,0x01);
$this->writePageOffset(0x0A,0xA6,0x05);
//0x0c--文件结束
$data .= pack('H*',"230C000109");
$random = 4 + strlen($data) + ftell($this->handle);
$data .= $this->dec2hex($random,4);
fwrite($this->handle,$data,strlen($data));
unset($data);
flock($this->handle,LOCK_UN);
fclose($this->handle);
@chmod($filename,0755);
return true;
}
}
//test
$umd = new UMD();
$umd->addBookInfo(array('title'=>'测试umd生成'));
$umd->addChapter('第一章','内容1111111111111111111111111111111111111');
$umd->addChapter('第二章','内容22222222222222222222222222222222222222222');
$umd->makeUmd('aaa.umd');
?>

umd文件结构深度解剖相关推荐

  1. c语言深度解剖 pdf,c语言深度解剖(解密).pdf.pdf

    c语言深度解剖(解密).pdf.pdf 还剩 130页未读, 继续阅读 下载文档到电脑,马上远离加班熬夜! 亲,很抱歉,此页已超出免费预览范围啦! 如果喜欢就下载吧,价低环保! 内容要点: * Str ...

  2. C语言深度解剖 PDF 分享

    链接:https://pan.baidu.com/s/1U-8L7ZY5_sLpk4p0J302Mg           提取码:d8nu 相关推荐 [Objective-c程序设计] 中文编程·学习 ...

  3. 《C语言深度解剖》中的.c/.h 程序模板及函数注释风格

    编程规范和变量命令规范对于代码的可阅读性.可维护性有着很大的影响.编程规范有很多,大公司也会制定自己公司的编程规范,如<华为技术有限公司c语言编程规范>等.对于个人编程来说没必要将自己编写 ...

  4. C语言深度解剖读书笔记(1.关键字的秘密)

    开始本节学习笔记之前,先说几句题外话.其实对于C语言深度解剖这本书来说,看完了有一段时间了,一直没有时间来写这篇博客.正巧还刚刚看完了国嵌唐老师的C语言视频,觉得两者是异曲同工,所以就把两者一起记录下 ...

  5. C语言深度解剖:关键字

    第一个C语言程序 内存 定义与声明 变量是什么 为什么要定义变量 定义变量的本质 定义声明 关键字 - auto 局部与全局变量 作用域 vs 生命周期 auto 关键字 - register 寄存器 ...

  6. 谷歌人工智能深度解剖:从HAL的太空漫游到AlphaGo,AI的春天来了

    谷歌人工智能深度解剖:从HAL的太空漫游到AlphaGo,AI的春天来了 人工智能驱动的年代到了-谷歌以AI为本,融入生活,化不可能为可能 早在1968年斯坦利库布里克作品<2001:太空漫游& ...

  7. C语言 | C语言深度解剖 ——章节2 符号

    C语言 | C语言深度解剖 --章节2 符号 C语言基本符号表 注释符号 // /* 几个似非而是的注释问题 y=x/*p 出色注释的基本要求 连接符和转义符 \ 单引号.双引号 花括号 运算符 10 ...

  8. C语言深度解剖读书笔记

    开始本节学习笔记之前,先说几句题外话.其实对于C语言深度解剖这本书来说,看完了有一段时间了,一直没有时间来写这篇博客.正巧还刚刚看完了国嵌唐老师的C语言视频,觉得两者是异曲同工,所以就把两者一起记录下 ...

  9. 深度解剖dubbo源码-知识结构图

    深度解剖dubbo源码-知识结构图

  10. 深度解剖dubbo源码

    -----------学习dubbo源码,能给你带来什么好处?----------- 1.提升SOA的微服务架构设计能力    通过读dubbo源码是一条非常不错的通往SOA架构设计之路,毕竟SOA的 ...

最新文章

  1. 软考可以一次报两门吗
  2. 机器人学习--室内定位方法综述
  3. V-7 Openstack 在ceph中转换镜像格式
  4. Git之深入解析48个经典操作场景的分析和处理,专治不会合并代码
  5. 如何判断2服务器性能好或坏_无服务器革命:好,坏和丑
  6. 获取apk安装包sha1的值
  7. Vue3 高级语法(二)—— 自定义指令、Teleport、Vue插件
  8. th标签能包裹select吗_电影《八佰》过后,他能摘掉马思纯前男友标签了吗?
  9. 按钮提交所有数据_多人编辑,自动汇总,领导可见所有?用 SeaTable 表格更简单...
  10. SpringBoot启动流程解析
  11. 亚马逊RDS使用的第三方扩展有漏洞,可导致内部凭据遭泄露
  12. Perl学习笔记(二)--标量数据
  13. helperdialect mysql_Mybatis使用pageHelper步骤(动态分页)
  14. Python技能树及Markdown编辑器测评 20212109施铖哲
  15. 抢Google等巨头生意,纽约大学小伙挖掘并出售自己数据
  16. 微信服务号解决开启服务配置后自定义菜单失效的方法
  17. SpringBoot整合rabbitmq使用案例
  18. iOS内存管理和malloc源码解读
  19. 新乡腰椎间盘突出 腰椎间盘突出如何治疗
  20. Java测试服务器的上传速度和下载速度

热门文章

  1. kotlin的Viewpage2+Fragment的简单使用(setUserVisibleHint方法过时)
  2. dell主板恢复出厂设置_戴尔恢复出厂设置【搞定办法】
  3. python与开源_Python与开源GIS
  4. mini_c编译器的简单代码逻辑
  5. Laravel 使用百度地图实现地理位置转经纬度
  6. 菲尼克斯交换机FL SWITCH SFN 5TX
  7. linux下调用扫描仪sane协议
  8. Qt5.5.1 VS2010中文乱码解决办法
  9. 三维点云数据处理软件供技术原理说明_三维点云数据获取方法及获取系统技术方案...
  10. 几种前端h264播放器记录