登录  | 立即注册

游客您好!登录后享受更多精彩

查看: 155|回复: 2

模拟外挂-初级篇-键盘模拟

[复制链接]

40

主题

3

回帖

59

积分

版主

积分
59
发表于 2025-3-14 20:56:03 | 显示全部楼层 |阅读模式
本帖最后由 天道酬勤 于 2025-3-14 20:58 编辑

说起模拟式外挂,大家一定都不陌生。顶顶大名的按键精灵就是模拟外挂的代表。不过,用别人的软件总归还是学不到什么东西的。下面这一章,就是着重讲解模拟式外挂。相对于别的外挂来说,模拟式外挂可以算是最基础,最简单的。用途是最广泛的。我相信,这也是一大堆菜鸟朋友的最爱。因为,内存外挂 ,封包外挂,指令修改式外挂,都需要很强的基本功。模拟式外挂的门槛底,也就成了一个外挂的起步点。学了这个,你至少能用自己编写的外挂在网络游戏里做一些简单的辅助了。用自己写的外挂,会极大的兴趣提高你继续学下去的兴趣。由兴趣入手,从而使技术登上另一个台阶。



什么叫事件?
在 WINDOWS 中,有很多对象,比如窗口类对象,控件类对象,事件也是一种对象,不同于其它对象。事件比较抽象。我们可以把它看成是一个标志位。一个标志位有 '1' 和 '0' 两种状态。事件也有两种状态,置位和复位。
使用键盘事件模拟键盘
Windows 提供了一个模拟键盘的函数。Keybd_event ,该函数产生一个键盘事件,发送给当前获得输入焦点的程序。需要注意的事,这个键盘事件只针对应用程序,并不针对系统的。也就是说,你模拟按下 " ctrl + alt + del " 来调出任务管理器是没有用的。所以,这样看来 keybd_event 的权限是很低的,而且并不是真正意义上的硬件模拟键盘。对于有些游戏来说,是没有用的。但对“大话西游等游戏来说,还是有用的。这对于刚才外挂的初学者来说,即使是简陋的,无疑也是令人兴奋的。我们来认识一下这个 keybd_event 函数
VOID keybd_event(
BYTE bVk, //虚拟键码,见2.2章的表格
BYTE bScan, // 硬件扫描码。一般不用
DWORD dwFlags, // 函数选项标识
ULONG_PTR dwExtraInfo //一般不用

函数功能:该函数合成一次击键事件。系统可使用这种合成的击键事件来产生 WM_KEYUP 或 WM_KEYDOWN 消息, 键盘 驱 动程 序的 中 断处 理程 序 调用keybd_event 函数这个函数很简单,第一个我们在热键那一节己经知道了。第二个和第四个都不用。我们着重介绍一下第三个函数 dwFlags。
dwFlag:
我们在文章的开头说过。事件对象可以看成一个 0 和 1 两种状态的标志位。键盘事件也有两种状态。“按下”和“弹起。它有两个标志。KEYEVENTF_EXTENDEDKEY和 KEYEVENTF_KEYUP dwFlag 为 0 则表示按下这个键,KEYEVENTF_EXTENDEDKEY表示这个位被指定后,扫描码的前缀将加上 0xE0,这个在 WinIO 那一节会讲到但我们一般不用。KEYEVENTF_KEYUP 的值是 2。当指定了它的时候,就表示按下的那个键被松开。说了这么多,其实我们只要记住 0 和 2 就行了。 0 是按下2 是松开。合起来,就是一次击键过程。
bVk:
要注 意的是, 模拟字母 键的时候 ,只能 用大写的 字符比如 'A',我 们用keydb_event('A',0,0,0) 就可 以 了 。 如 果 需 要 模 拟 大 写 的 A ,需 要 先 用keybd_event(VK_SHIFT,0,0,0)来按下 Shitf 键。然后再模拟按下 A。由于系统按键的速度太快,要实现打字效果的话,还要加上延迟函数 Sleep();用法很简单,它的参数是毫秒。比如要延迟一秒种,可以这样写 Sleep(1000);。



一个小例子

下面,我们做个模拟键盘的例子。新建一个控制台工程。在 main.c 里面添加如下代码。
#include <windows.h>
void main(void)
{
keybd_event(VK_LWIN,0,0,0); // 按下Windows键,就是左边ctrl键右边的那一个
keybd_event('F',0,0,0); // 按下F 键
keybd_event(VK_LWIN,0,2,0); //松开Windows键
keybd_event('F',0,2,0); //松开F键
}


F9运行程序,点击 “确定 ”按钮。呵呵,看到了什么,我们弹出一个WINDOWS搜索框。





后台键盘模拟
相信大家一定见过某些外挂,即使把游戏最小化了,游戏还能照样喊话,打怪。游戏,聊天两不误。这一节,就讲讲如何实现这种功能。在些之前,我们再仔细看看 keybd_event 上一次函数的说明。
keybd_event 函数合成一次击键事件。系统可使用这种合成的击键事件来产生WM_KEYUP 或 WM_KEYDOWN 消息,键盘驱动程序的中断处理程序调用 keybd_event函数。
我们可以看来,keybd_event 的内部实现是,往当前活动的窗口发送一个 WM_KEYUP 或 WM_KEYDOWN 消息来模拟按键的。所以,我们使用 keybd_event函数的时候,通常要把游戏调到前台来。才能响应按钮。我们只要把往当前活动窗口发送的按钮消。改成游戏窗口发送消息。这样,程序就会一直往后台窗口发送消息,这样,就能实现游戏后台模拟了。常用的模拟发送消息函数有两个。SendMessage,和 PostMessage。我们来看看他们的函数意思。
LRESULT SendM essage(
HWND hWnd, // 目标窗口的句柄
UINT Msg, // 消息
WPARAM wParam, // 第一个消息参数r
LPARAM lParam // 第二个消息参数
);
1.png
2.png

我们测试一下键盘上的其它键。当我们按下“↑”键的时候,发现系统并没有发送 WM_CHAR 消息。当我们按下"Alt+a"的时候,消息又变成了WM_SYSKEYDOWN 消息和 WM_SYSKEYUP 消息。由此可见,发送字符按键,非字符按键和发送带 alt 键的键的消息。发送的消息是不一样的。
事实上 WM_CHAR 消息并不是键盘产生的。而是应用程序产生的。消息循环中的 TranslateMessage 函数的作用。它的作用是把 WM_KEYDOWN 和 WM_KEYUP 消息,合并成一条 WM_CHAR 消息来处理的。




这样的话,应用程序只需要在消息循环中检测 WM_CHAR 消息就可以了,而不需要检测 WM_KEYDOWN 和 WM_KEYUP 消息。这大大的方便了编写应用程序。
这两个消息有点像我们使用 keybd_event 函数的标志。就是多了一个 char消息。相信大家己经跃跃欲试了,想用 PostMessage 来模拟 WM_KEYDOWN 等消息
不过,不要忘了,我们还不知道 WM_KEYDOWN 这三个消息的格式呢。我们不打无准备的仗。省得到时候写程序写到一半才想起来,应该怎么写呢。
现在,我们来看看这三个消息的格式。
WM_KEYDOWN
消息功能:
当一个非系统键被按下的时候,WM_KEYDOWN 消息被投递到窗口的键盘输入焦点。非系统键是指当 alt
键没有按下时按下的键。
参数:
wParam: 非系统键的虚拟键码
lPram: 重复计数,扫描码,扩展键标志,先前键状态,环境代码,转换状态,它的值应该照按下表定



描述
0–15
当前消息的重复次数。重复计数是该消息所表示的击键次数。大多数情况下,重复计数设置为。
如果 在 一 个 击 键 消 息 处 理 前 有 多 次 按 下 该 键 , 则 增 加 重 复 计 数 。 WM_KEYUP 或
WM_SYSKEYUP 消息的重复计数总为。
16–23
扫描码。硬件使用的编码
24
扩展码。如果击键结果来自 IBM 增强键盘的附加键之一,那么扩展键标志为。现在我们使用的
键盘一般都是属于 IBM 增强键盘。对于键盘右端的 Alt 和 Ctrl 键,以及非数字键盘上的光标移
动键(包括 Insert 和 Delete 键)、数字键盘上的斜杠(/)、 Enter、Num Lock 键,该标志均置。
25–28
保留,不使用
29
环境代码.对于 WM_KEYDOWN 消息来说,这一位通常是0
30
先前的键状态. 在消息发送前,如果这个键是按下的,则这一位为1,或者键是弹起的,这一位为
0
31
转换状态. 对于 WM_KEYDOWN 消息来说,这一位通常是0




如果怕麻烦,WM_KEYDOWN 消息的第这个参数可以直接填 0。我是很怕麻烦的。
WM_KEYUP
消息功能:
当一个非系统键被释放的时候,WM_KEYDOWN 消息被投递到窗口的键盘输入焦点。非系统键是指当 alt
键没有按下时按下的键。
参数:
wParam:同 WM_KEYUP
lPram:除了以下要注意的外,其它同 WM_KEYUP
WM_CHAR
消息功能:

描述
0–15
当前消息的重复次数。重复计数是该消息所表示的击键次数。大多数情况下,重复计数设置为。
如果 在 一 个 击 键 消 息 处 理 前 有 多 次 按 下 该 键 , 则 增 加 重 复 计 数 。 WM_KEYUP 或
WM_SYSKEYUP 消息的重复计数总为。
16–23
扫描码。硬件使用的编码
24
扩展码。如果击键结果来自 IBM 增强键盘的附加键之一,那么扩展键标志为。现在我们使用的
键盘一般都是属于 IBM 增强键盘。对于键盘右端的 Alt 和 Ctrl 键,以及非数字键盘上的光标移
动键(包括 Insert 和 Delete 键)、数字键盘上的斜杠(/)、 Enter、Num Lock 键,该标志均置。
25–28
保留,不使用
29
环境代码.对于 WM_KEYDOWN 消息来说,这一位通常是0
30
先前的键状态. 在消息发送前,如果这个键是按下的,则这一位为1,或者键是弹起的,这一位为
0
31
转换状态. 对于 WM_KEYDOWN 消息来说,这一位通常是0
30
先前键的状态。对于 WM_KEYUP 消息来说,这一位通常是1
31
转换状态。对于 WM_KEYUP 消息来


WM_CHAR
消息功能:


当一个非系统键被按下的时候,WM_KEYDOWN 消息被投递到窗口的键盘输入焦点。非系统键是指当 alt
键没有按下时按下的键。
参数:
wParam:字符码,(TCHAR)
lPram:除了以下要注意的外,其它同 WM_KEYUP
万事俱备,只欠东风,让我们做个小例子实验一下吧。
新建一个控制台程序。在 main.c 的源文件里面添加如下实例代码。
#include <windows.h>
#include <stdio.h>
HWND hNote; //记事本Edit类句柄
int main(void)
{
hNote = FindWindow(NULL,"无标题- 记事本"); //找到记事本父窗口句柄
if( 0 == hNote)
printf("记事本未运行..");
else
printf("记事本己打开....");
/* ToDo:在这里添加外挂初始化代码*/
hNote = FindWindowEx(hNote,0,"Edit",0); //找到记事本Edit类的句柄
while(1)
{
Sleep (5000); //等待秒
PostMessage(hNote,WM_KEYDOWN,'A',0); //按下
PostMessage(hNote,WM_KEYUP,'A',0xC0000000); //标志为1100 0000 0000 0000 弹起
}
exit(EXIT_SUCCESS);
}

点击“开始”=>“运行”->输入“notepad”。然后按“F9”运行程序。然后,程序就会每隔五秒打印一个字符 a。无论是把记本最小化,还是最大化,都不影响字符的输入。怎么样,挺简单吧。不过,这种方法的权限并不是很高。游戏也会屏蔽类似FindWindow 的函数。要论权限的话,下一节,我将会介绍另外一种方法。



驱动级键盘模拟
在这一节里,我们将讲述模拟式外挂的一些高级内容。这将要涉及一些高级的系统及硬件的知识。笔者尽量的把一些复杂的东西简化一下。让读者感到直观。如果感到本节内容难以读懂的话。不妨跳过本节。因为本节原本就是准备安排在书后面的部分。在下面的章节中,我会采用步骤形式的讲解方法。因为其中有的函数你只需要了解一次就行了。并不需要了解它的全部用途。比如mouse_event()函数。只要了解它的单击事件标志就行了。因为我们只用到它的单击事件。笔者也不可能面面俱到。所有的细节都讲透。因为这是一本讲述外挂的书籍。并不是讲述 windows 系统的书籍。所以如果大家有不明白的地方,而本书又没有讲清楚的话,建议大家去翻阅一下 WINDOWS 系统的书藉。这类书藉还是挺多的。也可以在百度上面搜索函数的具体用途。而且也要学会查微软提供的MSDN。当读者拿着用 keybd_event 写好的自动喊话器喜哄哄的去测试游戏的时候,却发现游戏纹丝不动。一定非常失望。接着便丧失了编写外挂的兴趣。从此一蹶不振。的确,第一次写的模拟外挂被游戏屏蔽了,无论如何都不是一件值得高兴的事。笔者在介绍 keybd_event 的时候说过,keybd_event 权限是很低的 。很容易就被游戏屏蔽掉了。为了不让读者失掉兴趣。我们把这一高级章节插入进来。
3.1.4.1 MapVirtualkey 函数
首先要介绍一个函数,我们将会和它打交道非常多。MapVirtualKey。在这一章节的开头我们说过。以后只介绍函数的的常用方法。这样也较容易记忆 。我们通常用 MapVirtualKey 函数来把虚拟码转换成扫描码。那么什么是扫描码呢? 简单的说,扫描码就是键盘的敲击产生的编码。当键盘的 8048 芯片在检测到一个击键动作的时候,会把这个码发计算机。也就是说,它是一种底层硬件编码。扫描码分 make 和 break 码两套。按下是 make 码,松开是 break 码。扫描码又是一张表,我们是不是又要像上一章节一样去查表呢?NOwindows 系统给我们提供了 MapVirtualKey 来完成转换。下面我们来看看 MapVirtualKey 的定义
_______________________________________________________________________________
UINT MapVirtualKey (
UINT uCode,
//键的扫描码或者虚拟码
UINT uMapType
// 转化的形式
);______________________________________________________________________________
函数功能:
MapVirtualKey 的作用是把虚拟码转换成扫描码。当函数执行成功。将返回被转换出来的扫描码。
uCode

按键的扫描码或者虚拟码。我们一般只填虚拟码。
uMapType :转换的形式。它的值可以是 0,1,2,3。分别对应不同的转换形式。要得到更多信息请查找百
度或者 MSDN。我们只填 0。标志 0 代表函数是将虚拟码转换成扫描码。

3.png

要从底层模拟键盘,就要使用 IO 端口。WINDOWS 系统是建定在保护模式的基础上的。保护模式对 IO 端口设置了权限。一般程序并不能访问。在windows中,有 4 种权限(如图 3.1.4.1)ring 0 - ring 3,数值越大,级数越低。ring 0是操作系统使用的级别。Ring 1 和 ring 2 是操作系统服务程序使用的级别。Ring 3 才是应用程序所在的级别。我们从图中可以看到。应用程序的权限是最低的。所以,我们一般是不可以直接访问端口的。在这里,我们将借助一个工具 。WinIO。它通过特别的方法躲过了windows 的验证。使应用程序能够读写 IO。
下载 WinIO,解压出来会生成 5 个文件。(图 3.1.4.2)

4.png

如何使用 WinIo
下面来讲讲如何使用 WinIo。
第一步:
新建一个工程 "main"。将头文件"WinIo.h"和"WinIo.lib"拷贝到工程目录里面。如图(3.1.4.3) 。在 main.c 的头文件里面包含 WinIO.h 在 WinIO 之前需要先包含 Windows.h。如图(3.1.4.4)WinIo.h 是 c++格式。我把需要去掉里面的一些内容。以下是我修
改后的 winio 头文件。

5.png

修改后C 下面使用的WinIo 的头文件
#define WINIO_API _declspec(dllimport)
WINIO_API BOOL _stdcall InitializeWinIo();
WINIO_API void _stdcall ShutdownWinIo();
WINIO_API PBYTE _stdcall MapPhysToLin(PBYTE pbPhysAddr, DWORD dwPhysSize, HANDLE
*pPhysicalM emoryHandle);
WINIO_API BOOL _stdcall UnmapPhysicalM emory (HANDLE PhysicalM emoryHandle, PBYTE pbLinAddr);
WINIO_API BOOL _stdcall GetPhysLong(PBYTE pbPhysAddr, PDWORD pdwPhysVal);
WINIO_API BOOL _stdcall SetPhysLong(PBYTE pbPhysAddr, DWORD dwPhysVal);
WINIO_API BOOL _stdcall GetPortVal(WORD wPortAddr, PDWORD pdwPortVal, BYTE bSize);
WINIO_API BOOL _stdcall SetPortVal(WORD wPortAddr, DWORD dwPortVal, BYTE bSize);
WINIO_API BOOL _stdcall InstallWinIoDriver(PSTR pszWinIoDriverPath, BOOL IsDemandLoaded);
WINIO_API BOOL _stdcall RemoveWinIoDriver();
extern BOOL IsNT;
extern HANDLE hDriver;
extern BOOL IsWinIoInitialized;
BOOL _stdcall StartWinIoDriver();
BOOL _stdcall StopWinIoDriver();
6.png
7.png
8.png

第三步:初始化 WINIO 库。Winio 提供了一个初始化函数 InitializeWinIo(),
它没有参数,当初始化成功的时候它返回 true ,否则返回 false .
第四步:用 GetPortVal 函数和 SetPortVal 函数读写端口它。他们的原型是:
bool GetPortVal( bool GetPortVal(
WORD wPortAddr, // 端口号 WORD wPortAddr, //端口号
PDWORD pdwPortVal, //需要写入的值 PDWORD pdwPortVal, //储存dword变量地址
BYTE bSize // 写入的值的大小 BYTE bSize //取出的大小
);__
wPortAddr:是一个字大小的端口号。在键盘模拟中,我们要使用两个端口。键盘状态端口0x64和数据端口
0x60。
bSize :是写入值的大小。有四个值。1 表示 byte 。2 表示 word 4 表示 dword ,我们模拟键盘使用
byte 就行了。
第五步:如果使用完了。记得卸载 WinIo 库。卸载函数是 ShutdownWinIo(),它没有参数,也没有返回值。功能是卸载掉 WinIO 库。


键盘的底层IO 端口

9.png

IO 端口就是计算机与外部设备打交道的方式。比如打印机,计算机使用0x378端口来操作。更加详细的 IO 端口资料请查阅计算机原理和汇编方面的书籍。下面来看看键盘端口的模型。虽然端口众多,但在键盘和鼠标的的模拟中,真真正正根我们密切相关的只有两个端口。0x64端口和 0x60 端口。虽然键盘上和主板上都有芯片。但CPU主要是跟主板上的8042芯片打交道。(如图3.1.4.8)而8048是键盘上的芯片,主要是把键盘产生的动作发给8042。比如按了哪个键,是按下还是弹起或者是一直保持按下。从图3.1.4.8当中,我们发现了3个寄存器。但还有一个寄存器没写出来,这个寄存器叫控制寄存器,也叫(command byte)

10.png

下面我们来看看可以对0x60和0x64端口进行的操作。

11.png

需要注意的是,这里的输入和输出是相对8042芯片而言的。比如“系统把输入缓冲区数据输入到8042,用来控制8042和8048“8042把输出缓冲区数据输出到系。要模拟键盘,就需要往输入缓冲区写入数据。从图3.1.4.8可以看出,输入缓冲区是由两部分组成的。分别由0x60和0x64写入填充。根据上表,我们知道,往端口0x64作写操作写得是输入缓冲区的命令部分,往0x60作写操作是数据部分。所以往输入缓冲区写入数据需要先往0x64端口写入命令。再往0x60端口写入数据。
那我们往0x64端口写入什么命令呢?大家看看下表。


命令
说明
20
读键盘的控制寄存器
60
写键盘的控制寄存器
aa
自检
ab
接口测试
ad
禁止键盘
ae
允许键盘
c0
读输入端口
d0
读输出端口
d1
写输出端口
e0
读测试输入
fe
系统重启
a7
禁止鼠标端口
a8
允许鼠标端口
a9
测试鼠标端口
d4
写入鼠标
00-1f
读键盘控制器 RAM
20-3f
读键盘控制器 RAM
40-5f
写键盘控制器 RAM
60-7f
写键盘控制器 RAM
90-93
Synaptics multiplexer prefix
90-9f
写端口 13 - 10
a0
读取版权
a1
读固件版本
a2
开关速度
a3
开关速度
a4
检查鼠标的安装
a5
载入密码
a6
检查密码
ac
诊断转存
af
读键盘版本
b0-b5
重置键盘控制线
b8-bd
设置键盘控制线
c1
Continuous input port poll, low
c2
Continuous input port poll, high
c8
解除地址线 P22 和 P23
c9
设置地址线 P22 和 P23
ca
读键盘的控制模式
cb
写键盘的控制模式
d2
写键盘的输出缓冲区
d3
写鼠标的输出缓冲区
dd
禁止 A20 地址线
df
打开 A20 地址线
f0-ff
变动的输出位




以上对0x64端口所有的命令。跟据上表可以查到,我们要写入数据到输入缓冲区,就需要使用 0xd2 命令。再看看 d3是什么?呵呵,你猜的没错,这个端口不仅可以模拟键盘,还可以模拟鼠标。好了,一切就绪,我们是不是该行动了?别急,我们还少了一个知识。关于扫描码的知识。有人会说,不是可以用MapVirtualKey 来转换的吗?在前面的章节中我们知道。扫描码是分为两种,一种是 make 码,表示键被按下。一种是 break 码,表示键被松开。MapVirtualKey函数只能转换成 make 码。那么,break 码怎么得到呢? 我们看一下一部分扫描码的表。



12.png

我们只取一部分表出来就可以了,发现什么了没有? 把 MAKE 扫描码和0x80进行逻辑或操作,就是 BREAK 扫描码了。那么,接下来,让我们大干一场吧 。


新建一个工程,在main.c 源文件里面添加如下代码。需要注意的是,除了 pause 键外,其它的扩展键在模拟之前都要先发送0xE0。
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "WinIo.h"
int main()
{
    InitializeWinIo(); //初始化WinIo库
    SetPortVal(0x64,0xd2,1); //把xD2发送到x64端口,通知将要向x60写入数据
    SetPortVal(0x60,M apVirtualKey (VK_NUMLOCK,0),1); //把数字灯键的make扫描码发送到x60端口
    Sleep (500); //延迟.5秒 按下和释放之间一定要延迟,否则太快了键盘模拟会有错误
    SetPortVal(0x64,0xd2,1);
    SetPortVal(0x60,M apVirtualKey (VK_NUMLOCK,0) | 0x80 ,1); //把break扫描码发送到x60
    ShutdownWinIo(); //卸载IO库
}
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "WinIo.h"
int main()
{
    InitializeWinIo(); //初使化WinIo
    SetPortVal(0x64,0xd2,1);
    SetPortVal(0x60,M apVirtualKey (VK_CONTROL,0),1);
    Sleep (300); //发送control键的make码,模拟按下control
    SetPortVal(0x64,0xd2,1);
    SetPortVal(0x60,M apVirtualKey (VK_MENU,0),1);
    Sleep (300); //发送alt键的make码,模拟按下alt
    SetPortVal(0x64,0xd2,1);
    SetPortVal(0x60,0xE0,1);
    Sleep (300); //发送扩展键的前缀E0
    SetPortVal(0x64,0xd2,1);
    SetPortVal(0x60,M apVirtualKey (VK_DELETE,0),1);
    Sleep (300); //发送扩展键DELETE的make码,按下DELETE键
    SetPortVal(0x64,0xd2,1);
    SetPortVal(0x60,M apVirtualKey (VK_CONTROL,0) | 0x80 ,1);
    Sleep (300); //松开control键
    SetPortVal(0x64,0xd2,1);
    SetPortVal(0x60,M apVirtualKey (VK_MENU,0) | 0x80 ,1);
    Sleep (300); //松开alt键
    SetPortVal(0x64,0xd2,1);
    SetPortVal(0x60,0xE0 ,1);
    Sleep (300); //释放xE0 扩展键前缀
    SetPortVal(0x64,0xd2,1);
    SetPortVal(0x60,M apVirtualKey (VK_DELETE,0) | 0x80 ,1);
    //松开DELETE键
}


0

主题

189

回帖

145

积分

注册会员

积分
145
发表于 前天 15:14 | 显示全部楼层
支持一下!!
回复

使用道具 举报

0

主题

41

回帖

117

积分

注册会员

积分
117
发表于 6 小时前 | 显示全部楼层
不错,又占了一个沙发!
回复

使用道具 举报

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

本版积分规则

QQ|Archiver|手机版|小黑屋|断点社区 |网站地图

GMT+8, 2025-4-4 07:03 , Processed in 0.111761 second(s), 24 queries , Yac On.

Powered by XiunoBBS

Copyright © 2001-2025, 断点社区.

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