郁金香2021年游戏辅助技术中级班(一)
郁金香2021年游戏辅助技术中级班(一)
- 用代码读取utf8名字
- 字节数组搜索UTF-8字符串
- 用CE和xdbg分析对象名字
- 从LUA函数的角度进行分析
- 复习怪物名字偏移
- 用CE和xdbg分析对象数组
- 认识虚函数表
- 分析对象数组
- 分析对象数组链表部分
- 链表的定义
- 链表的数据在内存里面究竟是一个什么样的形式
用代码读取utf8名字
上图从33行开始到43行的那个代码块是UniCodeToAscii函数。
字节数组搜索UTF-8字符串
现在比较新的CE版本是支持搜索UTF-8字符串的:
较老一点版本的CE是搜索不到UTF-8字符串的,当时也是有办法的,我们可以修改上图字符串类型为字节数组类型:
通过搜索上图这个字节数组也是能够找到UTF-8字符串的:
现在我们用的CE7.2这个比较新的版本,它是支持UTF-8的,所以可以直接搜索该字符串。
用CE和xdbg分析对象名字
早期的CE搜不到UTF-8字符串,这里可以用该工具把我们普通的UTF-8字符串转换成字节集的,然后在CE中搜索该字节集。
搜索怪物名字,来找到怪物名字的偏移,但是有可能会搜到很多结果,哪一个才是我们身边选中的怪物呢?
这时候要通过修改名字来过滤筛选了:
发现身边怪物名字没有变化,那就删除选中的这一大片名字(例如二分法),继续再选大部分名字进行修改,不断重复该步骤。
还是没有影响到身边该怪物的名字,删除,继续选择剩下的所有名字进行修改:
注意,再修改名字的时候,不要移动人物、击打怪物,避免刷新地图上的怪物,导致数据过期;另外可以用鼠标来回选中最近的怪物(或者先选中自己,再选那个怪物),切换一下看看选中怪物的血量面板上的名字是否改变(如上图所示,面板上的改变了,模型上的名字没有改变)。
先选中自己,再选最近怪物,如果怪物面板上的名字已改变,删除剩下的部分,只留下受影响的部分,继续二分法修改(少的时候可以从后往前一半一半的过滤),直至确定当前选中怪物名字的地址,然后就可以去找它的偏移了。
我们最好是在xdbg中去分析该地址:
在内存窗口该地址处下个4字节的访问断点,直接断下,可以看到eax中就是2D05A2C8,我们来看看eax的来源:
我们发现[ebp+8]是等于0的,所以eax=[ecx+5c],这是第一个偏移。
我们在内存窗口中查看,ecx+5c刚好就是存放2D05A2C8的地址,所以ecx+5c就是怪物名字的第一个偏移;
而第二个偏移(查找ecx的来源)就要到上一层去看看了(通过堆栈窗口双击返回地址):
也就是上一层的74F857这个地方调用了53B8E0这个获取怪物名字CALL。
而我们要找的ecx就来源于67B6A0这个CALL的返回值,即[CALL 67B6A0的返回值+5C]就是怪物名字地址,所以现在的重点就是要进入到67B6A0这个CALL里面分析该CALL的返回值。
由于该CALL比较复杂,我们先在该CALL调用处下个断来跟进去,分析它是从什么地方返回这个eax返回值的;
首先,我们F8单步步过该CALL,看看[eax+5c]处的内容是不是怪物的名字:
说明我们的找法(分析的位置)是没有问题的,好,我们重新让游戏运行起来,重新断在74F84A地址这个CALL,然后按F7跟进去分析返回值eax,进去后我们一直按F8,去追eax是在什么地方返回的:
上图就说明了所处位置不对,肯定不是从上图这个地方返回的(否则是能够读出怪物名字的),我们继续向下F8跟:
从上图这里来看的话,它还有一个0x18的偏移:
经测试,0x18确实是怪物名字的一个偏移,然后我们再来看一下0F26EF20又是从什么地方来的,我们可以看 - 减号一步一步退回去,看一下0F26EF20这个eax来源于什么地方。
这个eax来源于CALL 6F6020的返回值。
这个CALL 6F6020感觉是到怪物数组里面去把那个怪物对象取出来(可以看到ds:[edx+eax*4+4]
这种全局数组或静态数组的反汇编形式),有点这种感觉,我们在该CALL开头做个标记。
这个CALL我们感觉有些熟悉,在初级班29课分析角色对象的属性那课:
在CALL 4D4DB0查询对象这个CALL里面有个CALL 4D4BB0:
这个CALL 4D4DB0和我们刚才分析的CALL 6F6020很相像。
对比分析,感觉都是一个模式印出来的,相似度都是非常高的,只是这两个CALL的地址不一样,从代码来分析的话基本上都是一样的。
目前我们还是回到CALL 67B6A0分析上来,就是说这个地方有可能就是遍历怪物数组的地方,遍历完就返回这个怪物对象,基本上就是返回的这个对象+0x18+5C就是怪物名字的地址了;
而且我们在74F84F地址下断,发现返回值eax总是固定的值F0FFCF8(而F0FFCF8-0x18就刚好是上图内层CALL 6F6020的返回值F0FFCE0),即使我们将视角变化一下,可视范围内怪物对象多一些,返回值eax也不变,可能是由于这是选中的那个怪物对象,那么我们换一下怪物看看,甚至选中远处的怪物,结果我们发现返回值eax还是不变,这就比较奇怪了,返回的老是相同的一个数值,这需要下来之后作进一步的分析了。
接着我们看一下传给CALL 6F6020的参数是什么,都是什么内容,猜测都是干什么用的:
我们在内存窗口中转到当前esp,然后用64位十六进制整数查看,这是为了分析该数值是否是怪物的ID:
然后用CE搜一下3ACFD370000012B这个8字节的数值:
发现只有一处地方有这个数值,目前还不知道该数值是不是怪物的ID(只有猜测)。
有可能CALL 6F6020这个CALL是用怪物的ID来查询怪物的名字,具体我们下去再分析一下。
我们用CE搜一下什么地方存了eax=0F20E4F0这个指针:
发现搜到的结果比较多,那么我们进到0074F84A地址处的CALL 0067B6A0里面:
我们发现上图堆栈窗口中每次断下CALL 006F6020的时候传入的参数2会发生变化,ECX没有变动。
从LUA函数的角度进行分析
实际上另外有一种方法可以快速的分析到,通过游戏注册的LUA函数:
在UnitName函数这里,如果传入的是选中的对象,也可以跟到这里。
我们看一下eax的返回值,因为这是个LUA的函数,最终会返回一个名字,如上图所示这时候返回的是玩家的名字。
我们先把断点删除,让游戏跑起来,选择一个怪物,然后在上图LUA函数开头下断,重新断一下再看看它返回的目标又是什么。
我们也可以在游戏中聊天窗口那里运行宏/run UnitName("target")
,如下图所示准备好该LUA指令:
由于怪物一直在攻击我,该LUA函数这里不好下断,所以我们要在不被攻击的情况下选中目标(可以选稍远一些的):
内存窗口中转到eax的值,可以看到目标名字就是从CALL 4FD0E0这个CALL返回的,在这个CALL里面必定能找到怪物的名字和偏移。
我们在该CALL下断,然后在游戏里输入lua指令/run UnitName("target")
,回车就会断在该CALL,此时eax=2D1AEF90,我们记下来,然后继续F8单步到CALL 4FD0E0:
可以看到此时ecx=2D1AEF90,会作为参数传入CALL 4FDDE0,我们跟进该CALL里面,看一下名字是从什么地方来的:
继续往下跟:
怪物名字应该是从CALL 72A000这个CALL里面来的,我们跟进去:
走到这里的时候,ecx=2D1AEF90仍然是我们的怪物对象:
怪物名字的两个偏移一下子就出来了。
UnitName这个LUA的接口就是显示我们选中目标(参数为"target")的名字,当然它实际不会显示出来,如果你要显示出来可以输入/run print(UnitName("target"))
:
如果选中自己就会显示角色名字:
选中NPC同理,用LUA指令就很简单了。
另一条分析的路径(就是那个+0x18)应该也是能够走通的,具体我们还要以后继续分析该路径,难点就是6F6020这个CALL比较复杂,它里面存在很多数组操作。
复习怪物名字偏移
我们以NPC五毛为例:
搜到之后对名字进行修改,这时候我们选自己,然后再选五毛,查看角色血量面板,可以看到NPC五毛的名字已经被修改了,NPC人物模型头上的名字变,为了五23(一定不要去看人物头顶上的名字),而面板上的变为五毛11,我们这里要以面板显示的为准。
我们把五毛11改为五毛12,就确定了CE搜到的怪物名字的地址。
我们再搜一下存放这个名字的地址:
这样就找到了它的第一个偏移+5C,然后我们可以继续搜esi的值,也可以显示反汇编程序看一下附近有没有更上层的偏移:
可以看到它有另外一个偏移+964。
接下来我们用xdbg接着分析:
在wow.exe后面加一个点和一个数字零,即wow.exe+32A27F
就可以在xdbg中直接转到该地址处:
我们打开内存2窗口,转到esi的地址处:
如上图所示复制esi所在地址处开始的内容,这是一个很重要的特征,esi此时的地址值里面保存的内容A34D90是判断它是否是一个对象的关键,虽然它不能用来判断是否是怪物对象,但是如果A34D90是虚函数表vftable的地址,那么这个esi大概率就是一个对象;
因为对象一般它的首地址大概率存在虚函数表vftable,当然不是绝对的,只不过游戏里面怪物对象或者游戏对象的首地址如果是一个虚函数表vftable的话,那么它们就是一个对象,到底是不是,我们在xdbg中内存3这里查看分析一下:
可以看到这两个地址都是一个标准的函数头。
这样就能够确定A34D90就是一个虚函数表vftable,或者说是虚函数指针。
这样的话,我们就得到了怪物名字的两个偏移了。
通过这个虚函数指针确定它是一个对象,我们就不用继续找了,找这两个偏移就可以了,一个+964,一个+5C,最终就可以读出怪物名字了。
一定不要去看人物头顶上的名字,否则你是找不到的,要看面板上的信息。
用CE和xdbg分析对象数组
认识虚函数表
创建基于对话框的MFC应用。
我们在项目文件目录中新建一个怪物类的目录(筛选器),创建一个怪物的类,用来模拟怪物对象:
当然这个模拟的怪物数组对象和我们游戏里的不同,因为它直接就是一个全局变量,它没有偏移,直接就是一个基址。
用x32dbg打开该exe文件,转到MessageBeep函数那里下断,运行后点击按钮就会中断在这里:
在双击右下角堆栈窗口中的返回地址,就可以返回到我们代码那里了:
这里直接就能看到怪物数组,在内存窗口中我们看一下怪物数组的对象,它首地址里面有一个注释vftable(当然游戏中是没有这个注释的),实际上就是虚函数表指针:
通过这个虚函数表指针0132898C,进到该地址处才是虚函数表:
这里面每一个其实都是一个函数的地址:
这里面大部分都是我们继承的CDialog基类里面的成员函数,这是我们为了模拟分析虚函数表才选择它来继承:
某一种怪物它们的虚函数指针一般都是相同的,所以说我们还可以用这个虚函数表来区分不同类型的对象,比如说玩家的和怪物的虚函数表指针有可能不同,如果它们真是同一个怪物类的话,那有可能就是相同的,而且很多功能也可能在这个虚函数表里面。
这对我们分析对象是有很大帮助的,因为一旦我们得到类似地址进去之后,就能发现这可能是一个对象的头部,因为这种对象的头部可能会存在类似这种一大片的函数指针。
我们来看一下游戏中的玩家对象(角色对象):
现在eax就是我们的角色对象,我们转入00A326C8里面看看,因为理论上来说00A326C8就是所谓的虚函数表指针:
这些每一个都是一个函数的头部,所以说,我们在区分对象的时候,如果它的头部是一个虚函数表指针,那么它具有这种非常明显的特征,而00A326C8就是虚函数表地址(也就是虚函数表指针),而且我们看到角色对象里面的成员函数有很多,有的是用来绘图的,有的是用来获取某些数据的(血量、属性等)。
我们要看到对象具有这种特征,我们这节课主要就是要了解怪物对象或者是玩家对象,如果它是一个对象,它的第一个地址进去就是一片函数,这样能够让我们确定找到的这个东西它是一个对象,比如说怪物对象或者玩家对象。
你就简单的知道,对象的第一个地址进去,里面的一大片全部都是函数的地址,就可以确定它就是一个对象;
就是说,这个指针可以帮助我们确定我们找到的这个是对象的首地址,因为对象的首地址它具有这个特征。
这个0x1372C88是怪物数组的基址,0x1D0是怪物类的大小。
我们从上面两张图可以看到,怪物数组[0]和怪物数组[3]这两个对象,它们具有相同的虚函数表指针0136898C,因为它们是同一个类。
我们看到这些对象虚函数表指针下面都是大片的0,这是因为我们没有为该对象里面的成员写数据,我们给其中某些成员赋值来对比看看:
这里简单介绍一下xdbg条件断点:
条件断点一般是下载频繁断下的地方,例如窗口过程,还可以结合逻辑与&&、逻辑或||、逻辑非!来添加多个判断条件。
分析对象数组
我们就从0x60C1F0进去用xdbg分析怪物对象数组。
我们要分析怪物数组的话,可以从怪物的名字去找怪物对象,这样慢慢的追到怪物数组,另外我们也可以通过玩家对象的来源去追这个怪物数组。
前面我们也说了,大部分游戏中怪物和玩家对象、NPC、掉落的地面物品,一般都是放在同一个数组或者链表里面的,所以说玩家对象是从0x601CF0这个CALL里面返回的,但是这个玩家对象是不是也包含在怪物数组里面呢。
所以我们可以从玩家指针的来源去找这个怪物数组,所以我们要从0x60C1F0这个CALL里面来找。
打开角色信息,断在该CALL开头,我们一直按F8看看最后返回的eax是从哪里来的:
如果是我们玩家对象,就会有一个明显的特征,就是[玩家指针+0D0]+174是否是玩家的护甲值,所以返回值eax就是从004D4DB0这个查询对象CAL里面来的,现在我们基本上就能够确定这个CALL就是玩家对象的来源,也就是说更深层的CALL就是从004D4DB0里面来返回的我们角色对象。
该玩家对象首地址中的值00A326C8其实就是之前讲的那个虚函数表首地址,00A326C8里面存放的全都是独立的一个一个的函数:
我们到4D4DB0那个位置去看看究竟返回值eax是从什么地方返回的:
我们之前分析过4D4DB0这个CALL的参数,我们怀疑第1个和第2个参数是ID1、ID2,用来查询那个对象数组的,通过这两个参数来查询返回的对象,也就是说通过这个CALL应该也能返回其他的怪物对象。
我们继续往该CALL里面跟一下:
可以看到玩家对象是从4D4BB0这个CALL里面来的,所以说关键的那个数组可能还在这一层CALL里面。
但是之前我们说过这个CALL,它是一个非常干净的一个CALL,里面没有再调用其他函数,可以从上图看到这里面有一个循环,这就有很大的概率是在循环的做一个遍历。
我们先来看一下4D4BB0这个CALL的参数:
第1个参数是0xC,第2个参数edx=039FF6B0是ebp-8的地址,从内存窗口转到该地址(如上图所示的039FF6B0),可以看到里面存放的是一个0xC和一个0。
在4D4BB0函数尾部下断,然后不断点运行,观察发现返回值eax大部分时候不变,但偶尔会变,我们来看看[2CFD05C8+D0]+174里面是什么(如上图左下角内存窗口中的内容),可以看到这个地方明显就不是护甲值了,可能是一个其他的什么东西,我们把这个对象和玩家对象里面的数值做一个比较。
我们在4D4BB0函数尾部这个地方下断,主要是怀疑从这个地方会返回怪物对象或者是其他的对象,但是我们不能直接在4D4BB0这个地方下断,否则游戏会一直断下直到卡死掉,所以需要在上一层下断,然后再在这里下断,按运行断到这个地方。
我们在内存窗口中转到[eax+D0]+174
来看看,如果是玩家的话这个地方就会是护甲值,此时证明2C318C50是角色对象,如果返回的是其他值的话可能就是怪物对象了:
我们发现如果不是玩家(此时eax=2D4E79F0,而[2D4E79F0+D0]+174地址处的内容如上图所示为0),这个地方就没有护甲值。
我们再来对比角色对象2C318C50和另一个对象2D4E79F0在内存窗口中的内容:
我们可以看到不同对象的虚函数表指针也是不一样的,说明它们属于不同的类型,那么这个对象2D4E79F0究竟是什么呢?
我们之前分析了怪物的名字(/run print(UnitName("target"))
),当时我们分析到了两个偏移,+964和+5C:
我们用这个公式[[eax+964]+5C]
来看看这个对象2D4E79F0有没有怪物名字:
这就确定了这个对象2D4E79F0是怪物对象,所以从4D4BB0这里返回的一个是角色对象,另外一个是怪物对象,当然可能也有其他的对象(坐骑、地面物品、矿物药草之类的),究竟还可以返回哪些类型的对象就需要进一步的分析,从目前来看至少有这两种对象。
接下来我们就要看看这个怪物对象或者玩家对象究竟是从4D4BB0这个CALL里面的什么地方来的:
我们可以看到ecx是很明显的一个关键,这个edx应该是数组的基址,这个eax应该是某一个数组的下标。
那么ecx+1c是什么呢?我们返回上一层看一下这个ecx的来源:
从上图我们可以看到ecx最终来源于ds:[edx+8],而edx的来源又与eax这个数组下标相关,eax的来源ds:[D439BC]的内容从左下角内存窗口中看到是0,所以edx=[ecx+eax4]=[ecx+04]=[ecx],所以关键又回到这个ecx=fs:[2C]上了,这个fs:[2C]之前我们也讲过,你就把它看成是一个普通的内存地址就可以了(这个线程的TEB在内存中映射的地址),我们把fs:[2c]代入公式里面,所以edx=[ecx]=[fs:[2c]],即最终ecx=[[fs:[2c]]+8]就得到了一个新的表达式了。
我们来验证一下:
内存窗口中我们通过表达式[[fs:[2c]]+8]转到的地址处是136FC3A0,而ecx寄存器的值也正好是136FC3A0。
我们先进4D4BB0这个CALL里面,我们用一个等价替换,把下图中的eax=[ecx+24]用ecx的表达式[[fs:[2c]]+8]替换,得到eax=[[[fs:[2c]]+8]+24]
,我们跟进4D4BB0这个CALL里面来验证一下(记得要断在这里,才能在游戏的主线程中,得到的fs:[2c]才是正确的):
可以看到eax的值为1F,而公式[[[fs:[2c]]+8]+24]
的值如上图所示确实也是1F,就证明这个公式是正确的,我们接着往下执行:
证明edx=[ecx+1c]=[[[fs:[2c]]+8]+1c]这个表达式也是正确的,这里的这个edx就是比较关键的地方,就是那个数组的地址了。
我们继续往下看:
eax的值1F的十进制是31,从这里and eax, esi
来看这个31可能就是最大的数组上限,它把多余的超出31的那部分值给截断掉(例如上图我们把esi=0000000c修改为esi=1234567c,以验证and eax, esi这条指令的效果),最终得到eax=c,来保证是在数组元素个数范围内。
(验证完记得把esi的值恢复为0000000c保证程序的正常环境)
我们继续来做等价替换:
在执行完and eax, esi
指令后eax的值为0c,所以实际上eax就是传入的参数1了(eax有可能是下标):
上图这个公式可能就是最终的怪物数组了,我们把参数1替换为0,最终的公式就是[[[[fs:[2c]]+8]+1c]+8+0*0c]
,计算得到2D4F5960,而2D4F5960里面的内容是:
这确实是一个对象,这就证明了我们这个公式确实能取得数组里面的对象。
我们继续往下看代码:
发现eax在下面的代码中没有被重写过,所以最终返回的就是这个eax的值。
我们再来试一下怪物的名字(名字的偏移是+964,再+5c):
如果是怪物那么就能读出怪物的名字,如果是玩家或者其他类型的对象,那么有可能是读取不出来的,从上图计算得到的数值可以看到目前是能够正确读取出来,这个索引为0的数组中第一个元素可能就是我们的怪物对象。
记得要断在这里,才能在游戏的主线程中,得到的fs:[2c]才是正确的,才能通过上述公式得到正确的结果(否则是访问不到的,必须是在主线程中才行)。
下标改成2我们就可以看到,这里可能也是一个对象,但是名字显示不了(该对象没有名字或者名字不在这个偏移位置),能够确定它不是怪物对象,我们修改公式来看看它是个什么对象:
看起来像是我们的玩家对象,如果是玩家对象的话,我们修改公式来查看玩家的护甲值:
结果看不到护甲值,说明这个对象既不是我们的玩家对象,也不是怪物对象,可能是其他类型的对象。
我们发现用该公式(此时当索引为2)还能取得NPC的名字,说明NPC和怪物可能是同一个类,为了证明这一点,我们查看一下对象的虚函数表指针:
上图这个A34D90是NPC的虚函数表指针。
我们知道索引为0的是怪物的,而它的虚函数表指针也是A34D90:
说明怪物和NPC是同一个类。
除了索引0和索引2,剩下的都不是,直到索引为1A的才有内容:
索引为1A的对象是无名这个NPC,该对象的虚函数表指针也是A34D90。
我们分析出来的这个怪物列表其实还不全(涉及到链表),这里我们先讲它的一部分,后面我们还会继续详细化全面分析怪物列表。
代码下面还有一个遍历链表的循环,里面应该还包含有其他的怪物对象,通过这个循环去进行遍历的:
这个fs:[2C]是一个特殊的内存地址,也就是这个线程的TEB在内存中映射的地址:
分析对象数组链表部分
在C++环境里面链表是怎么创建的,而且看一下链表的数据在内存里面是一个什么样的存放方式。
链表的定义
链表的数据在内存里面究竟是一个什么样的形式
// CreateList.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//#include <iostream>// 操作系统 win7 64
// 编译环境 Visual Stuido 2017#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>typedef int ElementType; // 定义数据类型,可根据需要进行其他类型定义
//class // 链表节点的定义
typedef struct ListNode {ElementType Element; // 数据域,存放数据int data1;int data2;ListNode* Next; // 链表指针 指向下一个链表节点float x,y,z;
}Node, *PNode;// 链表创建函数定义
PNode CreateList(void) {int len; // 用于定义链表长度int val; // 用于存放节点数值PNode PHead = (PNode)malloc(sizeof(Node)); // 创建分配一个头节点内存空间//头节点相当于链表的哨兵,不存放数据,指向首节点(第一个节点)if (PHead == NULL) // 判断是否分配成功{printf("空间分配失败 \n");exit(-1);}PNode PTail = PHead; // 链表的末尾节点,初始指向头节点PTail->Next = NULL; // 最后一个节点指针置为空printf("请输入节点个数:");scanf_s("%d", &len); // 输入节点个数for (int i = 0; i < len; i++) {PNode pNew = (PNode)malloc(sizeof(Node)); // 分配一个新节点if (pNew == NULL) {printf("分配新节点失败\n");exit(-1);}printf("请输入第 %d 个节点的数据:", i + 1);scanf_s("%d", &val); // 输入链表节点的数据pNew->Element = val; // 把数据赋值给节点数据域PTail->Next = pNew; // 末尾节点指针指向下一个新节点pNew->Next = NULL; // 新节点指针指向为空PTail = pNew; // 将新节点复制给末尾节点 }printf("创建链表成功\n");return PHead; // 返回头节点
}// 主函数
int main() {PNode List = CreateList(); //创建一个指针,使其指向新创建的链表的头指针 printf("List链表头=%p\n", List);getchar();return 0;
}
可以看到List这个链表头里面的数据是未初始化的,它只有Next被初始化为指向首元结点,而首元结点的Element的值就是我们刚才输入的111这个数据,而这个首元结点的Next展开就是指向第二个结点:
最后,当Next指针指向NULL的时候,我们遍历到这里的时候整个链表的遍历就结束了。
接着我们在xdbg中分析链表的反汇编:
添加MessageBeep(1);用来方便定位。
我们在内存窗口中查看链表头7BCAE0里面的内容,发现都是一些未知的数据,我们要找到链表头中的指针域:
所以第1个结点的地址就在+C偏移的指针域位置,它的地址就是7BF950:
所以第1个结点中偏移+C位置的值7BF9A8就是第2个结点:
这就是链表的数据结构在内存里面的存放方式。
上图这是个遍历对象链表的循环,游戏里面采用了这种链表的结构,我们之前讲过的对象数组,里面的元素是一个类对象,该对象里面的成员有可能是一个链表,这么说吧,就是说怪物对象里面有一个成员就是那个链表头。
头结点的指针域Next指向第一个结点(数据域中的数值为3),第一个结点的指针域Next指向第二个结点(数据域中的数值为2),第二个结点的指针域Next指向第三个结点(数据域中的数值为1),第三个结点的指针域Next指向NULL,遍历到这里就结束。
相关文章:

郁金香2021年游戏辅助技术中级班(一)
郁金香2021年游戏辅助技术中级班(一) 用代码读取utf8名字字节数组搜索UTF-8字符串 用CE和xdbg分析对象名字从LUA函数的角度进行分析复习怪物名字偏移 用CE和xdbg分析对象数组认识虚函数表分析对象数组 分析对象数组链表部分链表的定义链表的数据在内存里…...

加密货币交易所偿付能力的零知识证明
如何检测下一个 FTX 和 Mt. Gox 加密货币交易所 FTX 的内爆导致数十亿客户资金流失,这是加密货币历史上交易所破产的最新例子。历史可以追溯到 2014 年,当时处理 70% 比特币交易的历史最悠久、规模最大的交易所 Mt. Gox 丢失了用户的 850,000 个比特币。…...
软考网络工程师防火墙配置考点总结
(考试重点) 一、访问控制列表 管理网络当中的数据流量,实现数据过滤的重要手段。可以在路由器、三层交换、二层交换和防火墙上实现。 隐藏规则:当前面的规则都匹配不上,华为默认允许,思科默认拒绝。 分…...

【IDEA】idea恢复pom.xml文件显示灰色并带有删除线
通过idea打开spring boot项目后,发现每个服务中的pom.xml文件显示灰色并带有删除线,下面为解决方案 问题截图 解决方案 打开file——settings——build,execution,deployment——Ignored Files,把pom.xml前面的复选框去掉,去掉之…...
Python数据分析之Excel
Openpyxl库 1、Openpyxl模块2、Excel写入2.1、新建2.2、添加数据2.3、单元格格式 3、Excel读取4、Excel的CRUD4.1、查4.2、改4.3、删 1、Openpyxl模块 Openpyxl是一个用于处理xlsx格式Excel表格文件的第三方python库,几乎支持Excel表格的所有操作 基本概念&#x…...

NISP证书是什么?NISP含金量如何呢?
一、NISP是什么 NISP证书是国家信息安全水平考试(National Information Security Test Program,简称NISP),是由中国信息安全测评中心实施培养国家网络空间安全人才的项目。由国家网络空间安全人才培养基地运营/管理,并…...

操作系统备考学习 day6(2.3.2 - 2.3.4)
操作系统备考学习 day6 第二章 进程与线程2.3 同步与互斥2.3.2 实现临界区互斥的基本方法单标记法双标志先检查法双标志后检查法Peterson算法 进程互斥的硬件实现方法中断屏蔽方法TestAndSet指令Swap指令 2.3.3 互斥锁2.3.4 信号量整型信号量记录型信号量 第二章 进程与线程 2…...

家电行业 EDI:Miele EDI 需求分析
Miele是一家创立于1899年的德国公司,以其卓越的工程技术和不懈的创新精神而闻名于世。作为全球领先的家电制造商,Miele的经营范围覆盖了厨房、洗衣和清洁领域,致力于提供高品质、可持续和智能化的家电产品。公司的使命是为全球消费者创造更美…...
Android ConstraintLayout app:layout_constraintHorizontal_weight
Android ConstraintLayout app:layout_constraintHorizontal_weight <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:…...

FPGA行业应用一:LED控制器
什么是LED控制器 LED控制器已经有很多年头了,应该是上世纪90年代就开始有了。它的主要构成是: 1:视频信号源——如 电脑,机机,DVD,U盘等 2:视频处理器——通过 HDMI/DVI/网口接收来自视频源的…...
Pyspark读写csv,txt,json,xlsx,xml,avro等文件
1. Spark读写txt文件 读: df spark.read.text("/home/test/testTxt.txt").show() ------------- | value| ------------- | a,b,c,d| |123,345,789,5| |34,45,90,9878| -------------2. Spark读写csv文件 读: # 文件在hdfs上…...

LeetCode 接雨水 双指针
原题链接: 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 题面: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:…...

【Linux】【网络】传输层协议:UDP
文章目录 UDP 协议1. 面向数据报2. UDP 协议端格式3. UDP 的封装和解包4. UDP 的缓冲区 UDP 协议 UDP传输的过程类似于寄信。 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接。不可靠:没有确认机制,没有重传机制&am…...

数字音频工作站FL Studio 21中文版下载及电音编曲要用乐理吗 电音编曲步骤
FL Studio 21是一款强大的数字音频工作站(DAW)软件,为您提供一个完整的软件音乐制作环境。它是制作高质量的音乐、乐器、录音等的完整解决方案。该程序配备了各种工具和插件,帮助你创建专业的虚拟乐器,如贝斯、吉他、钢…...

金蝶云星空与旺店通·企业奇门对接集成其他出库查询打通创建其他出库单
金蝶云星空与旺店通企业奇门对接集成其他出库查询打通创建其他出库单 源系统:金蝶云星空 金蝶K/3Cloud(金蝶云星空)是移动互联网时代的新型ERP,是基于WEB2.0与云技术的新时代企业管理服务平台。金蝶K/3Cloud围绕着“生态、人人、体验”&#…...

Visual Studio 如何删除多余的空行,仅保留一行空行
1.CtrlH 打开替换窗口(注意选择合适的查找范围) VS2010: VS2017、VS2022: 2.复制下面正则表达式到上面的选择窗口: VS2010: ^(\s*)$\n\n VS2017: ^(\s*)$\n\n VS2022:^(\s*)$\n 3.下面的替换窗口皆写入 \n VS2010: \n VS2017: \n VS2022: \n …...

java spring cloud 企业电子招标采购系统源码:营造全面规范安全的电子招投标环境,促进招投标市场健康可持续发展
功能描述 1、门户管理:所有用户可在门户页面查看所有的公告信息及相关的通知信息。主要板块包含:招标公告、非招标公告、系统通知、政策法规。 2、立项管理:企业用户可对需要采购的项目进行立项申请,并提交审批,查看所…...

112. 路径总和
力扣题目链接(opens new window) 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。 说明: 叶子节点是指没有子节点的节点。 示例: 给定如下二叉树,以及目标和 sum 22…...

国货疯抢流量,B站接连爆发800万播放实现破圈
近日,“79元商战”的消息洗刷全平台,众多国货品牌的“不容易”开始被越来越多的消费者注意到,消费者们自发性地开始重新审视真正做产品的国货品牌们,并为之全力支持。有网友笑称:这“泼天的富贵”终于落到了国货品牌的…...
(高阶) Redis 7 第14讲 数据统计分析 实战篇
面试题 存得进,取得出,反应快抖音电商商品评论:排序+展现+取前10条用户使用手机APP签到打卡:1天对应一系列用户签到记录,新浪微博/钉钉打卡,如何进行统计页面访问点击量:一个网页对应一系列的访问点击,淘宝网首页,多少人浏览首页公司系统上线后,UV、PV、DUV是什么亿级…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...