-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 683b3ce
Showing
5 changed files
with
354 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
.vscode/ | ||
.idea/ | ||
|
||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
|
||
# C extensions | ||
*.so | ||
|
||
# Distribution / packaging | ||
.Python | ||
env/ | ||
venv/ | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
|
||
# PyInstaller | ||
# Usually these files are written by a python script from a template | ||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | ||
*.manifest | ||
*.spec | ||
|
||
# Installer logs | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
|
||
# Unit test / coverage reports | ||
htmlcov/ | ||
.tox/ | ||
.coverage | ||
.cache | ||
nosetests.xml | ||
coverage.xml | ||
|
||
# Translations | ||
*.mo | ||
*.pot | ||
|
||
# Django stuff: | ||
*.log | ||
|
||
# Sphinx documentation | ||
docs/_build/ | ||
|
||
# PyBuilder | ||
target/ | ||
|
||
# ========================= | ||
# Operating System Files | ||
# ========================= | ||
|
||
# OSX | ||
# ========================= | ||
|
||
.DS_Store | ||
.AppleDouble | ||
.LSOverride | ||
|
||
# Thumbnails | ||
._* | ||
|
||
# Files that might appear on external disk | ||
.Spotlight-V100 | ||
.Trashes | ||
|
||
# Directories potentially created on remote AFP share | ||
.AppleDB | ||
.AppleDesktop | ||
Network Trash Folder | ||
Temporary Items | ||
.apdisk | ||
|
||
# Windows | ||
# ========================= | ||
|
||
# Windows image file caches | ||
Thumbs.db | ||
ehthumbs.db | ||
|
||
# Folder config file | ||
Desktop.ini | ||
|
||
# Recycle Bin used on file shares | ||
$RECYCLE.BIN/ | ||
|
||
# Windows Installer files | ||
*.cab | ||
*.msi | ||
*.msm | ||
*.msp | ||
|
||
# Windows shortcuts | ||
*.lnk |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# Geometry Dash Menu Music Randomizer | ||
|
||
This tool can randomize Geometry Dash menu music every time you return to the menu. Only Geometry Dash 2.113 is supported. | ||
|
||
## Install | ||
|
||
### Prerequisites | ||
|
||
- Windows 7 Service Pack 1 or later | ||
- [Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/zh-cn/download/details.aspx?id=48145) | ||
|
||
### Install from Python | ||
|
||
Prerequisites: | ||
|
||
- [Python 3.6](https://www.python.org/downloads/) or later | ||
- [pip](https://pip.pypa.io/en/stable/installing/) latest version | ||
|
||
1. Clone this project to your computer. | ||
|
||
```shell | ||
git clone https://github.com/WEGFan/Geometry-Dash-Menu-Music-Randomizer | ||
``` | ||
|
||
Or directly [download the zip file](https://github.com/WEGFan/Geometry-Dash-Menu-Music-Randomizer/archive/master.zip) | ||
2. Go to the project directory and run | ||
|
||
```bash | ||
pip install -r requirements.txt | ||
``` | ||
|
||
### Install from Windows releases (recommended) | ||
|
||
You can download the [latest releases](https://github.com/WEGFan/Geometry-Dash-Menu-Music-Randomizer/releases) for Windows so you don't need to install Python. | ||
|
||
## Usage | ||
|
||
1. Run the program. | ||
- from Python: `python menu_music_randomizer.py` | ||
- from Windows release: just click on the exe file | ||
2. Open Geometry Dash. | ||
3. The program will automatically create a folder and open it if you're running the first time. | ||
4. Copy your favorite music to the folder and enjoy! | ||
|
||
## Not working? | ||
|
||
If you encounter any problem, feel free to [create an issue](https://github.com/WEGFan/Geometry-Dash-Menu-Music-Randomizer/issues/new) or contact me on Discord (WEGFan#1440). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pyinstaller menu_music_randomizer.py ^ | ||
-F ^ | ||
-n GDMenuMusicRandomizer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
# -*- coding: utf-8 -*- | ||
import locale | ||
import msvcrt | ||
import os | ||
import random | ||
import sys | ||
import time | ||
import traceback | ||
from pathlib import Path | ||
from textwrap import dedent | ||
from typing import Optional, List, Union | ||
|
||
import colorama | ||
import pymem | ||
import win32gui | ||
import win32process | ||
from colorama import Fore, Style, Cursor | ||
|
||
__program_name__ = 'Geometry Dash Menu Music Randomizer' | ||
__author__ = 'WEGFan' | ||
__version__ = '1.0.0' | ||
|
||
|
||
def get_multi_level_offset(game: pymem.Pymem, offset_list: List[int]) -> int: | ||
"""Get the address result of [base+A]+B]+...]+C, [X] means the value at address X. | ||
:param game: a pymem.Pymem object, to load memory and get base address | ||
:param offset_list: a list contains a sequence of hex offset values | ||
:return: the address result | ||
""" | ||
if not isinstance(offset_list, list): | ||
raise TypeError("offset list must be 'list'") | ||
if len(offset_list) == 0: | ||
raise ValueError("offset list must not be empty") | ||
base_address = game.process_base.lpBaseOfDll | ||
address = base_address | ||
for offset in offset_list[:-1]: | ||
address = game.read_uint(address + offset) | ||
address += offset_list[-1] | ||
return address | ||
|
||
|
||
def get_process_id_by_window(class_: Optional[str], title: Optional[str]) -> Optional[int]: | ||
"""Get process id by windows class and/or title. | ||
If class or title is None, only search by another parameter. | ||
:param class_: Window class | ||
:param title: Window title | ||
:return: process id if window found, otherwise None | ||
""" | ||
window = win32gui.FindWindow(class_, title) | ||
if not window: | ||
return None | ||
(_, pid) = win32process.GetWindowThreadProcessId(window) | ||
return pid | ||
|
||
|
||
def pymem_hook(): | ||
"""Hook Pymem module""" | ||
# suppress outputs | ||
pymem.process.print = lambda *args, **kwargs: None | ||
pymem.logger.setLevel(9999999) | ||
|
||
# fix overflow error on windows 7 caused by process handle, don't know why it works | ||
@property | ||
def process_handle(self): | ||
if isinstance(self._process_handle, int): | ||
# on windows 7 process_handle returns a 8-byte value, so take low 4 bytes to prevent overflow error | ||
return self._process_handle & 0xffffffff | ||
return self._process_handle | ||
|
||
@process_handle.setter | ||
def process_handle(self, value): | ||
self._process_handle = value | ||
|
||
pymem.Pymem.process_handle = process_handle | ||
|
||
|
||
def main(): | ||
pymem_hook() | ||
|
||
os.system(f'title {__program_name__}') | ||
print(dedent( | ||
f'''\ | ||
{Fore.CYAN}{__program_name__} v{__version__} by {__author__} | ||
{Fore.MAGENTA}Randomize menu music every time you return to the menu. | ||
Usage: Simply copy files to <game installation folder>\\Resources\\Menu Music. | ||
The program will help you create and open it if it doesn't exist. | ||
Known issue: | ||
- Music will reset when returning to the title screen | ||
''' | ||
)) | ||
game = pymem.Pymem() | ||
while True: | ||
no_music_message_printed = False # only print hint message once | ||
while True: | ||
pid = get_process_id_by_window('GLFW30', 'Geometry Dash') | ||
if not pid: | ||
if not no_music_message_printed: | ||
print(f'{Fore.RED}Waiting for Geometry Dash... Please make sure the game is opened.') | ||
no_music_message_printed = True | ||
time.sleep(1) | ||
else: | ||
game.open_process_from_id(pid) | ||
print(f'{Fore.GREEN}Game loaded.') | ||
break | ||
|
||
# get os encoding to correctly decode ascii characters | ||
os_encoding = locale.getpreferredencoding() | ||
game_file_path = Path(game.process_base.filename.decode(os_encoding)).resolve() | ||
game_file_size = game_file_path.stat().st_size | ||
|
||
# check whether game version is 2.113 by file size | ||
correct_game_file_size = 6854144 | ||
if game_file_size != correct_game_file_size: | ||
print(f'{Fore.RED}Your game version is not 2.113 ' | ||
f'(exe size is {game_file_size} bytes, should be {correct_game_file_size} bytes)') | ||
print(f'{Fore.RED}Press any key to exit the program.') | ||
msvcrt.getch() | ||
sys.exit(1) | ||
|
||
# patch the memory | ||
offsets = [ | ||
0x24530, 0x24977, 0x249a4, 0xce8a8, 0x14bb1c, 0x1583ec, 0x18cefc, | ||
0x1907ef, 0x1ddf5c, 0x20d9e2, 0x21f989, 0x22471b, 0x22b308 | ||
] | ||
new_address = game.allocate(4 * 1024) | ||
for offset in offsets: | ||
address = get_multi_level_offset(game, [offset]) | ||
game.write_uint(address, new_address) | ||
game.write_string(new_address, 'menuLoop.mp3' + '\x00') | ||
|
||
game_directory = game_file_path.parent | ||
music_directory = game_directory / 'Resources' / 'Menu Music' | ||
if not music_directory.exists(): | ||
os.mkdir(music_directory) | ||
os.startfile(music_directory) | ||
|
||
print(f'{Fore.GREEN}Searching music in {music_directory}') | ||
|
||
no_music_message_printed = False | ||
music_found_previously = False | ||
try: | ||
while True: | ||
music_files = [item for item in music_directory.glob('*') if item.is_file()] | ||
|
||
if not music_files: | ||
if not no_music_message_printed: | ||
print(fr'{Fore.RED}There are no music files in Resources\Menu Music directory. ' | ||
'Menu song restored to default.') | ||
no_music_message_printed = True | ||
game.write_string(new_address, 'menuLoop.mp3' + '\x00') # restore to default menu music | ||
music_found_previously = False | ||
else: | ||
if music_found_previously: | ||
# clear the previous line to make "Found x music" always on the same line | ||
print(Cursor.UP() + colorama.ansi.clear_line(), end='') | ||
print(f'{Fore.GREEN}Found {len(music_files)} music.') | ||
music_file = random.choice(music_files) | ||
game.write_string(new_address, str(music_file.resolve()) + '\x00') | ||
no_music_message_printed = False | ||
music_found_previously = True | ||
|
||
time.sleep(1) | ||
except pymem.exception.MemoryWriteError as err: | ||
# check whether exception is caused by game close or unexpected errors | ||
all_process_id = [process.th32ProcessID for process in pymem.process.list_processes()] | ||
if game.process_id not in all_process_id: | ||
continue | ||
raise | ||
finally: | ||
game.close_process() | ||
|
||
|
||
if __name__ == '__main__': | ||
try: | ||
colorama.init(autoreset=False) | ||
print(Style.BRIGHT, end='') | ||
main() | ||
except (KeyboardInterrupt, EOFError) as err: | ||
sys.exit() | ||
except Exception as err: | ||
github_new_issue_url = 'https://github.com/WEGFan/Geometry-Dash-Menu-Music-Randomizer/issues/new' | ||
print(dedent( | ||
f'''\ | ||
{Fore.RED}Oops, something went wrong... | ||
Create an issue on Github ({github_new_issue_url}) with the following yellow lines to let me know what happened! | ||
''' | ||
)) | ||
print(f'{Fore.YELLOW}{traceback.format_exc()}') | ||
print(f'{Fore.RED}Press any key to exit the program.') | ||
msvcrt.getch() | ||
sys.exit(1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Pymem==1.2 | ||
colorama==0.4.3 | ||
pywin32==224 |