明明与阿里安特相连,一个永远的白昼,一个永远的暗月。
     这里充满了黑暗,科技,秘密,压迫,反抗,希望。
    玛加提亚的起源或许会是一个围绕天才、背叛和救赎的故事。
  “红色的土变成红色的风,风变成水,水又变成火,……一切皆为循环往复……”——吉米拉
玛加提亚

    “一开始,是我接到了一个委托,于是我来到了一个小镇上。那个小镇传说是炼金术士们的故乡,叫玛加提亚,坐落在尼哈沙漠东北部的不毛之地。”
    “那儿常年笼罩在迷雾之中,尖碑林立,人们行色匆匆。到处都挤满了古旧高大的砖石建筑,却有着最绚丽多彩的玻璃花窗。即使是白昼也如同最沉闷安静的夜晚。你简直不能想象一个人短短几步路就能看到多少炼金术的痕迹。”
    “玛加提亚分成了两大学派,坚持传统的魔法和炼金术相结合的蒙特鸠,和新兴的把机械工学引入炼金术的卡帕莱特。这两派互相勾心斗角,尔虞我诈,都想要打倒对方证明自己是正确的。也正是因为这样,村庄也愈发地破败下去。”
    “我接到的委托来自一个女妖精,她的女儿生了病,想请我去找她弟弟配药。她的女儿很可爱,也很聪明,好几次悄悄找我帮她做炼金术的研究。可惜是个妖精和人类的混血种……我为她去找人配好了药剂,女妖精很感谢我。然而我在她们附近发现了一个徘徊不去的……怪人。我甚至都不知道那能不能叫人,一个用齿轮和螺钉制造出来的生命……”
    “谁都不知道它的来历,连它自己也不知道。它对自己的身世一无所知,据它所说打从它有记忆的一开始就是在这儿。它想要送妖精礼物,因此它请求我,请求我帮他培养雪原玫瑰。它很悲伤,为了自己而惭愧着,希望能变成人类握住妖精的手。”
    “我替它把玫瑰送给了妖精。我一时好奇去找了后街小贩,花了大价钱混进了两个炼金协会,遇见了不少炼金术士。一个比一个会来事,指使着我跑上跑下。其中一个一开始见了挺吓人的,甚至向我下药,不过混熟之后就觉得他也有些意思的。据他说他原本是蒙特鸠的,因为左眼失明而去了卡帕莱特……”
    “他向我请求帮他找一个人,一个曾与他合作钻研这一领域的术士,也是那个妖精的丈夫,在数年前一场大爆炸中下落不明。那场前所未有爆炸引发的地动甚至损坏了玛加提亚赖以生存的魔法阵……我又去找小贩买了情报,找到了他最好的朋友,一个在世界各地流浪的炼金术士。他告诉我可以去找找他的记事本,这多少也算个有用的提议……”
“我去卡帕莱特的研究所里翻出了关于那件事的记录,还去咨询了两个会长,然而记录对此只是一笔带过,两个会长也不是含糊其辞就是推三阻四,我甚至把那个失踪的炼金术士的屋子搜了几遍,却完全没有头绪。所有的线索都中断了,看上去此事注定就此埋没在时光当中……按道理来说此时我应该可以离开了,但不知道为什么,我依然留在那个小镇上。”
    “直到有一天,我在无意中进入了一个废弃的实验室……我先前有说过两个研究所是建立在玛加提亚的地下的吗?那简直就是错综复杂的迷宫……那儿都是一些脾气暴躁的幽灵,不过其中一个倒也有点意思,一个疯疯癫癫不知道自己已经死了的疯老头子,甚至还会怕幽灵……”
    “我和他聊了聊,他说从前有个人也在那里做实验,他走后这里就彻底荒废了……为了证实自己的话,他甚至给我看了一段影像,影像中的那个博士有点面熟,唠唠叨叨要送妖精银坠子,这倒是个新线索……”
    “疯老头帮我搞到了连接两个研究所的秘密通道的通行证,我又去那间屋子搜出了坠子给了妖精,这才得到了她的信任。在她帮助下我终于拿到了那个博士的记事本,我找到流浪的炼金术士和大魔法师阿尔卡斯特,破解了上面的封印,终于看到了上面记载着的东西……原来他爱上了一个有着近乎永恒生命的妖精,还和她有了一个孩子,却越来越恐惧自己有一天不得不与她们分开……”
    “他开始做各种各样的实验,希望能延长自己的寿命,却统统失败了。不过相比起与他合作的炼金术士提出的用药物,他觉得自己最好的朋友的意见更有价值,那就是将生命转化为机械。”
    “除此之外,他还发现了一个惊人的事实,这个小镇是建立在黑魔法师的魔法阵上的,连炼金术士们的研究也是建立在黑魔法师的研究上。这也是为什么两个会长要努力抹消他的事,一旦这个消息流传出去,这个小镇必会遭到灭顶之灾……”
    “阿尔卡斯特请求我对真相保持沉默,还给了我三块魔法石去修复黑魔法师留下的魔法阵,我同意了。”
    “修复魔法阵之后,他把记事本还给了我……其实我可以随便把这本记事本交给两个协会中的任意一个,相信他们一定愿意为此支付一个让人满意的价钱……然而最终,我把那本记事本给了那个妖精。”
    “她很激动,又有点悲伤,我在这个小镇上再呆下去也没意义了,我离开了那儿。”
    “当我躺在我的床上,回味着我的玛加提亚之旅时,我脑海中忽然灵光一闪,我终于知道为什么我觉得那个博士面熟了……那是那个人造人的脸。”
    “我整个人都愣住了,我忽然意识到他成功了,他把自己转化成了可以近乎永恒存在下去的机器。当我离开的时候,那个人造人依然苦恼于自己只是个冷冰冰的机器,没有资格站在妖精的身边……可是即使把自己都忘记了,它还是深爱着他的妻子和孩子……它渴望着能变成人类去感受她们的体温,却再也不记得,不记得曾经的自己是那么绝望地想要活下去,甚至为此愿意付出一切。”

人造人A

        博士为了和自己身为妖精的妻子在一起进而研究生命的奥秘,无意间发现了黑魔法师的研究所进而得知了玛加提亚百年秘密。然而博士并没有收手,进而在黑魔法师的基础上继续研究,在创作出人造人A与自己女儿琦尼的药后因为一场实验爆炸失踪。无论如何,卡斯特莱的主席麦麦德也不得不承认“在某种程度上博士确实获得了永生,只是失去了记忆”。
       然而,在你帮琦尼完成去卡斯特莱搜集电线包,插头和D·罗伊的金属心脏后,她会说一句这样的话:
     “人造人A是个好人,总有一天我会让他变成人类的”
       这可能是最让我苦笑的一句话了——博士为了和妻女在一起(某种程度上)变成了机器人获得了永生,而自己的女儿又会将他变回人类。这个真相恐怕也只能埋葬在无数冒险家心中了,从而长成一棵畸形的树。

我不知道玛加提亚的真相到底多少人知道,但从三代主席(冰封雪域那个老人)阿斯卡特罗口中得知,因为这种事找他的不是第一次了,他自己也承认“玛加提亚是一个建立在谎言与傲慢上的城市”。两个炼金术师协会主席在一个永不见得光明的城市里等待着一位又一位冒险家到达这个城市,被质问,再悄悄隐瞒这个不得告知的秘密。

 除此之外,还有一个有趣的事。

罗密欧与朱丽叶

        那就是玛加提亚的另一个爱情故事,“罗密欧与朱丽叶”。
        朱丽叶罗密欧是属于两个不同的派系却相爱,难道大家不会去联想到博士与琵丽雅么??
        没错,如果玛加提亚依旧是这个自大又充满争斗的城市,一切都不过是轮回——无数爱情悲剧都会不断上演。

        德朗博士,为了得到永生你差点成了千古罪人,可得到永生后你又失去了太多,到最后还是有限生命的人类身体才是最美好的吗?——山豆根老师
       德朗博士的记事本:

“这里……好冷……”
“过量的使用药水,对健康却很好…”
“不要妄图去得知自己不该得知的东西。”
废都就是荒凉,腐败,勾心斗角。
内拉和吉姆、达克鲁和雪姬,都能体现废都的勾心斗角。
其实不认识的女人的任务也很温情,因为废都充满着猜忌和无情,而绷带女却很温柔的教导和帮助主角,跟其他人一比就很温情了。
每次经过废弃都市这个地方的时候,脑袋里总是在想:这里曾经是怎样一个地方。高耸却废弃的建筑,贴满破旧海报,通缉令又满是涂鸦的斑驳墙壁,南北两方都是停工的工地,废旧的地铁站没有一列载人的列车经过,整个城市永远被黄昏时透着余辉的云翳所笼罩,让人觉得颓废又毫无生气,和佩恩住的那地方没什么差别。
奇怪的是,这里居然会有人居住,而且居民不少,还有两位转职教官。不知道他们是喜欢这样的城市,还是这儿曾经遭受过巨大变故,他们正努力地重建自己的家园。
脑袋里净是这些个问题…………郁闷……不知不觉就走进了一家叫闹拉的医院中去了,医院很小,就两层,站在楼下可以就可以望到第二层。这儿没有一位医生或者是护士,二楼有一个巨大四叶的换气扇,来来回回交替着工作,把颓废的阳光反反复复地切碎,反反复复向房间里抛洒光的碎片。这个时候,我才注意到,换气扇的下面有一张白色的病床,旁边站着个理这短发,脸上缠着半边绷带,穿着白色病服,手里拿着根支架正在打点滴的女人,一个我不认识的女人。她嘴里似乎在这样碎碎念:“这里……好冷……”由于换气扇的缘故,她的声音被风声撕得零散。我在想,这里的医生都去了哪?为什么连护士也没有?X光的透视片还在机器里工作……这是为什么……?出于好奇,我打算上前询问她。靠近她时,我发现她的瞳孔深邃而无光,皮肤毫无血色像是冰块一般,纤瘦的身体像是轻盈的羽毛一般,好像吹一口气就会随风飘走,像桃乐斯一样……她……她是人么……..她见了我,没等我开口,就说:“我好冷……能帮我去弄20个破碎的镜子来吗?”我很难拒绝别人拜托我做的事,另外,我对她也很好奇。
我一出门,就搭车去勇士部落了。野猪对我来说,还算是比较麻烦的角色,弄了半天,终于凑齐了碎镜子的数量。就搭车去废都给她。她把碎镜子拼凑成一块完整的镜子,并试着将阳光借此反射,让自己觉得温暖,站在一旁的我都觉得整个房间都暖了起来,她却依然搓着手,貌似是没感觉,依旧觉得冷吗?的确,她还是说:“呃……我好冷……”
当她把脸凑到镜子前面时,我和她同时尖叫了起来。镜子中竟然没有她的脸!我吓得一屁股坐在地上,连手里的法杖都握不住了,对于我这样的冒险家来说,我似乎没有能力摆平一位幽灵小姐吧……!我的牙齿与整张脸由于惊恐而震颤地跳起舞来……她也很惊讶,她睁大了她那无光的瞳孔,就像长门使出全力要放地爆天心那样。恐惧和不安都写在了无光的瞳孔上了.“难道……我死了?!……不……不……”她抱着头,睁大着瞳孔,用颤抖的声音自言自语道…….然后转头扭向我。我叫得更厉害了。”不要杀我”,“我还不想死”之类的话,一句也喊不出了,只是双手抱着头,没命地叫着……
她轻声说了一句:“我不会杀你的,安吧……”借此来抚平我惊恐不安的心。过了很久我才平静了下来,她轻声细语的说:“能帮我弄来一百个道符吗?我想试着复活,拜托了!”我依旧无法拒绝,虽然我刚才被吓成那样。我相信她绝对需要帮助。
林中之城可是很远的呢,是这个岛的巨大地下迷宫的说。看来要搭长途巴士去了。车上,我一直在想,这女人……难道是……在手术中觉得恐惧,而使用了某种力量杀掉了医院里的人么?就像拥有六尾的羽高那样。还是……手术失败,她因此成了幽灵,医院里的所有人也因此而逃跑?可恶~!怎么能这样对待病人呢,何况还是个女人!混蛋一般的医生!我越想越气。
到了林中,面对僵尸蘑菇时,我在想:那女人复活之后,会和我面前的这个怪物一样么?我觉得很不安。虽然我是牧师,从属性上来说,对抗这些怪物比较轻松,但是我才转职不久,还是很费力。我跌跌撞撞,终于凑齐了100个道符,回去交给她。
她拿着道符摆开了复活的仪式,用不知名的液体在地上画着六芒星的阵式,然后用手将自己的能量注入阵式中。我和她只看到那阵式一次又一次地发出红色的荧光,她却毫无变化,看来……她失败了吧……她失了魂似的松了手,道符散落了一地,她开始跪坐在地上,开始啜泣。除了她的啜泣声与泪水的破裂声,不再有其他的声音。虽然我不认识她,但我的细胞里似乎天生就有照顾人的基因。所以,我走过去坐在她身边,打算安慰她。我刚一坐下,她就抱住了我。流泪的方式由啜泣变成了放声大哭。我扔下了法杖试着去抱紧她,她的身体真的很冷,就像冰块一样。我打算用我这活人的心去给予她温暖。她的泪在我的背上流淌,我似乎能在冥冥之中感受到她的伤痛与苦楚。她哽咽着对我说:“我不能复活!就算复活了,也会变成僵尸的.我不要……!但是……真的……很谢谢你!”说完,她渐渐地停止了啜泣。是哭累了么?还是感受到我所带来的温暖?算了,这都不重要。
她松开了我,不知道从哪里拿出了一件破旧的披风给我,说:“这是我还是个冒险家的时候用的,现在……我……用不着了,就给你吧,请收下!”我接过披风,感觉到这上面似乎还有她曾经作为冒险家的余温。嗯?是错觉吗?我不清楚呀,呵。
夜深了,月光和阳光一样被换气扇切得零碎,散在整个房间里,也散在她的脸上,显得她恬静又可爱,她就靠在我的肩上睡着了,这是我出来冒险那么久,第一次有人靠在我的肩上睡觉,感觉真微妙。同时,我又在想,在想前几天我转职的事情。我选择了牧师,一个圣职业,就是希望借助自己的力量去帮助别人,为别人做些什么,这可是很神圣的职业呢!现在我的修行还不够,但以后我会成为一名祭司,将来会成为一名主教,等我学会了复活术的时候,我会回来帮助你的,会借助自己的力量将你复活,请等着吧!幽!~!我觉得自己成为一名牧师是很有意义的!我顿时觉得自己的冒险人生有了目标。
我这么想的时候,幽的脸上突然绽放出不可名状的笑容,是莞尔一笑吧?!呵呵,不清楚呢。大概是梦到美好的事情了吧?哈?~难道她明白我所想的吗?呵,这都不重要呀,反正不是什么坏事。睡吧……
拂晓了,阳光一路照到我们身上,好像会有什么好事会发生……

一、配置python环境问题

1.首先安装Python(版本无所谓),安装的时候选的添加python路径到环境变量中

安装之后的文件夹如下所示:

2.在VS中配置环境和库

右击项目->属性->VC++目录

1)包含目录:

Python安装路径/include

2)库目录:

Python安装路径/libs

右击项目->属性->连接器->输入->附加依赖库

debug下:
python安装目录/libs/python37_d.lib
release下:
python安装目录/libs/python37.lib

注意
1、debug配置的时候可能没有python37_d.lib,那就把python37.lib复制一个,然后重命名为python37_d.lib就可以啦
2、如果一直报错,但是包含头文件等都没有问题,那么你需要看一下你的python是32位还是64位的。然后根据python的环境去配置vs的环境。

二、C++调用python函数并输出返回值

首先可能有个坑!一定要保证这个python函数所在的文件能够正常运行!然后把python代码放到和C++代码同一目录下。

1、定义Python函数

#!python3
# -*- coding:utf-8 -*-
import base64
import  hmac
from hashlib import sha1

def hash_hmac(code , key):
    #sha1加密签名算法
    hmac_code = hmac.new(key.encode() , code.encode() , sha1).digest()
    return base64.b64encode(hmac_code).decode()

2、编写C++代码

#include <iostream>
#include<python.h>
using namespace std;
int main()
{

    Py_Initialize();//使用python之前,要调用Py_Initialize();这个函数进行初始化
    if (!Py_IsInitialized())
    {
        printf("初始化失败!");
        return 0;
    }

    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./')");//这一步很重要,修改Python路径


    PyObject* pModule = NULL;//声明变量
    PyObject* pFunc = NULL;// 声明变量

    pModule = PyImport_ImportModule("hash_hmac");//这里是要调用的文件名hash_hmac.py
    if (pModule == NULL)
    {
        cout << "没找到" << endl;
    }

    pFunc = PyObject_GetAttrString(pModule, "hash_hmac");//这里是要调用的函数名
    //两个字符串参数
    PyObject* pParams = Py_BuildValue("(ss)", "/oss/upload?bucket=test&filekey=test/image/3b/3ba9d94cab2f8868823d71c4445e125a.png\n" , "q4mJAS777BUbbdVpEqh2XRcZZqNyDweU4GRnM690");
    char* result;
    PyObject* pRet = PyObject_CallObject(pFunc, pParams);//调用函数

    int res = 0;
    PyArg_Parse(pRet, "s", &result);//转换返回类型

    cout << "res:" << result << endl;//输出结果

    Py_Finalize();//调用Py_Finalize,这个根Py_Initialize相对应的。

    return 0;
}

3、解释部分C++代码

PyObject* pParams = Py_BuildValue("(ss)", "/oss/upload?bucket=test&filekey=test/image/3b/3ba9d94cab2f8868823d71c4445e125a.png\n" , "q4mJAS777BUbbdVpEqh2XRcZZqNyDweU4GRnM690");

在这里我输入了两个字符串类型的参数,Py_BuildValue()函数的作用和PyArg_ParseTuple()的作用相反,它将C类型的数据结构转换成Python对象。

该函数可以和PyArg_ParseTuple()函数一样识别一系列的格式串,但是输入参数只能是值,而不能是指针。

它返回一个Python对象和PyArg_ParseTuple()不同的一点是PyArg_ParseTuple()函数它的第一个参数为元组,Py_BuildValue()则不一定会生成一个元组。它生成一个元组仅仅当格式串包含两个或者多个格式单元,如果格式串为空,返回NONE。
在下面的描述中,括号中的项是格式单元返回的Python对象类型,方括号中的项为传递的C的值的类型。
“s” (string) [char *] :将C字符串转换成Python对象,如果C字符串为空,返回NONE。
“s#” (string) [char *, int] :将C字符串和它的长度转换成Python对象,如果C字符串为空指针,长度忽略,返回NONE。
“z” (string or None) [char *] :作用同”s”。
“z#” (string or None) [char *, int] :作用同”s#”。
“i” (integer) [int] :将一个C类型的int转换成Python int对象。
“b” (integer) [char] :作用同”i”。
“h” (integer) [short int] :作用同”i”。
“l” (integer) [long int] :将C类型的long转换成Pyhon中的int对象。
“c” (string of length 1) [char] :将C类型的char转换成长度为1的Python字符串对象。
“d” (float) [double] :将C类型的double转换成python中的浮点型对象。
“f” (float) [float] :作用同”d”。
“O&” (object) [converter, anything] :将任何数据类型通过转换函数转换成Python对象,这些数据作为转换函数的参数被调用并且返回一个新的Python对象,如果发生错误返回NULL。
“(items)” (tuple) [matching-items] :将一系列的C值转换成Python元组。
“[items]” (list) [matching-items] :将一系列的C值转换成Python列表。
“{items}” (dictionary) [matching-items] :将一系类的C值转换成Python的字典,每一对连续的C值将转换成一个键值对。

例如:
Py_BuildValue(“”) None
Py_BuildValue(“i”, 123) 123
Py_BuildValue(“iii”, 123, 456, 789) (123, 456, 789)
Py_BuildValue(“s”, “hello”) ‘hello’
Py_BuildValue(“ss”, “hello”, “world”) (‘hello’, ‘world’)
Py_BuildValue(“s#”, “hello”, 4) ‘hell’
Py_BuildValue(“()”) ()
Py_BuildValue(“(i)”, 123) (123,)
Py_BuildValue(“(ii)”, 123, 456) (123, 456)
Py_BuildValue(“(i,i)”, 123, 456) (123, 456)
Py_BuildValue(“[i,i]”, 123, 456) [123, 456] Py_BuildValue(“{s:i,s:i}”, “abc”, 123, “def”, 456) {‘abc’: 123, ‘def’: 456}
Py_BuildValue(“((ii)(ii)) (ii)”, 1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))

3、运行C++程序

与Python代码的预期相同。

三、Python代码处理

在发布软件的时候,通常我们都不希望代码可以直接被别人看到。

以上的Debug目录中的exe要想能够单独运行,必须把python脚本拷过去。为了不让别人能直接看到我的代码,我拷过去的是生成的.pyc文件,实现了一个简单的python代码的加密。不过据说可以反编译,但是对我来说已经够了。

四、.py和.pyc的区别

原来Python的程序中,是把原始程序代码放在.py文件里,而Python会在执行.py文件的时候。将.py形式的程序编译成中间式文件(byte-compiled)的.pyc文件,这么做的目的就是为了加快下次执行文件的速度。

所以,在我们运行python文件的时候,就会自动首先查看是否具有.pyc文件,如果有的话,而且.py文件的修改时间和.pyc的修改时间一样,就会读取.pyc文件,否则,Python就会读原来的.py文件。

其实并不是所有的.py文件在与运行的时候都会产生.pyc文件,只有在import相应的.py文件的时候,才会生成相应的.pyc文件。

五、使用.py生成.pyc

在命令行下使用下列命令即可:

python -m py_compile test.py#单文件

python -m py_compile /root/src/{file1,file2}.py#多文件

(1)什么是钩子 我们可以首先从字面上了解钩子,钩子是干什么的呢?日常生活中,我们的钩子是用来钩住某种东西的,比如,说,鱼钩是用来钓鱼的,一旦鱼咬了钩,钩子就一直钩住鱼了,任凭鱼在水里怎么游,也逃不出鱼钩的控制。同样的,Windows的钩子Hook也是用来钩东西的,比较抽象的是他是用来钩Windows事件或者消息的。最常见的就是鼠标和键盘钩子,用Hook钩子钩住鼠标、键盘,当你的鼠标、键盘有任何操作时,通过Hook就能知道他们都做了什么了,多么形象啊,把老鼠Mouse钩住了,不管你干什么,都逃不过我钩子Hook的手掌心。 技术上讲,钩子(Hook)是Windows消息处理机制的一个很重要的内容,谁叫Windows是基于消息的呢。应用程序可以通过钩子机制截获处理Window消息或是其他一些特定事件。 我们可以在同一个钩子上挂很多东西。 想起参加工作前要求被体检的时候,当你被登记之后,按照你的登记表上的顺序,就等着到各个科室一个一个的去检查吧。每一个科室都有决定你是否继续的可能,只有通过了这个,你才可以到下一个去,如果没有通过,那么,你是看不到最后的大夫了,可以直接over回家了。 如果把体检比喻为事件的话,当事件发生时,应用程序(体检过程)可以在相应的钩子Hook上设置多个钩子子程序(Hook Procedures)(多个科室的检查),由其组成一个与钩子相关联的指向钩子函数的指针列表(钩子链表)(体检表,确定了你要走的顺序)。当钩子所监视的消息出现时(你拿着表格来体检了),Windows(导诊员)首先将其送到调用链表中所指向的第一个钩子函数中(体检表上第一个科室,一般是身高体重吧,呵呵),钩子函数将根据其各自的功能(每个科室检查的项目不一样啊)对消息进行监视(有的大夫就随便看看了事)、修改(碰到好心的大夫还可以帮你往好里添点呢,呵呵)和控制(有的大夫好严格啊),并在处理完成后(当然有的大夫就直接把你刷下了,回家吧,没有下一个了)把消息传递给下一钩子函数(下一个项目的科室,当然,也可以强制消息的传递,直接打发你回家)直至到达钩子链表的末尾(检查完了!)。在钩子函数交出控制权后,被拦截的消息最终仍将交还给窗口处理函数(好了,拿着表去上班吧)。 虽然钩子函数对消息的过滤将会略加影响系统的运行效率,但在很多场合下通过钩子对消息的过滤处理可以完成一些其他方法所不能完成的特殊功能。 有道词典的屏幕取词可能就是这么完成的。。。

(2)比较专业的对钩子的技术性理解 钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。 Windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。而钩子是Windows系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,来完成普通应用程序难以实现的功能。钩子可以监视系统或进程中的各种事件消息,截获发往目标窗口的消息并进行处理。这样,我们就可以在系统中安装自定义的钩子,监视系统中特定事件的发生,完成特定的功能,比如截获键盘、鼠标的输入,屏幕取词,日志监视等等。可见,利用钩子可以实现许多特殊而有用的功能。 钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。 一个Hook都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指定的,应用程序定义的,被Hook子程调用的回调函数,也就是该钩子的各个处理子程。当与指定的Hook类型关联的消息发生时,系统就把这个消息传递到Hook子程。一些Hook子程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。 Windows并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。 大多数人或者网上文章认为全局钩子都要依赖于一个DLL才能正常工作的,常常会看到很多人在论坛上长期争论一个话题:“全局钩子一定要在DLL里面吗?”。实际上这里有一个概念的问题,究竟上面提到的全局钩子是指什么。通过对上面各种钩子的作用域的理解就会发现这个问题的答案。 上面一共提到了15种钩子。 钩子作用域: WH_JOURNALPLAYBACK,WH_JOURNALRECORD,WH_KEYBOARD_LL,WH_MOUSE_LL、WH_SYSMSGFILTER这5种钩子本身的作用域就是全局的,不管钩子是直接写在应用程序的代码里还是放在DLL中,他们都能够钩住系统的消息。剩下的10种钩子,他们的作用域既可以是线程的又可以是全局的,当将相应的钩子直接写在应用程序的代码中时,他们只能捕获当前线程上下文的消息。那么他们如何实现捕获全局消息的功能呢?当把钩子写入到一个单独的DLL中再引用后,系统自动将该DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程,从而达到捕获全局消息的目的。相对来说,前面5种钩子本身就是全局的,是不需要注入的。 因此,对于前面问题的答案就是:要实现捕获全局消息功能的钩子,是否要写在单独的DLL里面,取决于钩子的类型以及相应的作用域。 如果对于同一事件既安装了线程勾子又安装了全局勾子,那么系统会自动先调用线程勾子,然后调用全局勾子。 下面我们开始实现用键盘钩子截获密码等键盘输入。

(1)使用VS创建一个MFC应用程序。

(2)编辑界面,一个开始Hook的按钮,一个结束Hook的按钮,以及一个取消按钮(可有可无)。

(3) 编辑KeyboardHookDlg.cpp,实现截取键盘按键的功能。 分别双击两个按钮,就可以生成它们的事件函数。 开始Hook的按钮事件函数:

void CKeyboardHookDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	string str = "start:";
	SaveFile << str << endl;

	//这里调用SetWindowsHookExA()函数,因为hook的实现不是在DLL中,而是直接在KeyboardHookDlg.cpp中实现,所有第4个参数使用GetModuleHandle(NULL)
	glhHook = SetWindowsHookExA(WH_KEYBOARD_LL, KeyboardProc, GetModuleHandle(NULL), NULL);
	if (glhHook != NULL) {
		//AfxMessageBox(L"StartHook成功!");//用于打桩测试,通过后注释掉,不然太麻烦
	}
	else {
		AfxMessageBox(L"StartHook失败!");
	}
}

结束Hook的按钮事件函数:

void CKeyboardHookDlg::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码
	UnhookWindowsHookEx(glhHook);
}

同时需要在KeyboardHookDlg.cpp文件头部添加库函数和全局变量:

#include <iostream>  
#include <string>  
#include <fstream>
 
using namespace std;
 
//全局变量
HHOOK glhHook = NULL;			//安装的鼠标勾子句柄 
BOOL g_bCapsLock = FALSE;		//大小写锁定键	
BOOL g_bShift = FALSE;			//shift键
ofstream SaveFile("key.txt")

(4)编译并运行,然后点击Hook按钮,这时随便输入一个密码,再点击结束Hook按钮, 在目录下会生成“key.txt”,其中就记录着你的按键信息。

目前代码只实现了区分大小写字母、数字、数字小键盘输入,其他的按键如标点符号等暂时没有处理。

项目地址(VS2019):https://github.com/maplefan/EasyKeyboardHook

执行这些命令时,不要直接在系统自带cmd上去执行,因为这些命令工具所在的目录并没有添加到Path中;所以需要使用Visual Studio的开发者命令行工具。

或者也可以单独下载一个SignTool, 下载链接:https://www.wosign.com/download/Signtool.rar

具体命令指令查看: https://docs.microsoft.com/zh-cn/dotnet/framework/tools/signtool-exe

使用示例:

其中:

(1) /v:显示详细的签名结果;

(2) /f xx.pfx:加载代码签名证书。请把颁发给你的用户证书放到signtool目录下,或者指定文件路径;

(3) /p 密码:申请证书时候设置的密码;

(4) /t,/tr:为代码加上WoSign免费时间戳,确保签名后的代码永不过期;

(5) test.cab: 就是您要签名的Windows文件,如:.cab, .dll, .exe 等文件;

请注意:签名时,一定要保证能连上互联网,否则由于无法访问时间戳服务器而失败。

签名完成后,如果显示“Successfully signed and timestamped”(成功签名与加上时间戳),如下图所示,这表明签名已经成功:

QQ音乐

通过网页进入音乐的播放页面后,像这个界面一样

按F12(笔记本一般为Fn+F12),如下图所示:其中这一段就是我们正在收听的音乐的下载地址,不过下载下来的格式是.M4A格式的音乐,如果需要.MP3格式的音乐需要使用格式工厂等工具进行转码。

网易云音乐

首先进入网易云音乐官网,然后搜索想要下载的音乐,然后进入到该音乐的播放界面,如下图所示,记住这个音乐的ID。

然后在这个网址http://music.163.com/song/media/outer/url?id=.mp3的id标签后面加上这串数字。
比如上面的《倒带》的id是 209758。
网址就变成了http://music.163.com/song/media/outer/url?id=209758.mp3
然后直接在浏览器中输入这个网址就进入这个播放该音乐的界面,在该界面就可以成功下载了。

#include "Winver.h";
#pragma comment(lib,"Version.lib")
DWORD GetIeVersion()
{
	const TCHAR szFilename[] = _T("mshtml.dll");
	DWORD dwMajorVersion = 0, dwMinorVersion = 0;
	DWORD dwBuildNumber = 0, dwRevisionNumber = 0;
	DWORD dwHandle = 0;TCHAR szBuf[80];
	DWORD dwVerInfoSize = GetFileVersionInfoSize(szFilename, &dwHandle);//判断容纳文件版本信息需要一个多大的缓冲区
	if (dwVerInfoSize)
	{
		LPVOID lpBuffer = LocalAlloc(LPTR, dwVerInfoSize);//从堆中分配指定大小的字节数
		if (lpBuffer)
		{
			//从支持版本标记的一个模块里获取文件版本信息
			if (GetFileVersionInfo(szFilename, dwHandle, dwVerInfoSize, lpBuffer))
			{
				VS_FIXEDFILEINFO * lpFixedFileInfo = NULL;
				UINT nFixedFileInfoSize = 0;
				if (VerQueryValue(lpBuffer, TEXT("\\"), (LPVOID*)&lpFixedFileInfo, &nFixedFileInfoSize) && (nFixedFileInfoSize))
				{//从版本资源中获取信息
					dwMajorVersion = HIWORD(lpFixedFileInfo->dwFileVersionMS);//主版本号
					dwMinorVersion = LOWORD(lpFixedFileInfo->dwFileVersionMS);//副版本号
					dwBuildNumber = HIWORD(lpFixedFileInfo->dwFileVersionLS);//编译版本号
					dwRevisionNumber = LOWORD(lpFixedFileInfo->dwFileVersionLS);//修订版本号
				}
			}
			LocalFree(lpBuffer);
		}
	}
	else return 0;
	wchar_t buf[1024] = { 0 };
	wsprintfW(buf, L"IE 版本为 %d.%d.%d.%d", dwMajorVersion, dwMinorVersion, dwBuildNumber, dwRevisionNumber);
	OutputDebugStringW(buf);
	return dwMajorVersion;//返回主版本号
}

void getSystemName()
{
	std::string vname;
	//先判断是否为win8.1或win10
	typedef void(__stdcall*NTPROC)(DWORD*, DWORD*, DWORD*);
	HINSTANCE hinst = LoadLibrary(L"ntdll.dll");
	DWORD dwMajor, dwMinor, dwBuildNumber;
	NTPROC proc = (NTPROC)GetProcAddress(hinst, "RtlGetNtVersionNumbers"); 
	proc(&dwMajor, &dwMinor, &dwBuildNumber); 
	if (dwMajor == 6 && dwMinor == 3)	//win 8.1
	{
		vname = "Microsoft Windows 8.1";
		printf_s("此电脑的版本为:%s\n", vname.c_str());
		return;
	}
	if (dwMajor == 10 && dwMinor == 0)	//win 10
	{
		vname = "Microsoft Windows 10";
		printf_s("此电脑的版本为:%s\n", vname.c_str());
		return;
	}
	//下面判断不能Win Server,因为本人还未有这种系统的机子,暂时不给出
 
 
 
	//判断win8.1以下的版本
	SYSTEM_INFO info;                //用SYSTEM_INFO结构判断64位AMD处理器  
	GetSystemInfo(&info);            //调用GetSystemInfo函数填充结构  
	OSVERSIONINFOEX os;
	os.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
	#pragma warning(disable:4996)
	if (GetVersionEx((OSVERSIONINFO *)&os))
	{
 
		//下面根据版本信息判断操作系统名称  
		switch (os.dwMajorVersion)
		{                        //判断主版本号  
		case 4:
			switch (os.dwMinorVersion)
			{                //判断次版本号  
			case 0:
				if (os.dwPlatformId == VER_PLATFORM_WIN32_NT)
					vname ="Microsoft Windows NT 4.0";  //1996年7月发布  
				else if (os.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
					vname = "Microsoft Windows 95";
				break;
			case 10:
				vname ="Microsoft Windows 98";
				break;
			case 90:
				vname = "Microsoft Windows Me";
				break;
			}
			break;
		case 5:
			switch (os.dwMinorVersion)
			{               //再比较dwMinorVersion的值  
			case 0:
				vname = "Microsoft Windows 2000";    //1999年12月发布  
				break;
			case 1:
				vname = "Microsoft Windows XP";      //2001年8月发布  
				break;
			case 2:
				if (os.wProductType == VER_NT_WORKSTATION &&
					info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
					vname = "Microsoft Windows XP Professional x64 Edition";
				else if (GetSystemMetrics(SM_SERVERR2) == 0)
					vname = "Microsoft Windows Server 2003";   //2003年3月发布  
				else if (GetSystemMetrics(SM_SERVERR2) != 0)
					vname = "Microsoft Windows Server 2003 R2";
				break;
			}
			break;
		case 6:
			switch (os.dwMinorVersion)
			{
			case 0:
				if (os.wProductType == VER_NT_WORKSTATION)
					vname = "Microsoft Windows Vista";
				else
					vname = "Microsoft Windows Server 2008";   //服务器版本  
				break;
			case 1:
				if (os.wProductType == VER_NT_WORKSTATION)
					vname = "Microsoft Windows 7";
				else
					vname = "Microsoft Windows Server 2008 R2";
				break;
			case 2:
				if (os.wProductType == VER_NT_WORKSTATION)
					vname = "Microsoft Windows 8";
				else
					vname = "Microsoft Windows Server 2012";
				break;
			}
			break;
		default:
			vname = "未知操作系统";
		}
		printf_s("此电脑的版本为:%s\n", vname.c_str());
	}
	else
		printf_s("版本获取失败\n");
}

使用VS创建一个C#类库
写一个简单的int类型整数相加函数

调试生成TestDll.dll,然后新建一个C++的Win32控制台项目,直接生成解决方案,然后将C#生成的 TestDll.dll 放到C++项目目录中,并使用#using引用C#编写的DLL文件,使用using使用C#中你要使用的类所在的命名空间,然后使用C#中的类名生成一个对象并调用你要使用的函数,如下代码所示。

在编译之前应该右键项目名称->属性->配置属性->常规

将公共语言运行时支持从无公共语言运行时支持设置成公共语言运行时支持(/clr).

C++/CLI中使用gcnew关键字表示在托管堆上分配内存,并且为了与以前的指针区分,用^来替换* ,就语义上来说他们的区别大致如下:

1. gcnew返回的是一个句柄(Handle),而new返回的是实际的内存地址.

2. gcnew创建的对象由虚拟机托管,而new创建的对象必须自己来管理和释放.

#include "stdafx.h"
#include<windows.h>
#using "../Debug/TestDll.dll"
using namespace TestDll;
int _tmain(int argc, _TCHAR* argv[])
{
	int sum,x,y;
	x = 10;
	y = 20;
	Class1 ^a = gcnew Class1();
	sum = a->demoAdd(x ,y);
	printf("%d+%d=%d\n",x,y,sum);
	system("pause");
	return 0;
}
成功!

HMAC: Hash-based Message Authentication Code,即基于Hash的消息鉴别码

这周开始将图片上传、图片下载迁移到OSS上,在调用OSS的时候需要根据规则使用Hmac Sha1+BASE64对图片的一些数据进行计算token再上传,遇到了一个困扰一周的坑。

在生成token的规则中要求将sign_str加上一个换行符(\n)再进行Hmac Sha1计算,给的Python Demo如下:

sign_str = “{}?{}\n”.format( req_path , query_str )
b64_enc_sign_str = hash_hmac (sign_str , secret_key)

然后我直接在C++项目中

#include<openssl/hmac.h>

HMAC (EVP_sha1() , ch , strlen(ch) , data , dataLength , digest , &digest_len);

其中data是一个unsigned char*类型的数据,由字符串转化而来,而字符串的末尾加上了换行符,发现相同的加密串,相同的密钥,使用openssl中的Hmac和Python demo中的计算结果截然不同,然而都不加换行符的话计算结果则一模一样。

最后通过github寻找现成的Hmac_Sha1加密算法才得以解决。

#include <iostream>
#include <string>
#include <cmath>
#include <cstdio>
using namespace std;
unsigned long Rol(unsigned long x, int y);
unsigned long Ror(unsigned long x, int y);
unsigned long f(unsigned long B,unsigned long C,unsigned long D, int t);

unsigned long H[5];
unsigned long T[512]={0};
void HMAC(string text, string key);
void SHA1(string s);
// HMAC function
int i;
void HMAC(string text, string key)
{
	char c;
	string s;
	unsigned long Key[16] = {0};
	unsigned long X[16] = {0};
	unsigned long Y[16] = {0};
	unsigned long ipad = 0x36363636;
	unsigned long opad = 0x5c5c5c5c;
	int k;
	s = "";

	//Process string key into sub-key
	//Hash key in case it is less than 64 bytes
	if (key.length() > 64)
	{
		SHA1(key);
		Key[0] = H[0];
		Key[1] = H[1];
		Key[2] = H[2];
		Key[3] = H[3];
		Key[4] = H[4];
	}
	else
	{
		for(int i=0; i<16; i++)
		{
			for(int j=0; j<4; j++)
			{
				if (4*i+j <= key.length())
				{
					k = key[4*i+j];
				}
				else
				{
					k = 0;
				}
				if (k<0)
				{
					k = k + 256;
				}
				Key[i]= Key[i] + k*pow(256,(double)3-j);
			}
		}
	}

	for(int i=0; i<16; i++)
	{
		X[i] = Key[i]^ipad;
		Y[i] = Key[i]^opad;
	}

	//Turn X-Array into a String
	for(i=0; i<16; i++)
	{
		for(int j=0; j<4; j++)
		{
			c = ((X[i] >> 8*(3-j)) % 256);
			s = s + c;
		}
	}

	//Append text to string
	s = s + text;

	//Hash X-Array
	SHA1(s);

	s = "";

	//Turn Y-Array into a String
	for(i=0; i<16; i++)
	{
		for(int j=0; j<4; j++)
		{
			c = ((Y[i] >> 8*(3-j)) % 256);
			s = s + c;
		}
	}

	//Append Hashed X-Array to Y-Array in string
	for(i=0; i<5; i++)
	{
		for(int j=0; j<4; j++)
		{
			c = ((H[i] >> 8*(3-j)) % 256);
			s = s + c;
		}
	}

	//Hash final concatenated string
	SHA1(s);

}

// SHA-1 Algorithm
void SHA1(string s)
{
	unsigned long K[80];
	unsigned long A,B,C,D,E,TEMP;
	int r,k,ln;
	H[0]=0x67452301;
	H[1]=0xefcdab89;
	H[2]=0x98badcfe;
	H[3]=0x10325476;
	H[4]=0xc3d2e1f0;

	ln=s.length();
	r = int((ln+1)/64);

	if (((ln+1) % 64) > 56)
		{
		r=r+1;
		}

	// initialize Constants
	for(int t=0; t<80; t++)
		{
			if (t<20)
				{
					K[t] = 0x5a827999;
				}

			if ((t>19)&(t<40))
				{
					K[t] = 0x6ED9EBA1;
				}
			if ((t>39)&(t<60))
				{
					K[t] = 0x8F1BBCDC;
				}
			if (t>59)
				{
					K[t] = 0xca62c1d6;
				}
		}

	for(int l=0; l <= r; l++)
	{
		unsigned long W[80]={0};
		//Initialize Text
		for (int i=0; i<16; i++)
			{
			for(int j=0; j<4; j++)
				{
					if (4*i+j <= ln)
					{
						k = s[64*l+4*i+j];
					}
					else
					{
						k = 0;
					}

					if (k<0)
					{
						k = k +256;
					}

					if (4*i+j == ln)
					{
						k = 0x80;
					}

					W[i]= W[i] + k*pow(256,(double)3-j);
				}
			}
		if ((W[14]==0)&(W[15]==0))
		{
			W[15]=8*s.length();
		}

	// Hash Cycle

		for (int t = 16; t <80; t++)
			{
				W[t] = Rol(W[t-3]^W[t-8]^W[t-14]^W[t-16],1);
			}

		A = H[0];
		B = H[1];
		C = H[2];
		D = H[3];
		E = H[4];

		for(int t = 0; t < 80; t++)
		{
			TEMP = Rol(A,5) + f(B,C,D,t) + E + W[t] + K[t];
			E = D;
			D = C;
			C = Rol(B,30);
			B = A;
			A = TEMP;
		}

		H[0] = H[0] + A;
		H[1] = H[1] + B;
		H[2] = H[2] + C;
		H[3] = H[3] + D;
		H[4] = H[4] + E;

		ln = ln - 64;
	}

}

unsigned long f(unsigned long B,unsigned long C,unsigned long D, int t)
{
	if (t < 20)
		{
			return ((B & C)^((~B) & D));
		}
	if ((t > 19) & (t < 40))
		{
			return (B ^ C ^ D);
		}
	if ((t > 39) & (t < 60))
		{
			return ((B & C)^(B & D)^(C & D));
		}
	if (t > 59)
		{
			return (B ^ C ^ D);
		}
}


unsigned long Rol(unsigned long x, int y)
{
	if (y % 32 == 0) {return x;}
	else {return ((x << y)^(x >> -y));}
}

unsigned long Ror(unsigned long x, int y)
{
	if (y % 32 == 0) {return x;}
	else {return ((x >> y)^(x << -y));}
}
int main()
{
	HMAC("helloworld\n","q4mJAS777BUbbdVpEqh2XRcZZqNyDweU4GRnM690");
	int i = 0;
	for(i = 0;i < 5;i++)
	{
		printf("%.8X\n",H[i]);
	}
}

然后以为这一关就过了。。。然后在进行Base64加密的时候又遇到了一个坑。

给出的Python demo的代码如下:

b64_enc_sign_str = base64.b64encode( hmac_code ).decode()

然后我使用常规的Base64算法进行计算,又发现两个相同的字符串进行加密后得到的结果大相径庭。

最后在 https://1024tools.com/hmac 找到了原因,常规的Base64算法就像 https://blog.csdn.net/wo541075754/article/details/81734770 所说,而我们这里需要将HMAC计算返回的原始二进制数据后进行Base64编码。

首先将HMAC_Sha1加密得出的结果转换为二进制编码。

void CCommonFunction::HexToBin(CString hexDight , CString& binDight){
	binDight = "";
	int f = 0,c = 0;
	char e;
	for(int f = 0; f <= hexDight.GetLength() ; f++){
		e = hexDight[f];
		if(e >= 'a' && e <= 'f'){
			int a = static_cast<int>(e-'a'+10);
			switch(a){
				case 10 : binDight = binDight + "1010";
					break;
				case 11 : binDight = binDight + "1011";
					break;
				case 12 : binDight = binDight + "1100";
					break;
				case 13 : binDight = binDight + "1101";
					break;
				case 14 : binDight = binDight + "1110";
					break;
				case 15 : binDight = binDight + "1111";
					break;
			}
		}
		else if( e >= '0' && e <= '9'){
			int b = static_cast<int>(e-'0');
			if(f == 0){
			switch(b){
				case 0: 
					break;
				case 1: binDight = binDight + "1";
					break;
				case 2: binDight = binDight + "10";
					break;
				case 3: binDight = binDight + "11";
					break;
				case 4: binDight = binDight + "100";
					break;
				case 5: binDight = binDight + "101";
					break;
				case 6: binDight = binDight + "110";
					break;
				case 7: binDight = binDight + "111";
					break;
				case 8: binDight = binDight + "1000";
					break;
				case 9: binDight = binDight + "1001";
					break;		
			}
			}
			else{
				switch(b){
				case 0 : binDight = binDight + "0000";
						 break;
				case 1: binDight = binDight + "0001";
					break;
				case 2: binDight = binDight + "0010";
					break;
				case 3: binDight = binDight + "0011";
					break;
				case 4: binDight = binDight + "0100";
					break;
				case 5: binDight = binDight + "0101";
					break;
				case 6: binDight = binDight + "0110";
					break;
				case 7: binDight = binDight + "0111";
					break;
				case 8: binDight = binDight + "1000";
					break;
				case 9: binDight = binDight + "1001";
					break;		
				}
			}
		}
	}
}

然后判断二进制串是否是6的倍数,不是6的倍数的话在末尾补0直到该二进制串是6的倍数,然后每6位取一次6位的二进制串,转换为10进制,然后去Base64编码对照表中找出这个十进制数字对应的字符,将这些所有的字符拼接起来并在末尾加上一个固定的“=”即可,代码如下:

//Base64编码表
const  char Base64EncodeMap[64] =
{
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
	'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
	'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
	'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
	'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
	'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
	'w', 'x', 'y', 'z', '0', '1', '2', '3',
	'4', '5', '6', '7', '8', '9', '+', '/'
};

void CCommonFunction::BinToBase64(CString binStr , CString &base64Str)
{
	while(binStr.GetLength() % 6 != 0){
		binStr = binStr + "0";
	}
	base64Str = "";
	CString tmp = "";
	int index = 0;
	int num = 0;
	while(index < binStr.GetLength()){
		tmp = binStr.Mid(index , 6);
		index = index + 6;
		num = BinToDecInt(tmp);
		base64Str = base64Str + Base64EncodeMap[num];
	}
	base64Str = base64Str + "=";
}