[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程批处理在线视频分享
返回列表 发帖

[其他] image3.5发布,最强大的批处理界面辅助工具

本帖最后由 Byaidu 于 2018-4-30 12:10 编辑

image3.5包含了许多重大更新
包括绘图命令的完善以及绘图脚本的模块化

添加了“图元”和“画布”的概念
load pic mypic.bmp
上面这条命令的作用就是把mypic.bmp图片加载为一个叫pic的“图元”,然后再把这个pic“图元”绘制到一个叫pic的同名“画布”
然后mouse功能就会通过cmd“画布”的图元索引树来判定用户点击的了哪个“图元”
“图元”在image里只是一个虚拟的概念,只使用方便判定碰撞箱,不占用实际内存
“画布”在image里是一个实体的概念,没有对应的碰撞箱,仅用来承载“图元”,每个“画布”都有对应的HDC,会占用实际内存

其实我是想投到开源原创工具版块里的,不过貌似等级不够,所以就先投在这里了
image
控制台显示图片 Ver 3.5 by Byaidu

help                                                显示帮助
load tag [file]                                        新建画布tag,并加载图片file为图元tag,再将图元tag绘制到画布tag
unload tag                                        删除画布tag
save tag file                                        将画布tag的内容存储到file中
target tag                                        切换当前绘图目标为画布tag
buffer tag [w] [h] [r] [g] [b ]                        新建画布tag,并新建一个颜色为rgb(默认为白色)、大小为w*h(默认为当前绘图目标的大小)图元tag,再将图元tag绘制到画布tag
resize tag w h                                        将画布tag缩放到w*h的大小,如果目标cmd,则会同时将控制台设置为最适合绘图的状态
info tag                                        将画布tag的宽和高存储到变量image
rotate tag degree                                将画布tag顺时针旋转degree度
draw tag x y [trans [r] [g] [b ] | alpha a]        将画布tag绘制到当前绘图目标的x,y位置上
                                                若指定了trans,则以rgb为透明色(默认为白色)
                                                若指定了alpha,则以a为透明度
font r g b [w] [h]                                设置当前绘图目标所用字体的rgb值和大小
text string                                        在当前绘图目标的x,y的位置上输出字符串string
getpix tag x y                                        将画布tag上x,y位置的rgb值存储到变量image
setpix tag x y r g b                                设置画布tag上x,y位置的rgb值
cls                                                清空画布cmd的内容
export                                                将画布cmd的句柄存储到变量image
import tag handle                                通过句柄将另一个控制台的画布cmd映射到此控制台的画布tag
sleep time                                        延时time毫秒
list file [label]                                执行image指令脚本file,若指定了label则会直接跳转到脚本中的标签label
exit                                                退出当前image指令脚本
union tag                                        合并图层tag中的所有图元成一个与图层tag同名的图元tag
debug                                                以图形形式输出图元索引树,用于查看画布cmd上的各个图元
mouse time [region1] [region2] ...                捕获鼠标坐标及事件,坐标以像素为单位,时间以毫秒为单位
                                                若time>=0,当发生点击事件或时间超过限制时会将鼠标坐标x,y以及坐标在画布cmd上所在图元tag的tag存储到变量image,并将图元tag的tag单独再存储到变量errorlevel
                                                若time<0,不设置时间限制
                                                若指定了region,那么返回的的就不是图元tag的tag而是region的序号,如果鼠标坐标不在任何一个指定的region中,则返回序号0
                                                region应以如下的形式给出:x1,y1,x2,y2

核心代码
image.cpp
  1. /***********************************************************
  2. image
  3. 控制台显示图片 Ver 3.5 by Byaidu
  4. 完整代码及档案下载:https://github.com/Byaidu/image
  5. 部分代码参考:https://github.com/YinTianliang/CAPIx
  6. ************************************************************/
  7. #define _CRT_SECURE_NO_WARNINGS
  8. #define _CRT_NON_CONFORMING_SWPRINTFS
  9. #include <windows.h>
  10. #include <gdiplus.h>
  11. #include <wchar.h>
  12. #include <iostream>
  13. #include <fstream>
  14. #include <string>
  15. #include <cstdio>
  16. #include <map>
  17. #include "regionmgr.cpp"
  18. using namespace std;
  19. using namespace Gdiplus;
  20. #define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
  21. #define DLL_EXPORT __declspec(dllexport)
  22. #define wtoi _wtoi
  23. #define wcsicmp _wcsicmp
  24. #define match(x,y) if (wstring(argv[x])==wstring(y)||wstring(argv[x]).substr(0,2)==wstring(y).substr(0,2))
  25. #define matchclsid(x,y) if (wstring(argv[x]).substr(wstring(argv[x]).length()-3)==wstring(y))
  26. #pragma comment(lib,"msimg32.lib")
  27. #pragma comment(lib,"GdiPlus.lib")
  28. struct imageres { //画布结构体
  29. HDC dc;
  30. HBITMAP oldbmp;
  31. int w, h;
  32. BUF region; //图元索引树
  33. imageres() {};
  34. imageres(wchar_t *file) //初始化画布,并加载图元到画布上
  35. {
  36. BITMAP bi;
  37. //为了防止多个hbmp同时用一个hdc发生冲突,所以这里给所有的hbmp分配各自的hdc
  38. dc = CreateCompatibleDC(nullptr);
  39. //HBITMAP bmp = (HBITMAP)LoadImageA(nullptr, file, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
  40. HBITMAP bmp;
  41. Bitmap *bm = new Bitmap(file);
  42. bm->GetHBITMAP(0, &bmp);
  43. delete bm;
  44. oldbmp = (HBITMAP)SelectObject(dc, bmp);
  45. GetObject(bmp, sizeof(BITMAP), &bi);
  46. w = bi.bmWidth;
  47. h = bi.bmHeight;
  48. }
  49. void regioninit(wchar_t *tag,int w,int h) {region = BUF(tag, w - 1, h - 1);}
  50. }*hTarget;
  51. map<wstring, imageres> resmap; //画布映射表
  52. HWND hCMD; //控制台窗口句柄
  53. double scale; //校正缩放比
  54. void image(wchar_t *); //主函数
  55. void Init_image(); //初始化
  56. bool WINAPI DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpvReserved)
  57. {
  58. switch (dwReason)
  59. {
  60. case DLL_PROCESS_ATTACH:
  61. //HookAPI(SetEnvironmentVariableW, SetCall_image);
  62. DisableThreadLibraryCalls(hModule);
  63. Init_image();
  64. break;
  65. case DLL_PROCESS_DETACH:
  66. break;
  67. }
  68. return true;
  69. }
  70. extern "C" DLL_EXPORT int WINAPI Init(void)//随便给个导出函数,方便加载
  71. {
  72. return 0;
  73. }
  74. extern "C" __declspec(dllexport) void call(wchar_t *varName, wchar_t *varValue)
  75. {
  76. //判断变量名是否为image, 是则调用image
  77. if (!wcsicmp(varName, L"image")) image(varValue);
  78. return;
  79. }
  80. void Init_image() //初始化
  81. {
  82. GdiplusStartupInput gdiplusStartupInput;
  83. ULONG_PTR gdiplusToken;
  84. GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
  85. imageres hRes;
  86. //获取cmd大小以及绘图句柄
  87. hCMD = GetConsoleWindow();
  88. HDC hDC = GetDC(hCMD);
  89. DEVMODE dm;
  90. dm.dmSize = sizeof(DEVMODE);
  91. EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &dm);
  92. int ax = dm.dmPelsWidth;
  93. int bx = GetSystemMetrics(SM_CXSCREEN);
  94. scale = (double)ax / bx;//校正缩放比
  95. RECT rc;
  96. GetClientRect(hCMD, &rc);
  97. hRes.dc = hDC;
  98. hRes.w = int(scale*(rc.right - rc.left));
  99. hRes.h = int(scale*(rc.bottom - rc.top));
  100. hRes.regioninit((wchar_t*)L"cmd", hRes.w, hRes.h);
  101. resmap[L"cmd"] = hRes; //把cmd添加到画布映射表中
  102. hTarget = &resmap[L"cmd"];//getres("cmd"); //绘图默认指向cmd
  103. //获取desktop大小以及绘图句柄
  104. hDC = GetDC(nullptr);
  105. hRes.dc = hDC;
  106. hRes.w = dm.dmPelsWidth;
  107. hRes.h = dm.dmPelsHeight;
  108. hRes.regioninit((wchar_t*)L"desktop", hRes.w, hRes.h);
  109. resmap[L"desktop"] = hRes; //把desktop添加到画布映射表中
  110. TextOutA(hTarget->dc, 0, 0, 0, 0);//第一次使用TextOutA无效,大概是个bug
  111. return;
  112. }
  113. imageres * getres(wchar_t *tag) //查找画布
  114. {
  115. if (!resmap.count(tag)) //如果在画布映射表中找不到,则先加载图元到画布,再将画布添加到画布映射表
  116. {
  117. imageres hRes(tag);
  118. hRes.regioninit(tag, hRes.w, hRes.h);
  119. resmap[tag] = hRes;
  120. }
  121. return &resmap[tag];
  122. }
  123. void delres(wchar_t *tag) //销毁原来的画布,防止内存泄漏
  124. {
  125. imageres * hRes = getres(tag);
  126. HBITMAP bmp = (HBITMAP)SelectObject(hRes->dc, hRes->oldbmp);
  127. DeleteObject(bmp);
  128. DeleteDC(hRes->dc);
  129. resmap.erase(tag);
  130. return;
  131. }
  132. //不能用SelectObject获取cmd等特殊画布的hbitmap,所以要复制一份出来,注意使用之后要DeleteObject
  133. HBITMAP copyhbitmap(imageres *hSrc)
  134. {
  135. imageres hRes;
  136. hRes.dc = CreateCompatibleDC(hSrc->dc);
  137. HBITMAP hBitmap = CreateCompatibleBitmap(hSrc->dc, hSrc->w, hSrc->h);
  138. hRes.oldbmp = (HBITMAP)SelectObject(hRes.dc, hBitmap);
  139. BitBlt(hRes.dc, 0, 0, hSrc->w, hSrc->h, hSrc->dc, 0, 0, SRCCOPY);
  140. SelectObject(hRes.dc, hRes.oldbmp);
  141. DeleteDC(hRes.dc);
  142. return hBitmap;
  143. }
  144. void rotateres(wchar_t **argv)
  145. {
  146. imageres * hRes = getres(argv[1]);
  147. HBITMAP hSrc = copyhbitmap(hRes);
  148. Rect rect(0, 0, hRes->w, hRes->h);
  149. //用于加载旧位图
  150. Bitmap bitmap(hSrc, nullptr);
  151. BitmapData bitmapData;
  152. bitmap.LockBits(&rect, ImageLockModeRead, PixelFormat24bppRGB, &bitmapData);
  153. byte* pixels = (byte*)bitmapData.Scan0;
  154. //用于加载新位图
  155. Bitmap bitmap2(hSrc, nullptr);
  156. BitmapData bitmapData2;
  157. bitmap2.LockBits(&rect, ImageLockModeWrite, PixelFormat24bppRGB, &bitmapData2);
  158. byte* pixels2 = (byte*)bitmapData2.Scan0;
  159. //旋转
  160. double pi = 3.1415926;
  161. double angle = -(double)wtoi(argv[2]) / 180 * pi;
  162. double sina = sin(angle), cosa = cos(angle);
  163. int cx = hRes->w / 2, cy = hRes->h / 2;
  164. for (int i = 0; i<hRes->w; i++)
  165. for (int j = 0; j<hRes->h; j++)
  166. {
  167. int x = (int)(cx + (i - cx)*cosa - (j - cy)*sina), y = (int)(cy + (i - cx)*sina + (j - cy)*cosa);//原坐标
  168. if (x >= 0 && x < hRes->w&&y >= 0 && y < hRes->h)
  169. {
  170. for (int k = 0; k < 3; k++)
  171. pixels2[j*bitmapData2.Stride + 3 * i + k] = pixels[y*bitmapData.Stride + 3 * x + k];
  172. }
  173. else
  174. {
  175. for (int k = 0; k < 3; k++)
  176. pixels2[j*bitmapData2.Stride + 3 * i + k] = 0xFF;
  177. }
  178. }
  179. bitmap.UnlockBits(&bitmapData);
  180. bitmap2.UnlockBits(&bitmapData2);
  181. //复制临时画布到目标画布
  182. HDC hDCMem = CreateCompatibleDC(hRes->dc);
  183. HBITMAP hBitmap;
  184. bitmap2.GetHBITMAP(0, &hBitmap);
  185. HBITMAP oldbmp = (HBITMAP)SelectObject(hDCMem, hBitmap);
  186. BitBlt(hRes->dc, 0, 0, hRes->w, hRes->h, hDCMem, 0, 0, SRCCOPY);
  187. //销毁临时复制的画布
  188. DeleteObject(hSrc);
  189. SelectObject(hDCMem, oldbmp);
  190. DeleteObject(hBitmap);
  191. DeleteDC(hDCMem);
  192. }
  193. void alphares(wchar_t **argv)
  194. {
  195. double alpha = (double)wtoi(argv[5])/255;
  196. //用于加载源位图
  197. imageres * hRes = getres(argv[1]);
  198. HBITMAP hSrc = copyhbitmap(hRes);
  199. Rect rect(0, 0, hRes->w, hRes->h);
  200. Bitmap bitmap(hSrc, nullptr);
  201. BitmapData bitmapData;
  202. bitmap.LockBits(&rect, ImageLockModeRead, PixelFormat24bppRGB, &bitmapData);
  203. byte* pixels = (byte*)bitmapData.Scan0;
  204. //用于加载目标位图
  205. //不能SelectObject获取cmd等特殊画布的hbitmap,所以要复制一份出来,注意使用之后要DeleteObject
  206. HBITMAP hSrc2 = copyhbitmap(hTarget);
  207. Rect rect2(0, 0, hTarget->w, hTarget->h);
  208. Bitmap bitmap2(hSrc2, nullptr);
  209. BitmapData bitmapData2;
  210. bitmap2.LockBits(&rect2, ImageLockModeRead, PixelFormat24bppRGB, &bitmapData2);
  211. byte* pixels2 = (byte*)bitmapData2.Scan0;
  212. //用于加载新位图
  213. Rect rect3(0, 0, hTarget->w, hTarget->h);
  214. Bitmap bitmap3(hSrc2, nullptr);
  215. BitmapData bitmapData3;
  216. bitmap3.LockBits(&rect3, ImageLockModeWrite, PixelFormat24bppRGB, &bitmapData3);
  217. byte* pixels3 = (byte*)bitmapData3.Scan0;
  218. //alpha混合
  219. int cx = wtoi(argv[2]), cy = wtoi(argv[3]);
  220. for (int i = 0; i<hTarget->w; i++)
  221. for (int j = 0; j<hTarget->h; j++)
  222. {
  223. int x = i - cx, y = j - cy;//源坐标
  224. if (x >= 0 && x < hRes->w&&y >= 0 && y < hRes->h)
  225. {
  226. for (int k = 0; k < 3; k++)
  227. pixels3[j*bitmapData3.Stride + 3 * i + k] =
  228. (byte)((1 - alpha) * pixels2[j*bitmapData2.Stride + 3 * i + k] +
  229. alpha * pixels[y*bitmapData.Stride + 3 * x + k]);
  230. }
  231. else
  232. {
  233. for (int k = 0; k < 3; k++)
  234. pixels3[j*bitmapData3.Stride + 3 * i + k] = pixels2[j*bitmapData2.Stride + 3 * i + k];
  235. }
  236. }
  237. bitmap.UnlockBits(&bitmapData);
  238. bitmap2.UnlockBits(&bitmapData2);
  239. bitmap3.UnlockBits(&bitmapData3);
  240. //复制临时画布到目标画布
  241. HDC hDCMem = CreateCompatibleDC(hTarget->dc);
  242. HBITMAP hBitmap;
  243. bitmap3.GetHBITMAP(0, &hBitmap);
  244. HBITMAP oldbmp = (HBITMAP)SelectObject(hDCMem, hBitmap);
  245. BitBlt(hTarget->dc, 0, 0, hTarget->w, hTarget->h, hDCMem, 0, 0, SRCCOPY);
  246. //销毁临时复制的画布
  247. DeleteObject(hSrc);
  248. DeleteObject(hSrc2);
  249. SelectObject(hDCMem, oldbmp);
  250. DeleteObject(hBitmap);
  251. DeleteDC(hDCMem);
  252. }
  253. void image(wchar_t *CmdLine)
  254. {
  255. //wcout << CmdLine << endl;
  256. int argc;
  257. wchar_t **argv;
  258. argv = CommandLineToArgvW(CmdLine, &argc);
  259. match(0, L"help")
  260. {
  261. printf(
  262. "image\n"
  263. "控制台显示图片 Ver 3.5 by Byaidu\n"
  264. );
  265. }
  266. match(0, L"load") //加载图元到同名画布,再将画布到画布映射表
  267. {
  268. wchar_t *tag = argv[1]; //画布名称
  269. //销毁原来的画布,防止内存泄漏
  270. if (resmap.count(tag)) delres(tag);
  271. imageres hRes(argc > 2 ? argv[2] : argv[1]);
  272. hRes.regioninit(tag, hRes.w, hRes.h);
  273. resmap[tag] = hRes;
  274. }
  275. match(0, L"unload") //卸载画布
  276. {
  277. //销毁原来的画布,防止内存泄漏
  278. delres(argv[1]);
  279. }
  280. match(0, L"save") //保存为图片
  281. {
  282. imageres * hRes = getres(argv[1]);
  283. HBITMAP hSrc = copyhbitmap(hRes);
  284. Rect rect(0, 0, hRes->w, hRes->h);
  285. Bitmap bitmap(hSrc, nullptr);
  286. //https://stackoverflow.com/questions/1584202/gdi-bitmap-save-problem
  287. CLSID Clsid;
  288. matchclsid(2, L"bmp") CLSIDFromString(L"{557cf400-1a04-11d3-9a73-0000f81ef32e}", &Clsid);
  289. matchclsid(2, L"jpg") CLSIDFromString(L"{557cf401-1a04-11d3-9a73-0000f81ef32e}", &Clsid);
  290. matchclsid(2, L"png") CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &Clsid);
  291. bitmap.Save(argv[2], &Clsid, nullptr);
  292. DeleteObject(hSrc);
  293. }
  294. match(0, L"target") //更改绘图目标
  295. {
  296. hTarget = getres(argv[1]);
  297. }
  298. match(0, L"buffer") //新建一个buffer对象
  299. {
  300. wchar_t *tag = argv[1];
  301. //销毁原来的画布,防止内存泄漏
  302. if (resmap.count(tag)) delres(tag);
  303. imageres hRes;
  304. hRes.dc = CreateCompatibleDC(hTarget->dc);
  305. HBITMAP hBitmap = CreateCompatibleBitmap(hTarget->dc,argc>2?wtoi(argv[2]):hTarget->w,argc>3?wtoi(argv[3]):hTarget->h);
  306. hRes.oldbmp = (HBITMAP)SelectObject(hRes.dc, hBitmap);
  307. int color = argc>6?RGB(wtoi(argv[4]),wtoi(argv[5]),wtoi(argv[6])):RGB(255,255,255);
  308. colorregion(hRes.dc, color, 0, 0, hTarget->w - 1, hTarget->h - 1);
  309. hRes.w = hTarget->w;
  310. hRes.h = hTarget->h;
  311. //把buffer添加到画布调用表中
  312. hRes.regioninit(tag, hRes.w, hRes.h);
  313. resmap[tag] = hRes;
  314. }
  315. match(0, L"resize") //缩放
  316. {
  317. imageres * hRes = getres(argv[1]);
  318. match(1,L"cmd")
  319. {
  320. //防止快速编辑功能刷掉图像
  321. // 获取标准输入输出设备句柄  
  322. HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
  323. HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
  324. DWORD oldConMode;
  325. GetConsoleMode(hIn, &oldConMode); // 备份
  326. SetConsoleMode(hIn, (oldConMode | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT)&(~ENABLE_QUICK_EDIT_MODE));
  327. //隐藏光标
  328. CONSOLE_CURSOR_INFO cursor_info = { (DWORD)25, FALSE };
  329. SetConsoleCursorInfo(hOut, &cursor_info);
  330. RECT rc,rc2;
  331. SetScrollRange(hCMD, 0, 0, 0, 1);
  332. SetScrollRange(hCMD, 1, 0, 0, 1);
  333. GetClientRect(hCMD, &rc);
  334. GetWindowRect(hCMD, &rc2);
  335. int w = (rc2.right - rc2.left) - (rc.right - rc.left) + int((wtoi(argv[2])) / scale);
  336. int h = (rc2.bottom - rc2.top) - (rc.bottom - rc.top) + int((wtoi(argv[3])) / scale);
  337. //printf("scale:%f\n", scale);
  338. //printf("C:%dx%d\n", rc.right - rc.left, rc.bottom - rc.top);
  339. //printf("W:%dx%d\n", rc2.right - rc2.left, rc2.bottom - rc2.top);
  340. MoveWindow(hCMD, rc2.left, rc2.top, w, h, 0);
  341. Sleep(10);
  342. SetScrollRange(hCMD, 0, 0, 0, 1);
  343. SetScrollRange(hCMD, 1, 0, 0, 1);
  344. Sleep(10);
  345. hRes->w = (int)wtoi(argv[2]);
  346. hRes->h = (int)wtoi(argv[3]);
  347. }else{
  348. HDC hDCMem = CreateCompatibleDC(hRes->dc);
  349. HBITMAP hBitmap = CreateCompatibleBitmap(hRes->dc, wtoi(argv[2]), wtoi(argv[3]));
  350. HBITMAP oldbmp = (HBITMAP)SelectObject(hDCMem, hBitmap);
  351. StretchBlt(hDCMem, 0, 0, wtoi(argv[2]), wtoi(argv[3]), hRes->dc, 0, 0, hRes->w, hRes->h, SRCCOPY);
  352. //销毁原来的画布,防止内存泄漏
  353. HBITMAP bmp = (HBITMAP)SelectObject(hRes->dc, hRes->oldbmp);
  354. DeleteObject(bmp);
  355. DeleteDC(hRes->dc);
  356. //替换原来的画布
  357. hRes->oldbmp = oldbmp;
  358. hRes->dc = hDCMem;
  359. hRes->w = wtoi(argv[2]);
  360. hRes->h = wtoi(argv[3]);
  361. }
  362. hRes->regioninit(argv[1], hRes->w, hRes->h);
  363. }
  364. match(0, L"cls")
  365. {
  366. //清屏并重置cmd图层的图元索引树
  367. imageres * hRes = getres((wchar_t*)L"cmd");
  368. hRes->regioninit((wchar_t*)L"cmd", hRes->w, hRes->h);
  369. InvalidateRect(hCMD, nullptr, true);
  370. UpdateWindow(hCMD);
  371. Sleep(10);
  372. }
  373. match(0, L"rotate")
  374. {
  375. rotateres(argv);
  376. }
  377. match(0, L"draw")
  378. {
  379. //直接在目标上绘图
  380. imageres * hRes = getres(argv[1]);
  381. //提前清除目标区域的图元索引树结构可以避免两个图元索引树交叉在一起使图元索引树变得更复杂
  382. BUF clearbuf(L"", hRes->region.p->x2, hRes->region.p->y2);
  383. complexupdate(clearbuf.p, 0, 0, hRes->region.p->x2, hRes->region.p->y2, wtoi(argv[2]), wtoi(argv[3]), hTarget->region.p);
  384. complexupdate(hRes->region.p, 0, 0, hRes->region.p->x2, hRes->region.p->y2, wtoi(argv[2]), wtoi(argv[3]), hTarget->region.p);
  385. if (argc == 4)
  386. {
  387. BitBlt(hTarget->dc, wtoi(argv[2]), wtoi(argv[3]), hRes->w, hRes->h, hRes->dc, 0, 0, SRCCOPY);
  388. }
  389. else
  390. {
  391. match(4, L"trans")
  392. TransparentBlt(hTarget->dc, wtoi(argv[2]), wtoi(argv[3]), hRes->w, hRes->h, hRes->dc, 0, 0, hRes->w, hRes->h, argc==8?RGB(wtoi(argv[5]),wtoi(argv[6]),wtoi(argv[7])):RGB(255, 255, 255));
  393. match(4, L"alpha")
  394. alphares(argv);
  395. }
  396. }
  397. match(0, L"text")
  398. {
  399. //显示两次才会刷新出来,大概是个bug
  400. for (int i = 0; i < 2;i++) TextOutW(hTarget->dc, wtoi(argv[2]), wtoi(argv[3]), argv[1], wcslen(argv[1]));
  401. }
  402. match(0, L"font")
  403. {
  404. SetBkMode(hTarget->dc, TRANSPARENT);
  405. SetTextColor(hTarget->dc, RGB(wtoi(argv[1]), wtoi(argv[2]), wtoi(argv[3])));
  406. if (argc > 4)
  407. {
  408. HFONT hFont = CreateFontW(
  409. argc > 5 ? wtoi(argv[5]) : 0,
  410. argc > 4 ? wtoi(argv[4]) : 0,
  411. argc > 6 ? wtoi(argv[6]) : 0/*不用管*/,
  412. argc > 7 ? wtoi(argv[7]) : 0/*不用管*/,
  413. argc > 8 ? wtoi(argv[8]) : 400/*一般这个值设为400*/,
  414. argc > 9 ? wtoi(argv[9]) : 0/*不带斜体*/,
  415. argc > 10 ? wtoi(argv[10]) : 0/*不带下划线*/,
  416. argc > 11 ? wtoi(argv[11]) : 0/*不带删除线*/,
  417. DEFAULT_CHARSET, //这里我们使用默认字符集,还有其他以 _CHARSET 结尾的常量可用
  418. OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, //这行参数不用管
  419. DEFAULT_QUALITY, //默认输出质量
  420. FF_DONTCARE, //不指定字体族*/
  421. argc > 12 ? argv[12] : L"新宋体" //字体名
  422. );
  423. SelectObject(hTarget->dc, hFont);
  424. }
  425. }
  426. match(0, L"sleep")
  427. {
  428. Sleep(wtoi(argv[1]));
  429. }
  430. match(0, L"info")
  431. {
  432. wchar_t info[100];
  433. imageres * hRes = getres(argv[1]);
  434. swprintf(info, L"%d %d", hRes->w, hRes->h);
  435. SetEnvironmentVariableW(L"image", info);
  436. }
  437. match(0, L"export")
  438. {
  439. wchar_t info[100];
  440. swprintf(info, L"%d", (int)hCMD);
  441. SetEnvironmentVariableW(L"image", info);
  442. }
  443. match(0, L"import")
  444. {
  445. wchar_t *tag = argv[1];
  446. //销毁原来的画布,防止内存泄漏
  447. if (resmap.count(tag)) delres(tag);
  448. imageres hRes;
  449. //获取cmd大小以及绘图句柄
  450. HWND hCMD2 = (HWND)wtoi(argv[2]);
  451. HDC hDC = GetDC(hCMD2);
  452. DEVMODE dm;
  453. dm.dmSize = sizeof(DEVMODE);
  454. EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &dm);
  455. int ax = dm.dmPelsWidth;
  456. int bx = GetSystemMetrics(SM_CXSCREEN);
  457. double scale = (double)ax / bx;//校正缩放比
  458. RECT rc;
  459. GetClientRect(hCMD2, &rc);
  460. hRes.dc = hDC;
  461. hRes.w = (int)ceil(scale*(rc.right - rc.left));
  462. hRes.h = (int)ceil(scale*(rc.bottom - rc.top));
  463. hRes.regioninit(tag, hRes.w, hRes.h);
  464. resmap[tag] = hRes; //把cmd作为画布添加到调用表中
  465. }
  466. match(0, L"getpix")
  467. {
  468. wchar_t info[100];
  469. COLORREF color=GetPixel(hTarget->dc, wtoi(argv[1]), wtoi(argv[2]));
  470. swprintf(info, L"%d %d %d", GetRValue(color), GetGValue(color), GetBValue(color));
  471. SetEnvironmentVariableW(L"image", info);
  472. }
  473. match(0, L"setpix")
  474. {
  475. SetPixel(hTarget->dc, wtoi(argv[1]), wtoi(argv[2]), RGB(wtoi(argv[3]), wtoi(argv[4]), wtoi(argv[5])));
  476. }
  477. match(0, L"list")
  478. {
  479. bool skip = 0;
  480. if (argc > 2) skip = 1;
  481. ifstream in(argv[1]);
  482. string str;
  483. wchar_t wstr[100];
  484. while (!in.eof())
  485. {
  486. getline(in, str);
  487. MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, wstr, sizeof(wstr));
  488. if (skip&&L":" + wstring(argv[2]) == wstring(wstr)) { skip = 0; continue; }
  489. if (skip) continue;
  490. if (wstring(L"exit") == wstring(wstr)) break;
  491. //wcout << wstring(wstr) << endl;
  492. image(wstr);
  493. }
  494. in.close();
  495. }
  496. match(0, L"mouse")
  497. {
  498. imageres *hRes = getres((wchar_t*)L"cmd");
  499. wchar_t info[100];
  500. POINT mosPos;
  501. int x, y;
  502. int timer = wtoi(argv[1]);
  503. //这里要重新设置一次,要不然ReadConsoleInput会卡住
  504. // 获取标准输入输出设备句柄  
  505. HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
  506. HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
  507. DWORD oldConMode;
  508. GetConsoleMode(hIn, &oldConMode); // 备份
  509. SetConsoleMode(hIn, (oldConMode | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT)&(~ENABLE_QUICK_EDIT_MODE));
  510. if (timer < 0)
  511. {
  512. INPUT_RECORD Rec;
  513. DWORD res;
  514. while (1)
  515. {
  516. ReadConsoleInputW(hIn, &Rec, 1, &res);
  517. if (Rec.EventType == MOUSE_EVENT)
  518. {
  519. if (Rec.Event.MouseEvent.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED)
  520. {
  521. GetCursorPos(&mosPos);
  522. ScreenToClient(hCMD, &mosPos);
  523. x = min(max((int)scale*mosPos.x, 0), hRes->w);
  524. y = min(max((int)scale*mosPos.y, 0), hRes->h);
  525. break;
  526. }
  527. }
  528. /*
  529. if (Rec.EventType == KEY_EVENT)
  530. {
  531. if (Rec.Event.KeyEvent.bKeyDown == 1)
  532. {
  533. }
  534. }
  535. */
  536. }
  537. }
  538. if (timer >= 0)
  539. {
  540. DWORD tstart = GetTickCount();
  541. while (!(KEYDOWN(VK_LBUTTON) || int(GetTickCount() - tstart) >= timer)) Sleep(10);
  542. GetCursorPos(&mosPos);
  543. ScreenToClient(hCMD, &mosPos);
  544. x = min(max((int)scale*mosPos.x, 0), hRes->w);
  545. y = min(max((int)scale*mosPos.y, 0), hRes->h);
  546. }
  547. if (argc >= 3)
  548. {
  549. //在指定的region列表中查找
  550. int ret = 0;
  551. for (int i = 2; i < argc; i++)
  552. {
  553. int x1, y1, x2, y2;
  554. swscanf(argv[i], L"%d,%d,%d,%d", &x1, &y1, &x2, &y2);
  555. if (x >= x1 && x <= x2 && y >= y1 && y <= y2) ret = i - 1;
  556. }
  557. swprintf(info, L"%d %d %d", x, y, ret);
  558. SetEnvironmentVariableW(L"image", info);
  559. swprintf(info, L"%d", ret);
  560. SetEnvironmentVariableW(L"errorlevel", info);
  561. }else{
  562. //在图元索引表中查找
  563. wstring ret = query(resmap[L"cmd"].region.p, x, y);
  564. swprintf(info, L"%d %d %s", x, y, ret.c_str());
  565. SetEnvironmentVariableW(L"image", info);
  566. swprintf(info, L"%s", ret.c_str());
  567. SetEnvironmentVariableW(L"errorlevel", info);
  568. }
  569. SetConsoleMode(hIn, oldConMode);
  570. }
  571. match(0, L"debug")
  572. {
  573. imageres *hRes = getres((wchar_t*)L"cmd");
  574. show(hRes->region.p);
  575. }
  576. match(0, L"union")
  577. {
  578. //合并图层中的所有图元成一个与图层同名的图元,即重置图层的图元索引树
  579. imageres * hRes = getres(argv[1]);
  580. hRes->regioninit(argv[1], hRes->w, hRes->h);
  581. }
  582. match(0, L"cmd")
  583. {
  584. _wsystem(argv[1]);
  585. }
  586. //todo:支持鼠标键盘同时控制
  587. LocalFree(argv);
  588. return;
  589. }
复制代码
regionmgr.cpp
  1. #pragma   once
  2. #include <windows.h>
  3. #include <cstdio>
  4. #include <iostream>
  5. #include <algorithm>
  6. #include <map>
  7. #include <stack>
  8. #include <ctime>
  9. using namespace std;
  10. struct P{
  11. int single;
  12. wstring src;
  13. int x1, y1, x2, y2, x0, y0;
  14. P *l, *r;
  15. };
  16. struct BUF{
  17. P *p;
  18. BUF(){};
  19. BUF(wstring src,int w,int h){
  20. p = new P{ 1,src,0,0,w,h,0,0 };
  21. }
  22. };
  23. map<wstring,BUF> bufmap;
  24. /*
  25. void Init_regionmgr() //初始化
  26. {
  27. //srand(time(0));
  28. //获取cmd大小以及绘图句柄
  29. hCMD = GetConsoleWindow();
  30. HDC dc = GetDC(hCMD);
  31. DEVMODE dm;
  32. dm.dmSize = sizeof(DEVMODE);
  33. EnumDisplaySettings(nullptr, ENUM_CURRENT_SETTINGS, &dm);
  34. int ax = dm.dmPelsWidth;
  35. int bx = GetSystemMetrics(SM_CXSCREEN);
  36. double scale = (double)ax / bx;//校正缩放比
  37. RECT rc;
  38. GetClientRect(hCMD, &rc);
  39. int w = (int)ceil(scale*(rc.right - rc.left));
  40. int h = (int)ceil(scale*(rc.bottom - rc.top));
  41. srcmap[0] = dc;//0号资源为控制台dc
  42. srcmap[1] = 0;//1号资源为纯黑背景
  43. BUF buf(1, w-1, h-1);
  44. bufmap["cmd"] = buf; //把cmd作为资源添加到调用表中
  45. hTarget = &bufmap["cmd"];//绘图默认指向cmd
  46. return;
  47. }
  48. */
  49. //合并剪枝
  50. void singleunion(P *p)
  51. {
  52. if (p->r->single == 1 && p->l->single == 1 && p->r->src == p->l->src&&
  53. p->r->x1 - p->l->x1 == p->r->x0 - p->l->x0&&
  54. p->r->y1 - p->l->y1 == p->r->y0 - p->l->y0)
  55. {
  56. p->x0 = p->l->x0;
  57. p->y0 = p->l->y0;
  58. p->src = p->l->src;
  59. p->single = 1;
  60. //todo:delete point
  61. return;
  62. }
  63. if (p->r->single &&p->r->x1== p->x1&&p->r->y1 == p->y1&&p->r->x2 == p->x2&&p->r->y2 == p->y2)
  64. {
  65. p->x0 = p->r->x0;
  66. p->y0 = p->r->y0;
  67. p->src = p->r->src;
  68. p->single = 1;
  69. return;
  70. }
  71. if (p->l->single &&p->l->x1 == p->x1&&p->l->y1 == p->y1&&p->l->x2 == p->x2&&p->l->y2 == p->y2)
  72. {
  73. p->x0 = p->l->x0;
  74. p->y0 = p->l->y0;
  75. p->src = p->l->src;
  76. p->single = 1;
  77. return;
  78. }
  79. }
  80. //x1y1x2y2为目标p坐标,x0y0为源v坐标
  81. void singleupdate(P *p,int x1,int y1,int x2,int y2,int x0,int y0, wstring v,int now=0)
  82. {
  83. if (p->single==1&&p->x1==x1&&p->y1==y1&&p->x2==x2&&p->y2==y2){
  84. if (!(p->x0==x0&&p->y0==y0&&p->src==v)){
  85. p->x0=x0,p->y0=y0,p->src=v;
  86. /*
  87. if (hTarget==&bufmap["cmd"]){
  88. //printf("BITBLT:(%d,%d)(%d,%d):%d(%d,%d)\n",p->x1,p->y1,p->x2,p->y2,p->src,p->x0,p->y0);
  89. BitBlt(srcmap[0], p->x1, p->y1, p->x2 - p->x1 + 1, p->y2 - p->y1 + 1, srcmap[p->src], p->x0, p->y0, SRCCOPY);
  90. }
  91. */
  92. }
  93. return;
  94. }
  95. if (p->single){
  96. if (now) {
  97. if (x1 != p->x1) {
  98. p->l = new P{ 1,p->src ,p->x1, p->y1, x1 - 1, p->y2, p->x0, p->y0 };
  99. p->r = new P{ 1,p->src ,x1,p->y1,p->x2,p->y2,p->x0 + x1 - p->x1,p->y0 };
  100. singleupdate(p->r, x1, y1, x2, y2, x0, y0, v, !now);
  101. }
  102. else {
  103. p->l = new P{ 1,p->src ,p->x1,p->y1,x2,p->y2,p->x0,p->y0 };
  104. p->r = new P{ 1,p->src ,x2 + 1,p->y1,p->x2,p->y2,p->x0 + x2 + 1 - p->x1,p->y0 };
  105. singleupdate(p->l, x1, y1, x2, y2, x0, y0, v, !now);
  106. }
  107. }
  108. else {
  109. if (y1 != p->y1) {
  110. p->l = new P{ 1,p->src ,p->x1, p->y1, p->x2, y1 - 1, p->x0, p->y0 };
  111. p->r = new P{ 1,p->src ,p->x1,y1,p->x2,p->y2,p->x0,p->y0 + y1 - p->y1 };
  112. singleupdate(p->r, x1, y1, x2, y2, x0, y0, v, !now);
  113. }
  114. else {
  115. p->l = new P{ 1,p->src ,p->x1,p->y1,p->x2,y2,p->x0,p->y0 };
  116. p->r = new P{ 1,p->src ,p->x1,y2 + 1,p->x2,p->y2,p->x0,p->y0 + y2 + 1 - p->y1 };
  117. singleupdate(p->l, x1, y1, x2, y2, x0, y0, v, !now);
  118. }
  119. }
  120. p->single=0;
  121. }else{
  122. if (now){
  123. if (x2<= p->l->x2) singleupdate(p->l,x1,y1,x2,y2,x0,y0,v,!now);
  124. else if (x1>= p->r->x1) singleupdate(p->r,x1,y1,x2,y2,x0,y0,v,!now);
  125. else singleupdate(p->l,x1,y1, p->l->x2,y2,x0,y0,v,!now),singleupdate(p->r,p->r->x1,y1,x2,y2,x0+p->r->x1-x1,y0,v,!now);
  126. }else{
  127. if (y2<= p->l->y2) singleupdate(p->l,x1,y1,x2,y2,x0,y0,v,!now);
  128. else if (y1>= p->r->y1) singleupdate(p->r,x1,y1,x2,y2,x0,y0,v,!now);
  129. else singleupdate(p->l,x1,y1,x2, p->l->y2,x0,y0,v,!now),singleupdate(p->r,x1,p->r->y1,x2,y2,x0,y0+p->r->y1-y1,v,!now);
  130. }
  131. }
  132. singleunion(p);
  133. }
  134. //x1y1x2y2为源p坐标,x0y0为目标v坐标
  135. void complexupdate(P *p,int x1,int y1,int x2,int y2,int x0,int y0,P *v,int now=0)
  136. {
  137. if (p->single){//检索区域与节点x有交集,且节点x为单源图块
  138. singleupdate(v,x0,y0,x0+x2-x1,y0+y2-y1,p->x0+x1-p->x1,p->y0+y1-p->y1,p->src);
  139. return;
  140. }
  141. if (now){
  142. if (x2<= p->l->x2) complexupdate(p->l,x1,y1,x2,y2,x0,y0,v,!now);
  143. else if (x1>= p->r->x1) complexupdate(p->r,x1,y1,x2,y2,x0,y0,v,!now);
  144. else complexupdate(p->l,x1,y1, p->l->x2,y2,x0,y0,v,!now),complexupdate(p->r, p->r->x1,y1,x2,y2,x0+p->r->x1-x1,y0,v,!now);
  145. }else{
  146. if (y2<= p->l->y2) complexupdate(p->l,x1,y1,x2,y2,x0,y0,v,!now);
  147. else if (y1>= p->r->y1) complexupdate(p->r,x1,y1,x2,y2,x0,y0,v,!now);
  148. else complexupdate(p->l,x1,y1,x2, p->l->y2,x0,y0,v,!now),complexupdate(p->r,x1, p->r->y1,x2,y2,x0,y0+p->r->y1-y1,v,!now);
  149. }
  150. }
  151. void colorregion(HDC hDC,int color,int x1,int y1,int x2,int y2)
  152. {
  153. HPEN gPen = CreatePen(PS_SOLID, 1, color);
  154. HBRUSH gBrush = CreateSolidBrush(color);
  155. HPEN oPen = (HPEN)SelectObject(hDC, gPen);
  156. HBRUSH oBrush = (HBRUSH)SelectObject(hDC, gBrush);
  157. //Rectangle(srcmap[0], 500,500,505,505);
  158. Rectangle(hDC, x1, y1, x2 + 1, y2 + 1);
  159. //getchar();
  160. //printf("(%d,%d)(%d,%d):%d(%d,%d)\n",p->x1,p->y1,p->x2,p->y2,p->src,p->x0,p->y0);
  161. SelectObject(hDC, oPen);
  162. SelectObject(hDC, oBrush);
  163. DeleteObject(gPen);
  164. DeleteObject(gBrush);
  165. }
  166. //先序遍历
  167. void show(P *p)
  168. {
  169. HWND hCMD = GetConsoleWindow();
  170. HDC hDC = GetDC(hCMD);
  171. if (p->single){
  172. int color = rand() % 16777216;
  173. colorregion(hDC, color, p->x1, p->y1, p->x2, p->y2);
  174. } else {
  175. //printf("(%d,%d)(%d,%d)\n", p->x1, p->y1, p->x2, p->y2);
  176. show(p->l);
  177. show(p->r);
  178. }
  179. ReleaseDC(hCMD,hDC);
  180. }
  181. wstring query(P *p, int x, int y, int now = 0)
  182. {
  183. if (p->single) return p->src;
  184. if (now) {
  185. if (x <= p->l->x2) return query(p->l, x, y, !now);
  186. else return query(p->r, x, y, !now);
  187. } else {
  188. if (y <= p->l->y2) return query(p->l, x, y, !now);
  189. else return query(p->r, x, y, !now);
  190. }
  191. }
  192. /*
  193. void image(wchar_t *CmdLine)
  194. {
  195. int argc;
  196. wchar_t **argvw = CommandLineToArgvW(CmdLine, &argc);
  197. //wchar_t转char
  198. for (int i = 0; i < argc; i++)
  199. {
  200. size_t len = 2 * wcslen(argvw[i]) + 1;
  201. size_t converted = 0;
  202. wcstombs_s(&converted, argv[i], len, argvw[i], _TRUNCATE);
  203. }
  204. match(0, "load")
  205. {
  206. char *tag; //资源描述符
  207. tag = (argc == 3) ? argv[2] : argv[1];
  208. HBITMAP bmp=(HBITMAP)LoadImageA(NULL, argv[1], IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
  209. HDC dc = CreateCompatibleDC(nullptr);
  210. SelectObject(dc, bmp);
  211. srcmap[++srccnt]=dc;
  212. BITMAP bi;
  213. GetObject(bmp, sizeof(BITMAP), &bi);
  214. BUF buf(srccnt,bi.bmWidth-1,bi.bmHeight-1);
  215. bufmap[tag]=buf;
  216. }
  217. match(0, "buffer")
  218. {
  219. BUF buf(1, hTarget->p->x2, hTarget->p->y2);
  220. bufmap[argv[1]] = buf;
  221. }
  222. match(0, "target")
  223. {
  224. hTarget = &bufmap[argv[1]];
  225. }
  226. match(0, "draw")
  227. {
  228. BUF *buf = &bufmap[argv[1]];
  229. complexupdate(buf->p, 0, 0, buf->p->x2, buf->p->y2, atoi(argv[2]), atoi(argv[3]), hTarget->p);
  230. }
  231. match(0, "show")
  232. {
  233. BUF *buf = &bufmap[argv[1]];
  234. show(buf->p);
  235. }
  236. LocalFree(argvw);
  237. return;
  238. }
  239. */
复制代码
下面展示的这个样例,借助于image脚本的模块化,成功实现了界面与逻辑分离
x.bat
  1. @echo off
  2. %1start /b "" "%~dp0cmd.exe" "/c %~fs0 :"&exit
  3. setlocal EnableDelayedExpansion
  4. set image=draw button1.bmp 500 500
  5. set image=list x.txt init
  6. :act1
  7. set image=list x.txt act1
  8. set image=list x.txt !errorlevel!
  9. if "!errorlevel!"=="cmd" goto act1
  10. call:act1_!errorlevel!
  11. goto act2
  12. :act1_button1
  13. rem to do something
  14. goto:eof
  15. :act1_button2
  16. rem to do something
  17. goto:eof
  18. :act2
  19. set image=list x.txt act2
  20. set image=list x.txt !errorlevel!
  21. if "!errorlevel!"=="cmd" goto act2
  22. :act3
  23. set image=list x.txt act3
  24. set image=list x.txt !errorlevel!
  25. if "!errorlevel!"=="cmd" goto act3
  26. echo 演示完毕
  27. pause
复制代码
x.txt
  1. :init
  2. resize cmd 1000 1000
  3. load button1 button1.bmp
  4. draw button1.bmp 0 0
  5. load button2 button2.bmp
  6. font 0 255 255 50 100
  7. text "加载完成" 0 0
  8. mouse -1
  9. cls
  10. exit
  11. :act1
  12. text "ACT 1" 0 0
  13. draw button1 100 500
  14. draw button2 600 500
  15. mouse -1
  16. cls
  17. exit
  18. :act2
  19. text "ACT 2" 0 0
  20. draw button1 100 500 alpha 100
  21. draw button2 600 500 alpha 100
  22. mouse -1
  23. cls
  24. exit
  25. :act3
  26. text "ACT 3" 0 0
  27. draw button1 100 500 alpha 50
  28. draw button2 600 500 alpha 50
  29. mouse -1
  30. cls
  31. exit
  32. :button1
  33. text "YOU CLICK BUTTON 1" 0 100
  34. exit
  35. :button2
  36. text "YOU CLICK BUTTON 2" 0 100
  37. exit
  38. :cmd
  39. text "YOU CLICK NOTHING" 0 100
  40. exit
复制代码
更多精彩样例尽在:https://github.com/Byaidu/image
4

评分人数

非常好,刚用习惯2.0又来个3.5……

TOP

回复 2# zhangzsky


    感谢对image的支持

TOP

VS版本太低编译不了...
求已编译的32和64位的版本

TOP

回复 4# CrLf


    image3.5的编译环境是VS2017,已经编译完的动态库和所有功能的演示样例都已经上传到了github上:https://github.com/Byaidu/image/tree/master/Release

TOP

回复 5# Byaidu


    所以实现方式是从独立的第三方变成注入版的cmd了吗?

TOP

回复 6# CrLf


    大概是这样,但也不完全对。
image3.5利用了bailong360修改过IAT表的cmd.exe,这个修改过的cmd在启动时会自动加载init.dll,然后init.dll再hook掉setenv函数并加载文件夹下的image.dll
每当设置变量使hook回调的时候,init.dll就会传递参数来调用image.dll的相关功能
通过这个方法注入dll不会报毒
1

评分人数

TOP

没想到今年image更新这么快,直接3.5了。

TOP

回复 6# CrLf


    其实image内部的实现是相对独立的
不管是用什么方法,只要能调用image函数就行
之所以没写独立的exe接口是因为这样效率和注入dll相比差太多了

TOP

回复 8# happy886rr


    最近学习压力越来越大了,以后可能有很长的一段时间不会再更新
所以趁现在还有时间就先把一些重要的功能都先写出来了

TOP

返回列表