├── README.md └── SeedMiner.py /README.md: -------------------------------------------------------------------------------- 1 | # SeedMiner 2 | outdated don't use anymore, try https://gist.github.com/Specnr/c851a92a258dd1fdbe3eee588f3f14d8 3 | -------------------------------------------------------------------------------- /SeedMiner.py: -------------------------------------------------------------------------------- 1 | import keyboard 2 | import mouse 3 | import time 4 | from tkinter import * 5 | import tkinter as tk 6 | from os.path import expanduser 7 | import os.path 8 | import glob 9 | import os 10 | import json 11 | from PIL import ImageGrab 12 | from colorthief import ColorThief 13 | from win32com.client import Dispatch 14 | import win32com.client 15 | import win32gui 16 | import win32process 17 | import tempfile 18 | from win32gui import GetWindowText, GetForegroundWindow 19 | from global_hotkeys import * 20 | import pygetwindow as gw 21 | import pyautogui 22 | import mss 23 | import mss.tools 24 | from PIL import Image 25 | from global_hotkeys.keycodes import vk_key_names 26 | import win32con 27 | import tkinter.font as tkFont 28 | import d3dshot 29 | from ahk import AHK 30 | from ahk.window import Window 31 | import playsound 32 | import random 33 | import string 34 | import win32api 35 | import win32con 36 | import requests 37 | 38 | 39 | ahk = AHK(executable_path='.\AutoHotkey\AutoHotkey.exe') 40 | 41 | speak = Dispatch("SAPI.SpVoice") 42 | 43 | Enabled = True 44 | version = "3.3" 45 | 46 | root = tk.Tk() 47 | windowed = tk.IntVar() 48 | easyDiff = tk.IntVar() 49 | resetHotkey = tk.StringVar() 50 | borderHotkey = tk.StringVar() 51 | savesPath = StringVar() 52 | currentWorldName = '' 53 | lastCheckedWorld = '' 54 | waitingForQuit = False 55 | speechText = tk.StringVar() 56 | fsMode = tk.IntVar() 57 | fps = tk.IntVar() 58 | multiInstance = IntVar() 59 | fps.set(30) 60 | cachedWindow = 0 61 | jojoeScenes = False 62 | 63 | mcPid = 0 64 | mcActualPid = 0 65 | 66 | sid = len(gw.getWindowsWithTitle('SeedMiner v')) 67 | globalCapture = d3dshot.create(capture_output="pil") 68 | if sid > 0: 69 | Label(root, text="SeedMiner ID: " + str(sid), fg="green").grid(row=16, sticky=E) 70 | 71 | def setDefaults(): 72 | volSlider.set(50) 73 | windowed.set(0) 74 | fsMode.set(0) 75 | resetHotkey.set('end') 76 | savesPath.set(expanduser("~") + "\AppData\Roaming\.minecraft\saves") 77 | speechText.set('Seed') 78 | borderHotkey.set('delete') 79 | multiInstance.set(0) 80 | fps.set(30) 81 | easyDiff.set(1) 82 | 83 | def enumHandler(mcWin, ctx): 84 | title = win32gui.GetWindowText(mcWin) 85 | if title.startswith('Minecraft') and (title[-1].isdigit() or title.endswith('Singleplayer') or title.endswith('Multiplayer (LAN)')): 86 | style = win32gui.GetWindowLong(mcWin, -16) 87 | if style == 369623040: 88 | if fsMode.get() == 2: 89 | rect = win32gui.GetWindowRect(mcWin) 90 | if rect[3] == 1080: 91 | win32gui.SetWindowPos(mcWin, win32con.HWND_TOP, 0, 0, 1920, 1027, 0x0004) 92 | return False 93 | else: 94 | win32gui.SetWindowPos(mcWin, win32con.HWND_TOP, 0, 0, 1920, 1080, 0x0004) 95 | return False 96 | style = 382664704 97 | win32gui.SetWindowLong(mcWin, win32con.GWL_STYLE, style) 98 | if fsMode.get() == 3: 99 | win32gui.SetWindowPos(mcWin, win32con.HWND_TOP, 650, 0, 700, 1050, 0x0004) 100 | elif fsMode.get() == 1: 101 | win32gui.SetWindowPos(mcWin, win32con.HWND_TOP, 0, 320, 1920, 400, 0x0004) 102 | else: 103 | win32gui.SetWindowPos(mcWin, win32con.HWND_TOP, 530, 250, 900, 550, 0x0004) 104 | else: 105 | style &= ~(0x00800000 | 0x00400000 | 0x00040000 | 0x00020000 | 0x00010000 | 0x00800000) 106 | win32gui.SetWindowLong(mcWin, win32con.GWL_STYLE, style) 107 | win32gui.SetWindowPos(mcWin, win32con.HWND_TOP, 0, 0, 1920, 1080, 0x0004) 108 | return False 109 | 110 | def loadConfig(): 111 | global forestIs, beach, desert, plains, tundra, speechText, savesPath, volSlider, Jojoe, sid 112 | if sid == 0: 113 | fileConfig = expanduser("~") + "/.mcResetSettings.json" 114 | else: 115 | fileConfig = expanduser("~") + "/.mcResetSettings" + str(sid) + ".json" 116 | if os.path.isfile(fileConfig): 117 | try: 118 | readFile = open(fileConfig, 'r') 119 | contents = readFile.read() 120 | readFile.close() 121 | settings = json.loads(contents) 122 | except: 123 | setDefaults() 124 | saveConfig() 125 | readFile = open(fileConfig, 'r') 126 | contents = readFile.read() 127 | readFile.close() 128 | settings = json.loads(contents) 129 | if "savesPath" in settings: 130 | savesPath.set(settings["savesPath"]) 131 | if "speechText" in settings: 132 | speechText.set(settings["speechText"]) 133 | if "volume" in settings: 134 | volSlider.set(settings["volume"]) 135 | else: 136 | volSlider.set(50) 137 | if "windowed" in settings: 138 | windowed.set(settings['windowed']) 139 | if "resetHotkey" in settings: 140 | resetHotkey.set(settings['resetHotkey']) 141 | if "borderHotkey" in settings: 142 | borderHotkey.set(settings['borderHotkey']) 143 | if "fsMode" in settings: 144 | fsMode.set(settings["fsMode"]) 145 | if "fps" in settings: 146 | fps.set(settings['fps']) 147 | if "multiInstance" in settings: 148 | multiInstance.set(settings['multiInstance']) 149 | if "jojoeScenes" in settings: 150 | jojoeScenes = True 151 | if "easyDiff" in settings: 152 | easyDiff.set(settings['easyDiff']) 153 | else: 154 | setDefaults() 155 | saveConfig() 156 | 157 | def saveConfig(): 158 | global forestIs, beach, desert, plains, tundra, speechText, savesPath, sid 159 | if sid == 0: 160 | fileConfig = expanduser("~") + "/.mcResetSettings.json" 161 | else: 162 | fileConfig = expanduser("~") + "/.mcResetSettings" + str(sid) + ".json" 163 | writeFile = open(fileConfig, 'w') 164 | 165 | settings = { 166 | 'savesPath':savesPath.get(), 167 | 'speechText':speechText.get(), 168 | 'volume':volSlider.get(), 169 | 'windowed':windowed.get(), 170 | 'resetHotkey':resetHotkey.get(), 171 | 'fps':fps.get(), 172 | 'borderHotkey':borderHotkey.get(), 173 | 'fsMode':fsMode.get(), 174 | 'multiInstance':multiInstance.get(), 175 | 'easyDiff':easyDiff.get() 176 | } 177 | 178 | writeFile.write(json.dumps(settings)) 179 | writeFile.close() 180 | root.after(1000, saveConfig) 181 | 182 | def getMcWin(): 183 | #global mcPid 184 | return cachedWindow 185 | #return ahk.find_window(id=mcPid) 186 | 187 | def getMostRecentFile(dir): 188 | try: 189 | fileList = glob.glob(dir.replace('\\', "/")) 190 | if not fileList: 191 | return False 192 | latest = max(fileList, key=os.path.getctime) 193 | return latest 194 | except: 195 | return False; 196 | 197 | def resetRun(): 198 | global waitingForQuit 199 | waitingForQuit = True 200 | win = getMcWin() 201 | time.sleep(0.01) 202 | print('reset') 203 | win.send('{escape}') 204 | time.sleep(0.01) 205 | if windowed.get(): 206 | win.send('{shift Down}{tab}{shift Up}') 207 | else: 208 | ahk.send_input("{shift Down}{tab}{shift Up}") 209 | time.sleep(0.03) 210 | win.send('{enter}') 211 | 212 | def selectMC(): 213 | global mcPid, mcActualPid, cachedWindow 214 | mcPid = 0 215 | mcLabel.config(text='Click on Minecraft',fg='red') 216 | root.update() 217 | for i in range(20): 218 | win = ahk.active_window 219 | try: 220 | title = win.title.decode(sys.stdout.encoding) 221 | except: 222 | title = "aaaaaaaa" 223 | if title.startswith('Minecraft'): 224 | if title[-1].isdigit() or title.endswith('Singleplayer') or title.endswith('Multiplayer (LAN)'): 225 | mcPid = win.id 226 | mcActualPid = win.pid 227 | cachedWindow = win 228 | mcLabel.config(text='Found Minecraft (' + str(win.pid) + ')',fg='green') 229 | print('Found MC') 230 | return 231 | time.sleep(0.2) 232 | mcLabel.config(text='Could not find Minecraft',fg='red') 233 | 234 | fontStyle = tkFont.Font(family="TkDefaultFont", size=9) 235 | volSlider = Scale(root, from_=0, to=100, orient=HORIZONTAL, font=fontStyle) 236 | volSlider.grid(row=5, sticky=W, padx=80) 237 | volLabel = Label(root, text="TTS Volume:", font=fontStyle).grid(row=5, sticky=W, pady=1) 238 | loadConfig() 239 | root.columnconfigure(0, weight=1) 240 | root.columnconfigure(1, weight=1) 241 | 242 | #Checkbutton(root, text="Multi-instance mode", variable=multiInstance, font=fontStyle).grid(row=14, padx=0, sticky=E) 243 | Checkbutton(root, text="Unfocused mode", variable=windowed, font=fontStyle).grid(row=15, padx=0, sticky=E) 244 | Radiobutton(root, text="Window", padx=7, variable=fsMode, value=0, font=fontStyle).grid(row=13, padx=150, sticky=W) 245 | Radiobutton(root, text="Abuse Planar", padx=7, variable=fsMode, value=1, font=fontStyle).grid(row=16, padx=150, sticky=W) 246 | Radiobutton(root, text="Perfect Travel", padx=7, variable=fsMode, value=2, font=fontStyle).grid(row=15, padx=150, sticky=W) 247 | Radiobutton(root, text="Microlensing", padx=7, variable=fsMode, value=3, font=fontStyle).grid(row=14, padx=150, sticky=W) 248 | 249 | Checkbutton(root, text="Easy difficulty", variable=easyDiff, font=fontStyle).grid(row=14, sticky=E) 250 | 251 | statusLabel = Label(root, text="Running", fg='green', font=fontStyle) 252 | statusLabel.grid(row=1, padx=0, sticky=E) 253 | latestSeedLabel = Label(root, text="", font=fontStyle, fg='blue') 254 | latestSeedLabel.grid(row=2, padx=0, sticky=E) 255 | savesLabel = Label(root, text="Saves folder:", font=fontStyle) 256 | savesLabel.grid(row=1, sticky=W) 257 | savesPathEntry = Entry(root, width=32, exportselection=0, textvariable=savesPath, font=fontStyle).grid(row=2, padx=(5, 0), sticky=W) 258 | speechLabel = Label(root, text="Text to speak:", font=fontStyle).grid(row=3, sticky=W) 259 | speechTextEntry = Entry(root, width=30, exportselection=0, textvariable=speechText, font=fontStyle).grid(row=4, padx=(5, 0), sticky=W) 260 | hotkeyLabel = Label(root, text="Reset World Hotkey:", font=fontStyle) 261 | hotkeyLabel.grid(row=13, sticky=W) 262 | hotkeyEntry = Entry(root, width=20, exportselection=0, textvariable=resetHotkey, font=fontStyle).grid(row=14, padx=5, sticky=W) 263 | warningLabel = Label(root, text="", font=fontStyle) 264 | warningLabel.grid(row=21, sticky=W) 265 | Label(root, text="FPS you record at:", font=fontStyle).grid(row=3, sticky=E) 266 | Radiobutton(root, text="30", padx=7, variable=fps, value=30, font=fontStyle).grid(row=4, sticky=E) 267 | Radiobutton(root, text="60", padx=7, variable=fps, value=60, font=fontStyle).grid(row=5, sticky=E) 268 | 269 | borderHotkeyLabel = Label(root, text="Toggle Borderless Hotkey:", font=fontStyle) 270 | borderHotkeyLabel.grid(row=15, sticky=W) 271 | borderHotkeyEntry = Entry(root, width=20, exportselection=0, textvariable=borderHotkey, font=fontStyle).grid(row=16, padx=5, sticky=W) 272 | restartWarning = Label(root, text="Restart SeedMiner after changing hotkeys", font=fontStyle) 273 | restartWarning.grid(row=17, sticky=W) 274 | 275 | Button(root, text="Assign MC Window", command=selectMC).grid(row=17, sticky=E, padx=5) 276 | mcLabel = Label(root, text="No Minecraft window found", fg="red", font=fontStyle) 277 | mcLabel.grid(row=16, sticky=E) 278 | 279 | root.resizable(False, False) 280 | root.title("SeedMiner v" + version) 281 | 282 | def scanForMc(): 283 | global mcPid, mcActualPid, cachedWindow 284 | window = ahk.find_window(title=b'Minecraft* 1.16.1') 285 | if not window: 286 | window = ahk.find_window(title=b'Minecraft* 1.16.1 - Singleplayer') 287 | if not window: 288 | window = ahk.find_window(title=b'Minecraft* 1.16.1 - Multiplayer (LAN)') 289 | if not window: 290 | print('Can''t find MC F') 291 | return 292 | mcPid = window.id 293 | mcActualPid = int(window.pid) 294 | cachedWindow = window 295 | mcLabel.config(text='Found Minecraft (' + str(window.pid) + ')',fg='green') 296 | 297 | def canCheck(): 298 | global currentWorldName, waitingForQuit, lastCheckedWorld 299 | currentWorld = getMostRecentFile(savesPath.get() + "/*") 300 | # adv folder does not exist during early world creation 301 | if currentWorld == False: 302 | savesLabel.config(text="Saves folder: (currently invalid)", fg='red') 303 | return False 304 | if savesLabel['text'] == "Saves folder: (currently invalid)": 305 | savesLabel.config(text="Saves folder:", fg='black') 306 | if not (os.path.isdir(currentWorld + "/advancements")): 307 | return False 308 | if waitingForQuit == True: 309 | try: 310 | lockFile = open(currentWorld + "/session.lock", "r") 311 | # fails with error 13 (permission denied) if world is running 312 | lockFile.read() 313 | waitingForQuit = False 314 | makeWorld() 315 | except IOError as e: 316 | return False 317 | advCreation = os.stat(currentWorld + "/advancements").st_ctime 318 | timeElapsed = time.time() - advCreation; 319 | return timeElapsed < 5 and lastCheckedWorld != os.path.basename(currentWorld) and waitingForQuit == False 320 | 321 | def switchToScene(scene): 322 | if not jojoeScenes: 323 | return 324 | print("Switch to scene " + scene) 325 | headers = { 326 | 'Content-type': 'application/json', 327 | } 328 | data = '{"scene-name":"' + scene + '"}' 329 | requests.post('http://127.0.0.1:4445/emit/SetCurrentScene', headers=headers, data=data) 330 | 331 | def reportSeed(): 332 | global speechText, mcPid 333 | win32api.keybd_event(0x83, 0) 334 | win32api.Sleep(50) 335 | win32api.keybd_event(0x83, 0, win32con.KEYEVENTF_KEYUP) 336 | switchToScene('Stream') 337 | #title = ahk.active_window.title 338 | #win = ahk.find_window(id=mcPid) 339 | #win.send('{escape}') 340 | #time.sleep(0.05) 341 | #if not (title.startswith(b'Minecraft') and (str(title[-1]).isdigit() or title.endswith(b'Singleplayer') or title.endswith(b'Multiplayer (LAN)'))): 342 | # win.activate() 343 | 344 | if random.random() > 0.9999 and os.path.isfile('CFIUUS_FFUISM_FFFHJDSJS.mp3'): # not a virus, just a special sound ;) 345 | playsound.playsound('CFIUUS_FFUISM_FFFHJDSJS.mp3') 346 | return 347 | if speechText.get() == '{mp3}' and os.path.isfile('seed.mp3'): 348 | playsound.playsound('seed.mp3') 349 | else: 350 | speak.Volume = volSlider.get() 351 | speak.Speak(speechText.get()) 352 | 353 | def checkBiome(): 354 | global currentWorldName, waitingForQuit, lastCheckedWorld, currentWorldLabel 355 | print('Checking biome') 356 | currentWorld = getMostRecentFile(savesPath.get() + "/*") 357 | if currentWorld == False: 358 | print('Can''t check biome, no world') 359 | return 360 | lastCheckedWorld = os.path.basename(currentWorld) 361 | print(lastCheckedWorld) 362 | reportSeed() 363 | 364 | def waitForColours(): 365 | d = d3dshot.create(capture_output="pil") 366 | win = getMcWin() 367 | rect = win.rect 368 | if rect != (0, 0, 1920, 1080): 369 | rect = (rect[0] + 8, rect[1] + 24, rect[2] - 16, rect[3] - 32) 370 | rect = (rect[0] + 50, rect[1] + 200, rect[0] + 52, rect[1] + 202) 371 | root.update() 372 | img = d.screenshot(region=rect) 373 | color = img.getpixel((1, 1)) 374 | startTime = time.time() 375 | while (time.time() - startTime) < 3 and not (color[0] > 13 and color[0 < 18] and color[1] > 9 and color[1] < 15 and color[2] > 5 and color[2] < 10): 376 | img = d.screenshot(region=rect) 377 | print("hmm") 378 | color = img.getpixel((1, 1)) 379 | time.sleep(0.02) 380 | 381 | def waitForWorlds(): 382 | waitForColours() 383 | time.sleep(0.03) 384 | 385 | def makeWorld(): 386 | delay = 0.07 387 | if fps.get() == 60: 388 | delay /= 2 389 | elif fps.get() == 120: 390 | delay /= 4 391 | win = getMcWin() 392 | time.sleep(0.1) 393 | win.send('{tab}') 394 | time.sleep(delay) 395 | win.send('{enter}') 396 | waitForWorlds() 397 | time.sleep(delay) 398 | win.send('{tab}') 399 | time.sleep(delay) 400 | win.send('{tab}') 401 | time.sleep(delay) 402 | win.send('{tab}') 403 | time.sleep(delay) 404 | win.send('{enter}') 405 | time.sleep(delay) 406 | if os.path.isfile("attempts.txt"): 407 | countFile = open("attempts.txt", 'r+') 408 | counter = int(countFile.read()) 409 | countFile.seek(0) 410 | countFile.write(str(counter + 1)) 411 | countFile.close() 412 | time.sleep(delay) 413 | if easyDiff.get(): 414 | win.send('{tab}') 415 | time.sleep(delay) 416 | win.send('{tab}') 417 | time.sleep(delay) 418 | win.send('{enter}') 419 | time.sleep(delay) 420 | win.send('{enter}') 421 | time.sleep(delay) 422 | win.send('{enter}') 423 | time.sleep(delay) 424 | win.send('{tab}') 425 | time.sleep(delay) 426 | win.send('{tab}') 427 | time.sleep(delay) 428 | win.send('{tab}') 429 | time.sleep(delay) 430 | win.send('{tab}') 431 | time.sleep(delay) 432 | win.send('{tab}') 433 | time.sleep(delay) 434 | if fps.get() == 120: 435 | time.sleep(0.03) 436 | win.send('{enter}') 437 | time.sleep(delay) 438 | time.sleep(1) 439 | win32api.keybd_event(0x82, 0) 440 | win32api.Sleep(50) 441 | win32api.keybd_event(0x82, 0, win32con.KEYEVENTF_KEYUP) 442 | switchToScene('Loading Screen') 443 | 444 | def hotkeyReset(): 445 | if multiInstance.get(): 446 | print('toggle focus') 447 | if not str(ahk.active_window.pid) == str(mcActualPid): 448 | print(getMcWin().pid) 449 | getMcWin().activate() 450 | return 451 | win = getMcWin() 452 | 453 | currentWorld = getMostRecentFile(savesPath.get() + "/*") 454 | if currentWorld == False: 455 | print('No world found') 456 | return 457 | win32api.keybd_event(0x84, 0) 458 | win32api.Sleep(10) 459 | win32api.keybd_event(0x84, 0, win32con.KEYEVENTF_KEYUP) 460 | if windowed.get() and win.active: 461 | print('unfocus') 462 | root.focus_force() 463 | time.sleep(0.01) 464 | try: 465 | lockFile = open(currentWorld + "/session.lock", "r") 466 | # fails with error 13 (permission denied) if world is running 467 | lockFile.read() 468 | makeWorld() 469 | except IOError as e: 470 | if e.args[0] == 13: 471 | resetRun() 472 | else: 473 | print('Some generic error happened when checking world hotkey reset idk ' + e.args[1]) 474 | 475 | def toggleBorder(): 476 | global mcActualPid 477 | if multiInstance.get(): 478 | if not str(ahk.active_window.pid) == str(mcActualPid): 479 | return 480 | printable = set(string.printable) 481 | script = open("bd.txt", "r").read().replace("$PID_HERE$", str(mcActualPid)) 482 | if fsMode.get() == 3: 483 | script = script.replace("$RES$", "650, 0, 700, 1050") 484 | elif fsMode.get() == 2: 485 | script = script.replace("$RES$", "0, 0, 1940, 1070") 486 | elif fsMode.get() == 1: 487 | script = script.replace("$RES$", "0, 320, 1920, 400") 488 | else: 489 | script = script.replace("$RES$", "530, 250, 900, 550") 490 | script = ''.join(filter(lambda x: x in printable, script)) 491 | ahk.run_script(script, blocking=False) 492 | else: 493 | try: 494 | win32gui.EnumWindows(enumHandler, mcActualPid) 495 | except: 496 | return 497 | 498 | if resetHotkey.get() != '': 499 | bindings = [ 500 | [[resetHotkey.get()], None, hotkeyReset], 501 | [[borderHotkey.get()], None, toggleBorder] 502 | ] 503 | try: 504 | register_hotkeys(bindings) 505 | except: 506 | print('Error, invalid hotkey') 507 | start_checking_hotkeys() 508 | 509 | def checkHotkeys(): 510 | if not resetHotkey.get().lower() in vk_key_names: 511 | hotkeyLabel.config(text='Reset Hotkey (INVALID):', fg='red') 512 | restartWarning.config(fg='red') 513 | else: 514 | hotkeyLabel.config(text='Reset Hotkey:', fg='black') 515 | if not borderHotkey.get().lower() in vk_key_names: 516 | borderHotkeyLabel.config(text='Toggle Borderless Hotkey (INVALID):', fg='red') 517 | restartWarning.config(fg='red') 518 | else: 519 | borderHotkeyLabel.config(text='Toggle Borderless Hotkey:', fg='black') 520 | root.after(500, checkHotkeys) 521 | 522 | scanForMc() 523 | 524 | def mainLoop(): 525 | if Enabled and canCheck() and mcPid != 0: 526 | checkBiome() 527 | root.after(50, mainLoop) 528 | root.after(0, mainLoop) 529 | root.after(0, checkHotkeys) 530 | root.after(1000, saveConfig) 531 | root.geometry('410x260') 532 | root.mainloop() 533 | stop_checking_hotkeys() 534 | --------------------------------------------------------------------------------