2021-08-08 16:26:46 +00:00
import argparse
import os , sys
import re
import atexit
from subprocess import Popen
from shutil import copyfile
2021-08-08 20:36:12 +00:00
from time import strftime
2021-11-10 14:35:43 +00:00
import logging
2021-08-08 16:26:46 +00:00
2021-08-08 16:57:50 +00:00
import requests
2021-08-08 16:26:46 +00:00
import Utils
atexit . register ( input , " Press enter to exit. " )
2021-08-08 16:57:50 +00:00
# 1 or more digits followed by m or g, then optional b
max_heap_re = re . compile ( r " ^ \ d+[mMgG][bB]?$ " )
2021-08-08 16:26:46 +00:00
def prompt_yes_no ( prompt ) :
yes_inputs = { ' yes ' , ' ye ' , ' y ' }
no_inputs = { ' no ' , ' n ' }
while True :
choice = input ( prompt + " [y/n] " ) . lower ( )
if choice in yes_inputs :
return True
elif choice in no_inputs :
return False
else :
print ( ' Please respond with " y " or " n " . ' )
2021-08-08 16:57:50 +00:00
# Find Forge jar file; raise error if not found
def find_forge_jar ( forge_dir ) :
2021-08-08 16:26:46 +00:00
for entry in os . scandir ( forge_dir ) :
if " .jar " in entry . name and " forge " in entry . name :
2021-11-10 14:35:43 +00:00
logging . info ( f " Found forge .jar: { entry . name } " )
2021-08-08 16:57:50 +00:00
return entry . name
raise FileNotFoundError ( f " Could not find forge .jar in { forge_dir } . " )
2021-08-08 16:26:46 +00:00
2021-08-08 16:57:50 +00:00
# Create mods folder if needed; find AP randomizer jar; return None if not found.
def find_ap_randomizer_jar ( forge_dir ) :
2021-08-08 16:26:46 +00:00
mods_dir = os . path . join ( forge_dir , ' mods ' )
if os . path . isdir ( mods_dir ) :
ap_mod_re = re . compile ( r " ^aprandomizer-[ \ d \ .]+ \ .jar$ " )
for entry in os . scandir ( mods_dir ) :
match = ap_mod_re . match ( entry . name )
2021-08-08 16:57:50 +00:00
if match :
2021-11-10 14:35:43 +00:00
logging . info ( f " Found AP randomizer mod: { match . group ( ) } " )
2021-08-08 16:57:50 +00:00
return match . group ( )
return None
2021-08-08 16:26:46 +00:00
else :
os . mkdir ( mods_dir )
2021-11-10 14:35:43 +00:00
logging . info ( f " Created mods folder in { forge_dir } " )
2021-08-08 16:57:50 +00:00
return None
2021-08-08 16:26:46 +00:00
2021-08-08 16:57:50 +00:00
# Create APData folder if needed; clean .apmc files from APData; copy given .apmc into directory.
def replace_apmc_files ( forge_dir , apmc_file ) :
if apmc_file is None :
return
apdata_dir = os . path . join ( forge_dir , ' APData ' )
2021-08-15 13:16:30 +00:00
copy_apmc = True
2021-08-08 16:57:50 +00:00
if not os . path . isdir ( apdata_dir ) :
os . mkdir ( apdata_dir )
2021-11-10 14:35:43 +00:00
logging . info ( f " Created APData folder in { forge_dir } " )
2021-08-08 16:57:50 +00:00
for entry in os . scandir ( apdata_dir ) :
2021-08-15 13:16:30 +00:00
if entry . name . endswith ( " .apmc " ) and entry . is_file ( ) :
if not os . path . samefile ( apmc_file , entry . path ) :
os . remove ( entry . path )
2021-11-10 14:35:43 +00:00
logging . info ( f " Removed { entry . name } in { apdata_dir } " )
2021-08-15 13:16:30 +00:00
else : # apmc already in apdata
copy_apmc = False
if copy_apmc :
copyfile ( apmc_file , os . path . join ( apdata_dir , os . path . basename ( apmc_file ) ) )
2021-11-10 14:35:43 +00:00
logging . info ( f " Copied { os . path . basename ( apmc_file ) } to { apdata_dir } " )
2021-08-08 16:57:50 +00:00
# Check mod version, download new mod from GitHub releases page if needed.
2021-08-08 18:31:03 +00:00
def update_mod ( forge_dir ) :
ap_randomizer = find_ap_randomizer_jar ( forge_dir )
2021-08-08 16:26:46 +00:00
client_releases_endpoint = " https://api.github.com/repos/KonoTyran/Minecraft_AP_Randomizer/releases "
resp = requests . get ( client_releases_endpoint )
if resp . status_code == 200 : # OK
latest_release = resp . json ( ) [ 0 ]
if ap_randomizer != latest_release [ ' assets ' ] [ 0 ] [ ' name ' ] :
2021-11-10 14:35:43 +00:00
logging . info ( f " A new release of the Minecraft AP randomizer mod was found: "
f " { latest_release [ ' assets ' ] [ 0 ] [ ' name ' ] } " )
2021-08-08 16:26:46 +00:00
if ap_randomizer is not None :
2021-11-10 14:35:43 +00:00
logging . info ( f " Your current mod is { ap_randomizer } . " )
2021-08-08 16:26:46 +00:00
else :
2021-11-10 14:35:43 +00:00
logging . info ( f " You do not have the AP randomizer mod installed. " )
2021-08-08 16:26:46 +00:00
if prompt_yes_no ( " Would you like to update? " ) :
old_ap_mod = os . path . join ( forge_dir , ' mods ' , ap_randomizer ) if ap_randomizer is not None else None
new_ap_mod = os . path . join ( forge_dir , ' mods ' , latest_release [ ' assets ' ] [ 0 ] [ ' name ' ] )
2021-11-10 14:35:43 +00:00
logging . info ( " Downloading AP randomizer mod. This may take a moment... " )
2021-08-08 16:26:46 +00:00
apmod_resp = requests . get ( latest_release [ ' assets ' ] [ 0 ] [ ' browser_download_url ' ] )
if apmod_resp . status_code == 200 :
with open ( new_ap_mod , ' wb ' ) as f :
f . write ( apmod_resp . content )
2021-11-10 14:35:43 +00:00
logging . info ( f " Wrote new mod file to { new_ap_mod } " )
2021-08-08 16:26:46 +00:00
if old_ap_mod is not None :
os . remove ( old_ap_mod )
2021-11-10 14:35:43 +00:00
logging . info ( f " Removed old mod file from { old_ap_mod } " )
2021-08-08 16:26:46 +00:00
else :
2021-11-10 14:35:43 +00:00
logging . error ( f " Error retrieving the randomizer mod (status code { apmod_resp . status_code } ). " )
logging . error ( f " Please report this issue on the Archipelago Discord server. " )
2021-08-08 16:26:46 +00:00
sys . exit ( 1 )
2021-08-08 16:57:50 +00:00
else :
2021-11-10 14:35:43 +00:00
logging . error ( f " Error checking for randomizer mod updates (status code { resp . status_code } ). " )
logging . error ( f " If this was not expected, please report this issue on the Archipelago Discord server. " )
2021-08-08 16:57:50 +00:00
if not prompt_yes_no ( " Continue anyways? " ) :
2021-08-08 20:36:12 +00:00
sys . exit ( 0 )
# Check if the EULA is agreed to, and prompt the user to read and agree if necessary.
def check_eula ( forge_dir ) :
eula_path = os . path . join ( forge_dir , " eula.txt " )
if not os . path . isfile ( eula_path ) :
# Create eula.txt
with open ( eula_path , ' w ' ) as f :
f . write ( " #By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula). \n " )
f . write ( f " # { strftime ( ' %a % b %d %X % Z % Y ' ) } \n " )
f . write ( " eula=false \n " )
with open ( eula_path , ' r+ ' ) as f :
text = f . read ( )
if ' false ' in text :
# Prompt user to agree to the EULA
2021-11-10 14:35:43 +00:00
logging . info ( " You need to agree to the Minecraft EULA in order to run the server. " )
logging . info ( " The EULA can be found at https://account.mojang.com/documents/minecraft_eula " )
2021-08-08 20:36:12 +00:00
if prompt_yes_no ( " Do you agree to the EULA? " ) :
f . seek ( 0 )
f . write ( text . replace ( ' false ' , ' true ' ) )
f . truncate ( )
2021-11-10 14:35:43 +00:00
logging . info ( f " Set { eula_path } to true " )
2021-08-08 20:36:12 +00:00
else :
sys . exit ( 0 )
2021-08-08 16:26:46 +00:00
2021-08-08 16:57:50 +00:00
# Run the Forge server. Return process object
2021-08-08 18:31:03 +00:00
def run_forge_server ( forge_dir , heap_arg ) :
forge_server = find_forge_jar ( forge_dir )
2021-08-08 16:26:46 +00:00
java_exe = os . path . abspath ( os . path . join ( ' jre8 ' , ' bin ' , ' java.exe ' ) )
if not os . path . isfile ( java_exe ) :
java_exe = " java " # try to fall back on java in the PATH
heap_arg = max_heap_re . match ( max_heap ) . group ( )
if heap_arg [ - 1 ] in [ ' b ' , ' B ' ] :
heap_arg = heap_arg [ : - 1 ]
heap_arg = " -Xmx " + heap_arg
argstring = ' ' . join ( [ java_exe , heap_arg , " -jar " , forge_server , " -nogui " ] )
2021-11-10 14:35:43 +00:00
logging . info ( f " Running Forge server: { argstring } " )
2021-08-08 16:26:46 +00:00
os . chdir ( forge_dir )
2021-08-08 16:57:50 +00:00
return Popen ( argstring )
if __name__ == ' __main__ ' :
2021-11-10 14:35:43 +00:00
Utils . init_logging ( " MinecraftClient " )
2021-08-08 16:57:50 +00:00
parser = argparse . ArgumentParser ( )
2021-08-08 17:07:03 +00:00
parser . add_argument ( " apmc_file " , default = None , nargs = ' ? ' , help = " Path to an Archipelago Minecraft data file (.apmc) " )
2021-08-08 16:57:50 +00:00
args = parser . parse_args ( )
2021-08-23 17:13:07 +00:00
apmc_file = os . path . abspath ( args . apmc_file ) if args . apmc_file else None
2021-08-08 16:57:50 +00:00
2021-08-08 19:19:36 +00:00
# Change to executable's working directory
os . chdir ( os . path . abspath ( os . path . dirname ( sys . argv [ 0 ] ) ) )
2021-08-15 11:13:58 +00:00
options = Utils . get_options ( )
forge_dir = options [ " minecraft_options " ] [ " forge_directory " ]
max_heap = options [ " minecraft_options " ] [ " max_heap_size " ]
2021-08-08 19:19:36 +00:00
2021-08-08 16:57:50 +00:00
if apmc_file is not None and not os . path . isfile ( apmc_file ) :
raise FileNotFoundError ( f " Path { apmc_file } does not exist or could not be accessed. " )
if not os . path . isdir ( forge_dir ) :
raise NotADirectoryError ( f " Path { forge_dir } does not exist or could not be accessed. " )
if not max_heap_re . match ( max_heap ) :
raise Exception ( f " Max heap size { max_heap } in incorrect format. Use a number followed by M or G, e.g. 512M or 2G. " )
2021-08-08 18:31:03 +00:00
update_mod ( forge_dir )
2021-08-08 16:57:50 +00:00
replace_apmc_files ( forge_dir , apmc_file )
2021-08-08 20:36:12 +00:00
check_eula ( forge_dir )
2021-08-08 18:31:03 +00:00
server_process = run_forge_server ( forge_dir , max_heap )
2021-08-08 16:57:50 +00:00
server_process . wait ( )