|
|
|
谁能帮我翻译一下这篇文章!(不胜感激!)
哈哈,我翻译了五分之一,暂时放在内部区,等内部通过后,就会放出来。 |
|
od 脚本编辑器 v1.0欢迎大家提出宝贵意见
再次支持! |
|
|
|
|
|
|
|
[翻译]新手的观点:压缩
新手的观点:压缩 -=-=-=-=-==-=-=-=-=- 一:介绍 -=-=-=-=-==-= 压缩是一门减少原始数据大小的艺术。我在一些场合遇到有人把压缩称为加密,的确如此,因为加密是一门把数据隐藏成其他数据的艺术,而压缩也是如此。 当你第一次考虑压缩,你通常会想到某个数字,除以数据中的每个字节或字(byte/word),然后再压缩。事实远不止如此,尽管它可能有用,但通常你最终还是得到同样的大小。由于你还必须保持原样,所以某些情况下甚至会增加代码的大小。 压缩主要分成两种类型:无损压缩(这种压缩减少数据大小,但当你解压缩时,你重新获得与原始数据大小一模一样的数据。)和有损压缩(这种压缩通过去掉不必要的部分来减少数据大小,但当你解压时很可能你得到另一种数据。这种方法主要用于多媒体,因为人的感官有限。通过去除人类看不见或听不见的部分,它还可减少声音和图像文件的大小)。 本文中,我们将概述一些简单的无损压缩方法。然后,我还要向你们程序员提出一个简单的挑战。 二:运行长度编码(Run Length Encoded)(RLE) -=-=-=-=-==-=-=-=-=--=-=-=-=-==-=-=-=-=--=-=-= RLE是一种很简单的压缩方法,在一些地方非常的有用,而在另一些地方可能一点用都没有。假设你要压缩某个数据,它里面有图形数据(RLE对此十分有用..),它是原始数据,也就是说,原始像素。通常,位图有一些区域填充同样的像素,基本上是同一数据。所以不是写成:00000000 00000000 00000000 00000000 (4个黑色像素)(4 black pixels),你可以这样写:1600,意思是我们在代码的下一部分里有16字节(bytes)个“00”。多数情况下可不是这样的,真的,在文本文件中要少些,但这仍然很有用。因为我们在文本文件里重复字符要少些,但我们有Lempel Ziv (LZ77)对付它。 记住使用标记代码来拆分其他原始像素,因为如果有两个同样颜色的像素,使用RLE方法就不总是那么有用了。 三:Lempel Ziv '77 (LZ77)算法 -=-=-=-=-==-=-=-=-=--=-=-=-=-==-=-= Lempel Ziv是基于算法的字典,它还不是字替换(word replacing),所以大多数已用字(used words)都将被替换为一个短符号(short sign)。这种算法创建自已的字典。扫描数据后,它创建一个字典缓冲区,然后扫描下一块(block(s)),当遇到前面的已用符号时,就用该符号在字典中的索引替换它。 举个例子: WATCHMATCH是一个数据,我们定义字典大小为5个Bytes(WATCH),现在,我们要从字典中扫描数据的下一个块(block),由于'W'(译者:我觉得应该是'M',不知对不对?)在字典中未被引用,将被保留,而'A'将被编为位置1,长度为4(we do not count 'M' its 0),因为它是位置1的拷贝,且前面有4个字节(bytes)。所以,我们可以这么做:HORSEWHORE,字典大小为5(HORSE),我们有: 'W' Pos: 0, Length: 5 (H) Pos: 1, Length: 5 (O) Pos: 2, Length: 5 (R) Pos: 4, Lentgh: 4 (E) 当然,你可以使用各种大不的数据,例如: SUPER-MANBATMAN 然后使用9个字母的字典,每个部分是3个字母,现在我们有: SUP|ER-|MAN 'BAT' Pos: 2, Length: 1 (MAN) 所以,像这样,你能将更多的数据压缩到更少的空间(有时候)。比如,一次压缩4-8个字节比逐字逐字压缩(对文本文件可能更有用)更有效率。 大多数已经使用的压缩方法,如RAR、ZIP,正使用LZ77的某个变种(LZ77 variant),每种都有其优缺点。 四:哈夫曼算法(Huffman) -=-=-=-=-==-=-=-=-=--=-=-=-=- 哈夫曼算法是一种使用频率很高的基础压缩方法,它十分有用。它不像LZ77和RLE,相似的数据不必一个接一个,或在相似的块,它能覆盖所有的数据。 哈夫曼算法使用以下的方式: --频率测试 --建立哈夫曼树 --编码 频率测试是决定哪个字节在数据中使用最多的测试。就像在固定的密码学(subitute cryptography)里,你努力找出使用频率最多的字母,然后用逻辑解密字母替换它们。例如,'E'是英语里使用频率最多的字母,你仍可以写出一个没有'E'的句子。"Dan is away from his daddy",看到吗?句子里没有'E'。如果你想想,你能创建一个更复杂的没有'E'的句子。所以哈夫曼算法并没有依靠已经做好的频率表,而是每次都创建一个新的。这对代码非常有用,因为一些机器码(opcodes)比其他的代码重复更多次,FPU比x86代码使用频率更低(我们正在x86平台上讨论),所以一些代码比其他的使用频率更高。那为什么不用它来减少代码的大小呢?像在多数代码中,机器码(opcode)85C0(test eax,eax)出现次数多于D9E1(fabs),为什么不用2或1位(bit)代替85C0呢?这恰恰是哈夫曼算法正在做的。做了频率测试后,使用频率最多的字节/字符/字(bytes/characters/words)被替换为短位序列(shorts bits sequence),使用频率较少的被替换为长位序列。 哈夫曼树是确定一个位序列的解码值的二进制树(binary tree),但首先让我们看看它们是如何建立的。在一文本文件里,我们要压缩下面这行: "MONEY IS ROOT OF ALL EVIL" 去除空格,变成"MONEYISROOTOFALLEVIL" 查看ASCII表的十六进制和十进制,上面这行可表示成: 4D 4F 4E 45 59 49 53 52 4F 4F 54 4F 46 41 4C 4C 45 56 49 4C 用二进制表示为: 0100 1101 0100 1111 0100 1110 0100 0101 0101 1001 0100 1001 0101 0011 0101 0010 0100 1111 0100 1111 0101 0100 0100 1111 0100 0110 0100 0001 0100 1100 0100 1100 0100 0101 0101 0110 0100 1001 0100 1100 我们来分析十六进制字符串:(译者:也就是4D 4F 4E 45 59 49 53 52 4F 4F 54 4F 46 41 4C 4C 45 56 49 4C) 1 1 2 1 3 1 4 16 5 7 6 2 9 3 C 3 D 1 E 1 F 4 (1) (1) (1) (16) (7) (2) (3) (3) (1) (1) (4) 1 2 3 4 5 6 9 C D E F 这是我们基本分支列表,包含该行所有的字(nibbles)以及它们在文本文件中出现的次数。要把它们建立成树,我们需要一步一步做两个分支并把它们统一在一个更大的分支,并加上它所拥有的所有子分支数的总和。 (2) (1) (16) (7) (2) (3) (3) (1) (1) (4) (1) (1) 3 4 5 6 9 C D E F 1 2 再重复,直到我们得到下面的完整树: _________________(40)______________ / \ _______(17)________ (23) / \ / \ / (5) \ /(12)\ (16) (7) (3) (2) (6) (6) [4] [5] (1) (2) (1) (1) (3) (3) (4) (2) [1] (1) (1) [D] [E] [9] [C] [F] [6] [2] [3] 这是最终结果,我们回顾一下: 顶部的40意味着代码中有40个字(译注:nibble,四位字,即半个字节),分成两组,每组根据关键词0或1,如果是0,则到左分支,如果是1,到右分支。 所以: 11 = 5,因为它到1右边,所以它要么是4要么是5,然后再重复一次,右边,我们得到5。 下面是我们的新表: Encoded|Real| Old(重新编码|真正字符|原始编码) ------------------ 0000 | 1 | 0001 00010 | 2 | 0010 00011 | 3 | 0011 0010 | D | 1101 0011 | E | 1110 0100 | 9 | 1001 0101 | C | 1100 0110 | F | 1111 0111 | 6 | 0110 10 | 4 | 0100 11 | 5 | 0101 这棵树还未高度优化,它还能做得更好。 它仍可将"MONEYISROOTOFALLEVIL"从下面的 01001101010011110100111001000101010110010100100101010011010100100100 11110100111101010100010011110100011001000001010011000100110001000101 010101100100100101001100 (160 bit) 压缩成: 10001010011110001110110101100110100111000111100010100111100111111010 0101111001111000001001011001011011110111101001100101 (120 bit) 几乎剪掉了1/4。这是个很好的比率,一棵优化过的树可以产生更好的比率,因为它会减少不止两个符号(如你所见,这里仅4和5减掉了一半)。 哈夫曼算法会十分有用,特别是如果用RLE和LZ77,或者其中之一编过码。由于你有LZ77字典,减少所有的4和5,减少了位置和长度,哈夫曼树会缩得更小。 哈夫曼树的解码方向相反。先读取第一个位,然后顺着树,一旦读到一个解码值,停下来。 所以如果你读到: 1,往右,4或5 0,往左,是4 下一步: 0,往左,它不是4或5 就这样反复下去,直到你完成整个数据的解码。 记住,建立一个树时,你可以要多大空间就用多大空间,取决于你的内存、时间和空间。如果你有够大内存,你可以建立一个64bit值的树。可我认为考虑到速度和大小,4至8bit的树是最好的。它没有很多入口点,也不会太复杂。 哈夫曼需要很多时间坐下来编码。与多数与逻辑相关的事物一样,理论简单,但编码有时很难完成。最重要的是不要放弃,一旦你有了对代码的第一次的搞定,你就能做很多伟大的事。 五:错误FAQ 和技巧 我想这些问题在你们编写压缩软件的日常工作时可能会问过你们自己,我努力尽可能回答。还有什么问题你们可以随时问我。 Q:我的RLE/LZ错误地解码,这是怎么回事? A:在使用这些方法时,前缀编码是很重要的。记住使用永远不会在待压缩数据(packed data)中出现的标记。如果是文本文件,用00(NULL),或一个控制字符,因为它不会出现在文本数据中。 Q:我的哈夫曼不会解码。我的代码有问题吗? A:通常,哈夫曼在每个数据都创造不同的树。记住把树附载(attach)到已编码数据中,或者如果解码器在每个地方(如.exe包)都是私有的,你可以把树附载到解压缩器本身。 Q:来自内存里的数据奇怪得很,为什么? A:Little Endian,位顺序保留了,你可以交换修复它(Little Endian, the bit order is reversed, you can use bswap to fix it.)。由于很多格式使用Big Endian bit order,所以如果你自己写格式,记住编码和解码要么交换,要么一点都不交换,要协调一致。选择取决于你。 Q:我的LZ压缩器十分慢,而其他的压缩器却很快。我的代码为什么这么慢呢? A:将每个字节与全部位置比较是很慢,创建一种数据结构可以加快速度。 Q:除了树之外,有没有其他的方法解码哈夫曼树? A:哈夫曼编码的一个重要属性就是每个节点(prefix)是唯一的。如果你有一个N位的代码,假设是代码A,然后你确定没有多于N位的代码,它的前N位与代码A是一样的。所以,如果你在A后面加入一些随机位数(random bits),以便将N位填充到M位(比如16),还是没有其他的方法,使得代码能够解释成除代码A以外的其他任何代码。 上面的意思是说,你可以建立一个表,一次使用多个数位(multiple bits)快速解码。你可以从位流(bitstream)中截取M位,把这M位数用作表的索引。你必须努力使所有的代码达到M位,并把每个代码的符号和长度存储到所有花巨大努力做成的表位置(table-positions)里. 现在,当你要解一个代码,查查表,找到代码的实际的符号和实际长度。输出该符号,并且以相应的代码长度完善位流(bitstream)。 哈夫曼编码的另一个属性是最短的代码属于数据中使用频率最多的符号。 如果你有一棵很大的哈夫曼树,相应的表也会特别的大。它不适合(fit in)L1 cache(对于16位,你将需要至少2个字节的2^16个入口点(entries)(一个字节用于符号,另一字节用于长度。所以你需要128 Kb))。 把表一分为二 是个好主意。 表应该排序(turned)以更好的适合L1 cache。该表将存储最小的代码(也是使用频率最多的代码。举12位为例,你会得到(2^12)*2 = 8 kb,这个数字非常适合L1 cache)。 你可以用一特殊长度值(如-1)去提示代码未找到。 然后你从流中抓取剩余的位,查看第二个表。 在这种情况下,你可以二者之中任选一棵树( Alternatively you could walk down the tree in such a case),但通常情况下大可不必,因为即使是第二张表也是十分的快(它在L2 cache中,只会部分填充,对第一张表中的代码来讲会有‘代沟’,所以第二张表仅有少部分存到缓存(cache),它们也很少使用。) 答案由Scali贡献。 Q:已经有很多现成的库(libs),它们的代码比我编的好得多,那我为什么还要去如何编写压缩算法? A:是的,你不必那样做。可是,你有一个袖珍(hand-held)计算器,为什么还要去学习如何加数和乘数?学习压缩能帮助你以不同的方式处理编程和逆向工程任务。你懂得汇编,也许是个更好的方法,但那意味着你一直用它吗?C, Delphi呢?他们在各自的领域也非常有用。对压缩编程来说,这还不够。关键是了解它是如何工作的。就像汽车引擎,你知道它如何工作,你就能更好的开车,知道何时换档。可这意味着你一定要做个自己的引擎吗?不。学习压缩是不错的,对一些课题来讲它可能有点麻烦,但它还是能给你很多帮助的。决定取决于你。 Q:我想编一个将文件打包的文件保护工具,我应使用哪种压缩呢? A:方法是完全变化的,你可以结合某个LZ变体和Huffman设计一个良好的压缩,也可以创建你自己的算法,如果你知道的话。记住要遵守一些原则: 对许多方法而言,前缀编码是重要的。 你可以选择一种或两种方法,但不要太多了。 “不要只见树木不见森林”。 尝试努力前学习可执行类型头,这样你就不会每次试图打包时毁掉文件了。 有很多这样的规则,可因为本文中我们不会处理二进制打包,所以我没有加它们。 Q:我写了一个压缩代码,但压缩后的数据不能用我的压缩器解压缩,我做错了什么吗? A:我无法确切的告诉你在什么地方出错了,试着从新开始写一个新的解压器,使用压缩的正确逆向方法,如果你用rol,那么就是ror。相反方向做,如果rol是最后一个指令,让ror成为第一个指令。这样做以后,你的解压器应该运行无误了。 Q: A: 六:结论 压缩是一门艺术。你不必掌握它,可以只学你需要的部分。除了直接帮助你压缩外,压缩还能帮助你解决编程问题。你可能在试着编写压缩代码时会遇到许多困难,不要放弃。从一个几乎不能运行的Huffman到你自己的生动活泼的压缩方法,每一次尝试都会更成功。 关于有损压缩方法,我不能把它们写在纸上,因为对新手来讲,这是一篇论文。要解释让只知道压缩的读者提高自己,帮助他更好理解与论文相关的压缩的最简单方法,我可能改天会写一篇有损压缩论文,但现在不是时候。网上有大量关于文本文件的论文,去搜搜它们。 下面是关于压缩的更丰富资源: Introduction to Data Compression, Second Edition by Khalid Sayood Compression Algorithms for Real Programmers (For Real Programmers) by Peter Wayner Data Compression: The Complete Reference by David Salomon Text Compression (Prentice Hall Advanced Reference Series) by Timothy C. Bell, Ian H. Witten, Ian Whitten (Contributor), John Cleary (Contributor) Introduction to Information Theory and Data Compression by Darrel . Hankersson The Data Compression Book by Mark Nelson #Compression at EFNet (IRC) http://www.cs.pdx.edu/~idr/compression/ http://www.ics.uci.edu/~dan/pubs/DataCompression.html http://www.faqs.org/faqs/compression-faq/ http://datacompression.info/ http://www.data-compression.com/ http://www.dogma.net/markn/ http://www.arturocampos.com/ http://www.ross.net/compression/ http://www.cbloom.com/ 这些链接提供你最基本的信息,仅从上面你就能丰富你的知识。 本文为新手而作,我希望我已经把事情说清楚了,解释了压缩的基本内容。至于我,在多数,如果不是全部,压缩相关问题,我也是个新手。所以我想借此机会感谢曾帮助我学习这一艺术基本知识的人: Jorgen Ibsen (Jibz),他教会我从头开始的一切,在本文中我应用了他的方法,因为我记得这些方法对新手是多么有吸引力。 Scali,他教会我哈夫曼表(Huffman tables)的巨大威力,对FAQ也贡献了一些问题和解答。 X-Lock,他在运行长度编码(RLE)和LZ算法方面帮助了我,同时对压缩资源亦有贡献。 附加文件:编程的挑战、压缩算法 ParaBytes springkang[DFCG][NSSG][TT] 译于2004.08.31晚12:00 |
|
|
|
[翻译]如何在自己的代码中播放XM音乐
如何在自己的代码中播放XM音乐 ============================ 总括 ==== 嘿,各位好。播放XM音乐最完美的方式就是使用MiniFmod。它免费使用,我们用它可以做出十分酷的注册机。我选择注册机作为播放音乐的完美目标,因为我们最终都会知道它的酷。 好吧,我们开始吧。 首先,我们要处理一下音乐。找到我们要的XM音乐的最好途径是http://www.modarchive.com/网站上的mod 档案库(mod archive)。这是个巨大的档案库,可以找到大量的酷音乐。所以在编码前选择你的音乐文件(推荐2k-30k)。我特别喜欢"Hybrid Song.XM"(我在Worms的安装文件上第一次听到它)或"trainer.XM",但我确信那儿还有成千上万的好音乐。 一旦我们选好音乐,我们要抓取(dump)它的内容!!现在,由于本文是针对Visual c++编码人员,所以我们抓取的就是C++格式的十六进制文件。对于抓取文件的转换(dumping rutine),我们将使用Thigo的优秀的Table Extractor,位于protools/anticrack..或在google上找。 抓取表(Dumping Table) ===================== /* Dumping Hybrid Song.XM (51k) */ 用Thigo的Table extractor加载你的XM文件,现在我们需要提供一点信息给程序: 地址(十六进制):0 (Address(hex): 0) 项目:Bytes (Items:Bytes) 表(十六进制)的大小(Bytes): CCD4 /文件大小/ Size (Bytes) of Table(Hex): CCD4 /* File Size */ 每条线上项目数:20 /输入多少由你喜欢,我个人是20/ Number Of Items on a Line: 20 /* put how many you like personally i put 20 */ C/C++ /抓取所用的语言风格/ C/C++ /* Dump Style Language */ 抓取到文件:勾选 Dump to file: checked /* [v] */ 好,我们不再需要填充选项了。按“开始”按钮,我们看到程序正努力尽可能快的的抓取映像文件。:-),0....100%,完美的表(Table)抓取到result.txt(与XM文件在同一目录)。 编码者的观点 ============ 看看抓到的results.txt..天哪,十分的大..(300k)。是的,那就是20多K以上大的文件的界限(coast)了。所以请记住,因为你是在做注册机,文件应该尽可能的小,但考虑那是你能得到的最好声音。把文件保存到某个地方,现在我们需要利用一些源码。 MiniFmod ======== 关于MiniFmod 1.6: 这个小小的XM播放系统仅增加5k到exe中!现在包括了全部源代码!!!FDXP工具输出一个基于你的音乐的头,它将被编译到MiniFmod而不是整个代码部分。 预先缓冲输出保证0延迟,高输出稳定性保证100%自由点击。(Pre-buffered output for 0 latency, and high output stability 100% click free.) XM例子回调用户生成或压缩的XM例子! 文件系统回调以便你能确定你喜欢的任何载入系统(磁盘/wad/内存) Copyright 2001-2002 Firelight Technologies Pty. 可以在:http://www.Fmod.org找到MiniFmod或快速下载http://www.fmod.org/files/minifmod160.zip 注意:我不用GCC,所以如果你用,请自己解决。 工具 ======= 在MiniFmod包内,有一个叫Fexp.exe的小工具。它是我们对抓取的result.txt的XM效果优化器。此外,还有将包含到我们程序里的*.c/*.h文件。 我已经将注册机模板打包,里面有源码,满足了我们的需要。这样,我们就可以一步一步的编码了。好,找到模板,用VC++ IDE打开DSW。这是一个最基本的注册机,有3个按钮--“生成generate,播放play,关于about”。我们的主要目标是通过play按钮就能播放声音。好,我们让keygen.cpp和其他文件做好了准备。 开始编码 ========= 当然,声音必须有一个句柄,这样我们就能决定何时停止、重新播放甚至与其他声音播放器(如Winamp)共享。 作为全局变量,我们开始于: FMUSIC_MODULE *mod; /* fmod music handler */ 现在,在增加更多代码前,我们将minifmod函数和主要代码增加到工程里,在注册机的主文件夹建立一新的文件夹,取名“lib”,在minifmod16.zip里也可以找到它。将minifmod.zip\lib拷到lib文件夹里面。 注意:在minifmod.zip\lib里有2个名为“dubug和final”的文件夹,不要拷它们。 好了,现在回到我们的工程。在工程里新建一个名为MiniFmod.c的文件夹(就在源文件文件夹里面),把所有的*.c文件从lib文件夹里增加到里面去。这些文件是: 1. Fmusic.c 2. Fsound.c 3. mixer_clipcopy.c 4. mixer_fpu_ramp.c 5. music_formatxm.c 6. system_file.c 现在我们要增加头文件(*.h),新建一名为MiniFmod.h的文件夹(就在头文件文件夹里面),把所有的*.h文件从lib文件夹添加到里面。 1. minifmod.h 2. Mixer.h 3. mixer_clipcopy.h 4. mixer_fpu_ramp.h 5. Music.h 6. music_formatxm.h 7. Sound.h 8. system_file.h 9. system_memory 10. xmeffects.h 11. winmm.lib ;不是一个头文件,但不要忘记添加它 not an *.h file, but dont forget to add it too!!! 现在,由于它是个注册机,使用标准的minifmod源文件还不够,我们将对代码作些修改。这就是说,一些原始文件将做轻微修改以满足我们的需要。 注意:修改文件已经增加到源代码中了,所以我已经使用了它。 我们要做的另一件事是创建一个名为loadmisic.h的新文件夹(就在头文件文件夹里面)。现在听仔细了,记得我们抓取的result.txt吗?我们用它的十六进制抓取文件作为文件本身,改名result.txt为music.h(在修改文件里确定的 :-))。打开(仔细点,编辑大的文件会造成蹦溃。)现在我们需要对文件做点编辑。 原始抓取文件 ============ static unsigned char table[52436] = {}; 修改的抓取文件 ============== #define MUSIC_LEN 4092 static unsigned char music[]= { }; 注意:当试图加载新的音乐到你的注册机里时,这些区别总是要做的(the diffrences, always do this when trying to load new music into ur keygen!!!)。一旦完成编辑并保存,我们需要新建一个*.h文件: 打开记事本,写或粘贴如下代码: //=============================================================== #ifndef __LOADMUSIC_H__ #define __LOADMUSIC_H__ // Memory functions unsigned int memopen(char *name); void memclose(unsigned int handle); int memread(void *buffer, int size, unsigned int handle); void memseek(unsigned int handle, int pos, signed char mode); int memtell(unsigned int handle); void loadmusic(void); // Load music fucntion #endif //============================================================== 这些是另外加上去的,基本上使我们更容易加载抓取的XM文件到内存中执行。保存这个文件为loadmusic.h。最后,我们增加music.h 和loadmusic.h到loadmusic.h文件夹里。 最后,但并非至少,我们需要增加loadmusic.cpp,它当然不是minifmod的一部分,正如我所说的,它是我们修改的代码。 新建一名为loadmusic.c的文件夹(就在源文件Source Files文件夹下面),增加这个文件(loadmusic.cpp)。 好了,有意思的是minifmod的安装部分。 我们现在需要将那些#define增加到keygen.cpp #include "loadmusic\loadmusic.h" #include "lib\minifmod.h" #include "lib\buffer.h" //译者注:在源码包里我好像没找到这个文件哦!!好像也没什么影响! 注意:如果你看看loadmusic.cpp,你就能看见music.h以及我们新修改的函数在那里有声明。 最后的接触 ========== 我们已经声明fmod music的句柄,记得吗? FMUSIC_MODULE *mod; 好,我们所要做的就是设置Fmod并加载抓取的XM文件(music.h)到内存,并调用play函数。 在play按钮id code(case IDC_PLAY:),增加下列代码: if (mod == NULL) // mod handle is free? (though it will work fine with other loaded audio devices) { // We defined our music file to be loaded in loadmusic.cpp // //=============================================================// loadmusic(); // Call & set ready memory to load the music if(!FSOUND_Init(44100, 0)) // intialize memory for sound and format { break; } mod = FMUSIC_LoadSong(NULL, NULL); // handle = LoadSong() FMUSIC_PlaySong(mod); // Play it (from memory), way cool!! } 注意:如果你将这段代码放在WM_INITDIALOG,在加载注册机时会有自动播放效果。 如果要停止音乐,我们只要新增一个Stop按钮,对它选个ID,并使用下列代码: if (mod != NULL) // handle is loaded (playing)? { FMUSIC_FreeSong(mod); // Free memory (handle) FSOUND_Close(); // Close it (stop it from playing) mod=NULL; // make handle to be Free again } 注意:在WM_CLOSE增加同样的代码 重要的技巧 ========== 这部分如果你没有阅读过,你会在播放XM文件时获得一个有趣的结果。 /* from Firelight's readme */ 利用XMEFFECTS.H 和 FEXP.EXE使EXE更小 --注意所有的XM重播代码效果打包在 #ifdef。它们在xmeffects.h中有定义。 --看看zip/lib目录,里面有FEXP.EXE --用FEXP.EXE对一个XM文件处理,会得到xmeffects.h --用新的xmeffects.h重新编译minifmod 库 --你的EXE大小再次降下来了。 --(仅播放你用FEXP处理的音乐,可能会激活其他的音乐 :)..) 这就是说,对每个抓取的音乐文件用一个新的xmeffects.h,否则,如前所说 ,会引发别的音乐。 因为fexp Hybrid_Song.XM -> xmeffects.h 覆盖keygen\lib\folder里面的旧xmeffets.h,重新编译源码,音乐正常播放了。 一些小注意:minifmod会使音质有点小的松散,这是可理解的。因为minifmod并非fmod库的一部分,所以它会丢失一些重要的东东,但是,没有什么事是完美的。对我而言,注册机中的音乐是很酷的,你们的也是。 结束 ==== 好的,现在结束本文,从本文学习 的最好方法就是使用包含在已编译注册机里的文件,这样你总可以把它作为一个基本注册机。 MiniFmod 由FireLight编码 Keygen source code + music由Ben & [Goofy] of FreeStylerS 2002年编译。 感谢 所有的RCE成员,以及REA社区。 springkang[DFCG][NSSG][TT] 译于2004.08.29 凌晨 0:30 |
|
关于手工建立PE格式文件的文章,不知有没有中文版,有就算了,没有我来译!
PE文件格式 ========== 目的 ----- PE(“portable executable")文件格式是微软windows NT,windows95和 win32系列操作系统上使用的的二进制可执行文件格式(包括DLL和程序)。在windowsNT中,驱动程序也是使用这种格式。PE也可用在对象文件和库文件中。 PE格式是由微软设计的,主要基于对COFF(common object file format 通用对象文件格式)的良好理解。后者用于多种UNIXes 和 VMS 上的对象文件和可执行文件。 win32 SDK 包括 <winnt.h>头文件,里面有对PE格式的预定义(#define)和自定义类型(typedef)。我在讲解的时候会介绍struct-member-names和#define。 你将发现imagehelp.dll会有用。它是winNT的一部分,但有关文档相当少。它的一些功能在”Developer Network"里有描述。 总体布局 -------- 在PE文件的起始处我们会发现MS-DOS 可执行文件"stub"(MS-DOS executable "stub");这使得任何PE 文件都是有效的MS-DOS可执行文件。 在DOS-stub之后是32-bit-signature,其magic number是0x00004550(IMAGE_NT_SIGNATURE). 之后有一文件头(在COFF格式里)告诉二进制文件应运行在哪种机器上,二进制文件里有多少区块(sections),链接时间,是否是一个可执行文件(executable)或DLL文件等等。(在本文中,exe文件和DLL文件的区别在于:DLL不能直接运行,只能被别的二进制文件使用。而二进制文件不能链接到一个exe文件。) 此后,我们会看到一个可选头(optional header)(它始终存在,但仍被称为"optional"----COFF将“optional header"用于库文件(libraries)而不是对象文件(objects)--这也是为什么被称为optional)。它告诉我们更多有关二进制文件如何被加载的信息:起始地址,保留栈的数量,数据段的大小等等。 可选头的一个有趣部分是数据目录(data directories)的踪迹数组(trailing array);这些目录将指针包含到区块(sections)的数据中。例如,如果二进制文件有一个输出目录(export directory),你会在数组成员(array member)IMAGE_DIRECTORY_ENTRY_EXPORT该目录中发现一个指针,它指向某个区块。 紧跟可选头(optional headers)的是区块(sections),他们由区块头部(section headers)所引入。基本上讲,区块的内容是真正执行文件所需要的,而所有的头部和目录只是帮助你找到这些内容。 每个区块都有一些关于对齐、它包含哪种数据(如“初始化数据”等等)、是否共享等以及数据本身的一些标记。大多数(并非所的)的区块包含了一个或多个目录,这些目录引用了可选头部(optional headers)的数据目录数组("data directory" array),如输出函数目录、基址重定向目录(directory of base relocations)。举个例子,可执行代码或初始化数据就是无目录类型的内容。 +-------------------+ | DOS-stub | +-------------------+ | file-header | | 文件头 | +-------------------+ | optional header | | 可选头部 | |- - - - - - - - - -| | | | data directories | | 数据目录 | +-------------------+ | | | section headers | | (区块头部) | +-------------------+ | | | section 1 | | 区块1 | +-------------------+ | | | section 2 | | 区块2 | +-------------------+ | | | ... | | | +-------------------+ | | | section n | | 区块N | +-------------------+ DOS--stub and Signature ------------------------ DOS--stub的概念从16位windows可执行文件(它们是"NE"格式)开始就众所周知了。stub用于OS/2可执行文件,也用于自解压文件和其他应用程序。对PE文件来说,MS-DOS可执行文件(MS-DOS-executable)由100bytes字节组成,用于输出一个诸如“本程序需运行在windows NT”的错误信息。 通过一个结构为IMAGE_DOS_HEADER的有效的DOS-header,你可以认识DOS-stub。前2个字节应该是“MZ”(IMAGE_DOS_SIGNATURE对此有#define ).你可以通过踪迹签名(trailing signature)将一个PE二进制文件同另一个区分开来。踪迹签名可在头部成员’e_lfanew'(32 bits 长,起始于60 byte offset处)所给的偏移量处找到。对OS/2和windows的二进制文件,签名(signature)大小是16 bit word;对PE文件,它是32 bit longword,其值由IMAGE_NT_SIGNATURE #defined 为0x00004550。 文件头(File Header) ------------------- 要找到IMAGE_FILE_HEADER,先验证DOS-header的”MZ“(前两个字节),然后找到DOS-stub's header的‘e_lfanew’数值,再忽略文件开始处的许多字节。检测你找到的签名,文件头--结构体IMAGE_FILE_HEADER--就在其后。文件头的成员从头自尾描述。 第一个成员是16bit值(16-bit-value)‘machine’,它说明二进制文件所将运行的系统。已知的有效值是: IMAGE_FILE_MACHINE_I386 (0x14c) for Intel 80386 processor or better //Intel 80386及以上处理器 0x014d for Intel 80486 processor or better //Intel 80486及以上处理器 0x014e for Intel Pentium processor or better //Intel 奔腾以上处理器 0x0160 for R3000 (MIPS) processor, big endian IMAGE_FILE_MACHINE_R3000 (0x162) for R3000 (MIPS) processor, little endian IMAGE_FILE_MACHINE_R4000 (0x166) for R4000 (MIPS) processor, little endian IMAGE_FILE_MACHINE_R10000 (0x168) for R10000 (MIPS) processor, little endian IMAGE_FILE_MACHINE_ALPHA (0x184) for DEC Alpha AXP processor IMAGE_FILE_MACHINE_POWERPC (0x1F0) for IBM Power PC, little endian 第二个是16bit值(16-bit-value)的“NumberOfSections"。其值就是紧跟头部的区块数。我们后面会讨论这个区块。 第三个是时间印戳(timestamp)"TimeDateStamp",说明文件创建的时间。通过它我们可以区分同一文件的不同版本,即使所谓”官方”版本号没有改变。(时间印戳的格式没有写入文档,除非同一文件的不同版本有某种统一。很显然,“自UTC 1970年1月1日0时0分0秒以来多少秒”为一格式被多数C编译器用作时间印戳。) 时间印戳用作绑定我们稍后会讨论的输入目录。 警告:一些编译器试图设置时间印戳为无意义值。 成员‘PointerToSymbolTable' 和 'NumberOfSymbols'(二者都是32bit)用作调试信息。我不知道如何描述他们,但我已经发现它们的指针总是为0。 'SizeOfOptionalHeader' (16 bit)只是简单的sizeof(IMAGE_OPTIONAL_HEADER)。你可以用它来验证PE 文件结构的正确性。 'Characteristics'是16bits,由一套标记组成,大部分标记只对对象文件和库文件有效。 Bit 0 (IMAGE_FILE_RELOCS_STRIPPED):如果文件中没有重定向信息,就设置。它引用区块本身的每一区块的重定向信息。它不用于可执行文件,因为下面描述的基址重定向目录已经有重定向信息。 Bit 1 (IMAGE_FILE_EXECUTABLE_IMAGE):如果文件是可执行文件,就设置。(也就是说,该文件不是对象文件或库文件。) Bit 2 (IMAGE_FILE_LINE_NUMS_STRIPPED):如果行数信息被除去,就设置。它不用于可执行文件。 Bit 3 (IMAGE_FILE_LOCAL_SYMS_STRIPPED):如果文件中没有本地符号信息,就设置(不用于可执行文件)。 Bit 4 (IMAGE_FILE_AGGRESIVE_WS_TRIM) : 如果操作系统应该整理正在运行的进程的工作环境(进程所用的内存大小),并积极在屏幕上输出(aggressivly by paging it out),就设置。如果是个很烂的、大部分时间在等待,一天只醒来一次的程序,或类似的程序,也应该设置。 Bits 7 (IMAGE_FILE_BYTES_REVERSED_LO) 和 15(IMAGE_FILE_BYTES_REVERSED_HI): 如果文件的endianess不是机器所期望的,就设置,所以在机器读取之前就应交换字节(swap bytes)。这对可执行文件来说是不可靠的(操作系统期望可执行文件是正确按字节排序的)。 Bit 8 (IMAGE_FILE_32BIT_MACHINE) :如果机器预期是32bit 机器,就设置。该标记总是设置。 Bit 9 (IMAGE_FILE_DEBUG_STRIPPED): 如果文件没有调试信息,就设置。对可执行文件没用。 Bit 10 (IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP) :如果应用程序不会从一个可移除的媒介如软驱或光驱运行,就设置。在这种情况下,建议操作系统将文件拷到交换文件区执行。 Bit 11 (IMAGE_FILE_NET_RUN_FROM_SWAP):如果应用程序不会从网络运行,就设置。在这种情况下,建议操作系统将文件拷到交换文件区执行。 Bit 12 (IMAGE_FILE_SYSTEM):如果文件是系统文件,比如驱动程序,就设置。对可执行文件无用。 Bit 13 (IMAGE_FILE_DLL):如果文件是DLL文件,就设置。 Bit 14 (IMAGE_FILE_UP_SYSTEM_ONLY) :如果文件并非设计在多处理器上运行,就设置(也就是说,文件如果在某种方式上只能依赖于一个处理器上运行,它就会蹦溃。)。 相对虚拟地址(Relative Virtual Addresses) ---------------------------------------- PE格式大量使用所谓的RVAs(相对虚拟地址)。一个RVA用于描述内存地址(如果你不知道基地址)。其值加上基地址就会得到你想要的线性地址。 基地址是PE映像加载的地址,可能不同的机器上会发生变化(may vary from one invocation to the next)。 例如:假设一EXE文件加载到 0x400000 地址(address)处,从RVA 0x1560开始执行。有效的执行将从 0x401560地址(address)开始。如果EXE文件加载到0x100000,执行将从0x101560开始。 PE文件的一些部分(区块)并不一定要对齐到映像文件加载的同一方式,从而使事情变得复杂了。比如:文件的区块通常对齐到512-byte-borders,而加载的映像文件可能对齐到4096-byte-borders。参见下面的 'SectionAlignment' 和 'FileAlignment'。 所以为在PE文件中寻找特定RVA的一点信息,你必须计算出偏移量(offsets),假定文件已加载,但根据文件偏移量来忽略。(as if the file were loaded, but skip according to the file-offsets) 举个例子,假定你知道文件从RVA 0x1560开始执行,并想反汇编从那里开始的代码。为找到在文件中的地址,你必须要得知内存中的区块对齐到4096 bytes,并且'.code'区块在内存中从RVA 0x1000开始,长度为16384 bytes;然后你要知道RVA 0x1560在该区块中是偏移量0x560。你得知区块在文件中是对齐到 512-byte-borders,并且'.code'在文件中开始于偏移量0x800处,你就会知道文件中的执行代码开始于0x800+0x560=0xd60 byte处。 如果知道它是如何工作的就很容易了。:-) 可选头(Optional Header) ----------------------- 紧跟文件头的是IMAGE_OPTIONAL_HEADER(尽管名字是可选,可它总是存在)。它包含有关如何准确区分PE文件的信息。其成员也是从头到尾描述。 第一个16位字(16-bit-word)是'magic',就我对PE文件的了解,其值总是0x010b。 随后2个字节是生成文件的链接器的版本号('MajorLinkerVersion' and 'MinorLinkerVersion')。同样,这些值是不可靠的,并不总是正确地反映了链接器的版本号。(为简单,一些链接器不设置这个区域) 让我们想想看,如果你对使用了哪种链接器一无所知,这个版本号又有什么用呢? 再往后面3个长字(longwords)(每个32bit)用来表示可执行代码的大小('SizeOfCode')、初始化数据的大小('SizeOfInitializedData', 所谓的 "data segment")、和未初始化数据的大小('SizeOfUninitializedData', 所谓的"bss segment")。同样,这些值是不可靠的(例如:数据段可能实际上被编译器或链接器分为几个段)。通过检查可选头后面的区块,你可以获得更准确的大小。 后面是一个32位值(32-bit-value)的RVA。该RVA是代码入口点('AddressOfEntryPoint')处的偏移量.文件从那里执行。例如它是DLL文件的LibMain()函数、程序的起始代码(也叫Main())或驱动程序的DriverEntry()。如果你去手工加载映像文件,在处理完所有的修复和重定向后,调用这一地址来开始该程序。 后面2个是32位值的可执行代码('BaseOfCode')和初始化数据('BaseOfData')的偏移量。他们两个同样都是RVAs,而且都没有什么意义,因为你可以通过检查可选头后面的区块获得更可靠的信息。 未初始化数据没有偏移量是因为由于未初始化,映像文件中提供这一数据没有意义。 下一入口是一个32位值的RVA,它给出优先加载地址preferred load address('ImageBase').这一地址是文件已经被链接器重定向的地址;事实上,如果二进制文件能被加载到该地址,加载器就不必再次重定向文件了,从而节省了大量的加载时间。 如果其他的映像文件已经加载到这一地址(“地址冲突”,如果你加载了几个DLL文件,它们都重定向到链接器默认的地址,就会经常发生“地址冲突”),或者有争议(in question)的内存被用作其他目的(stack,malloc(),未初始化数据等什么的),优先加载地址就不能再用了。在这些情况下,映像必须加载到其他某个地址并重定向(参见下面的'重定向目录').如果映像是个DLL文件,这还要进一步处理,因为“绑定输入表”(bound imports)不再有效,必须对调用DLL文件的二进制文件进行修复(参见下面的“输入目录”)。 后面2个32位值是PE文件区块在内存中的对齐('SectionAlignment',当映像被加载)和在文件中的对齐('FileAlignment')。通常二个值都是32,或者FileAlignment是512 而SectionAlignment是4096。区块在后面会讨论。 后面的2个16-bit-words是操作系统版本('MajorOperatingSystemVersion' 和 'MinorOperatingSystemVersion')版本信息应是操作系统(如NT,win95)的版本,对应于子系统的版本(如Win32);它经常没有提供,或提供错误。加载器显然并不用它。 (待续)P355 |
|
zmworm好像失踪多日了
;) 对病毒进行解剖、分析! |
|
关于手工建立PE格式文件的文章,不知有没有中文版,有就算了,没有我来译!
只好辛苦自己了:( |
操作理由
RANk
{{ user_info.golds == '' ? 0 : user_info.golds }}
雪币
{{ experience }}
课程经验
{{ score }}
学习收益
{{study_duration_fmt}}
学习时长
基本信息
荣誉称号:
{{ honorary_title }}
能力排名:
No.{{ rank_num }}
等 级:
LV{{ rank_lv-100 }}
活跃值:
在线值:
浏览人数:{{ visits }}
最近活跃:{{ last_active_time }}
注册时间:{{ user_info.create_date_jsonfmt }}
勋章
兑换勋章
证书
证书查询 >
能力值