umd文件结构深度解剖

3 篇文章 0 订阅
1 篇文章 0 订阅

[转]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');
 
?>

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果在使用Vue CLI时使用了一个UMD文件并出现了错误,有以下几个可能的原因: 1. 没有正确引入UMD文件:请确保在Vue组件中正确引入UMD文件。在Vue组件的`<script>`标签中,应该先引入Vue,然后再引入UMD文件。例如: ```javascript import Vue from 'vue'; import MyLibrary from 'my-library'; Vue.use(MyLibrary); ``` 2. UMD文件没有正确定义:请确保UMD文件按照正确的方式定义。UMD文件应该包含对于全局变量`Vue`的检查,并在需要时将组件注册到Vue中。例如: ```javascript (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.MyLibrary = factory()); }(this, (function () { 'use strict'; // check for Vue if (typeof Vue === 'undefined') { throw new Error('MyLibrary requires Vue'); } // define component var MyComponent = Vue.extend({ // ... }); // register component Vue.component('my-component', MyComponent); // return library object return { MyComponent: MyComponent }; }))); ``` 3. Vue CLI构建配置错误:如果你使用了Vue CLI,可能是你的构建配置错误导致了错误。请检查`vue.config.js`文件中的配置,并确保UMD文件已经正确设置。例如: ```javascript module.exports = { configureWebpack: { output: { library: 'MyLibrary', libraryTarget: 'umd', filename: 'my-library.js' } } }; ``` 如果以上方法仍然无法解决您的问题,请提供更多详细信息,以便更好地帮助您解决问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值