Skip to content

Commit

Permalink
add: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
WEGFan committed Jul 13, 2020
0 parents commit 683b3ce
Show file tree
Hide file tree
Showing 5 changed files with 354 additions and 0 deletions.
105 changes: 105 additions & 0 deletions .gitignore
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
47 changes: 47 additions & 0 deletions README.md
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).
3 changes: 3 additions & 0 deletions build_win.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pyinstaller menu_music_randomizer.py ^
-F ^
-n GDMenuMusicRandomizer
196 changes: 196 additions & 0 deletions menu_music_randomizer.py
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)
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Pymem==1.2
colorama==0.4.3
pywin32==224

0 comments on commit 683b3ce

Please sign in to comment.