Plotly离线安装完整指南:依赖闭包与跨平台wheel打包
2026/10/2 13:26:05
import pymem import pymem.process import tkinter as tk from tkinter import ttk, messagebox import threading import time # ==================== 配置区 ==================== PROCESS_NAME = "KawaiQQ.EXE" OFFSET_HINT = 0xA3194 # 提示数量 WORD OFFSET_LIFE = 0xA30D4 # 生命值 WORD OFFSET_SCORE = 0xA3134 # 分数 FLOAT OFFSET_TIME = 0xA3148 # 时间条 FLOAT ← 越小时间越长 # 时间条换算:输入 1~100 的档位,内部映射到内存值 # 档位 100 → 内存 1.0 → 时间条最长 # 档位 1 → 内存 100 → 时间条较短 def ui_to_mem_time(ui_value): ui_value = max(1.0, min(100.0, float(ui_value))) return 101.0 - ui_value def mem_to_ui_time(mem_value): """内存值反向显示成档位,方便直观""" try: v = float(mem_value) ui = 101.0 - v return max(1.0, min(100.0, ui)) except Exception: return 0.0 # ==================== 内存操作核心 ==================== class LLKModifier: def __init__(self): self.pm = None self.base = None self.locked = {} self.lock_thread = None self.running = False def attach(self): try: self.pm = pymem.Pymem(PROCESS_NAME) module = pymem.process.module_from_name( self.pm.process_handle, PROCESS_NAME ) self.base = module.lpBaseOfDll return True, f"附加成功!基址: 0x{self.base:X}" except Exception as e: self.pm = None self.base = None return False, f"附加失败: {e}" def is_attached(self): return self.pm is not None and self.base is not None def read_int(self, addr): return self.pm.read_int(addr) def write_int(self, addr, v): self.pm.write_int(addr, v) def read_word(self, addr): return self.pm.read_short(addr) def write_word(self, addr, v): self.pm.write_short(addr, v) def read_float(self, addr): return self.pm.read_float(addr) def write_float(self, addr, v): self.pm.write_float(addr, v) def get_hint_addr(self): return self.base + OFFSET_HINT def get_life_addr(self): return self.base + OFFSET_LIFE def get_score_addr(self): return self.base + OFFSET_SCORE def get_time_addr(self): return self.base + OFFSET_TIME def read_hint(self): return self.read_word(self.get_hint_addr()) def read_life(self): return self.read_word(self.get_life_addr()) def read_score(self): return self.read_float(self.get_score_addr()) def read_time(self): return self.read_float(self.get_time_addr()) def write_hint(self, v): self.write_word(self.get_hint_addr(), v & 0xFFFF) def write_life(self, v): self.write_word(self.get_life_addr(), v & 0xFFFF) def write_score(self, v): self.write_float(self.get_score_addr(), float(v)) def write_time(self, v): self.write_float(self.get_time_addr(), float(v)) def start_lock(self): if self.running: return self.running = True self.lock_thread = threading.Thread(target=self._lock_loop, daemon=True) self.lock_thread.start() def stop_lock(self): self.running = False def _lock_loop(self): while self.running: if self.is_attached(): for addr, (value, dtype) in list(self.locked.items()): try: if dtype == 'int': self.write_int(addr, value) elif dtype == 'word': self.write_word(addr, value) elif dtype == 'float': self.write_float(addr, value) except Exception: pass time.sleep(0.05) def set_lock(self, addr, value, dtype='int'): self.locked[addr] = (value, dtype) def remove_lock(self, addr): self.locked.pop(addr, None) def clear_locks(self): self.locked.clear() # ==================== GUI ==================== class ModifierGUI: def __init__(self, root): self.root = root self.root.title("连连看3.5完全版-KawaiQQ.EXE-修改器 v1.0") self.root.geometry("600x660") self.root.resizable(False, False) self.mod = LLKModifier() self._build_ui() self._auto_attach() def _build_ui(self): # 进程状态 top = ttk.LabelFrame(self.root, text="进程状态", padding=10) top.pack(fill="x", padx=10, pady=5) self.status_var = tk.StringVar(value="未附加") ttk.Label(top, textvariable=self.status_var, foreground="red").pack(side="left") ttk.Button(top, text="附加游戏", command=self._auto_attach).pack(side="right") ttk.Button(top, text="刷新", command=self.refresh_values).pack(side="right", padx=5) # 提示 / 生命 / 分数 self._make_row("提示数量 (WORD +A3194)", "hint", "9999") self._make_row("生命值 (WORD +A30D4)", "life", "9999") self._make_row("分数 (FLOAT +A3134)", "score", "999999.0") # ---------- 时间条(特殊:反向) ---------- time_frame = ttk.LabelFrame( self.root, text="时间条 (FLOAT +A3148) ★ 档位越大 = 时间越长 ★", padding=10 ) time_frame.pack(fill="x", padx=10, pady=4) ttk.Label(time_frame, text="当前档位:").grid(row=0, column=0, sticky="w") self.time_var = tk.StringVar(value="--") ttk.Label(time_frame, textvariable=self.time_var, width=14, foreground="blue").grid(row=0, column=1, sticky="w") ttk.Label(time_frame, text="设为档位 (1~100):").grid( row=0, column=2, sticky="w", padx=(20, 0)) self.time_entry = ttk.Entry(time_frame, width=12) self.time_entry.insert(0, "100") self.time_entry.grid(row=0, column=3, sticky="w") ttk.Button(time_frame, text="写入", command=self.do_write_time).grid(row=0, column=4, padx=5) self.time_lock_var = tk.BooleanVar() ttk.Checkbutton(time_frame, text="锁定", variable=self.time_lock_var, command=self.do_lock_time).grid(row=0, column=5, padx=5) # 快捷按钮 quick = ttk.Frame(time_frame) quick.grid(row=1, column=0, columnspan=6, pady=6, sticky="w") ttk.Label(quick, text="快捷:").pack(side="left") for label, val in [("较短 30", 30), ("中等 60", 60), ("较长 90", 90), ("最长 100", 100)]: ttk.Button(quick, text=label, width=8, command=lambda v=val: self._set_time_quick(v)).pack(side="left", padx=3) # 日志 log_frame = ttk.LabelFrame(self.root, text="日志", padding=5) log_frame.pack(fill="both", expand=True, padx=10, pady=5) self.log_text = tk.Text(log_frame, height=8, state="disabled") self.log_text.pack(fill="both", expand=True) # 底部 bottom = ttk.Frame(self.root) bottom.pack(fill="x", padx=10, pady=5) ttk.Button(bottom, text="全部锁定", command=self.start_all_lock).pack(side="left") ttk.Button(bottom, text="全部解锁", command=self.stop_all_lock).pack(side="left", padx=5) ttk.Button(bottom, text="退出", command=self.root.quit).pack(side="right") def _make_row(self, title, name, default): frame = ttk.LabelFrame(self.root, text=title, padding=10) frame.pack(fill="x", padx=10, pady=4) ttk.Label(frame, text="当前:").grid(row=0, column=0, sticky="w") val_var = tk.StringVar(value="--") ttk.Label(frame, textvariable=val_var, width=14, foreground="blue").grid( row=0, column=1, sticky="w") ttk.Label(frame, text="设为:").grid(row=0, column=2, sticky="w", padx=(20, 0)) entry = ttk.Entry(frame, width=12) entry.insert(0, default) entry.grid(row=0, column=3, sticky="w") ttk.Button(frame, text="写入", command=lambda: self.do_write(name)).grid(row=0, column=4, padx=5) lock_var = tk.BooleanVar() ttk.Checkbutton(frame, text="锁定", variable=lock_var, command=lambda: self.do_lock(name)).grid(row=0, column=5, padx=5) setattr(self, f"{name}_var", val_var) setattr(self, f"{name}_entry", entry) setattr(self, f"{name}_lock_var", lock_var) # ==================== 逻辑 ==================== def log(self, msg): self.log_text.config(state="normal") self.log_text.insert("end", f"[{time.strftime('%H:%M:%S')}] {msg}\n") self.log_text.see("end") self.log_text.config(state="disabled") def _auto_attach(self): def worker(): ok, msg = self.mod.attach() if ok: self.status_var.set(f"已附加 基址:0x{self.mod.base:X}") self.log(msg) self.mod.start_lock() self.refresh_values() else: self.status_var.set("未附加") self.log(msg) threading.Thread(target=worker, daemon=True).start() def refresh_values(self): if not self.mod.is_attached(): return try: self.hint_var.set(str(self.mod.read_hint())) except Exception: self.hint_var.set("--") try: self.life_var.set(str(self.mod.read_life())) except Exception: self.life_var.set("--") try: self.score_var.set(f"{self.mod.read_score():.2f}") except Exception: self.score_var.set("--") # 时间条:把内存值换算成档位显示 try: mem = self.mod.read_time() ui = mem_to_ui_time(mem) self.time_var.set(f"{ui:.1f} (内存{mem:.1f})") except Exception: self.time_var.set("--") def _check(self): if not self.mod.is_attached(): messagebox.showwarning("警告", "请先附加游戏") return False return True def do_write(self, name): if not self._check(): return entry = getattr(self, f"{name}_entry") try: if name in ('hint', 'life'): val = int(entry.get()) getattr(self.mod, f"write_{name}")(val) else: val = float(entry.get()) getattr(self.mod, f"write_{name}")(val) self.log(f"{name} → {val}") self.refresh_values() except Exception as e: messagebox.showerror("错误", str(e)) def do_lock(self, name): if not self.mod.is_attached(): return lock_var = getattr(self, f"{name}_lock_var") entry = getattr(self, f"{name}_entry") addr = getattr(self.mod, f"get_{name}_addr")() if lock_var.get(): try: if name in ('hint', 'life'): val = int(entry.get()) dtype = 'word' else: val = float(entry.get()) dtype = 'float' self.mod.set_lock(addr, val, dtype) self.log(f"锁定 {name} = {val}") except Exception: pass else: self.mod.remove_lock(addr) self.log(f"解锁 {name}") # ---------- 时间条专用(反向换算) ---------- def _set_time_quick(self, ui_value): self.time_entry.delete(0, "end") self.time_entry.insert(0, str(ui_value)) self.do_write_time() def do_write_time(self): if not self._check(): return try: ui_val = float(self.time_entry.get()) mem_val = ui_to_mem_time(ui_val) self.mod.write_time(mem_val) self.log(f"时间条 档位{ui_val:.0f} → 内存{mem_val:.1f} (越大越久)") self.refresh_values() except Exception as e: messagebox.showerror("错误", str(e)) def do_lock_time(self): if not self.mod.is_attached(): return addr = self.mod.get_time_addr() if self.time_lock_var.get(): try: ui_val = float(self.time_entry.get()) mem_val = ui_to_mem_time(ui_val) self.mod.set_lock(addr, mem_val, 'float') self.log(f"锁定时间条 档位{ui_val:.0f} → 内存{mem_val:.1f}") except Exception: pass else: self.mod.remove_lock(addr) self.log("解锁时间条") def start_all_lock(self): if not self._check(): return for name in ('hint', 'life', 'score'): getattr(self, f"{name}_lock_var").set(True) self.do_lock(name) self.time_lock_var.set(True) self.do_lock_time() self.log("=== 全部锁定 ===") def stop_all_lock(self): for name in ('hint', 'life', 'score'): getattr(self, f"{name}_lock_var").set(False) self.do_lock(name) self.time_lock_var.set(False) self.do_lock_time() self.mod.clear_locks() self.log("=== 全部解锁 ===") if __name__ == "__main__": root = tk.Tk() app = ModifierGUI(root) root.mainloop()