Compare commits
No commits in common. "0260d13ac1185a16562edd4f2b8fc743ad48e013" and "b2c41031a95da0ab73a26fb8471454957d46b144" have entirely different histories.
0260d13ac1
...
b2c41031a9
32
README.md
32
README.md
|
@ -2,16 +2,40 @@
|
|||
The Launcher for my mod pack
|
||||
|
||||
# General TODO List
|
||||
*[ ] Everything
|
||||
*[ ] Make a ToDo list and possibly create issues
|
||||
* [ ] Frontend
|
||||
* [ ] Port the GUI (and fix the design differences between Desktop environments) or write a new one (→ `gui.py`)
|
||||
* [ ] 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 for each individual file can be found in the same directory
|
||||
## Directories
|
||||
* __devtools__ contains custom development tools
|
||||
* __devtools__ contains development tools
|
||||
* __lib__ contains libraries for Minecraft and the launcher
|
||||
* __res__ contains the resources/assets used by Minecraft
|
||||
* __run__ contains the running directory / game directory
|
||||
## Files
|
||||
* __main.py__ the main file of the Launcher
|
||||
|
||||
# Per-File TODO list
|
||||
# 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
|
||||
## Files:
|
||||
* __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.
|
||||
* __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.
|
||||
* __rmpyc__ removes all `.pyc` files in the current working directory and all subdirectories.
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
#!/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()
|
|
@ -1,10 +0,0 @@
|
|||
#!/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
|
||||
This directory contains libraries, natives and runtimes used by the launcher and Minecraft.
|
||||
## Directories
|
||||
* __jar__ the libraries for minecraft (and the version jar ’cause it’s on the cp as well...)
|
||||
* __jar__ the libraries for minecraft (and the version jar...)
|
||||
* __launcher__ everything used by the launcher
|
||||
* __natives__ the natives for minecraft
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# lib/launcher
|
||||
This directory contains the libraries and runtimes used by the launcher.
|
||||
##Directories
|
||||
* __java__ the Java components
|
||||
* __python__ the python components
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# lib/launcher/java
|
||||
##Directories
|
||||
* __runtime__ will contain a java runtime
|
|
@ -0,0 +1,2 @@
|
|||
# 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.
|
|
@ -0,0 +1,52 @@
|
|||
# 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
|
|
@ -0,0 +1,125 @@
|
|||
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
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
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()
|
|
@ -0,0 +1,2 @@
|
|||
# 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.
|
|
@ -0,0 +1,49 @@
|
|||
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})
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#!/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.
|
||||
|
||||
## Files
|
||||
* __gather_assets.sh__ will conveniently place all your Minecraft assets here for you. [Should be moved to devtools...]
|
||||
* __gather_assets.sh__ will conveniently place all your Minecraft assets here for you.
|
||||
|
|
Reference in New Issue