记录我工作学习生活中的点点滴滴......

[转]umd文件结构深度解剖(附手机umd电子书生成算法类|PHP版)
发布于 基础编程
2011-02-22 15:13:53
4096
4
记得刚开始准备做小说下载站时(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#):

注:来自[email protected] 爱林-博客
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文件类源码(尚未做验证):


/**
+------------------------------------------------------------------------------
* 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');

?>


分享本文到:
除非特殊注明,本文版权归原作者所有,欢迎转载!转载请注明版权以及本文地址,谢谢。
转载保留版权:Pakey's BLog >>基础编程 >>[转]umd文件结构深度解剖(附手机umd电子书生成算法类|PHP版)
本文地址:http://www.pakey.net/blog/372.html