├── README.md ├── src ├── Fake_SCR.py ├── REmainv3.py ├── remain.py └── requirements.txt └── 外部一言格式说明.md /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![ToolBox_logo](https://github.com/user-attachments/assets/98db71e1-14e3-420c-9617-896179bed8d7) 3 | 4 | --- 5 | 6 | [![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/ZiHaoSaMa66/OsEasy-ToolBox?label=%E6%9C%80%E6%96%B0%E7%89%88&style=for-the-badge&include_prereleases&color=pink)](https://github.com/ZiHaoSaMa66/OsEasy-ToolBox/releases) 7 | [![GitHub release](https://img.shields.io/github/release/ZiHaoSaMa66/OsEasy-ToolBox.svg?color=green&style=for-the-badge&label=%E7%A8%B3%E5%AE%9A%E7%89%88)](https://github.com/ZiHaoSaMa66/OsEasy-ToolBox/releases/latest) 8 | ![GitHub all releases download](https://img.shields.io/github/downloads/ZiHaoSaMa66/OsEasy-ToolBox/total?style=for-the-badge&label=%E6%80%BB%E4%B8%8B%E8%BD%BD%E9%87%8F&color=orange) 9 | ![GitHub Repo stars](https://img.shields.io/github/stars/ZiHaoSaMa66/OsEasy-ToolBox?style=for-the-badge&color=yellow) 10 | 11 | > [!IMPORTANT] 12 | > **工具箱需要以管理员权限运行!** 13 | > 适用于``V10.8.2.4411``桌面云环境的噢易学生端 14 | > 如果系统为 **windows 7** 请使用[轻量版工具箱! (已暂时摆烂停更)](https://github.com/ZiHaoSaMa66/OsEasy-ToolBox-Lite) 15 | 16 | 17 | ## 免责声明 18 | 19 | 本项目仅供**教育研究**和**技术学习**目的,旨在学习研究软件运行机制和原理而生,禁止用于任何非法用途。 20 | 本软件包含可能影响系统稳定的实验性代码,操作不当可能导致: 21 | 系统数据丢失或损坏、应用程序异常崩溃等,使用者需自行承担所有操作风险,使用前应做好完整系统备份。 22 | 23 | 开发者/贡献者不对以下情况负责: 24 | - 因使用本项目造成的直接/间接损失 25 | - 违反用户所在机构相关规定导致的后果 26 | - 因软件使用导致的任何法律纠纷 27 | 28 | **继续使用即表示您已充分阅读、理解并同意承担所有相关风险及法律责任** 29 | 30 | ## 🚀 快速开始 31 | 32 | > [!IMPORTANT] 33 | > 在`V10.9.0.4881`的学生端使用时 34 | > 遇到脚本提示拒绝访问则需要**停止学生端根服务** 35 | > 如果你发现学生端没有自动重启可以手动重启服务 36 | > ***不保证功能在其他版本的噢易学生端中均可用*** 37 | 38 | ### **[点我看工具箱功能演示视频! (BiliBili)](https://www.bilibili.com/video/BV12ZgeetEWr)** 39 | 40 | > 简单说下我的使用过程 41 | > 具体如何使用看实际情况 仅供参考 42 | 43 | 44 | 1. **解除学生端自带的键鼠锁定 断网锁定 远程控制** 45 | 46 | `其他管理 -> 删除键盘锁驱动&控屏锁定程序&黑屏安静 -> 三者一起删除` 47 | 等待删除完毕后 可以按任意按键跳过注销脚本的等待 48 | 待注销完毕后回到登录回来继续步骤 49 | * 有人说学生端不再会自启动 如果要恢复学生端 50 | 长按 `进程管理 -> 开关学生端根服务` 51 | 如果机器太卡可自己开 cmd 运行 `sc start mmpc` 52 | 53 | 54 | 2. **解锁网络限制** 55 | 56 | `其他管理 -> 解除网络限制锁` 57 | 或者 `DLL工具 -> 执行:关闭网络管控` 58 | 如果老师平时不锁网络可以跳过这一步 59 | 如果不需要广播管理等功能可以直接跳过下面的步骤 60 | 直接 `进程管理 -> 挂起学生端主进程` 挂起进程后直接开玩 ( 61 | 62 | 63 | 3. **配置外置屏幕广播** 64 | 65 | 点击任务栏处的学生端 在弹出的菜单中点击设置 查看其教师机IP地址 66 | `广播命令 -> 输入教师机IP` 把看到的IP抄进去 67 | 随后按 `由教师机IP生成广播命令` 68 | 接着打开`广播管理`的快捷键相关选项 69 | 70 | **可能会出现的问题** 71 | 工具箱获取到的本机IP地址不正确 72 | 你需要使用`读取已拦截的广播命令`自行修改其`#local#`的值为本机IP 73 | 接着粘贴到上一个输入框中 随后 `手动更新广播命令` 74 | 75 | 76 | 4. **最后** 77 | 78 | `进程管理 -> 挂起学生端主进程` 79 | 至此完成全部功能的配置 80 | 81 | 82 | 83 | > 可能某些功能上手用的时候会有点抽象 84 | > (理解理解.jpg) 85 | 86 | 想要定制自己的的一言? 87 | 88 | [点我查看外部一言格式说明!](https://github.com/ZiHaoSaMa66/OsEasy-ToolBox/blob/main/外部一言格式说明.md) 89 | 90 | ---- 91 | 92 | ### ✨ 上机实战效果截图 93 | 94 | **效果图中老师均使用了全屏广播** 95 | 通过工具箱的拦截广播命令实现的 96 | **无视全屏广播**手动打开窗口广播 97 | 98 |
99 | 点击展开查看截图 100 | 101 | ![批注 2024-05-30 172101](https://github.com/ZiHaoSaMa66/OsEasy-ToolBox/assets/134737096/bd62df84-db76-4c0e-a591-c24ea8fdbab2) 102 | 103 | ![批注 2024-06-13 171855](https://github.com/ZiHaoSaMa66/OsEasy-ToolBox/assets/134737096/7845f270-824f-4399-92f9-2ff6b7e2f3d6) 104 | 105 | > 很早就想截了 但是一拖再拖( 106 | 107 |
108 | 109 | ---- 110 | 111 | ### 👀 工具箱界面截图 112 |
113 | 点击展开查看截图 114 | 115 | ![1](https://github.com/user-attachments/assets/d9b8b8bf-9a82-4ca7-b0a7-822d230b4910) 116 | 117 | ![2](https://github.com/user-attachments/assets/9b55ea88-4752-4e13-8be2-3ee4698dbcd0) 118 | 119 | ![3](https://github.com/user-attachments/assets/0c5d8c07-8538-45ee-8d84-c302ce4e8634) 120 | 121 | ![4](https://github.com/ZiHaoSaMa66/OsEasy-ToolBox/assets/134737096/3b011ff9-1808-4a26-81e2-89d72bccf383) 122 | 123 | ![5](https://github.com/user-attachments/assets/3aa601da-56d1-4a5f-9a63-642722c1cb7f) 124 | 125 | 126 |
127 | 128 | ---- 129 | 130 | ### 🌈 最后的最后.. 131 | 如果你喜欢我的破工具箱可以点个⭐Star⭐ 132 | 感谢有你们的Star鼓励和支持💖 133 | 134 | 有问题&发现Bug&提供建议可以提提issue 135 | 同时如果你有兴趣也可以开开PR 136 | 愿我们的电脑课都不再无聊~🥳 137 | -------------------------------------------------------------------------------- /src/Fake_SCR.py: -------------------------------------------------------------------------------- 1 | import sys 2 | # 假ScreenRender.exe 3 | 4 | MLsavepath = "C:\\Users\\Administrator\\prod\\SCCMD.txt" 5 | 6 | # if len(sys.argv) >=4: 7 | # print("? ERR > len(sys.argv) >=4:") 8 | # sys.exit(1) 9 | 10 | # get = sys.argv[1] 11 | emp = [] 12 | for i in sys.argv: 13 | # print("debug > ",i) 14 | emp.append(i) 15 | 16 | # print("emp > ",emp) 17 | # "C:\Program Files (x86)\Os-Easy\os-easy multicast teaching system\ScreenRender.exe" {#decoderName#:#h264#,#fullscreen#:0,#local#:#172.18.36.132#,#port#:7778,#remote#:#229.1.36.200#,#teacher_ip#:0,#verityPort#:7788} 18 | # get = str(get) 19 | 20 | 21 | for data_ in emp: 22 | data = str(data_) 23 | repcmd = data.replace("#fullscreen#:1","#fullscreen#:0").replace(" ","") 24 | 25 | 26 | fm = open(MLsavepath,"w") 27 | fm.write(str(repcmd)) 28 | fm.close() 29 | # print(f"\n\nGET_CMD >>>{repcmd}\n\n") 30 | print("拦截命令成功 你可以暴力脱离控制") 31 | print("并使用广播管理页的功能了") 32 | -------------------------------------------------------------------------------- /src/REmainv3.py: -------------------------------------------------------------------------------- 1 | from remain import * 2 | 3 | run_upto_admin() 4 | 5 | 6 | fstst = ToolBoxCfg.first_launch_check() 7 | if fstst == True: 8 | usecmd_runcmd( 9 | 'rename "C:\\Program Files\\Autodesk\\Autodesk Sync\\AdSyncNamespace.dll" "AdSyncNamespace.dll.bak"' 10 | ) 11 | # fixed pyqt bind to autodesk360 dll 12 | 13 | import flet as ft 14 | 15 | # 0.18.0 16 | 17 | import random 18 | import ctypes 19 | from ctypes import wintypes 20 | 21 | from pynput import keyboard 22 | 23 | 24 | fontpath = "C:\\Windows\\Fonts\\simhei.ttf" 25 | 26 | 27 | class Ui: 28 | 29 | def __init__(self) -> None: 30 | 31 | self.ver = "OsEasy-ToolBox v1.7 RC2.1" 32 | 33 | self.runwindows_lis = keyboard.Listener(on_press=self.run_windowskjj_onpress) 34 | 35 | # 构造监听器对象listener 36 | self.JieTu_listener = keyboard.Listener(on_press=self.JT_on_press) 37 | 38 | self.RunFullSC_listener = keyboard.Listener(on_press=self.FullSC_on_press) 39 | 40 | self.KillSCR_listener = keyboard.Listener(on_press=self.SCR_on_press) 41 | 42 | self.runwindows_press_alt = False 43 | self.runwindows_press_u = False 44 | 45 | self.SCR_Press_K = False 46 | self.SCR_Press_Alt = False 47 | 48 | self.Press_X = False 49 | self.Press_Alt = False 50 | 51 | self.guaqi_runstatus = False # 挂起进程状态 52 | self.bgtmd = 0.6 # 初始化 背景图片透明度值 53 | self.defult_yy = True # 默认一言库 54 | self.font_loadtime = 1 55 | 56 | self.NowSelIndex = "0" # 防止无变量的初始化 57 | 58 | self.yiyanshowtext = ft.Text("", size=16) 59 | self.yiyanshowtext2 = ft.Text("", size=16) 60 | 61 | self.loaded_bg = False 62 | 63 | pass 64 | 65 | def FullSC_on_press(self, key): 66 | """用于快捷键运行全屏控制窗口""" 67 | 68 | if str(key) == "<70>": 69 | if self.KillSCR_swc.value == False: 70 | self.show_snakemessage( 71 | "警告! 未开启快捷键杀广播进程\n尝试运行的操作已拦截...." 72 | ) 73 | else: 74 | status = get_yuancheng_cmd() 75 | if status == None: 76 | self.show_snakemessage("未拦截到控制命令参数") 77 | else: 78 | cmd = status.replace("#fullscreen#:0", "#fullscreen#:1") 79 | builded = build_run_srcmd(cmd) 80 | runcmd(builded) 81 | # Fix 黑框 82 | 83 | def dic_RunFullSC(self, *e): 84 | """按钮点击直接运行全屏广播指令""" 85 | status = get_yuancheng_cmd() 86 | 87 | if self.KillSCR_swc.value == True: 88 | 89 | if status == None: 90 | self.show_snakemessage("未拦截到控制命令参数") 91 | else: 92 | cmd = status.replace("#fullscreen#:0", "#fullscreen#:1") 93 | builded = build_run_srcmd(cmd) 94 | print("DEBUG with build cmd", builded) 95 | runcmd(builded) 96 | 97 | else: 98 | self.show_snakemessage( 99 | "警告! 未开启快捷键杀广播进程\n尝试运行的操作已拦截...." 100 | ) 101 | 102 | def SCR_on_press(self, key): 103 | """用于检测快捷键杀SCR_Y进程""" 104 | 105 | if key == keyboard.KeyCode(char="K") or key == keyboard.KeyCode(char="k"): 106 | self.SCR_Press_K = True 107 | if ( 108 | key == keyboard.Key.alt 109 | or key == keyboard.Key.alt_l 110 | or key == keyboard.Key.alt_r 111 | ): 112 | self.SCR_Press_Alt = True 113 | 114 | if self.SCR_Press_Alt and self.SCR_Press_K: 115 | self.SCR_Press_Alt = self.SCR_Press_K = False # 重置按键按下状态 116 | # get_scshot() 117 | runcmd("taskkill /f /t /im ScreenRender_Y.exe") 118 | runcmd("taskkill /f /t /im ScreenRender.exe") 119 | 120 | def dic_KillSCR(self, *e): 121 | """点击按钮直接杀屏幕广播进程""" 122 | runcmd("taskkill /f /t /im ScreenRender_Y.exe") 123 | runcmd("taskkill /f /t /im ScreenRender.exe") 124 | 125 | def JT_on_press(self, key): 126 | """当监听器检测到键盘按下""" 127 | 128 | if key == keyboard.KeyCode(char="x") or key == keyboard.KeyCode(char="X"): 129 | self.Press_X = True 130 | if ( 131 | key == keyboard.Key.alt 132 | or key == keyboard.Key.alt_l 133 | or key == keyboard.Key.alt_r 134 | ): 135 | self.Press_Alt = True 136 | 137 | if self.Press_Alt and self.Press_X: 138 | self.Press_Alt = self.Press_X = False # 重置按键按下状态 139 | get_scshot() 140 | 141 | pass 142 | 143 | def run_windowskjj_onpress(self, key): 144 | """快捷键触发运行窗口广播""" 145 | if key == keyboard.KeyCode(char="U") or key == keyboard.KeyCode(char="u"): 146 | self.runwindows_press_u = True 147 | if ( 148 | key == keyboard.Key.alt 149 | or key == keyboard.Key.alt_l 150 | or key == keyboard.Key.alt_r 151 | ): 152 | self.runwindows_press_alt = True 153 | 154 | if self.runwindows_press_alt and self.runwindows_press_u: 155 | self.runwindows_press_u = self.runwindows_press_alt = False 156 | 157 | self.Get_yccmd_loj("e") 158 | 159 | pass 160 | 161 | def theme_changed(self, *e): 162 | 163 | self.page.theme_mode = ( 164 | ft.ThemeMode.DARK 165 | if self.page.theme_mode == ft.ThemeMode.LIGHT 166 | else ft.ThemeMode.LIGHT 167 | ) 168 | self.ztqhb.label = ( 169 | "亮色主题" if self.page.theme_mode == ft.ThemeMode.LIGHT else "暗色主题" 170 | ) 171 | self.page.update() 172 | 173 | def try_get_history_path(self): 174 | """尝试获取历史路径""" 175 | if fstst != True: 176 | bgPath = ToolBoxCfg.get_style_path("bgPath") 177 | if bgPath: 178 | self.bgpath = bgPath 179 | self.bgtmdb.disabled = False 180 | self.loaded_bg = True 181 | self.reflashbg() 182 | 183 | yiyanPath = ToolBoxCfg.get_style_path("yiyanPath") 184 | if yiyanPath: 185 | self.yiyanfpath = yiyanPath 186 | self.loadyiyan() 187 | 188 | fontPath = ToolBoxCfg.get_style_path("fontPath") 189 | if fontPath: 190 | self.zdy_fontpath = fontPath 191 | self.setup_zidingyi_font() 192 | 193 | def enable_usb(self): 194 | pass 195 | 196 | def close_askdel_dlg(self, xueze): 197 | self.unlock_func_askdlg.open = False 198 | self.page.update() 199 | if xueze == None: 200 | self.show_snakemessage("取消解锁了") 201 | else: 202 | delLockExeAndLogout(xueze) 203 | 204 | def open_askdel_dlg(self, *e): 205 | self.page.dialog = self.unlock_func_askdlg 206 | self.unlock_func_askdlg.open = True 207 | self.page.update() 208 | 209 | def close_col_readme_dlg(self): 210 | self.col_readme_dlg.open = False 211 | self.show_snakemessage("Have Fun") 212 | self.page.update() 213 | 214 | def open_col_readme_dlg(self, *e): 215 | self.page.dialog = self.col_readme_dlg 216 | self.col_readme_dlg.open = True 217 | self.page.update() 218 | 219 | def main(self, bruh: ft.Page): 220 | self.page = bruh 221 | self.page.title = self.ver 222 | self.page.fonts = {"ht": fontpath} 223 | self.page.theme = ft.Theme(font_family="ht") 224 | self.page.update() 225 | 226 | self.page.window_height = 635 227 | self.page.window_width = 450 228 | 229 | self.page.window_max_height = 2000 230 | self.page.window_max_width = 455 231 | 232 | self.page.window_min_height = 620 233 | self.page.window_min_width = 449 234 | 235 | self.page.update() 236 | 237 | self.unlock_func_askdlg = ft.AlertDialog( 238 | modal=True, 239 | title=ft.Text("解锁选项"), 240 | content=ft.Text( 241 | "选择适合你的选项\n三者一起: 删除黑屏安静+解除键盘锁+删除控屏锁定程序 (需要注销)\n仅控屏: 仅删除控屏锁定程序" 242 | ), 243 | actions=[ 244 | ft.TextButton( 245 | "三者一起", on_click=lambda _: self.close_askdel_dlg(xueze=True) 246 | ), 247 | ft.TextButton( 248 | "仅控屏锁定程序", 249 | on_click=lambda _: self.close_askdel_dlg(xueze=False), 250 | ), 251 | ft.TextButton( 252 | "取消", on_click=lambda _: self.close_askdel_dlg(xueze=None) 253 | ), 254 | ], 255 | actions_alignment=ft.MainAxisAlignment.END, 256 | # on_dismiss=lambda _:self.close_askdel_dlg(xueze=None), 257 | ) 258 | 259 | self.col_readme_dlg = ft.AlertDialog( 260 | modal=True, 261 | title=ft.Text("控屏管理页使用说明"), 262 | content=ft.Text( 263 | "在使用前请先使用解锁键盘锁&删除控制锁定软件功能\n点击替换拦截程序后再恢复控屏软件\n等待老师控制屏幕后即完成拦截远程命令\n完成替换后即可重新删除控屏软件\n此时当老师处于控制状态时你可以主动运行命令弹出窗口化共享屏幕\n实现自由的同时不影响听课!!\n当老师来时你可以使用快捷键启动全屏参数的控制\n等待老师走后再用快捷键清理进程" 264 | ), 265 | actions=[ 266 | ft.TextButton("晓得了", on_click=lambda _: self.close_col_readme_dlg()), 267 | ], 268 | actions_alignment=ft.MainAxisAlignment.END, 269 | on_dismiss=lambda _: self.close_col_readme_dlg(), 270 | ) 271 | 272 | self.pick_files_dialog = ft.FilePicker(on_result=self.pick_files_result) 273 | 274 | self.yiyan_pick_files_dialog = ft.FilePicker( 275 | on_result=self.yiyan_pick_files_result 276 | ) 277 | 278 | self.font_pick_files_dialog = ft.FilePicker( 279 | on_result=self.font_pick_files_result 280 | ) 281 | # selected_files = ft.Text() 282 | 283 | self.bgfilepick = ft.ElevatedButton( 284 | "切换背景图片", 285 | icon=ft.icons.UPLOAD_FILE, 286 | on_click=lambda _: self.pick_files_dialog.pick_files( 287 | allow_multiple=False, file_type="IMAGE" 288 | ), 289 | ) 290 | # 切换背景图片按钮 291 | 292 | self.ztqhb = ft.Switch( 293 | label="亮色主题", on_change=self.theme_changed, value=True 294 | ) 295 | # 切换主题开关 296 | self.bgtmd_text = ft.Text("滑动以调整背景图片不透明度") 297 | 298 | self.bgtmdb = ft.Slider( 299 | min=0.0, 300 | max=1.0, 301 | divisions=0.1, 302 | value=0.6, 303 | on_change_end=self.change_bg_btmd, 304 | disabled=True, 305 | ) 306 | # 背景不透明度滑条 307 | self.yiyanbtn = ft.ElevatedButton( 308 | "加载外部一言文件", 309 | icon=ft.icons.UPLOAD_SHARP, 310 | on_click=lambda _: self.yiyan_pick_files_dialog.pick_files( 311 | allow_multiple=False, allowed_extensions=["txt"] 312 | ), 313 | ) 314 | # 一言加载 315 | 316 | self.zitibtn = ft.ElevatedButton( 317 | "更换显示字体", 318 | icon=ft.icons.UPLOAD_SHARP, 319 | on_click=lambda _: self.font_pick_files_dialog.pick_files( 320 | allow_multiple=False, allowed_extensions=["ttf"] 321 | ), 322 | ) 323 | # 自定义字体切换 324 | 325 | self.remove_rem = ft.ElevatedButton( 326 | "清除历史路径记忆", icon=ft.icons.DELETE_OUTLINE, on_click=del_historyrem 327 | ) 328 | 329 | self.list_all_pickdialog = [ 330 | self.pick_files_dialog, 331 | self.yiyan_pick_files_dialog, 332 | self.font_pick_files_dialog, 333 | ] 334 | # 选择文件对话框 需要在添加完组件后进行添加 不然无法进行选择文件 335 | 336 | self.guaqi_sw = ft.Switch( 337 | label="挂起学生端", active_color="pink", on_change=self.guaqi_chufa 338 | ) 339 | 340 | self.mmpc_sw = ft.FilledTonalButton( 341 | text="长按开&关学生端根服务", 342 | icon=ft.icons.BACK_HAND_OUTLINED, 343 | on_long_press=self.MMPC_shutdown_start_chufa, 344 | on_hover=self.only_update_MMPC_status, 345 | ) 346 | self.mmpc_Stext = ft.TextField( 347 | label="根服务状态", 348 | value="未知 (点我更新状态)", 349 | read_only=True, 350 | on_focus=self.only_update_MMPC_status, 351 | text_align=ft.TextAlign.CENTER, 352 | ) 353 | # self.stud_pid = ft.TextField(label="学生端PID", disabled=True, value="未知") 354 | 355 | self.FastGetSC = ft.Switch( 356 | label="Alt+X 快捷键屏幕截图", on_change=self.HotKey_screenshot 357 | ) 358 | 359 | # self.yiyanshowtext2,ft.Divider(), 360 | # self.yiyanshowtext2 = self.yiyanshowtext 361 | 362 | self.funcTab_Stuff = ft.Column( 363 | controls=[ 364 | self.yiyanshowtext, 365 | ft.Divider(height=1), 366 | self.mmpc_Stext, 367 | self.mmpc_sw, 368 | ft.FilledTonalButton( 369 | text="长按重启学生端", 370 | icon=ft.icons.RESTORE, 371 | on_long_press=handToStartStudent, 372 | ), 373 | ft.FilledTonalButton( 374 | text="重新获取学生端路径", 375 | icon=ft.icons.REFRESH, 376 | on_click=self.reflashStudentPath, 377 | ), 378 | ft.FilledTonalButton( 379 | text="注册粘滞键替换", 380 | icon=ft.icons.FILE_COPY_ROUNDED, 381 | on_click=selfunc_g1, 382 | ), 383 | ft.Switch( 384 | label="外部cmd守护进程", 385 | active_color="green", 386 | on_change=killerCmdProtect, 387 | ), 388 | self.guaqi_sw, 389 | ft.FilledTonalButton( 390 | text="打开噢易自带工具", 391 | icon=ft.icons.OPEN_IN_NEW, 392 | on_click=startOsEasySelfToolBox, 393 | ), 394 | ] 395 | ) 396 | 397 | self.func_SecondTab_Stuff = ft.Column( 398 | controls=[ 399 | self.yiyanshowtext, 400 | ft.Divider(height=1), 401 | ft.FilledTonalButton( 402 | text="长按以删除脚本文件", 403 | icon=ft.icons.CLEANING_SERVICES_OUTLINED, 404 | on_long_press=delSummonCmdFile, 405 | ), 406 | ft.FilledTonalButton( 407 | text="删除键盘锁驱动&控屏锁定程序", 408 | icon=ft.icons.KEYBOARD_SHARP, 409 | on_click=self.open_askdel_dlg, 410 | ), 411 | ft.FilledTonalButton( 412 | text="长按恢复所有备份文件", 413 | icon=ft.icons.RESTORE, 414 | on_long_press=lambda _: restoneKeyDll(), 415 | ), 416 | ft.FilledTonalButton( 417 | text="长按以恢复黑屏安静程序", 418 | icon=ft.icons.ACCOUNT_BOX, 419 | on_long_press=lambda _: restoneBlackSlt(), 420 | ), 421 | ft.FilledTonalButton( 422 | text="长按以仅恢复控屏锁定程序", 423 | icon=ft.icons.SCREEN_SHARE_SHARP, 424 | on_long_press=lambda _: restoneMutClient(), 425 | ), 426 | ft.FilledTonalButton( 427 | text="停止网络管控服务(不可逆)", 428 | icon=ft.icons.WIFI_PASSWORD_SHARP, 429 | on_click=lambda _: self.forunlocknettips(), 430 | ), 431 | ft.FilledTonalButton( 432 | text="[无法正常工作] 关闭USB管控服务", 433 | icon=ft.icons.USB_SHARP, 434 | on_click=lambda _: self.usb_unlock_tips(), 435 | ), 436 | self.FastGetSC, 437 | ] 438 | ) 439 | 440 | self.teachIp_input = ft.TextField(label="输入教师机IP地址") 441 | # 自动生成命令 442 | self.auto_gennerate_cmd = ft.FilledTonalButton( 443 | text="由教师机IP生成远程命令", 444 | icon=ft.icons.DRAW, 445 | on_click=lambda _: generate_yc_cmd_and_save(self.teachIp_input.value), 446 | ) 447 | 448 | self.conl_save_ycCmd_input = ft.TextField(label="键入完整的远程广播命令") 449 | self.conl_ycCmd_update_with_replace_ip = ft.FilledTonalButton( 450 | "自动替换本地IP并更新命令", 451 | on_click=lambda _: handin_save_yc_cmd( 452 | self.conl_save_ycCmd_input.value, True 453 | ), 454 | icon=ft.icons.DRAW, 455 | ) 456 | 457 | self.conl_ycCmd_update = ft.FilledTonalButton( 458 | "手动更新完整远程广播命令", 459 | on_click=lambda _: handin_save_yc_cmd( 460 | self.conl_save_ycCmd_input.value, False 461 | ), 462 | icon=ft.icons.MODE_EDIT_SHARP, 463 | ) 464 | 465 | self.conl_from_log_get_cmd = ft.FilledTonalButton( 466 | text="从日志文件获取远程命令", 467 | icon=ft.icons.BOOK, 468 | on_click=lambda _: from_scr_log_cmd_get_yccmd(), 469 | ) 470 | 471 | self.conl_getyccmd_btn = ft.FilledTonalButton( 472 | text="读取已拦截的广播命令", 473 | icon=ft.icons.BOOK, 474 | on_click=self.dev_read_lj_cmd_loj, 475 | ) 476 | 477 | self.col_readme_dig = ft.FilledButton( 478 | "点我查看此页面的使用说明", on_click=self.open_col_readme_dlg 479 | ) 480 | 481 | self.RunFullSC_btn = ft.FilledTonalButton( 482 | "长按运行全屏广播命令", 483 | on_long_press=self.dic_RunFullSC, 484 | icon=ft.icons.FULLSCREEN, 485 | ) 486 | 487 | self.restone_scr = ft.FilledTonalButton( 488 | text="恢复原有屏幕广播程序", 489 | on_click=self.restone_SCR_loj, 490 | icon=ft.icons.RESTORE_PAGE, 491 | ) 492 | self.tihuan_scr = ft.FilledTonalButton( 493 | text="替换拦截命令程序", 494 | on_click=self.replace_SCR_loj, 495 | icon=ft.icons.FIND_REPLACE, 496 | ) 497 | 498 | self.RunFullSC_swc = ft.Switch( 499 | label="Ctrl+Alt+F 以全屏运行广播命令", 500 | on_change=self.HotKey_RunFullSCR, 501 | active_color="pink", 502 | ) 503 | 504 | self.KillSCR_btn = ft.FilledTonalButton( 505 | "手动杀屏幕广播进程", 506 | icon=ft.icons.BACK_HAND_OUTLINED, 507 | on_click=self.dic_KillSCR, 508 | ) 509 | 510 | self.KillSCR_swc = ft.Switch( 511 | label="Alt+K 杀屏幕广播进程", 512 | on_change=self.HotKey_KillSCR, 513 | active_color="pink", 514 | ) 515 | 516 | self.runwindows_swc = ft.Switch( 517 | label="Alt+U 运行窗口屏幕广播", 518 | on_change=self.hotkey_runwindows, 519 | active_color="pink", 520 | ) 521 | 522 | self.try_read_sharecmd = ft.FilledTonalButton( 523 | text="运行窗口化广播命令", 524 | on_click=self.Get_yccmd_loj, 525 | icon=ft.icons.WINDOW_SHARP, 526 | ) 527 | 528 | self.waiguanTab_Stuff = ft.Column( 529 | controls=[ 530 | self.yiyanshowtext, 531 | ft.Divider(height=1), 532 | self.ztqhb, 533 | self.remove_rem, 534 | self.zitibtn, 535 | self.bgfilepick, 536 | self.bgtmd_text, 537 | self.bgtmdb, 538 | self.yiyanbtn, 539 | ] 540 | ) 541 | 542 | self.MyRail = ft.NavigationRail( 543 | selected_index=0, 544 | label_type="ALL", 545 | min_width=30, 546 | min_extended_width=30, 547 | group_alignment=-0.8, 548 | expand=False, 549 | destinations=[ 550 | ft.NavigationRailDestination( 551 | icon_content=ft.Icon(ft.icons.AUTO_FIX_HIGH_OUTLINED), 552 | selected_icon_content=ft.Icon(ft.icons.AUTO_FIX_HIGH), 553 | label="进程管理", 554 | ), 555 | ft.NavigationRailDestination( 556 | icon=ft.icons.INTEGRATION_INSTRUCTIONS_OUTLINED, 557 | selected_icon_content=ft.Icon(ft.icons.INTEGRATION_INSTRUCTIONS), 558 | label_content=ft.Text("其他管理"), 559 | ), 560 | ft.NavigationRailDestination( 561 | icon=ft.icons.SCREEN_SHARE_OUTLINED, 562 | selected_icon_content=ft.Icon(ft.icons.SCREEN_SHARE_SHARP), 563 | label_content=ft.Text("广播管理"), 564 | ), 565 | ft.NavigationRailDestination( 566 | icon=ft.icons.VPN_KEY_OUTLINED, 567 | selected_icon_content=ft.Icon(ft.icons.VPN_KEY), 568 | label="广播命令", 569 | ), 570 | ft.NavigationRailDestination( 571 | icon=ft.icons.KEYBOARD_OPTION_KEY_OUTLINED, 572 | selected_icon_content=ft.Icon(ft.icons.KEYBOARD_OPTION_KEY), 573 | label="DLL工具", 574 | ), 575 | ft.NavigationRailDestination( 576 | icon=ft.icons.STYLE_OUTLINED, 577 | selected_icon_content=ft.Icon(ft.icons.STYLE), 578 | label_content=ft.Text("外观"), 579 | ), 580 | ft.NavigationRailDestination( 581 | icon=ft.icons.FAVORITE_BORDER_OUTLINED, 582 | selected_icon_content=ft.Icon(ft.icons.FAVORITE, color="red"), 583 | label="关于", 584 | ), 585 | ], 586 | on_change=lambda e: self.selPages_Helper(e.control.selected_index), 587 | ) 588 | # on_change=lambda e: print("Selected destination:", e.control.selected_index) 589 | 590 | # self.base_mix = ft.Row(self.Rail , ft.VerticalDivider(width=1)) 591 | self.pickrandomyiyan() 592 | 593 | self.SWC_MainPages_0() 594 | 595 | self.added_pickdialog() 596 | 597 | self.try_get_history_path() 598 | 599 | self.reflashStudentPath() 600 | 601 | pass_ui_class(self) 602 | 603 | def reflashStudentPath(self, *e): 604 | global oseasypath 605 | """重新获取学生端路径\n 606 | 设计上的一点问题.. 干活的函数没办法直接弹窗\n 607 | 只能用个写在UI类里多余的函数来做""" 608 | 609 | # status, studentName = TryGetStudentPath() 610 | _ = tryGuessStudentClientVer() 611 | # 没啥用只是顺带需要更新一下学生端版本 612 | 613 | if ToolBoxCfg.oseasypath_have_been_modified != False: 614 | self.show_snakemessage( 615 | f"更新学生端路径成功\n{ToolBoxCfg.oseasypath}\n学生端进程名:{ToolBoxCfg.studentExeName}" 616 | ) 617 | else: 618 | self.show_snakemessage(f"更新路径失败\n也许是学生端未运行??") 619 | pass 620 | 621 | def HotKey_screenshot(self, *e): 622 | """快捷键截图开关触发函数""" 623 | 624 | if self.FastGetSC.value == True: 625 | 626 | self.JieTu_listener.run() 627 | 628 | elif self.FastGetSC.value == False: 629 | 630 | self.JieTu_listener.stop() 631 | pass 632 | 633 | def HotKey_RunFullSCR(self, *e): 634 | 635 | if self.RunFullSC_swc.value == True: 636 | 637 | self.RunFullSC_listener.run() 638 | elif self.RunFullSC_swc.value == False: 639 | 640 | self.RunFullSC_listener.stop() 641 | pass 642 | 643 | def HotKey_KillSCR(self, *e): 644 | """快捷键截图开关触发函数""" 645 | # print("DEBUG e obj > ",e) 646 | if self.KillSCR_swc.value == True: 647 | # print("DEBUG 启动了屏幕截图监听") 648 | 649 | self.KillSCR_listener.run() 650 | 651 | elif self.KillSCR_swc.value == False: 652 | # print("DEBUG 停止了屏幕截图监听") 653 | self.KillSCR_listener.stop() 654 | pass 655 | 656 | def hotkey_runwindows(self, *e): 657 | if self.runwindows_swc.value == True: 658 | 659 | self.runwindows_lis.run() 660 | 661 | elif self.runwindows_swc.value == False: 662 | self.runwindows_lis.stop() 663 | pass 664 | 665 | def selPages_Helper(self, index): 666 | """帮助切换页面选择器""" 667 | self.NowSelIndex = str(index) 668 | self.pickrandomyiyan() 669 | 670 | exc = "ToolBox.SWC_MainPages_" + str(index) + "()" 671 | eval(exc) 672 | 673 | def apply_bg_to_ui(self, needLoad_Stuff_list: list): 674 | 675 | if self.loaded_bg == True: 676 | bgb = ft.Stack(controls=[self.col_imgbg, needLoad_Stuff_list]) 677 | nedadd = ft.Row( 678 | [self.MyRail, ft.VerticalDivider(width=0), bgb], 679 | height=self.page.window_height, 680 | width=self.page.window_width, 681 | ) 682 | self.page.clean() 683 | self.page.update() 684 | self.page.add(nedadd) 685 | self.page.update() 686 | else: 687 | nedadd = ft.Row( 688 | [self.MyRail, ft.VerticalDivider(width=1), needLoad_Stuff_list], 689 | height=self.page.window_height, 690 | width=self.page.window_width, 691 | ) 692 | self.page.clean() 693 | self.page.update() 694 | self.page.add(nedadd) 695 | self.page.update() 696 | 697 | def SWC_MainPages_0(self): 698 | """切换至页面0_进程管理页面""" 699 | 700 | self.mmpc_Stext.value = "未知 (随时都可以点我更新状态)" 701 | 702 | self.apply_bg_to_ui(needLoad_Stuff_list=self.funcTab_Stuff) 703 | 704 | def SWC_MainPages_1(self): 705 | """切换至页面1_其他管理页面""" 706 | # print("Func Run SWC 1") 707 | 708 | self.apply_bg_to_ui(needLoad_Stuff_list=self.func_SecondTab_Stuff) 709 | 710 | def Get_yccmd_loj(self, *e): 711 | """获取远程控制命令的逻辑触发函数""" 712 | get = get_yuancheng_cmd() 713 | if get == None: 714 | self.show_snakemessage("未拦截到控制命令参数") 715 | else: 716 | bcmd = build_run_srcmd(YC_command=get) 717 | runcmd(bcmd) 718 | # fix 黑框 719 | pass 720 | 721 | def replace_SCR_loj(self, *e): 722 | """替换SCR程序为拦截程序的逻辑触发函数""" 723 | ser_status = check_MMPC_status() 724 | if ser_status == False: 725 | self.show_snakemessage("开始替换程序 请稍等...\n这大约需要6秒左右") 726 | status = replace_ScreenRender() 727 | if status == False: 728 | self.show_snakemessage( 729 | "替换拦截程序失败 未检测到可替换程序\n请确保ScreenRender_Helper.exe\n与工具箱处在同一目录" 730 | ) 731 | else: 732 | self.show_snakemessage("理论上已经成功替换拦截程序\n可自行检查替换结果") 733 | else: 734 | self.show_snakemessage("替换拦截程序失败\n请先手动关闭学生端根服务!") 735 | 736 | def restone_SCR_loj(self, *e): 737 | """恢复SCR程序的逻辑触发函数""" 738 | ser_status = check_MMPC_status() 739 | if ser_status == False: 740 | self.show_snakemessage("开始还原替换程序 请稍等...") 741 | status = restone_ScreenRender() 742 | if status == False: 743 | self.show_snakemessage( 744 | "尝试恢复拦截程序时失败\n未检测到被重命名的ScreenRender.exe" 745 | ) 746 | else: 747 | self.show_snakemessage("理论上已经成功恢复原有程序") 748 | else: 749 | self.show_snakemessage("还原拦截程序失败\n请先手动关闭学生端根服务!") 750 | 751 | def dev_read_lj_cmd_loj(self, *e): 752 | """读取已拦截的命令逻辑触发函数""" 753 | status = save_now_yccmd() 754 | if status == None: 755 | self.show_snakemessage("未拦截到控制命令参数") 756 | else: 757 | self.show_snakemessage("保存拦截命令成功") 758 | 759 | def update_replace_status(self, *e): 760 | """更新替换程序状态检查""" 761 | 762 | if check_tihuan_SCRY_status(): 763 | self.show_snakemessage("检测到目录下已有ScreenRender_Y.exe") 764 | self.replace_status.value = "已替换" 765 | else: 766 | self.show_snakemessage( 767 | "未检测到ScreenRender_Y.exe\n也许未执行替换或替换过程被打断" 768 | ) 769 | self.replace_status.value = "未替换" 770 | 771 | self.page.update() 772 | 773 | def SWC_MainPages_2(self): 774 | """切换至页面2_控屏管理界面""" 775 | 776 | self.replace_status = ft.TextField( 777 | label="替换程序状态", 778 | value="未知 (点我更新状态)", 779 | read_only=True, 780 | on_focus=self.update_replace_status, 781 | text_align=ft.TextAlign.CENTER, 782 | ) 783 | 784 | self.ConlTab_Stuff = ft.Column( 785 | [ 786 | self.yiyanshowtext, 787 | ft.Divider(height=1), 788 | self.col_readme_dig, 789 | self.replace_status, 790 | self.tihuan_scr, 791 | self.try_read_sharecmd, 792 | self.RunFullSC_btn, 793 | self.KillSCR_btn, 794 | self.restone_scr, 795 | self.runwindows_swc, 796 | self.KillSCR_swc, 797 | self.RunFullSC_swc, 798 | ] 799 | ) 800 | 801 | self.apply_bg_to_ui(needLoad_Stuff_list=self.ConlTab_Stuff) 802 | 803 | def SWC_MainPages_3(self): 804 | """切换至页面3_广播命令""" 805 | 806 | self.gbCommandStuff = ft.Column( 807 | controls=[ 808 | self.yiyanshowtext, 809 | ft.Divider(height=1), 810 | self.conl_save_ycCmd_input, 811 | self.conl_ycCmd_update, 812 | self.conl_ycCmd_update_with_replace_ip, 813 | self.teachIp_input, 814 | self.auto_gennerate_cmd, 815 | self.conl_from_log_get_cmd, 816 | self.conl_getyccmd_btn, 817 | ] 818 | ) 819 | 820 | self.apply_bg_to_ui(needLoad_Stuff_list=self.gbCommandStuff) 821 | 822 | pass 823 | 824 | def SWC_MainPages_5(self): 825 | """切换至页面5_外观调整界面""" 826 | 827 | self.apply_bg_to_ui(needLoad_Stuff_list=self.waiguanTab_Stuff) 828 | 829 | self.added_pickdialog() 830 | 831 | pass 832 | 833 | def SWC_MainPages_6(self): 834 | """切换至页面6_关于界面""" 835 | 836 | self.AboutTab_Stuff = ft.Column( 837 | controls=[ 838 | ft.Text("此工具箱在Github上发布", size=22), 839 | ft.Text("愿我们的电脑课都不再无聊~🥳", size=22), 840 | ft.ElevatedButton("点我打开工具箱Github页", on_click=opengithubres), 841 | ] 842 | ) 843 | 844 | self.apply_bg_to_ui(needLoad_Stuff_list=self.AboutTab_Stuff) 845 | 846 | def SWC_MainPages_4(self): 847 | """切换至页面4 dll 调试工具""" 848 | 849 | self.dll_usb_1 = ft.FilledTonalButton( 850 | text="执行:关闭USB管控", 851 | on_click=lambda _: run_easy_dll( 852 | "\\x64\\easyusbctrl.dll", 853 | "EasyUsb_StopWorking", 854 | ctypes.c_int, 855 | [], 856 | None, 857 | ), 858 | icon=ft.icons.USB, 859 | ) 860 | 861 | self.dll_usb_2 = ft.FilledTonalButton( 862 | text="执行:启动USB管控", 863 | on_click=lambda _: run_easy_dll( 864 | "\\x64\\easyusbctrl.dll", 865 | "EasyUsb_StartWorking", 866 | ctypes.c_int, 867 | [], 868 | None, 869 | ), 870 | icon=ft.icons.USB_OFF, 871 | ) 872 | 873 | self.dll_usb_3 = ft.FilledTonalButton( 874 | text="执行:查询USB管控状态", 875 | on_click=lambda _: run_easy_dll( 876 | "\\x64\\easyusbctrl.dll", 877 | "EasyUsb_IsWorking", 878 | ctypes.c_int, 879 | [ctypes.POINTER(wintypes.DWORD)], 880 | wintypes.DWORD(0), 881 | ), 882 | icon=ft.icons.CODE, 883 | ) 884 | 885 | self.dll_net_1 = ft.FilledTonalButton( 886 | text="执行:开启网络管控", 887 | on_click=lambda _: run_easy_dll( 888 | "\\x64\\OeNetlimit.dll", 889 | "DisableInternet", 890 | ctypes.c_int, 891 | [], 892 | None, 893 | ), 894 | icon=ft.icons.SIGNAL_WIFI_CONNECTED_NO_INTERNET_4, 895 | ) 896 | 897 | self.dll_net_2 = ft.FilledTonalButton( 898 | text="执行:关闭网络管控", 899 | on_click=lambda _: run_easy_dll( 900 | "\\x64\\OeNetlimit.dll", 901 | "EnableNet", 902 | ctypes.c_int, 903 | [], 904 | None, 905 | ), 906 | icon=ft.icons.SIGNAL_WIFI_4_BAR, 907 | ) 908 | 909 | # self.dll_test_case_5 = ft.FilledTonalButton( 910 | 911 | self.dllTab_Stuff = ft.Column( 912 | controls=[ 913 | self.yiyanshowtext, 914 | ft.Divider(height=1), 915 | self.dll_usb_1, 916 | self.dll_usb_2, 917 | self.dll_usb_3, 918 | self.dll_net_1, 919 | self.dll_net_2, 920 | ] 921 | ) 922 | 923 | self.apply_bg_to_ui(needLoad_Stuff_list=self.dllTab_Stuff) 924 | 925 | def added_pickdialog(self): 926 | """添加文件选择对话框""" 927 | for idlg in self.list_all_pickdialog: 928 | self.page.add(idlg) 929 | self.page.update() 930 | 931 | def reflashbg(self): 932 | """刷新背景""" 933 | 934 | ToolBoxCfg.set_style_path("bgPath", self.bgpath) 935 | 936 | self.loaded_bg = True 937 | self.col_imgbg = ft.Image( 938 | src=f"{self.bgpath}", 939 | height=self.page.window_height, 940 | width=self.page.window_width - 100, 941 | opacity=self.bgtmd, 942 | fit=ft.ImageFit.SCALE_DOWN, 943 | ) 944 | 945 | exc = "ToolBox.SWC_MainPages_" + self.NowSelIndex + "()" 946 | 947 | eval(exc) 948 | 949 | def guaqi_chufa(self, *e): 950 | """用于挂起进程开关的触发函数""" 951 | if self.guaqi_runstatus == False: 952 | self.page.window_visible = False 953 | self.page.update() 954 | status = guaqi_process(ToolBoxCfg.studentExeName) 955 | 956 | status_ = guaqi_process("MultiClient.exe") 957 | 958 | if status == True: 959 | self.guaqi_runstatus = True 960 | time.sleep(0.8) 961 | self.page.window_visible = True 962 | self.page.update() 963 | else: 964 | self.page.window_visible = True 965 | self.guaqi_sw.value = False 966 | self.page.update() 967 | self.show_snakemessage(status) 968 | else: 969 | status = huifu_process(ToolBoxCfg.studentExeName) 970 | status_ = huifu_process("MultiClient.exe") 971 | if status == True: 972 | self.guaqi_runstatus = False 973 | else: 974 | self.guaqi_sw.value = False 975 | self.page.update() 976 | self.show_snakemessage(status) 977 | 978 | def forunlocknettips(self, *e): 979 | self.show_snakemessage("解锁网络锁定中 请稍等") 980 | unlockedNet() 981 | self.show_snakemessage("执行完成 理论上网络已解锁") 982 | 983 | def usb_unlock_tips(self, *e): 984 | 985 | if not check_MMPC_status(): 986 | self.show_snakemessage( 987 | "尝试解锁USB... 请稍等 \n实验性功能 未进行实机测试 可能无效" 988 | ) 989 | 990 | usb_unlock() 991 | else: 992 | self.show_snakemessage("请先关闭学生端根服务") 993 | 994 | def pickrandomyiyan(self, *e): 995 | """挑选一个随机一言""" 996 | 997 | if self.defult_yy == False: 998 | # 如果已经加载了外部一言 999 | pickindex = random.randint(0, self.ex_fullindex - 1) 1000 | self.yiyanshowtext.value = self.yiyanlist[pickindex] 1001 | self.yiyanshowtext2.value = self.yiyanlist[pickindex] 1002 | 1003 | self.page.update() 1004 | elif self.defult_yy == True: 1005 | deft_yiyanlist = [ 1006 | "人生苦短,我用Python", 1007 | "亻尔 女子", 1008 | "《机房课时间管理》", 1009 | "就让你看看...这葫芦里卖的什么药!", 1010 | "让我来摸个鱼吧~", 1011 | ] 1012 | deft_pickindex = random.randint(0, 4) 1013 | self.yiyanshowtext.value = deft_yiyanlist[deft_pickindex] 1014 | self.yiyanshowtext2.value = deft_yiyanlist[deft_pickindex] 1015 | 1016 | self.page.update() 1017 | 1018 | pass 1019 | 1020 | def show_snakemessage(self, showtext: str): 1021 | """展示一条底部消息""" 1022 | 1023 | self.page.snack_bar = ft.SnackBar(ft.Text(showtext)) 1024 | self.page.snack_bar.open = True 1025 | 1026 | self.page.update() 1027 | 1028 | def loadyiyan(self): 1029 | """从外部加载一言库""" 1030 | ToolBoxCfg.set_style_path("yiyanPath", self.yiyanfpath) 1031 | 1032 | try: 1033 | fm = open(self.yiyanfpath, "r", encoding="utf-8") 1034 | get = fm.read() 1035 | fm.close() 1036 | 1037 | list_get = get.split("^") 1038 | 1039 | self.ex_fullindex = len(list_get) 1040 | 1041 | self.yiyanlist = list_get 1042 | 1043 | self.defult_yy = False # 关闭默认一言库 1044 | 1045 | self.show_snakemessage("成功加载外部一言库") 1046 | 1047 | except Exception as e: 1048 | self.show_snakemessage(f"加载外部一言时出现{e}异常") 1049 | pass 1050 | 1051 | def change_bg_btmd(self, e): 1052 | """改变背景图片不透明度的信号触发函数""" 1053 | self.bgtmd = e.control.value 1054 | self.reflashbg() 1055 | 1056 | def yiyan_pick_files_result(self, e: ft.FilePickerResultEvent): 1057 | 1058 | try: 1059 | _yiyanfpath = e.files[0] 1060 | self.yiyanfpath = os.path.join(_yiyanfpath.path) 1061 | self.loadyiyan() 1062 | 1063 | except TypeError: 1064 | self.show_snakemessage("未选择一言文件") 1065 | pass 1066 | 1067 | def setup_zidingyi_font(self): 1068 | """设置自定义字体""" 1069 | 1070 | ToolBoxCfg.set_style_path("fontPath", self.zdy_fontpath) 1071 | 1072 | self.font_loadtime += 1 1073 | print("[DEBUG] font_loadtime var = ", self.font_loadtime) 1074 | # 就是不知道为什么这里就直接是2了 1075 | if 10 >= self.font_loadtime > 2: # 删除旧的历史字体路径缓存 1076 | # 似乎无解了 尽力了 二次修改字体就会无效 1077 | # 牛逼 牛逼 整好了 以一种很抽象的方式解决了 1078 | # 不知道为什么覆盖掉的值不能用就很离谱 1079 | # print("Try DEL Old") 1080 | self.old_zidyingy_time = self.font_loadtime - 1 1081 | 1082 | del self.page.fonts[f"zidingyi{self.old_zidyingy_time}"] 1083 | # SyntaxError: cannot delete function call 1084 | elif self.font_loadtime > 10: 1085 | self.old_zidyingy_time = self.font_loadtime - 1 1086 | del self.page.fonts[f"zidingyi{self.old_zidyingy_time}"] 1087 | self.font_loadtime = 3 1088 | self.page.fonts.update({f"zidingyi{self.font_loadtime}": self.zdy_fontpath}) 1089 | self.page.theme = ft.Theme(font_family=f"zidingyi{self.font_loadtime}") 1090 | self.page.update() 1091 | 1092 | # sb了 不是普通括号 1093 | if self.loaded_bg == True: # 防止在新加载字体时把背景冲掉 1094 | 1095 | self.reflashbg() 1096 | 1097 | def font_pick_files_result(self, e: ft.FilePickerResultEvent): 1098 | try: 1099 | _fontfpath = e.files[0] 1100 | self.zdy_fontpath = os.path.join(_fontfpath.path) 1101 | self.setup_zidingyi_font() 1102 | 1103 | except TypeError: 1104 | self.show_snakemessage("未选择字体文件") 1105 | pass 1106 | 1107 | def pick_files_result(self, e: ft.FilePickerResultEvent): 1108 | try: 1109 | _bgpath = e.files[0] 1110 | self.bgpath = os.path.join(_bgpath.path) 1111 | self.bgtmdb.disabled = False 1112 | self.reflashbg() 1113 | except TypeError: 1114 | self.show_snakemessage("未选择背景图片") 1115 | pass 1116 | 1117 | def only_update_MMPC_status(self, *e): 1118 | """仅更新MMPC根服务状态""" 1119 | st = check_MMPC_status() 1120 | self.show_snakemessage(f"根服务状态: {st}") 1121 | if st == True: 1122 | self.mmpc_Stext.value = "正在运行" 1123 | self.page.update() 1124 | elif st == False: 1125 | self.mmpc_Stext.value = "未运行" 1126 | self.page.update() 1127 | 1128 | def MMPC_shutdown_start_chufa(self, *e): 1129 | """关闭/开启MMPC根服务的触发函数""" 1130 | st = check_MMPC_status() 1131 | if st == True: 1132 | 1133 | runcmd("sc stop MMPC") 1134 | elif st == False: 1135 | 1136 | runcmd("sc start MMPC") 1137 | 1138 | 1139 | ToolBox = Ui() 1140 | 1141 | 1142 | ft.app(target=ToolBox.main) 1143 | -------------------------------------------------------------------------------- /src/remain.py: -------------------------------------------------------------------------------- 1 | # import hmac 2 | import os, time 3 | from datetime import datetime 4 | 5 | # from tkinter.messagebox import * 6 | import pygetwindow as gw 7 | import webbrowser 8 | import ctypes 9 | from ctypes import wintypes 10 | import sys 11 | import psutil 12 | import pyautogui 13 | import socket 14 | import re 15 | import json 16 | 17 | # import wmi 18 | # from mainv3 import Ui 19 | 20 | 21 | bkppath = "C:\\Backups" 22 | 23 | cmdpath = "C:\\Users\\Administrator\\prod" 24 | 25 | 26 | RunBoxKiller = False 27 | 28 | RunProtectCMD = False 29 | 30 | MMPCServRun = True 31 | 32 | os.makedirs(cmdpath, mode=0o777, exist_ok=True) 33 | os.makedirs(bkppath, mode=0o777, exist_ok=True) 34 | 35 | 36 | class EasyDll: 37 | def __init__(self, dll_path): 38 | 39 | self.dll = ctypes.WinDLL(dll_path) 40 | 41 | def setup_function(self, func_name, restype=ctypes.c_int, argtypes=None): 42 | """ 43 | Configures a DLL function with the specified name, return type, and argument types. 44 | 45 | :param func_name: Name of the function in the DLL. 46 | :param restype: Return type of the function (default is c_int). 47 | :param argtypes: List of argument types (default is None). 48 | """ 49 | func = getattr(self.dll, func_name) 50 | func.restype = restype 51 | func.argtypes = argtypes or [] 52 | return func 53 | 54 | def get_error_message(self, error_code): 55 | """ 56 | Helper function to retrieve Windows error message for a given error code. 57 | 58 | :param error_code: Error code to look up. 59 | :return: The formatted error message string. 60 | """ 61 | msg_buffer = ctypes.create_unicode_buffer(256) 62 | ctypes.windll.kernel32.FormatMessageW( 63 | 0x00001000, # FORMAT_MESSAGE_FROM_SYSTEM 64 | None, 65 | error_code, 66 | 0, # Default language 67 | msg_buffer, 68 | len(msg_buffer), 69 | None, 70 | ) 71 | return msg_buffer.value 72 | 73 | 74 | def run_easy_dll( 75 | dll_name, func_name, return_type, argtypes, out_buffer, after_run_func=None 76 | ): 77 | """ 78 | ### 参数 79 | - `dll_name`: 要调用的dll文件名 80 | - `func_name`: 要调用的函数名 81 | - `return_type`: 要调用的函数的返回值类型 82 | - `argtypes`: 要调用的函数的参数类型 83 | - `out_buffer`: 要调用的函数的输出参数 84 | - `after_run_func`: 运行完毕后的回调函数 85 | 86 | """ 87 | 88 | print("dllUse debug >", dll_name, func_name, return_type, argtypes, out_buffer) 89 | 90 | # dll_path = "" + dll_name 91 | dll_path = ToolBoxCfg.oseasypath + dll_name 92 | 93 | easy_dll = EasyDll(dll_path) 94 | 95 | runner = easy_dll.setup_function(func_name, restype=return_type, argtypes=argtypes) 96 | 97 | try: 98 | if out_buffer == None: 99 | result = runner() 100 | else: 101 | result = runner(out_buffer) 102 | except Exception as e: 103 | Ui_CallShowSnakeMessage(f"调用失败 抛出异常:\n{e}") 104 | 105 | print("[DEBUG] dll result:", result) 106 | 107 | ui_show_msg = f"运行结果: \n函数: {func_name}\n返回值: {result}" 108 | if out_buffer != None: 109 | ui_show_msg += f"\n输出参数: {out_buffer.value}" 110 | 111 | if result != 0: 112 | error_msg = easy_dll.get_error_message(result) 113 | print("[DEBUG] Error message:", error_msg) 114 | ui_show_msg += f"\n错误信息: {error_msg}" 115 | 116 | Ui_CallShowSnakeMessage(ui_show_msg) 117 | 118 | if after_run_func != None: 119 | after_run_func() 120 | 121 | 122 | class ToolBoxConfig: 123 | 124 | def __init__(self): 125 | 126 | self.config_file_path = "C:\\ToolBoxConfig.json" 127 | self.running_student_client_ver = 0 128 | self.oseasypath_have_been_modified = False 129 | self.studentExeName = "Student.exe" 130 | self.oseasypath = ( 131 | "C:\\Program Files (x86)\\Os-Easy\\os-easy multicast teaching system\\" 132 | ) 133 | 134 | pass 135 | 136 | def first_launch_check(self) -> bool: 137 | """首次启动检查""" 138 | reads = self.get_config_key_data("first_launch_time") 139 | if not reads: 140 | self.write_first_launch_time() 141 | return True 142 | else: 143 | return False 144 | 145 | def write_first_launch_time(self) -> None: 146 | """写入首次启动时间""" 147 | self.set_config_key_data("first_launch_time", get_time_str()) 148 | 149 | def read_config(self) -> str: 150 | """从配置文件中读取""" 151 | if checkPointFileIsExcs(self.config_file_path): 152 | with open(self.config_file_path, "r", encoding="utf-8") as f: 153 | return f.read() 154 | else: 155 | self.write_config("{}") 156 | return "{}" 157 | 158 | def write_config(self, datas: str | dict) -> None: 159 | """写入配置文件""" 160 | 161 | if isinstance(datas, dict): 162 | datas = json.dumps(datas, ensure_ascii=False, indent=4) 163 | 164 | with open(self.config_file_path, "w", encoding="utf-8") as f: 165 | f.write(datas) 166 | 167 | def get_config_key_data(self, key) -> str | None: 168 | """获取配置文件中指定键的数据""" 169 | return self.get_style_path(key) 170 | 171 | def set_config_key_data(self, key, value) -> None: 172 | """设置配置文件中的数据""" 173 | self.set_style_path(key, value) 174 | 175 | def clear_config_key_data(self, key) -> None: 176 | """清空配置文件中的数据""" 177 | cfg = self.read_config() 178 | if cfg == "{}": 179 | return 180 | jData: dict = json.loads(cfg) 181 | if key in jData: 182 | jData.pop(key) 183 | self.write_config(jData) 184 | 185 | def get_style_path(self, style_name: str) -> str | None: 186 | """获取自定义外观的路径\n 187 | style_name: ["yiyan","fort","bg"]\n 188 | 一言, 字体, 背景""" 189 | 190 | cfg = self.read_config() 191 | if cfg == "{}": 192 | return None 193 | jData = json.loads(cfg) 194 | if style_name in jData: 195 | return jData[style_name] 196 | return None 197 | 198 | def set_style_path(self, style_name: str, style_path: str) -> None: 199 | """设置自定义外观的路径\n 200 | style_name: ["yiyan","fort","bg"]\n 201 | 一言, 字体, 背景""" 202 | 203 | cfg = self.read_config() 204 | jData = json.loads(cfg) 205 | jData[style_name] = style_path 206 | self.write_config(jData) 207 | 208 | def get_god_potato_path(): 209 | # PyInstaller 提取的临时路径 210 | if hasattr(sys, "_MEIPASS"): 211 | return os.path.join(sys._MEIPASS, "resources", "gp_net35.exe") 212 | # 开发环境路径 213 | return os.path.join("resources", "gp_net35.exe") 214 | 215 | def run_cmd_with_god_potato(arguments:str): 216 | """ 217 | 使用神の土豆来运行命令 218 | 参数: 219 | - arguments: 要运行的命令 220 | 如:run_god_potato_cmd("net start MMPC") 221 | """ 222 | ntsd_path = get_god_potato_path() 223 | if not os.path.exists(ntsd_path): 224 | raise FileNotFoundError(f"ntsd.exe not found at {ntsd_path}") 225 | 226 | cmd = f'"{ntsd_path}" -cmd "cmd /c {arguments}"' 227 | 228 | runcmd(cmd,False) 229 | 230 | 231 | ToolBoxCfg = ToolBoxConfig() 232 | 233 | 234 | Ui_Class = None 235 | 236 | 237 | def pass_ui_class(ui: classmethod) -> None: 238 | """传递Ui类到此处让这里的函数可以调用主Ui的函数""" 239 | global Ui_Class 240 | Ui_Class = ui 241 | 242 | 243 | def Ui_CallShowSnakeMessage(*msg: tuple) -> None: 244 | """Ui类 显示底部弹窗""" 245 | mix = "" 246 | for i in msg: 247 | mix += str(i) + " " 248 | msg = mix.strip() 249 | Ui_Class.show_snakemessage(msg) 250 | 251 | 252 | def TryGetStudentPath() -> tuple[str, str] | tuple[bool, None]: 253 | """尝试获取学生端路径 并更新全局变量""" 254 | 255 | Spath = get_program_path("Student.exe") 256 | Spath_2 = get_program_path("MmcStudent.exe") 257 | # v10.9.1 学生端改名为MmcStudent.exe 258 | 259 | if Spath == None and Spath_2 == None: 260 | print("[DEBUG] > 未找到运行中的学生端") 261 | 262 | isModed = ToolBoxCfg.get_config_key_data("studentPath_have_been_modified") 263 | print(f"[DEBUG] 配置文件 > 学生端路径是否被修改:{isModed}") 264 | if not isModed: 265 | return False, None 266 | 267 | ToolBoxCfg.oseasypath_have_been_modified = True 268 | 269 | ToolBoxCfg.oseasypath = ToolBoxCfg.get_config_key_data("studentPath") 270 | ToolBoxCfg.studentExeName = ToolBoxCfg.get_config_key_data("studentExeName") 271 | 272 | print(f"[DEBUG] 配置文件 > 学生端路径为:{ToolBoxCfg.oseasypath}") 273 | print(f"[DEBUG] 配置文件 > 学生端进程名为:{ToolBoxCfg.studentExeName}") 274 | 275 | ToolBoxCfg.set_config_key_data("studentPath", ToolBoxCfg.oseasypath) 276 | ToolBoxCfg.set_config_key_data("studentExeName", ToolBoxCfg.studentExeName) 277 | 278 | return ToolBoxCfg.oseasypath, ToolBoxCfg.studentExeName 279 | 280 | if Spath_2: 281 | ToolBoxCfg.studentExeName = "MmcStudent.exe" 282 | Spath = Spath_2 283 | elif Spath: 284 | ToolBoxCfg.studentExeName = "Student.exe" 285 | 286 | Spath = ( 287 | str(Spath) 288 | .replace("/", "\\") 289 | .replace("MmcStudent.exe", "") 290 | .replace("Student.exe", "") 291 | ) 292 | ToolBoxCfg.oseasypath_have_been_modified = True 293 | ToolBoxCfg.oseasypath = Spath 294 | 295 | print(f"[DEBUG] 学生端路径为:{ToolBoxCfg.oseasypath}") 296 | print(f"[DEBUG] 学生端进程名为:{ToolBoxCfg.studentExeName}") 297 | 298 | ToolBoxCfg.set_config_key_data("studentPath", ToolBoxCfg.oseasypath) 299 | ToolBoxCfg.set_config_key_data("studentExeName", ToolBoxCfg.studentExeName) 300 | ToolBoxCfg.set_config_key_data("studentPath_have_been_modified", True) 301 | 302 | return ToolBoxCfg.oseasypath, ToolBoxCfg.studentExeName 303 | 304 | 305 | def get_program_path(program_name) -> str | None: 306 | """ 307 | 获取指定程序的运行路径 308 | 309 | :param program_name: 程序名称,如 'exp.exe' 310 | 311 | :return: 程序的运行路径 312 | 313 | """ 314 | for proc in psutil.process_iter(["pid", "name", "exe"]): 315 | try: 316 | if proc.info["name"] == program_name: 317 | return proc.info["exe"] 318 | except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): 319 | continue 320 | return None 321 | 322 | 323 | def usb_unlock(): 324 | """尝试解锁USB管控""" 325 | # 经过了好一段时间的研究可能真的就这样? 326 | summon_unlocknet() 327 | summon_unlock_usb() 328 | runbat("net.bat") 329 | time.sleep(2) 330 | runbat("usb.bat") 331 | # time.sleep(2) 332 | # runcmd("sc delete easyusbflt") 333 | # time.sleep(1) 334 | 335 | 336 | def tryGuessStudentClientVer() -> int: 337 | """尝试通过检测LissHeler.exe此类旧版本没有的程序\n 338 | 来猜测学生端版本""" 339 | 340 | if not ToolBoxCfg.oseasypath_have_been_modified: 341 | _, _2 = TryGetStudentPath() 342 | 343 | versions = { 344 | 109: f"{ToolBoxCfg.oseasypath}LissHelper.exe", 345 | 108: f"{ToolBoxCfg.oseasypath}MultiClient.exe", 346 | 105: f"{ToolBoxCfg.oseasypath}MouseKeyBoradControl.exe", 347 | } 348 | 349 | for version, path in versions.items(): 350 | if checkPointFileIsExcs(path): 351 | print(f"[Student Ver Guess] maybe is v{version // 10}.{version % 10}") 352 | ToolBoxCfg.running_student_client_ver = version 353 | ToolBoxCfg.set_config_key_data("studentClientVer", version) 354 | return ToolBoxCfg.running_student_client_ver 355 | 356 | print("[Student Ver Guess] 超出检测范围 学生端本体可能损坏或路径不正确") 357 | ToolBoxCfg.running_student_client_ver = 0 358 | return ToolBoxCfg.running_student_client_ver 359 | 360 | pass 361 | 362 | 363 | def HighVer_CloseMMPCProtect_Helper(): 364 | """检查学生端版本来决定\n 365 | 需不需要关闭MMPC保护服务\n 366 | """ 367 | if not ToolBoxCfg.running_student_client_ver: 368 | _ = tryGuessStudentClientVer() 369 | 370 | if ToolBoxCfg.running_student_client_ver >= 109: 371 | mpStatus = check_MMPC_status() 372 | if mpStatus: 373 | runcmd("sc stop MMPC") 374 | time.sleep(1) 375 | 376 | pass 377 | 378 | 379 | def HighVer_AddMMPC_Control_CommandLine(IsStop = True): 380 | """检查学生端版本 返回根服务控制指令\n 381 | 用于直接插入到脚本中 382 | """ 383 | 384 | if not ToolBoxCfg.running_student_client_ver: 385 | _ = tryGuessStudentClientVer() 386 | 387 | if ToolBoxCfg.running_student_client_ver >= 109: 388 | if IsStop == True: 389 | return "sc stop MMPC\n" 390 | else: 391 | return "sc start MMPC\n" 392 | return "" 393 | 394 | 395 | def checkPointFileIsExcs(filePath) -> bool: 396 | """检查文件是否存在""" 397 | return os.path.isfile(filePath) 398 | 399 | 400 | def replace_ScreenRender() -> bool: 401 | """替换原有scr用于拦截远程命令""" 402 | global bkppath 403 | filename = "ScreenRender_Helper.exe" 404 | nowcurhelper = os.path.join(os.getcwd(), filename) 405 | copypath = os.path.join(ToolBoxCfg.oseasypath, filename) 406 | 407 | onetime_protectcheck() 408 | if not checkPointFileIsExcs(nowcurhelper): 409 | return False 410 | 411 | runcmd(f'rename "{ToolBoxCfg.oseasypath}ScreenRender.exe" "ScreenRender_Y.exe"') 412 | time.sleep(2.5) 413 | runcmd(f'copy "{nowcurhelper}" "{copypath}"') 414 | time.sleep(2.5) 415 | runcmd( 416 | f'rename "{ToolBoxCfg.oseasypath}ScreenRender_Helper.exe" "ScreenRender.exe"' 417 | ) 418 | return True 419 | 420 | 421 | def restone_ScreenRender() -> bool: 422 | """还原原有的ScreenRender""" 423 | 424 | onetime_protectcheck() 425 | path = f"{ToolBoxCfg.oseasypath}ScreenRender.exe" 426 | 427 | a = check_tihuan_SCRY_status() 428 | if a == False: 429 | return False 430 | 431 | try: 432 | os.remove(path) 433 | except FileNotFoundError: 434 | pass 435 | runcmd(f'rename "{ToolBoxCfg.oseasypath}ScreenRender_Y.exe" "ScreenRender.exe"') 436 | 437 | return True 438 | 439 | 440 | def get_yuancheng_cmd() -> str | None: 441 | """从文件中读取拦截到的远程命令\n 442 | 未读取到返回None""" 443 | getpath = os.path.join(cmdpath, "SCCMD.txt") 444 | try: 445 | with open(getpath, "r") as f: 446 | return f.read() 447 | except FileNotFoundError: 448 | return None 449 | 450 | 451 | def parse_screenrender_log(): 452 | """ 453 | 读取 `%appdata%/Mmc/ScreenRender.log` 文件,\n 454 | 筛选符合特定格式的日志,\n 455 | 并返回替换 " 为 # 的日志命令部分。\n 456 | 457 | `Returns` 458 | `list`: 包含处理后的命令部分的列表。 459 | """ 460 | # 获取 %appdata% 路径 461 | appdata_path = os.getenv("APPDATA") 462 | if not appdata_path: 463 | # raise EnvironmentError("无法获取 %APPDATA% 路径") 464 | Ui_CallShowSnakeMessage("无法获取 %APPDATA% 路径") 465 | return False, [] 466 | 467 | log_path = os.path.join(appdata_path, "Mmc", "ScreenRender.log") 468 | if not os.path.exists(log_path): 469 | # raise FileNotFoundError(f"日志文件不存在: {log_path}") 470 | Ui_CallShowSnakeMessage(f"日志文件不存在: {log_path}") 471 | return False, [] 472 | 473 | # 匹配特定格式的正则表达式 474 | pattern = re.compile(r"\d{2}-\d{2} \d{2}:\d{2}:\d{2} (\{.*\})") 475 | 476 | result = [] 477 | 478 | try: 479 | with open(log_path, "r", encoding="gbk") as log_file: 480 | for line in log_file: 481 | match = pattern.search(line) 482 | if match: 483 | command = match.group(1) 484 | # 替换 " 为 # 485 | processed_command = command.replace('"', "#") 486 | result.append(processed_command) 487 | except Exception as e: 488 | # raise RuntimeError(f"读取日志文件时发生错误: {e}") 489 | Ui_CallShowSnakeMessage(f"读取日志文件时发生错误: {e}") 490 | return False, [] 491 | 492 | if len(result) == 0: 493 | return False, [] 494 | 495 | return True, result 496 | 497 | 498 | # "C:\Program Files (x86)\Os-Easy\os-easy multicast teaching system\ScreenRender.exe" {#decoderName#:#h264#,#fullscreen#:0,#local#:#172.18.36.132#,#port#:7778,#remote#:#229.1.36.200#,#teacher_ip#:0,#verityPort#:7788} 499 | 500 | 501 | def save_scr_log_cmd_to_file(log_list=None) -> None: 502 | """传入`parse_screenrender_log`函数返回的命令列表\n 503 | 或直接调用\n 504 | 保存广播命令日志中的命令到文件""" 505 | 506 | if log_list == []: 507 | return 508 | elif log_list == None: 509 | return save_scr_log_cmd_to_file(parse_screenrender_log()) 510 | 511 | path = os.getcwd() + "\\" + "scr_log_cmd.txt" 512 | with open(path, "w") as f: 513 | f.write("\n".join(log_list)) 514 | 515 | 516 | def from_scr_log_cmd_get_yccmd() -> None: 517 | """从屏幕广播日志中提取广播命令\n并保存到文件""" 518 | 519 | status, log_list = parse_screenrender_log() 520 | if not status: 521 | return 522 | 523 | save_scr_log_cmd_to_file(log_list) 524 | 525 | handin_save_yc_cmd(log_list[0], replace_ip=False) 526 | 527 | 528 | def get_ipv4_address() -> str | None: 529 | """获取机器IPv4地址""" 530 | try: 531 | return socket.gethostbyname(socket.gethostname()) 532 | except Exception as e: 533 | print(f"获取IPv4地址时出现错误: {e}") 534 | return None 535 | 536 | 537 | def handin_save_yc_cmd(save_cmd, replace_ip=True) -> None: 538 | """手动保存拦截的命令""" 539 | global cmdpath 540 | 541 | if replace_ip: 542 | 543 | localIp = get_ipv4_address() 544 | 545 | Ui_CallShowSnakeMessage(f"已自动替换本地IP地址为{localIp}") 546 | 547 | save_cmd = re.sub(r"(#local#:)(#.*?#)", rf"\1#{localIp}#", save_cmd) 548 | 549 | getpath = os.path.join(cmdpath, "SCCMD.txt") 550 | 551 | with open(getpath, "w") as f: 552 | f.write(save_cmd) 553 | 554 | 555 | def generate_yc_cmd_and_save(teacher_ip) -> None: 556 | """生成拦截的命令并保存""" 557 | global cmdpath 558 | localIp = get_ipv4_address() 559 | 560 | cmd_base = "{#decoderName#:#h264#,#fullscreen#:0,#local#:#172.18.36.132#,#port#:7778,#remote#:#229.1.36.200#,#teacher_ip#:0,#verityPort#:7788}" 561 | 562 | save_cmd = re.sub(r"(#local#:)(#.*?#)", rf"\1#{localIp}#", cmd_base) 563 | save_cmd = re.sub(r"(#remote#:)(#.*?#)", rf"\1#{teacher_ip}#", save_cmd) 564 | 565 | getpath = os.path.join(cmdpath, "SCCMD.txt") 566 | print("[DEBUG]", save_cmd) 567 | 568 | Ui_CallShowSnakeMessage( 569 | f"已尝试按照模板生成广播命令\n若无法使用请使用拦截方案获取命令" 570 | ) 571 | 572 | with open(getpath, "w") as f: 573 | f.write(save_cmd) 574 | 575 | 576 | def build_run_srcmd(YC_command) -> str: 577 | """构造执行显示命令""" 578 | 579 | status = check_tihuan_SCRY_status() 580 | if status == True: 581 | fdb = f'"{ToolBoxCfg.oseasypath}ScreenRender_Y.exe" {YC_command}' 582 | return fdb 583 | else: 584 | fdb = f'"{ToolBoxCfg.oseasypath}ScreenRender.exe" {YC_command}' 585 | return fdb 586 | 587 | 588 | def save_now_yccmd() -> bool | None: 589 | """开发者选项 - 保存现在获取到的远程指令到程序目录""" 590 | getpath = cmdpath + "\\SCCMD.txt" 591 | savepath = os.getcwd() + "\\" + "command.txt" 592 | 593 | try: 594 | fm = open(getpath, "r") 595 | cmd = fm.read() 596 | fm.close() 597 | except FileNotFoundError: 598 | return None 599 | 600 | fm = open(savepath, "w") 601 | fm.write(cmd) 602 | fm.close() 603 | return True 604 | 605 | 606 | def check_tihuan_SCRY_status() -> bool: 607 | """通过检查SCR_Y是否存在 608 | \n来检查是否已经完成替换拦截程序 609 | \n返回True/False""" 610 | check_path = f"{ToolBoxCfg.oseasypath}ScreenRender_Y.exe" 611 | # try: 612 | # fm = open(check_path,'r') 613 | # fm.close() 614 | # return True 615 | # except FileNotFoundError: 616 | # return False 617 | 618 | return checkPointFileIsExcs(check_path) 619 | 620 | 621 | def get_pid(name) -> int | None: 622 | """ 623 | 根据进程名获取进程pid\n 624 | 未寻找到返回None 625 | """ 626 | pids = psutil.process_iter() 627 | print("[" + name + "]'s pid is:") 628 | for pid in pids: 629 | if pid.name() == name: 630 | print(pid.pid) 631 | return pid.pid 632 | return None 633 | 634 | 635 | def get_time_str() -> str: 636 | """返回一个时间字符串""" 637 | time_str = datetime.now().strftime("%Y_%m_%d_%H_%M_%S") 638 | return time_str 639 | 640 | 641 | def get_scshot() -> None: 642 | """保存一张屏幕截图""" 643 | 644 | savepath = os.getcwd() 645 | 646 | PMsize = pyautogui.size() 647 | print("DEBUG 屏幕尺寸 > ", PMsize) 648 | win_h = PMsize.height 649 | win_w = PMsize.width 650 | 651 | img = pyautogui.screenshot() 652 | 653 | mix_name = savepath + "\\" + get_time_str() + ".jpg" 654 | img.save(mix_name) 655 | print("DEBUG SavePath > ", mix_name) 656 | 657 | 658 | def check_MMPC_status() -> bool: 659 | """检查MMPC根服务状态\n 660 | 返回True/False""" 661 | name = "MMPC" 662 | service = None 663 | try: 664 | service = psutil.win_service_get(name) 665 | service = service.as_dict() 666 | except Exception as ex: 667 | 668 | return False 669 | 670 | if service and service["status"] == "running": 671 | return True 672 | else: 673 | return False 674 | 675 | 676 | def run_upto_admin() -> None: 677 | """用于在非管理员运行时尝试提权""" 678 | if ctypes.windll.shell32.IsUserAnAdmin() == 0: 679 | ctypes.windll.shell32.ShellExecuteW( 680 | None, "runas", sys.executable, "".join(sys.argv), None, 1 681 | ) 682 | sys.exit() 683 | 684 | 685 | def del_historyrem(*e) -> None: 686 | """删除保存的历史路径文件""" 687 | neddel = ["fontPath", "bgPath", "yiyanPath"] 688 | 689 | for i in neddel: 690 | ToolBoxCfg.set_config_key_data(i, None) 691 | 692 | 693 | def guaqi_process(process_name) -> str | bool: 694 | """挂起进程""" 695 | try: 696 | for process in psutil.process_iter(["pid", "name"]): 697 | if process.info["name"] == process_name: 698 | pid = process.info["pid"] 699 | psutil.Process(pid).suspend() 700 | print(f"Process {process_name} (PID {pid}) suspended.") 701 | return True 702 | 703 | print(f"Process {process_name} not found.") 704 | return "尝试挂起的进程未找到" 705 | except psutil.AccessDenied as e: 706 | print(f"Permission error: {e}") 707 | return "尝试挂起进程失败" 708 | 709 | 710 | def huifu_process(process_name) -> str | bool: 711 | """恢复挂起进程""" 712 | try: 713 | for process in psutil.process_iter(["pid", "name"]): 714 | if process.info["name"] == process_name: 715 | pid = process.info["pid"] 716 | psutil.Process(pid).resume() 717 | print(f"Process {process_name} (PID {pid}) resumed.") 718 | return True 719 | 720 | print(f"Process {process_name} not found.") 721 | return "尝试恢复挂起的进程未找到" 722 | except psutil.AccessDenied as e: 723 | print(f"Permission error: {e}") 724 | return "尝试恢复挂起进程失败" 725 | 726 | 727 | def onetime_protectcheck() -> None: 728 | """检测是否开启了击杀脚本\n 729 | 若未开启则帮助启动一次\n 730 | 已经开启则忽略""" 731 | try: 732 | window = gw.getWindowsWithTitle("OsEasyToolBoxKiller")[0] 733 | except: 734 | summon_killer() 735 | runbat("k.bat") 736 | 737 | 738 | def opengithubres(*e) -> None: 739 | """在浏览器打开github仓库页面""" 740 | webbrowser.open("https://github.com/ZiHaoSaMa66/OsEasy-ToolBox") 741 | 742 | 743 | def startprotect() -> None: 744 | global RunProtectCMD 745 | """启动守护进程""" 746 | ptct = 0 747 | while RunProtectCMD == True: 748 | try: 749 | window = gw.getWindowsWithTitle("OsEasyToolBoxKiller")[0] 750 | time.sleep(0.5) 751 | except: 752 | runbat("k.bat") 753 | ptct += 1 754 | time.sleep(1) 755 | 756 | 757 | def delcmdfiles() -> None: 758 | """删除生成的脚本文件""" 759 | global cmdpath 760 | for filename in ["k.bat", "d.bat", "temp.bat", "kv2.bat", "net.bat", "usb.bat"]: 761 | try: 762 | os.remove(os.path.join(cmdpath, filename)) 763 | except FileNotFoundError: 764 | continue 765 | 766 | 767 | def summon_unlocknet() -> None: 768 | """生成解锁网络锁定脚本""" 769 | global cmdpath 770 | mp = cmdpath + "\\net.bat" 771 | fm = open(mp, "w") 772 | cmdtext = f"""@ECHO OFF\n 773 | title OsEasyToolBoxUnlockNetHeler\n 774 | {HighVer_AddMMPC_Control_CommandLine(True)} 775 | :a\n 776 | taskkill /f /t /im {ToolBoxCfg.studentExeName}\n 777 | taskkill /f /t /im DeviceControl_x64.exe\n 778 | goto a 779 | """ 780 | fm.write(cmdtext) 781 | fm.close() 782 | 783 | 784 | def summon_unlock_usb() -> None: 785 | """生成解锁USB脚本""" 786 | global cmdpath 787 | mp = cmdpath + "\\usb.bat" 788 | fm = open(mp, "w") 789 | cmdtext = """@ECHO OFF\n 790 | title OsEasyToolBoxUnlockUSBHeler\n 791 | 792 | sc delete easyusbflt\n 793 | sc delete easyusbflt\n 794 | timeout 1\n 795 | 796 | del C:\\Windows\\System32\\drivers\\easyusbflt.sys\n 797 | timeout 5\n 798 | shutdown /l\n 799 | """ 800 | fm.write(cmdtext) 801 | fm.close() 802 | 803 | 804 | def summon_killerV2() -> None: 805 | """生成V2击杀脚本""" 806 | global cmdpath 807 | mp = cmdpath + "\\kv2.bat" 808 | fm = open(mp, "w") 809 | cmdtext = f"@ECHO OFF\ntitle OsEasyToolBoxKillerV2\n:awa\nfor %%p in (Ctsc_Multi.exe,DeviceControl_x64.exe,HRMon.exe,MultiClient.exe,OActiveII-Client.exe,OEClient.exe,OELogSystem.exe,OEUpdate.exe,OEProtect.exe,ProcessProtect.exe,RunClient.exe,RunClient.exe,ServerOSS.exe,{ToolBoxCfg.studentExeName},wfilesvr.exe,tvnserver.exe,updatefilesvr.exe,ScreenRender.exe) do taskkill /f /IM %%p\ngoto awa\n" 810 | fm.write(cmdtext) 811 | fm.close() 812 | 813 | 814 | def summon_killer() -> None: 815 | """生成击杀脚本""" 816 | global cmdpath 817 | mp = cmdpath + "\\k.bat" 818 | fm = open(mp, "w") 819 | cmdtext = f"""@ECHO OFF\n 820 | title OsEasyToolBoxKiller\n 821 | 822 | {HighVer_AddMMPC_Control_CommandLine(True)} 823 | 824 | taskkill /f /t /im MultiClient.exe\n 825 | taskkill /f /t /im MultiClient.exe\n 826 | taskkill /f /t /im BlackSlient.exe\n 827 | :a\n 828 | taskkill /f /t /im {ToolBoxCfg.studentExeName}\n 829 | goto a""" 830 | fm.write(cmdtext) 831 | fm.close() 832 | 833 | 834 | def backupOeKeyDll() -> None: 835 | """备份OE的关键文件""" 836 | global bkppath 837 | print("[INFO] 尝试备份关键文件") 838 | namelist = [ 839 | "oenetlimitx64.cat", 840 | "OeNetLimitSetup.exe", 841 | "OeNetLimit.sys", 842 | "OeNetLimit.inf", 843 | "MultiClient.exe", 844 | "MultiClient.exe", 845 | "LoadDriver.exe", 846 | "BlackSlient.exe", 847 | "\\x86\\LISSNetInfoSniffer.exe", 848 | ] 849 | for filename in namelist: 850 | 851 | oepath = ToolBoxCfg.oseasypath + filename 852 | 853 | needbkpath = bkppath + "\\" + filename 854 | 855 | runcmd(f'copy "{oepath}" "{needbkpath}"') 856 | 857 | 858 | def restoneBlackSlt(*e) -> None: 859 | """恢复黑屏安静程序""" 860 | global bkppath 861 | filename = "BlackSlient.exe" 862 | oepath = ToolBoxCfg.oseasypath + filename 863 | needbkpath = bkppath + "\\" + filename 864 | runcmd(f'copy "{needbkpath}" "{oepath}"') 865 | 866 | 867 | def restoneMutClient() -> None: 868 | """恢复用于控屏的MultiClient""" 869 | global bkppath 870 | filename = "MultiClient.exe" 871 | oepath = ToolBoxCfg.oseasypath + filename 872 | needbkpath = bkppath + "\\" + filename 873 | runcmd(f'copy "{needbkpath}" "{oepath}"') 874 | 875 | 876 | def restoneKeyDll() -> None: 877 | """恢复OE关键文件""" 878 | global bkppath 879 | print("尝试还原关键文件") 880 | namelist = [ 881 | "oenetlimitx64.cat", 882 | "OeNetLimitSetup.exe", 883 | "OeNetLimit.sys", 884 | "OeNetLimit.inf", 885 | "MultiClient.exe", 886 | "LoadDriver.exe", 887 | "BlackSlient.exe", 888 | # "\\x86\\LISSNetInfoSniffer.exe", 889 | ] 890 | 891 | faild_file_name = [] 892 | 893 | for filename in namelist: 894 | oepath = ToolBoxCfg.oseasypath + filename 895 | needbkpath = bkppath + "\\" + filename 896 | 897 | runcmd(f'copy "{needbkpath}" "{oepath}"') 898 | 899 | time.sleep(3) 900 | 901 | for filename in namelist: 902 | 903 | oepath = ToolBoxCfg.oseasypath + filename 904 | 905 | cSta = checkPointFileIsExcs(oepath) 906 | 907 | print(f"filename {filename} 复制检测状态 > {cSta}") 908 | 909 | if not cSta: 910 | faild_file_name.append(filename) 911 | 912 | if len(faild_file_name) > 0: 913 | msg_mix = " , ".join(faild_file_name) 914 | Ui_CallShowSnakeMessage(f"在恢复文件时检测到可能复制失败的文件有: \n{msg_mix}") 915 | return 916 | 917 | Ui_CallShowSnakeMessage("恢复文件完成") 918 | 919 | 920 | 921 | def runbat(batname: str) -> None: 922 | """运行指定名称的bat脚本""" 923 | global cmdpath 924 | batp = os.path.join(cmdpath, batname) 925 | os.startfile(batp) 926 | 927 | 928 | def summon_deldll(delMtc: bool, shutdown: bool) -> None: 929 | """生成删除dll脚本""" 930 | global cmdpath 931 | backupOeKeyDll() 932 | 933 | mp = cmdpath + "\\d.bat" 934 | fm = open(mp, "w") 935 | cmdtext = f"@ECHO OFF\ntitle OsEasyToolBox-Helper\ncd /D {ToolBoxCfg.oseasypath}\ntimeout 1\ndel /F /S OeNetLimitSetup.exe\ndel /F /S OeNetLimit.sys\ndel /F /S OeNetLimit.inf\ndel /F /S LockKeyboard.dll\ndel /F /S LoadDriver.exe\ndel /F /S LoadDriver.exe\ndel /F /S oenetlimitx64.cat\ndel /F /S BlackSlient.exe\ncd x86\ndel /F /S LISSNetInfoSniffer.exe\ncd .." 936 | if delMtc == True: 937 | cmdtext += "\ndel /F /S MultiClient.exe" 938 | if shutdown == False: 939 | pass 940 | elif shutdown == True: 941 | cmdtext += "\ntimeout 5\nshutdown /l" 942 | # cmdtext += "\ntimeout 10\nshutdown /l" 943 | cmdtext += "\nexit" 944 | fm.write(cmdtext) 945 | fm.close() 946 | 947 | 948 | def regkillercmd() -> None: 949 | """生成击杀脚本并绑定粘滞键""" 950 | summon_killer() 951 | # mp = cmdpath + "\\r.bat" 952 | # fm = open(mp,"w") 953 | # cmdtext = 'REG ADD "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\sethc.exe" /v Debugger /t REG_SZ /d "C:\\Program Files\\dotnet\\k.bat"' 954 | # fm.write(cmdtext) 955 | # fm.close() 956 | # os.system("start C:\\Program Files\\dotnet\\r.bat") 957 | runcmd( 958 | f'REG ADD "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\sethc.exe" /v Debugger /t REG_SZ /d "{cmdpath}\\k.bat"' 959 | ) 960 | 961 | 962 | def regkillerV2cmd() -> None: 963 | """生成击杀脚本V2并绑定粘滞键""" 964 | summon_killerV2() 965 | runcmd( 966 | f'REG ADD "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\sethc.exe" /v Debugger /t REG_SZ /d "{cmdpath}\\kv2.bat"' 967 | ) 968 | 969 | 970 | def boxkiller() -> None: 971 | global RunBoxKiller 972 | while RunBoxKiller == True: 973 | # os.system(command="taskkill /f /t /im Student.exe") 974 | opt = os.system(f"taskkill /f /t /im {ToolBoxCfg.studentExeName}") 975 | # print("test run") 976 | time.sleep(0.2) 977 | # print(f"[DEBUG] Killer Runned {opt}") 978 | 979 | 980 | def runcmd(givecmd: str, *quiterun: bool) -> None: 981 | """运行指定的命令""" 982 | if not quiterun: 983 | os.popen(cmd=givecmd) 984 | elif quiterun == False: 985 | os.system(command=givecmd) 986 | elif quiterun == True: 987 | os.popen(cmd=givecmd) 988 | else: 989 | os.system(command=givecmd) 990 | 991 | 992 | def usecmd_runcmd(cmd: str) -> None: 993 | """生成一个临时cmd文件运行指定命令""" 994 | global cmdpath 995 | mp = cmdpath + "\\temp.bat" 996 | fm = open(mp, "w") 997 | cmdtext = "@ECHO OFF\n" 998 | cmdtext += cmd 999 | cmdtext += "\nexit" 1000 | fm.write(cmdtext) 1001 | fm.close() 1002 | runcmd(f"start {mp}") 1003 | 1004 | 1005 | def delSummonCmdFile(*e) -> None: 1006 | # 清理生成的脚本文件 1007 | delcmdfiles() 1008 | 1009 | 1010 | def selfunc_g1(*e) -> None: 1011 | # 注册粘滞键替换击杀脚本 1012 | regkillercmd() 1013 | 1014 | 1015 | def selfunc_g1plus(*e) -> None: 1016 | # 注册V2版本的替换击杀脚本 1017 | regkillerV2cmd() 1018 | 1019 | 1020 | def delLockExeAndLogout(need_shutdown: bool) -> None: 1021 | # showwarning("温馨提醒","此功能略微需要手速\n在工具箱帮助你注销以后\n只要看见可以重新登录后即可点出粘滞键的脚本完成解锁") 1022 | # showwarning("温馨提醒","在注销后若无效果请手动重启机器\n(如果你的机房电脑有重启立刻还原请无视)\n(可以再次打开工具箱再次尝试注销解锁)\n并在进入系统桌面前手动点开粘滞键的击杀脚本\n若不想要注销可手动X掉命令窗口!!") 1023 | summon_killer() 1024 | onetime_protectcheck() 1025 | summon_deldll(delMtc=True, shutdown=need_shutdown) 1026 | time.sleep(2) 1027 | runbat("d.bat") 1028 | 1029 | 1030 | def handToStartStudent(*e) -> None: 1031 | 1032 | usecmd_runcmd(f'"{ToolBoxCfg.oseasypath}{ToolBoxCfg.studentExeName}"') 1033 | 1034 | 1035 | # def selfunc_g5(*e): 1036 | # restoneKeyDll() 1037 | 1038 | 1039 | def killerCmdProtect(*e) -> None: 1040 | global RunProtectCMD 1041 | if RunProtectCMD == False: 1042 | RunProtectCMD = True 1043 | summon_killer() 1044 | startprotect() 1045 | elif RunProtectCMD == True: 1046 | RunProtectCMD = False 1047 | usecmd_runcmd( 1048 | 'taskkill /f /t /fi "imagename eq cmd.exe" /fi "windowtitle eq 管理员: OsEasyToolBoxKiller"' 1049 | ) 1050 | 1051 | 1052 | def unlockedNet() -> None: 1053 | summon_unlocknet() 1054 | runbat("net.bat") 1055 | time.sleep(2) 1056 | runcmd("sc stop OeNetlimit") 1057 | time.sleep(1) 1058 | usecmd_runcmd( 1059 | 'taskkill /f /t /fi "imagename eq cmd.exe" /fi "windowtitle eq 管理员: OsEasyToolBoxUnlockNetHeler"' 1060 | ) 1061 | time.sleep(1) 1062 | 1063 | 1064 | def startOsEasySelfToolBox(*e) -> None: 1065 | # print("执行功能8 请稍等...") 1066 | regkillercmd() 1067 | onetime_protectcheck() 1068 | time.sleep(2) 1069 | runcmd(f'"{ToolBoxCfg.oseasypath}AssistHelper.exe"') 1070 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | flet==0.18.0 2 | pynput 3 | pyautogui 4 | psutil 5 | python_version >= "3.10.9" 6 | -------------------------------------------------------------------------------- /外部一言格式说明.md: -------------------------------------------------------------------------------- 1 | ## 外部一言文本格式说明 2 | 3 | 你可以通过新建一个txt文件 4 | 来写点你想要显示在功能上方的文字 5 | 有多句话可以使用"^"来分割 6 | **但请注意在末尾不能有分割符号** 7 | 🌰 举个简单的例子 8 | ``` 9 | 示例文本1^示例文本2^示例文本3 10 | ``` 11 | 如果你希望句子中拥有换行 12 | 你可以直接在句子中换行 13 | 🌰 再举个简单的例子 14 | ``` 15 | 示例文本1^示例文本2 16 | 换行的示例2^示例文本3 17 | ``` --------------------------------------------------------------------------------