Compare commits
2 Commits
b2c41031a9
...
0260d13ac1
Author | SHA1 | Date |
---|---|---|
BodgeMaster | 0260d13ac1 | |
BodgeMaster | 16fe54de9e |
32
README.md
32
README.md
|
@ -2,40 +2,16 @@
|
||||||
The Launcher for my mod pack
|
The Launcher for my mod pack
|
||||||
|
|
||||||
# General TODO List
|
# General TODO List
|
||||||
* [ ] Frontend
|
*[ ] Everything
|
||||||
* [ ] Port the GUI (and fix the design differences between Desktop environments) or write a new one (→ `gui.py`)
|
*[ ] Make a ToDo list and possibly create issues
|
||||||
* [ ] Write a CLI / GUI hybrid program to handle the libraries (→ `main.py`)
|
|
||||||
* [ ] Library development
|
|
||||||
* [ ] Write a new game launching Library (implementation started) (→ `mchandler.py`)
|
|
||||||
* [x] Write a new authentication library (→ `yggdrasil.py`)
|
|
||||||
* [ ] Write a config handler (important features done) (→ `cfghandler.py`)
|
|
||||||
* [ ] Create server startup request library
|
|
||||||
* [ ] Modpack update helper (→ `updater.py`)
|
|
||||||
* [ ] Develop server side tools
|
|
||||||
* [ ] Server startup request listener
|
|
||||||
* [ ] Modpack update helper
|
|
||||||
* [ ] Web files (HTML files to be shown in and cached by the launcher)
|
|
||||||
* [ ] Server status page
|
|
||||||
* [ ] News page
|
|
||||||
* [ ] Credits page
|
|
||||||
* [ ] License!?
|
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
Documentation for each individual file can be found in the same directory
|
Documentation for each individual file can be found in the same directory
|
||||||
## Directories
|
## Directories
|
||||||
* __devtools__ contains development tools
|
* __devtools__ contains custom development tools
|
||||||
* __lib__ contains libraries for Minecraft and the launcher
|
* __lib__ contains libraries for Minecraft and the launcher
|
||||||
* __res__ contains the resources/assets used by Minecraft
|
* __res__ contains the resources/assets used by Minecraft
|
||||||
* __run__ contains the running directory / game directory
|
* __run__ contains the running directory / game directory
|
||||||
## Files
|
## Files
|
||||||
* __main.py__ the main file of the Launcher
|
|
||||||
|
|
||||||
# TODO list
|
# Per-File TODO list
|
||||||
* __main.py__
|
|
||||||
* [ ] run the updater
|
|
||||||
* [ ] CLI
|
|
||||||
* [ ] GUI
|
|
||||||
* [ ] decide whether to launch the GUI
|
|
||||||
* [ ] launch GUI (from `gui.py`)
|
|
||||||
* [ ] get user input from GUI
|
|
||||||
* [ ] Act according to the user input
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Development tools
|
# Development tools
|
||||||
## Files:
|
## Files:
|
||||||
* __fake_java.py__ can be used to get the command used to start Minecraft. When run, it will create a file called `fake_java_out.txt` in its working directory.
|
* __harvest_arguments.sh__ can be used to get the command used to start Minecraft. Just rename the Java executable to <name>_backup and put this file in its place. Output files will be created in the running directory. They are named by the current date.
|
||||||
* __rmpyc__ removes all `.pyc` files in the current working directory and all subdirectories.
|
* __rmpyc__ removes all `.pyc` files in the current working directory and all subdirectories.
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
#!/usr/bin/python2
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
first_argument=True
|
|
||||||
out=open("fake_java_out.txt", "w")
|
|
||||||
for element in range(len(sys.argv)):
|
|
||||||
out.write(sys.argv[element])
|
|
||||||
first_argument=False
|
|
||||||
if not first_argument:
|
|
||||||
out.write(" ")
|
|
||||||
out.close()
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
NOW=`date`
|
||||||
|
echo "Executable: $0" > "$NOW.log"
|
||||||
|
echo "Timestamp: $NOW" >> "$NOW.log"
|
||||||
|
echo -n "Arguments: " >> "$NOW.log"
|
||||||
|
echo "$*" | sed ':a;s/ -/\n >>>-/;ta' >> "$NOW.log"
|
||||||
|
echo "Full command: $0 $*" >> "$NOW.log"
|
||||||
|
BACKUPCMD="`readlink -f \`which $0\``_backup"
|
||||||
|
echo "Actual command: $BACKUPCMD $*" >> "$NOW.log"
|
||||||
|
echo "$BACKUPCMD $*" | sh
|
|
@ -1,6 +1,6 @@
|
||||||
# lib
|
# lib
|
||||||
This directory contains libraries, natives and runtimes used by the launcher and Minecraft.
|
This directory contains libraries, natives and runtimes used by the launcher and Minecraft.
|
||||||
## Directories
|
## Directories
|
||||||
* __jar__ the libraries for minecraft (and the version jar...)
|
* __jar__ the libraries for minecraft (and the version jar ’cause it’s on the cp as well...)
|
||||||
* __launcher__ everything used by the launcher
|
* __launcher__ everything used by the launcher
|
||||||
* __natives__ the natives for minecraft
|
* __natives__ the natives for minecraft
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
# lib/launcher
|
# lib/launcher
|
||||||
This directory contains the libraries and runtimes used by the launcher.
|
This directory contains the libraries and runtimes used by the launcher.
|
||||||
##Directories
|
##Directories
|
||||||
* __java__ the Java components
|
|
||||||
* __python__ the python components
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
# lib/launcher/java
|
|
||||||
##Directories
|
|
||||||
* __runtime__ will contain a java runtime
|
|
|
@ -1,2 +0,0 @@
|
||||||
# libs/launcher/java/runtime
|
|
||||||
I intend to package a java runtime with the launcher as it helps to prevent some issues. For instance, Minecraft runs only on Java 8 and below. If the default JRE on the operating system’s path is not Java 8, Minecraft will not even launch sucessfully.
|
|
|
@ -1,52 +0,0 @@
|
||||||
# lib/launcher/python
|
|
||||||
## Directories:
|
|
||||||
* __runtime__ will contain a python runtime
|
|
||||||
## Files
|
|
||||||
* __gui.py__ will contain the GUI (Wow! Really?). The GUI will be accessed by `main.py`
|
|
||||||
* __mchandler.py__ will be the launcher library. This time, it will not be a runnable hybrid library
|
|
||||||
* __updater.py__ will be used for updating the mod pack and possibly the launcher as well
|
|
||||||
* __cfghandler.py__ will deal with config files
|
|
||||||
* __yggdrasil.py__ is a nearly feature-complete Minecraft authentication library
|
|
||||||
|
|
||||||
# TODO list
|
|
||||||
|
|
||||||
* __gui.py__
|
|
||||||
* [ ] add a function to open the GUI
|
|
||||||
* [ ] add functions for reading values from the GUI
|
|
||||||
* [ ] add functions for writing values to the GUI
|
|
||||||
* [ ] put the port of the GUI here or in a new file?
|
|
||||||
* __mchandler.py__
|
|
||||||
* [ ] add a launch function using the access token
|
|
||||||
* ~~add a function to verify a given access token~~ _moved to `yggdrasil.py`_
|
|
||||||
* [x] add functtions to pass configuration data
|
|
||||||
* ~~launch command (with escape sequences for non-static values (configuration and user data)~~ _moved_
|
|
||||||
* ~~RAM amount~~
|
|
||||||
* ~~Game Directory~~
|
|
||||||
* ~~Additional arguments~~
|
|
||||||
* _handled by a dictionary_
|
|
||||||
* [ ] add the launch command builder
|
|
||||||
* [ ] add the launch command setter
|
|
||||||
* [x] add RAM auto detection
|
|
||||||
* [ ] add proper error handling
|
|
||||||
|
|
||||||
* __updater.py__
|
|
||||||
* [ ] add a function to determine whether updates are available
|
|
||||||
* [ ] add a function to download updates
|
|
||||||
* [ ] add a function to update the launcher
|
|
||||||
* [ ] add a function to update the mod pack
|
|
||||||
* [ ] add different types of updates
|
|
||||||
* [ ] required launcher updates
|
|
||||||
* [ ] required mod pack updates
|
|
||||||
* [ ] optional launcher updates
|
|
||||||
* [ ] optional mod pack updates
|
|
||||||
* [ ] setup change updates (used to install/uninstall components)
|
|
||||||
* [ ] add initial mod pack install by applying a setup change update **→** main.py
|
|
||||||
* __cfghandler.py__
|
|
||||||
* [x] provide read and write functions for config values
|
|
||||||
* [x] add a function to set the config file path
|
|
||||||
* [x] add functions to load and store the configuration to the file
|
|
||||||
* [ ] implement functions for config updates
|
|
||||||
* [ ] raise adequate exceptions where needed
|
|
||||||
* __yggdrasil.py__
|
|
||||||
* [x] add a function to verify user credentials and get the access token
|
|
||||||
* [x] add a function to verify a given acces token
|
|
|
@ -1,125 +0,0 @@
|
||||||
import json, os, errno
|
|
||||||
|
|
||||||
# self-explanatory
|
|
||||||
default_config = {}
|
|
||||||
|
|
||||||
# This will contain the current configuration after loading the file and
|
|
||||||
# combining it with the default configuration. All changes will be done here
|
|
||||||
config = None
|
|
||||||
|
|
||||||
# The file to work with
|
|
||||||
config_file_path = None
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# file interaction
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
# set the config file to use
|
|
||||||
def set_file_path(file_path):
|
|
||||||
global config_file_path
|
|
||||||
# error handling
|
|
||||||
if os.path.exists(file_path):
|
|
||||||
if os.path.isdir(file_path):
|
|
||||||
raise IOError(errno.EISDIR, "Is a directory", file_path)
|
|
||||||
if os.access(file_path, os.R_OK) and os.access(file_path, os.W_OK):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise IOError(errno.EACCES, "Read AND write permissions required", file_path)
|
|
||||||
else:
|
|
||||||
config_file = open(file_path, "w")
|
|
||||||
config_file.write("{}")
|
|
||||||
config_file.close()
|
|
||||||
# actually setting the file path
|
|
||||||
config_file_path = file_path
|
|
||||||
|
|
||||||
# loads the json file at file_path and updates the default configuration with it
|
|
||||||
# overwrites any existing configuration adjustments
|
|
||||||
# TODO: handle None as file name (raise an exception)
|
|
||||||
def load():
|
|
||||||
global config_file_path
|
|
||||||
global config
|
|
||||||
global default_config
|
|
||||||
# read the config file
|
|
||||||
config_file = open(config_file_path,"r")
|
|
||||||
config_string = config_file.read()
|
|
||||||
config_file.close()
|
|
||||||
# combine with default config
|
|
||||||
config = default_config.copy()
|
|
||||||
config.update(json.loads(config_string))
|
|
||||||
|
|
||||||
|
|
||||||
# stores the current configuration to the file at file_path
|
|
||||||
# TODO: handle None (raise an exception)
|
|
||||||
def store():
|
|
||||||
global config
|
|
||||||
global config_file_path
|
|
||||||
if not config is None:
|
|
||||||
config_file = open(config_file_path, "w")
|
|
||||||
config_file.write(json.dumps(config))
|
|
||||||
config_file.close()
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# default config / config setup
|
|
||||||
# These functions should be used BEFORE loading the config file
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
#defines a new option group in the default config
|
|
||||||
# TODO: handle existing groups (raise an exception)
|
|
||||||
def define_group(group):
|
|
||||||
global default_config
|
|
||||||
default_config.update({group : {}})
|
|
||||||
|
|
||||||
# defines a new option in the default config
|
|
||||||
# TODO: handle existing options (raise an exception)
|
|
||||||
def define_option(group, name, default_value):
|
|
||||||
global default_config
|
|
||||||
default_config[group].update({name : default_value})
|
|
||||||
|
|
||||||
# self-explanatory
|
|
||||||
def clear_definitions():
|
|
||||||
global default_config
|
|
||||||
default_config = {}
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# adjust an existing configuration for new needs
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
# remove the given option from the configuration and return the value to allow
|
|
||||||
# for conversion if desired
|
|
||||||
# TODO: implement
|
|
||||||
def remove_option(group, name):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# move the value of an option to another place
|
|
||||||
# TODO: implement
|
|
||||||
def rename_option(old_group, old_name, new_group, new_name):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# remove an entire group from the configuration, never to be seen again
|
|
||||||
# TODO: implement
|
|
||||||
def remove_group(group):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# self-explanatory
|
|
||||||
# TODO: implement
|
|
||||||
def rename_group(old_group, new_group):
|
|
||||||
pass
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# config interaction
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
# get the value of the given option
|
|
||||||
def fetch(group, name):
|
|
||||||
global config
|
|
||||||
if config is None:
|
|
||||||
load()
|
|
||||||
return config[group][name]
|
|
||||||
|
|
||||||
# set the value of a config option
|
|
||||||
def set(group, name, value):
|
|
||||||
global config
|
|
||||||
if config is None:
|
|
||||||
load()
|
|
||||||
config[group][name] = value
|
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
import os, psutil
|
|
||||||
|
|
||||||
# java -Xmx$RAM$ -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -Xmn128M -Djava.library.path=$NATIVES$ -cp $LIBRARIES$ net.minecraft.launchwrapper.Launch --username $USERNAME$ --version 1.7.10-Forge10.13.4.1614-1.7.10 --gameDir $GAMEDIR$ --assetsDir $ASSETS$ --assetIndex 1.7.10 --uuid $UUID$ --accessToken $ACCESSTOKEN$ --userProperties {} --userType mojang --width $WINDOWWIDTH$ --height $WINDOWHEIGHT$ --tweakClass cpw.mods.fml.common.launcher.FMLTweaker
|
|
||||||
# $RAM$ the amount of RAM with postfix (k, M, G ...)
|
|
||||||
# $WINDOWWIDTH$, $WINDOWHEIGHT$ window width and height in pixels without the WM’s decoration
|
|
||||||
# $USERNAME$ the username as shown in-game
|
|
||||||
# $UUID$ the player’s uuid
|
|
||||||
# $ACCESSTOKEN$ a valid accessToken
|
|
||||||
# $NATIVES$ the directory containing the natives
|
|
||||||
# $LIBRARIES$ a colon-separated (semicolon-separated on Windowze) list of library jars
|
|
||||||
# $GAMEDIR$ the game directory IMPORTANT! Change the cwd to that directory before launch. Some mods are coded to find the standard paths by using the cwd.
|
|
||||||
# $ASSETS$ the directory containing the assets
|
|
||||||
game_properties = {
|
|
||||||
"window width": "1366",
|
|
||||||
"window height": "768",
|
|
||||||
"ram": "4G",
|
|
||||||
"username": None,
|
|
||||||
"uuid": None,
|
|
||||||
"access token": None,
|
|
||||||
"native directory": "lib/natives",
|
|
||||||
"library directory": "lib/jar", # will be transformed by the launch function
|
|
||||||
"asset directory": "res",
|
|
||||||
"game directory": "run",
|
|
||||||
"java executable": "java",
|
|
||||||
"additional arguments": ""}
|
|
||||||
|
|
||||||
command = None
|
|
||||||
|
|
||||||
full_command = None
|
|
||||||
|
|
||||||
def autoram(minram, maxram, balance):
|
|
||||||
ram_available = psutil.virtual_memory().available
|
|
||||||
# not enough memory
|
|
||||||
if ram_available < minram:
|
|
||||||
# TODO: raise an exception
|
|
||||||
return -1
|
|
||||||
# plenty of memory
|
|
||||||
elif ram_available > maxram+balance:
|
|
||||||
return maxram
|
|
||||||
# enough memory but we do not want to occupy all
|
|
||||||
elif float(ram_available)*float(maxram)/float(maxram+balance) > minram:
|
|
||||||
return int(float(ram_available)*float(maxram)/float(maxram+balance))
|
|
||||||
# barely enough memory
|
|
||||||
elif ram_available > minram or ram_available == minram:
|
|
||||||
return ram_available
|
|
||||||
|
|
||||||
def launch():
|
|
||||||
global command
|
|
||||||
if command is None:
|
|
||||||
return 1
|
|
||||||
# TODO: Raise an exception
|
|
||||||
return os.system(command)
|
|
||||||
|
|
||||||
def set_parameter(parameter, value):
|
|
||||||
global game_propertoies
|
|
||||||
game_properties[parameter] = value
|
|
||||||
build_full_command()
|
|
||||||
|
|
||||||
def build_full_command():
|
|
||||||
global game_properties
|
|
||||||
global command
|
|
||||||
global full_command
|
|
||||||
# TODO: implement
|
|
||||||
|
|
||||||
def set_command(minecraft_command):
|
|
||||||
global command
|
|
||||||
command = minecraft_command
|
|
||||||
build_full_command()
|
|
|
@ -1,2 +0,0 @@
|
||||||
# lib/launcher/python/runtime
|
|
||||||
I intend to ship the launcher with a Python runtime included because not all of the Windowze noobs out there will want to install Python themselves.
|
|
|
@ -1,49 +0,0 @@
|
||||||
import json, urllib2
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# This software can be used privately for any purpose.
|
|
||||||
# It may be shared publickly as long as you give credit and provide
|
|
||||||
# a link to the original source.
|
|
||||||
# Do not use this for malicious purposes.
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
# This allows for easier addition of new actions
|
|
||||||
discardResponse = {
|
|
||||||
"validate": True,
|
|
||||||
"invalidate": True,
|
|
||||||
"signout": True,
|
|
||||||
"authenticate": False,
|
|
||||||
"refresh": False
|
|
||||||
}
|
|
||||||
|
|
||||||
# Interaction with the Mojang authentication server
|
|
||||||
def interact(action, data):
|
|
||||||
if discardResponse[action]:
|
|
||||||
try:
|
|
||||||
urllib2.urlopen(urllib2.Request(url="https://authserver.mojang.com/"+action, data=json.dumps(data).encode(),headers={"Content-Type": "application/json"})).read().decode()
|
|
||||||
return True, {}
|
|
||||||
except urllib2.HTTPError:
|
|
||||||
return False, {}
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
serverResponse = json.loads(urllib2.urlopen(urllib2.Request(url="https://authserver.mojang.com/"+action, data=json.dumps(data).encode(),headers={"Content-Type": "application/json"})).read().decode())
|
|
||||||
return True, serverResponse
|
|
||||||
except urllib2.HTTPError:
|
|
||||||
return False, {}
|
|
||||||
|
|
||||||
# Wrapper functions for interact
|
|
||||||
def authenticateUser(logname, passwd):
|
|
||||||
return interact("authenticate", {'username': logname, 'password': passwd, 'agent': {'version': 1, 'name': 'Minecraft'}})
|
|
||||||
|
|
||||||
def validateToken(accessToken, clientToken):
|
|
||||||
return interact("validate", {'accessToken': accessToken, 'clientToken': clientToken})
|
|
||||||
|
|
||||||
def refreshToken(accessToken, clientToken):
|
|
||||||
return interact("refresh", {'accessToken': accessToken, 'clientToken': clientToken})
|
|
||||||
|
|
||||||
def deauthenticateUser(logname, passwd):
|
|
||||||
return interact("signout", {'username': logname, 'password': passwd})
|
|
||||||
|
|
||||||
def invalidateToken(accessToken, clientToken):
|
|
||||||
return interact("invalidate", {'accessToken': accessToken, 'clientToken': clientToken})
|
|
||||||
|
|
44
main.py
44
main.py
|
@ -1,44 +0,0 @@
|
||||||
#!/usr/bin/python2
|
|
||||||
|
|
||||||
#proposed launcher startup sequence
|
|
||||||
#
|
|
||||||
# - look at command line arguments
|
|
||||||
# possible arguments:
|
|
||||||
# --ui:gui, --ui:cli-interactive, --ui:args-only, --ui:auto (default: auto)
|
|
||||||
# --update:if-available, --update:force, --update:noupdate (default: if-available)
|
|
||||||
# --launch, -l (default: not present)
|
|
||||||
# --silent, --quiet, -s, -q (default: only present in interactive cli mode)
|
|
||||||
#
|
|
||||||
# - decide whether to launch the GUI or not (either by command line argument
|
|
||||||
# or by determining if running from terminal)
|
|
||||||
#
|
|
||||||
# - if updating: install update and restart if in GUI mode or interactive CLI mode,
|
|
||||||
# update and exit in args-only mode
|
|
||||||
#
|
|
||||||
# - launch GUI or launch interactive CLI or follow instructions from the
|
|
||||||
# command line:
|
|
||||||
# If --ui:args-only is present, the default actions are update:if-available
|
|
||||||
# and exit without launching. If -l is present, the GUI or
|
|
||||||
# interactive CLI are only shown while updating and Minecraft will launch
|
|
||||||
# automatically.
|
|
||||||
#
|
|
||||||
# - load configuration
|
|
||||||
#
|
|
||||||
# - handle user actions (only in GUI or interactive mode, skipped with -l)
|
|
||||||
# - update and store configuration
|
|
||||||
# - GUI interaction
|
|
||||||
# - wait for launch or close actions
|
|
||||||
#
|
|
||||||
# - launch the game or exit
|
|
||||||
|
|
||||||
# import standard libraries
|
|
||||||
import sys
|
|
||||||
# adjust path
|
|
||||||
sys.path.append("./lib/launcher/python")
|
|
||||||
# import libraries from new path
|
|
||||||
import yggdrasil, cfghandler, mchandler
|
|
||||||
|
|
||||||
# temporary test stuff
|
|
||||||
a,b = yggdrasil.authenticateUser(raw_input("Username: "), raw_input("Password: "))
|
|
||||||
if a:
|
|
||||||
print b["accessToken"]
|
|
|
@ -2,4 +2,4 @@
|
||||||
This directory will contain the resources used by Minecraft.
|
This directory will contain the resources used by Minecraft.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
* __gather_assets.sh__ will conveniently place all your Minecraft assets here for you.
|
* __gather_assets.sh__ will conveniently place all your Minecraft assets here for you. [Should be moved to devtools...]
|
||||||
|
|
Reference in New Issue