Nw BBS 壬天堂世界

 找回密码
 注册
搜索
热搜: 资料集合
查看: 5011|回复: 6
收起左侧

转篇资料.动态内存地址(DMA) 转为 静态地址

[复制链接]
发表于 2007-11-21 01:16:07 | 显示全部楼层 |阅读模式
Advanced Game Training by [sheep] a.k.a RECLAIM!
原文是老师以前发的一篇文章,附在下面。
声明:文章不是本人翻译的,只是不小心翻到,惊喜之极,发来与诸君共享。
************************************************
主题: 代码注射 - 动态内存地址(DMA) 转为 静态地址.
需要工具: Softice, 内存搜索工具 (呃,选择) gamehack.

你现在有了一个讲解高级游戏修改的教程... 在后面几个月中我希望写一些关于高级游戏
修改各方面的系列教程......这一课,我将教你一些你需要的技能,
你将可以从 游戏修改新手 变为 游戏黑客(Game Hacker)..

在我们开始实际操作之前,我要告诉你,如果你没有一些基础知识就来尝试这篇教程,
你不会有什么大收获的...

我建议你最起码先先看看我以前的教程,包括动态内存地址的所有细节.. (nMing- 就是这篇教程的第一部分)
同样你也需要至少懂得一点 汇编语言 的基础知识...

全部搞掂我们就继续......


SOFTICE SETUP
*************

当你按 CTRL-D 弓单出softice时你应该看到下面的窗口..

REGISTER WINDOW
寄存器窗口     - 这个窗口总是在最上面,显示所有寄存器的内容..
(WR [return])   (用 WR 打开或关闭)
  
DUMP WINDOW     
数据窗口     - 一般位于最上面那个窗口的附近,分两边显示,一边是ascii码,一边是十六进制
(WD [return])   (用 WD 打开或关闭)

CODE WINDOW     
代码窗口     - 这是主窗口.. 在数据窗口的下面.. 包含所有将要运行的程序代码当你弓单出softice..
(WC [return])   代码是汇编语言..
            (用 WC 打开或关闭)

LANGUAGE       instrutions..

括号里的内容是你要输入的命令以打开不同的窗口..
同样你需要输入 CODE ON 将在汇编指令左边显示机器码,右边是内存地址...


LESSON START
************

ok..要完成用代码注射法去击败DMA的最终目标,你需要先学习一些新的概念,
下面我列出了我们将要学习的几个概念...


i)   代码注射原理
ii) 偏移量理论
iii) 动态内存地址转静态地址概要
iv) 代码注射法的实际操作

一旦你弄懂了前面3个概念你就可以去解决第4个了....祝你好运:))


代码注射原理
*********************

代码注射是高级游戏黑客用来完成一些特定任务时所用的一种方法,是在不能用
一般的修改方法修改时所用的..

代码注射的主要目的就是在游戏中创建一条路径让它去执行我们自己的代码...
在游戏执行完我们的代码后,一定要让它跳回去继续执行游戏自己的代码...
代码的类型是有限制的,是什么? 当然,就是 汇编 :))

代码注射是很灵活的,你可以做你想做的任何事...
breaking free of the restrictions of the normal NOP hacks....
(nMing - 这句话我实在不知如何翻译,:-| )

例如...
下面是一个代码片断...这只是一个例子,所以不要紧....


0120:00008096 01585A         ADD   [EAX+5A],EBX
0120:00008099 E9xxxxxxxx   .---- JMP   500000     <---
这里创建一条通道,通往我们的代码...
0120:0000809B B486       |   MOV   AH,86 <----.
0120:0000809F 55         |   PUSH   EBP       |
0120:000080A0 1E         |   PUSH   DS       |
0120:000080A1 50         |   PUSH   EAX       |
0120:000080A2 E86E078ED8   |   CALL   D88E8815   |
                  |                 |
                  |                 |
0120:00500000 我们的代码! <---’               |
0120:00500002 我们的代码!                   |
0120:00500004 我们的代码!                   |
0120:00500006 JMP 809B -------------------------------’ <--- 一旦我们的代码执行完后就跳回去...

如果你在创建<通道>的时候破坏了任何一条有用的汇编指令,...你就必须在<我们的代码>
中重新建一条被你破坏掉的指令...不然那很可能这个游戏就被你毁掉了...

不要忘了这只是它的原理...一个完全详尽的"怎样做(HOW TO)"将在[代码注射法的实际操作]中讲到...

偏移量理论
*************

我们都知道,无论我们开始一个新游戏或者是重新启动后, 动态内存地址(DMA) 都会引起游戏中的变量改变位置... 即使这是真的..
DMA 的变量, 实际上它们之间总是呆在相对的同一个位置.. 这是什么意思?? 我听见你在问了? :))

好了.. 我来给你们举一个例子...


00456777 : LIFE TOTAL(生命总数)
00456778 :
.........:
.........:
etc......:
.........:
00456999 : BULLET TOTAL(子弓单总数)


这上面的表是 两个DMA变量(动态变量) 的假想情形..
我们的生命总数在 456777 这个地址,子弓单总数在地址 456999 , 如果你把那两个地址相减,
你就会知道什么叫做 偏移量 了,... 456999h-456777h = 222h (h = hex).
你将会明白为什么我们需要偏移量了,马上..

OK!! 我重新启动我的游戏!!!

00659123 : LIFE TOTAL(生命总数)
00659124 :
.........:
.........:
etc......:
.........:
00659345 : BULLET TOTAL(子弓单总数)

现在我们重新启动了,变量改变了位置.. 但是!! 注意这个实际的地址 659345h-659123h = 222h.
即使变量被分配到内存的不同位置,但是它们之间总是保持着相同的距离..
这就是整个过程的钥匙..

它们分别放在相同距离的地方的原因,就是每个变量实际上是分配在内存中的某一块区域里的...
当游戏运行的时候,内存中将会有一大块地方包含游戏所有的变量...

用偏移量我们可以把我们需要的所有变量制作成一幅详细的地图.. 我们需要做的就是找一个变量当作主变量,然后找到这个主变量的地址..
从这个地址你立即就可以知道其它的变量位于内存的什么地方...

另一个例子....

这里是一幅虚构的偏移量地图...

HEALTH COUNTER 位置 = MASTER VARIABLE(主变量) <--- 查找这个变量的地址.
BULLETS     位置 = MASTER VARIABLE + 3445h
LIVES       位置 = MASTER VARIABLE + 2345h
SKILL POINTS   位置 = MASTER VARIABLE + 334h

现在.. 我们开始我们的游戏... 然后查找 HEALTH COUNTER..

000657444 : HEALTH COUNTER

我们现在可以把那幅地图换成真实的地址...像这样..

HEALTH COUNTER 位置 = 657444h + 0h   = 657444h
BULLETS     位置 = 657444h + 3445h = 65A889h
LIVES       位置 = 657444h + 2345h = 659789h
SKILL POINTS   位置 = 657444h + 334h = 657778h

那么,现在你可以看见一幅"变量"地图,只用一个主变量我们就可以用偏移量找到所有其它的变量 :)) 是个又酷又可爱的东西 :)))

好了.. 要继续..明白偏移量是非常必要的..如果你不明白那你就上去再重新读....


动态内存地址(DMA)转静态地址概要
*********************

一旦你明白了至少是"代码注射"的基础,在我解释动态内存地址转静态地址的原理的时候你就不会有太多的麻烦...

概要讲起来应该像这样 :-

我们在游戏代码中找到一个地方,它包含一个指针(POINTER)指向一个我们用到的变量,这个变量也就是 生命, 健康, 子弓单 等等...

一旦我们找到了这个指针,我们就需要确定它是不是一个"特殊的指针"(SPECIFIC POINTER)
意思就是它是不是唯一的!! 指向1个位置.. 如果它是一个普通的,它将循环穿过很多地方,会应付很多其它内存地址..
它对我们来说没有用...

一个指针将看起来像这样.. MOV EAX,[EBX+00000208] ’(nMing - 他所说的指针是EBX+00000208 )

EBX 包含一个DMA的基本地址.. 而 208 是偏移量,再加上来自指针的EBX,指向我们的变量(生命LIVES)..
我们知道 EBX+208 = 生命(LIVES).. 我们现在需要去查明这是不是一个"特殊的指针"(仅仅指向1个地址),而不是一个普通的指针
(指向和应付很多地址). 我们要做这个就必须设一个断点在 EAX,[EBX+00000208] 这条指令上...然后按F5退出softice环境..
每当softice再次弓单出时,看看EBX的内容..如果EBX里的数值改变了,如果是,那么这个指针对我们就没有什么好处..
如果EBX里的地址是"特殊的指针",你就应该检查这个数值至少10 或 20 次,以确定它没有改变...

好了,一旦我们找到了我们要的"特殊的指针".. 我们需要把放在EBX里的 DMA基值 储藏起来 (注意!!
不会总是EBX包含DMA基值..它可以是其它任何寄存器)
你要看看指针用的是什么寄存器..

例如..

MOV   EAX,[EBX+00000208]   <-- EBX 包含DMA基值
MOV   EAX,[ECX+00004564]   <-- ECX 包含DMA基值
MOV   EAX,[EDX+00001122]   <-- EDX 包含DMA基值
MOV   EAX,[ESI+00000234]   <-- ESI 包含DMA基值
MOV   EAX,[EDI+00004408]   <-- EDI 包含DMA基值

留意上面的所有东西.. 指针能具有很多种,除了上面我想的,还有很多类型,你会在所有游戏中遇到...

好了.. 那么我们需要储藏 DMA基值 到静态内存中.. 我们要做的是, 创建一条路径通向我们的代码(代码注射)..
我们的代码会把放在EBX里的DMA基值储藏到静态地址中.. 那么我们的修改器就可以在任何时候读出这个地址了... 读时还得加上偏移量..
然后再写入数值到那个指针..

这里是一幅图表用来更好的解释一下它...

下面,我们来寻找我们的指针...


我们还没有制作通道之前...
-----------------------------

0041A736 MOV   EAX,[EBX+00000208]   <----- 指针在这里...
0041A73C FLD   REAL4 PTR [EBX+000000CC]
0041A742 FDIV   REAL4 PTR [EBX+000000C8]
0041A748 MOV   [00786CA4],EAX

我们做了通道之后...
----------------------------

0041A736 JMP   9000000           <----- 通道做好了..
0041A73C FLD   REAL4 PTR [EBX+000000CC]
0041A742 FDIV   REAL4 PTR [EBX+000000C8]
0041A748 MOV   [00786CA4],EAX



我们自己的代码...
-------------------

09000005 MOV EAX,[EBX+00000208]     ; 重新建立刚才我们毁掉的指令
09000010 MOV [9000100],EBX         ; 把存在EBX里的DMA基值传送到一个静态地址..
09000015 JMP 41A73C             ; 跳回游戏的循环..
09000100 0000000000             ; 储藏EBX的位置

当你完成了上面的代码以后,现在..你就可以从 9000100 这个地址读出EBX的值..即使当游戏中的内存位置改变了,
这个代码也会把正确的值储藏到 9000100 这个地址里... 所以,你要做的就是读出这个值..再加上偏移量208,就得
到了生命(LIVES)的准确位置... 记住?? lives = EBX+208...

这些几乎是全部的交易.. 我们还可以从程序代码中读出 9000100 的数值,我将在下面的实际操作部分中向你们展示这些...
不要担心这都不是很难理解... 不管是否讨厌你还是得再读这部分... 为了在实际操作部分中能在我充分的解释下把他们全部理解....


代码注射法的实际操作
***********************************

在这个教程中我选择了一个叫SPACE TRIPPER的游戏作为示例...(谢谢keyboardjunkie)
你可以在这里下载.. http://www.pompom.org.uk/STpage1.htm ;

让我们开始吧...

首先我们要做的就是找到一个主变量.. 我选择 生命(LIVES) ..
不大幸运的就是这个游戏在找 生命(LIVES) 的值的时候,很烦人...但是,只要你有一点汇编知识就会好了...(现在你得用到所有东西 :))

好.. 我们用通常的搜索.. 死 .. 搜索死..

如果你做得正确你将找到仅仅一个地址.. 它应该是 786ca4
你可以试试去改这个地址,你会注意到屏幕上的值变了... 但是.你的 生命(LIVES) 仍然和以前一样,那是因为你没有找到生命(LIVES)的真正的地址..
别担心!! 我并不期待什么.. 我将告诉你为什么.. 这个游戏不是在每次开始新游戏时分配内存,而是在每次你死后分配内存....
所以,你不可能用内存搜索工具找到真正的值.. 还好,在屏幕上显示生命(LIVES)的变量的数值是静态的... 所以,你就可以靠这个数值很快找到真正的地址...

下面是教你怎么做...


用刚才找到的那个地址在 softice 里设一个断点,, 像这样..
输入 BPM 786ca4 W (回车)

死掉..

softice 弓单出来!!!!

你应该看到这个...


0041A736 8B8308020000 MOV   EAX,[EBX+00000208]; 指针把生命(LIVES)传到EAX里(EAX = 2)
0041A73C D983CC000000 FLD   REAL4 PTR [EBX+000000CC]
0041A742 D8B3C8000000 FDIV   REAL4 PTR [EBX+000000C8]
0041A748 A3A46C7800   MOV   [00786CA4],EAX   ;
把生命(LIVES)真正的值传到用来显示生命(LIVES的变量中
                                ; (在屏幕上显示LIVES的变量= 2 )

就像你看到的,[EBX+00000208]是一个指针,用来把生命(LIVES)的真正的地址保存在内存中..
EBX包含着DMA基址,以后我们需要把它储存到其它地方...
还有,把208跟DMA基址相加就得到生命(LIVES)真正的地址.( EBX+208=真的生命地址)
从真的生命地址中(EBX+00000208)传到EAX的值就是在屏幕上显示生命(LIVES)的值... 要证明这些你就要做以下..

在 softice 里..

输入 D EBX+208 (回车)

现在改一下数据窗口里最上面最靠左的那个数值, 生命(LIVES)的值就会变了.. 好,这个就是LIVES的真正的内存地址...
真幸运,我们只用一个断点就得到了两个重要的东西... 我们找到了生命(LIVES)的真正地址... 同样我们也可以用这种方法看看指针是特殊的指针还是一般的...
再次来到 SOFTICE..

首先,先清除所有其它的断点 .. 那么

输入 BC* (回车)

然后在 41a736 地址设一个断点...

输入 BPX 41A736 (回车)

然后按 F5 退出SOFTICE

softice 应该会弓单出!! 马上...

看看EBX的内容.. 看看它的值有没有变??

再按 F5..

再看看EBX的内容 :))

EBX里的值变了吗?? 如果没有!! we are IN!!! YAY!!!


那么,我们现在找到了可以把它保存为静态地址的一个有用的指针.. 现在要做的就是找一块好的地方放入我们的代码!! 有几种方法可以用...

1. 在你想要注射的地方的附近到处看看有没有一大串的 NOP .. (这是空的代码).. 同样一大串 0 也标志着这是我们写入代码的好地方...

2. 如果没有找到,那就在SOFTICE里搜索 909090909090.. 看看手册如果你不懂怎样去搜索.. 如今你也应该会了..

3. 这是我用的方法.. 有人告诉我他们用这种方法遇到了问题,但是我用这种方法做了大概20到30个修改器,没有一个不工作...
所以直到现在我继续用这种方法...

  i) 在softice里输入 TASK (回车) ..
  ii) 找到游戏窗体的名字..
iii) 输入 MAP32 <游戏窗体的名字> (回车)
  iv) 然后将显示游戏各部分的信息.. 我们想做的就是在 .data 部分的最后增加代码..
    下面是我从 Space Tripper 里看到的信息...
  

:map32 spacetripper
Owner   Obj Name Obj# Address     Size     Type
SPACETRIPP.text   0001 017F:00401000 0004BE10 CODE RO
SPACETRIPP.data   0002 0187:0044D000 00006718 IDATA RW
SPACETRIPP.bss     0003 0187:00454000 0035CDB4 UDATA RW
SPACETRIPP.idata   0004 0187:007B1000 000014F0 IDATA RW
SPACETRIPP.rsrc   0005 0187:007B3000 0000095C IDATA RO SHARED

.data 部分起始于44d000 ,但是我们要用的是这部分的最后面的地方.(这里通常有一些缓冲空间,所以我们可以高高兴兴的用它,
而不用去碰那些凌乱的游戏本身的数据). 再看看下一个部分, .bss这部分的起始位置就是.data的最底部..

那么, 让我们来搜寻一个漂亮的地方好插入<我们的代码>..


再次来到softice里面...

输入 D 454000 (回车)

我把数据窗口向上滑动一点就到 data部分 的最后了... 你会注意到当我们向上滑动一点点后,在数据窗口最上面的文字就会改变了...


SPACETRIPPER! .bss

SPACETRIPPER! .data+<offset>

这个你只需要把数据窗口向上滑动一点就可以了..

输入 EB (回车)

然后用PAGE UP / PAGE DOWN按键可以把窗口向上或下移动 :))

我坚决认为 453f00 是放入我们的代码的好地方.. 所以第一件事就是把这个地址记下来.. 以免你忘记...

现在我们有了<我们的代码>的地址.. 我们需要返回在(41a736)这里的指针, 还有做一个通道.. 它仍然在代码窗里,但是你现在看不见...
所以...

输入 U 41a736 (回车)

41a736 就显示在代码窗的最上面了..

现在我们创建通道...

***注意!!***
下面的步骤一定要做完 .. 你不能破坏他们中的任何一个,或者尝试退出softice, 这样你会毁掉这个游戏....

输入 A 41a736 (回车)
输入 jmp 453f00 (回车)
输入 nop (回车)     ( 我们需要用nop去掉多余的机器码来避免出错 )
输入 (回车)         ( 回车将退出上面调用的编辑(A)模式 )


在这一点,你应该在代码窗看到的变化..是从...

原始代码...
0041A736 8B8308020000 MOV   EAX,[EBX+00000208]
0041A73C D983CC000000 FLD   REAL4 PTR [EBX+000000CC]
0041A742 D8B3C8000000 FDIV   REAL4 PTR [EBX+000000C8]
0041A748 A3A46C7800   MOV   [00786CA4],EAX



通道创建以后...
0041A736 E9C5970300   JMP   00453F00
0041A73B 90         NOP
0041A73C D983CC000000 FLD   REAL4 PTR [EBX+000000CC] <-- 我们的代码执行完后要跳回这里..
0041A742 D8B3C8000000 FDIV   REAL4 PTR [EBX+000000C8]
0041A748 A3A46C7800   MOV   [00786CA4],EAX

ok.. 下一步...

输入 U 453f00 (回车)
输入 A 453f00 (回车)

我们现在准备创建我们的代码...

首先,我们必须重新建立在做通道时被破坏掉的那一条指令...

输入 mov eax,[ebx+208] (回车)

现在我们要把指针储藏到静态地址中.. 在你周围的所有地址都是静态的.. 所以,你可以选你喜欢的任何一个.. 不要太靠近代码部分.. 我选择 453f30....

输入 mov dword ptr [453f30],ebx (回车)

现在,最后我们需要跳回游戏代码区...

输入 jmp 41a73c (回车)   <-- 41a73c 就是在建通道时用了NOP指令的下一行...

输入 (回车) <-- 让你退出编辑(A)模式...

好.. 这些做完以后... 代码区应该看起来像这样....



通道部分...
0041A736 E9C5970300   JMP   00453F00
0041A73B 90         NOP
0041A73C D983CC000000 FLD   REAL4 PTR [EBX+000000CC]
0041A742 D8B3C8000000 FDIV   REAL4 PTR [EBX+000000C8]
0041A748 A3A46C7800   MOV   [00786CA4],EAX

我们的代码...
00453F00 8B8308020000 MOV   EAX,[EBX+00000208]
00453F06 891D303F4500 MOV   [00453F30],EBX
00453F0C E92B68FCFF   JMP   0041A73C
00453F11 0000       ADD   [EAX],AL
00453F13 0000       ADD   [EAX],AL

如果你的代码不像我的这个样子,那你可能做错了某些东西.. 你得回去重新做....

现在!! 如果你的代码跟我一样.. 你现在就可以按 F5 退出softice了... 还有游戏应该运行正常...

我们现在可以做一些漂亮的动作来看看它的实际工作情况....

按 CTRL-D 返回到softice ..

输入 D *453f30+280 (回车)

(如果它说了类似无效地址(INVALID ADDRESS)的话)) 你就退出softice然后返回softice再输入,然后回车...

如果一切如计划中的,那么在数据窗口的左上角你就会看到生命(LIVES)真正的地址了.. 你可以改这个地址的值,生命也会变了....


现在到了非常cool的部分!!!

失去所有生命.. 就是说 GAME OVER..

开始一个新游戏.. (不要载入游戏) 就开始一个新游戏...

如果你看着数据窗口.. 你会注意到生命(LIVES)位置的地址的值发生改变了.. 明显的!!
它是 DMA !! :)))

但是.. 我们的代码仍然在工作... 它仍然可以把DMA的值储藏到静态地址中. 你要做的就是再找到生命的值.. 就是输入那个命令.. :))

输入 D *453f30+280 (回车)

现在,在数据窗口的左上角你又可以看到生命真正的地址了..

pretty damn cool eh?? :))

我们已经击败了DMA .. DMA的基址将会始终被我们知道!!!! 储藏在453f30, 还有还要加上208.. 你就得到了生命的真正的地址...


FINAL WORDS
***********

做完这些以后,我们可以用来做些什么呢?
-------------------------

我敢肯定一些聪明的家伙已经把这个用在他们的修改器上了... 除了那些慢一点的人,我会为他们解释的...

一旦代码注射到游戏中以后.. 你可以使用 READPROCESSMEMORY 函数来读取 453f30 里的DMA基址.. 然后你再往 DMA基址+208
里写入数值.. 这做起来一样的简单...


注射代码注射剂! :)
-------------------------------

当你完成所有步骤以后,你总不会想每次都经历那些步骤去注射代码吧!.. 所以你需要制作一个修改器为你去注射这些代码..
这很简单.. 实际上所有的修改器用的都是同一种方法,用一个过程... 除非你想比平常花更多的字节.....


拿这篇教程做例子...
----------------------------

让注射剂时刻工作.. 这是在你的修改器上要实现的..


在 41a736 这个地址..

你应该要注入 E9,C5,97,03,00

在 5f3f00 这个地址..

你应该要注入 8B,83,08,02,00,00,89,1D,30,3F,45,00,E9,2B,68,FC,FF

还有.. 你要能从 5f3f30 这个地址读出 DMA基址 ..

基本上你要做的就是把所有那些指令的机器码写入正确的内存地址中...

警告!! 应该总是先写入<我们的代码>的机器码.... 然后再写入<通道>的机器码..

FEW!!! 另一篇教程完成了.. 兄弟们!! 这可是不少的工作啊..(man!! its a lot of work)

*******************************************************************************************
*******************************************************************************************
如果你什么问题或者注解可以email给我...
RECLAIM22@HOTMAIL.COM
访问我的网站以获得更多的教程指南.. http://WWW.SHEEPREC.CJB.NET ;
我要向那些支持我和鼓励我的人致意....

Odin, MiraMax, Keyboard Junkie, Calligula, Orr, DarkLighter, Kilby,
LordFinal, EverLast.
MiNiSTER, [NTSC], [Drone], Rizzah, Bengi...

没有顺序.. 就是所有人..
如果我漏掉了哪位..告诉我,下次把你补上 :))

转载这篇文章到任何网站时请保持免费!!!!!!



PS:工作很忙,没时间上来了.基本算是退役了,各位有新加入的就凑合着先看看.
回复

使用道具 举报

发表于 2007-11-21 13:12:05 | 显示全部楼层
死老白!!!!

天天在QQ里看到你~~

怎么Q你,就是不说话~~~

不要告诉我你把QQ群全部屏蔽了~~

想退役~没门!!!抓去干苦力,下煤洞挖煤去~
回复

使用道具 举报

发表于 2007-11-21 17:26:17 | 显示全部楼层
楼主是那个星组成员?
回复

使用道具 举报

发表于 2007-11-21 19:12:38 | 显示全部楼层
修改达人  白河愁

汉化过 GBA 合金弓单头的那位~
回复

使用道具 举报

发表于 2007-11-22 17:15:56 | 显示全部楼层
白河愁--久仰大名了~
回复

使用道具 举报

发表于 2007-11-22 19:58:26 | 显示全部楼层
不知所云..看来干什么的确实就是干什么的...
先膜拜下..GBA合金至今未通关
回复

使用道具 举报

 楼主| 发表于 2007-12-6 15:58:06 | 显示全部楼层
已经换了工作单位,现在上班爆忙,确实关闭了所有群的消息.
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|小黑屋|手机版|Archiver|Nw壬天堂世界 ( 京ICP备05022083号-1 京公网安备11010202001397号 )

GMT+8, 2024-9-29 15:29 , Processed in 0.017655 second(s), 3 queries , Redis On.

Powered by Discuz! X3.4 Licensed

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表