├── .gitignore
├── .idea
├── inspectionProfiles
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── pml.iml
└── vcs.xml
├── MANIFEST.in
├── README.md
├── main.py
├── main_login.py
├── pmlauncher
├── __init__.py
├── mdownloader.py
├── mevent.py
├── minecraft.py
├── mlaunch.py
├── mlaunchoption.py
├── mlibrary.py
├── mlogin.py
├── mnative.py
├── mprofile.py
├── mprofileinfo.py
├── mrule.py
└── pml.py
├── pycraft
└── put_pycraft_here.txt
├── setup.cfg
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
2 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
3 |
4 | # User-specific stuff
5 | .idea/**/workspace.xml
6 | .idea/**/tasks.xml
7 | .idea/**/usage.statistics.xml
8 | .idea/**/dictionaries
9 | .idea/**/shelf
10 |
11 | # Generated files
12 | .idea/**/contentModel.xml
13 |
14 | # Sensitive or high-churn files
15 | .idea/**/dataSources/
16 | .idea/**/dataSources.ids
17 | .idea/**/dataSources.local.xml
18 | .idea/**/sqlDataSources.xml
19 | .idea/**/dynamic.xml
20 | .idea/**/uiDesigner.xml
21 | .idea/**/dbnavigator.xml
22 |
23 | # Gradle
24 | .idea/**/gradle.xml
25 | .idea/**/libraries
26 |
27 | # Gradle and Maven with auto-import
28 | # When using Gradle or Maven with auto-import, you should exclude module files,
29 | # since they will be recreated, and may cause churn. Uncomment if using
30 | # auto-import.
31 | # .idea/modules.xml
32 | # .idea/*.iml
33 | # .idea/modules
34 | # *.iml
35 | # *.ipr
36 |
37 | # CMake
38 | cmake-build-*/
39 |
40 | # Mongo Explorer plugin
41 | .idea/**/mongoSettings.xml
42 |
43 | # File-based project format
44 | *.iws
45 |
46 | # IntelliJ
47 | out/
48 |
49 | # mpeltonen/sbt-idea plugin
50 | .idea_modules/
51 |
52 | # JIRA plugin
53 | atlassian-ide-plugin.xml
54 |
55 | # Cursive Clojure plugin
56 | .idea/replstate.xml
57 |
58 | # Crashlytics plugin (for Android Studio and IntelliJ)
59 | com_crashlytics_export_strings.xml
60 | crashlytics.properties
61 | crashlytics-build.properties
62 | fabric.properties
63 |
64 | # Editor-based Rest Client
65 | .idea/httpRequests
66 |
67 | # Android studio 3.1+ serialized cache file
68 | .idea/caches/build_file_checksums.ser
69 |
70 | pycraft/networking
71 | pycraft/__pycache__
72 | pycraft/__init__.py
73 | pycraft/authentication.py
74 | pycraft/compat.py
75 | pycraft/exceptions.py
76 | game/
77 | logs/
78 | __pycache__
79 | args.txt
80 | build/
81 | dist/
82 | pmlauncher.egg-info
83 | .vscode/settings.json
84 | .vs
85 | venv/
86 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/pml.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include main.py
2 | include README.md
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pml
2 | Python Minecraft Launcher Library - crossplatform, support forge, all version
3 | this project is ported version from [CmlLib](https://github.com/AlphaBs/MinecraftLauncherLibrary)
4 |
5 | **Not Updated for long time. so if this library does not work well, please create issue.**
6 |
7 | 1.4.*
8 | tested : windows 10 / ubuntu 18.04 LTS / macOS 10.14 Mojave
9 |
10 | Contacts
11 | -------------
12 |
13 | Email : ksi123456ab@naver.com
14 | Discord : ksi123456ab#3719
15 |
16 | License
17 | --------------
18 |
19 | 
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.
20 |
21 | ****NO COMMERCIAL****
22 |
23 | Dependency
24 | -------------
25 | Python 3.6.8
26 | requests 2.22
27 |
28 | Use venv
29 |
30 | How To Use
31 | -------------
32 | install :
33 |
34 | pip install pmlauncher
35 |
36 | check sample script
37 | [main.py](https://github.com/AlphaBs/pml/blob/master/main.py)
38 |
39 |
40 | Install pycraft
41 | -------------
42 | To launch premium minecraft, you have to add ['pyCraft' library](https://github.com/ammaraskar/pyCraft).
43 | Download release and unzip the library, and copy files of pyCraft/minecraft directory to pml/pycraft directory
44 | 
45 | and check sample script
46 | [main_login.py](https://github.com/AlphaBs/pml/blob/master/main_login.py)
47 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | from pmlauncher import pml, mlogin, mlaunchoption
2 | import subprocess
3 | import os
4 | import sys
5 |
6 |
7 | # initialize
8 | # p = os.environ["appdata"] + "\\.minecraft" # windows default game directory
9 | p = os.getcwd() + "/game"
10 | #p = os.path.abspath("/home/myu/.minecraft")
11 | pml.initialize(p)
12 | print("Initialized in " + pml.getGamePath())
13 |
14 |
15 | # login
16 | print("session : test user (tester123)")
17 | session = mlogin.session()
18 | session.username = "tester123"
19 | session.uuid = "uuid"
20 | session.access_token = "access_token"
21 |
22 |
23 | # get profiles
24 | profiles = pml.updateProfiles()
25 | for item in profiles:
26 | print(item.name)
27 |
28 | inputVersion = input("input version : ")
29 |
30 |
31 | # download event handler
32 | # filekind : library , minecraft, index, resource
33 | def downloadEvent(x):
34 | print(x.filekind + " - " + x.filename + " - " + str(x.currentvalue) + "/" + str(x.maxvalue))
35 |
36 |
37 | pml.downloadEventHandler = downloadEvent
38 |
39 | # download profile and create argument
40 | args = pml.startProfile(inputVersion,
41 | xmx_mb=1024,
42 | session=session,
43 |
44 | launcher_name="pml", # option
45 | server_ip="",
46 | jvm_args="",
47 | screen_width=0,
48 | screen_height=0)
49 |
50 |
51 | # start process
52 | with open("args.txt", "w") as f: # for debug
53 | f.write(args)
54 | print(args)
55 |
56 | mc = subprocess.Popen("java " + args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=pml.getGamePath(), shell=True)
57 |
58 | print("launched!")
59 |
60 |
61 | # write output
62 | with mc.stdout as gameLog:
63 | while True:
64 | try:
65 | line = gameLog.readline()
66 | if not line:
67 | break
68 | print(line.decode(sys.getdefaultencoding()))
69 | except:
70 | pass
71 |
72 | if mc.returncode:
73 | print(f"Client returned {mc.returncode}!")
74 |
75 |
--------------------------------------------------------------------------------
/main_login.py:
--------------------------------------------------------------------------------
1 | from pmlauncher import pml, mlogin, mlaunchoption
2 | import subprocess
3 | import os
4 | import sys
5 |
6 |
7 | # initialize
8 | # p = os.environ["appdata"] + "\\.minecraft" # windows default game directory
9 | p = os.getcwd() + "/game"
10 | #p = os.path.abspath("/home/myu/.minecraft")
11 | pml.initialize(p)
12 | print("Initialized in " + pml.getGamePath())
13 |
14 |
15 | # online-mode login (need pycraft : https://github.com/ammaraskar/pyCraft)
16 | # download pyCraft and copy 'minecraft' directory to 'pycraft' directory.
17 | from pycraft import authentication
18 |
19 | mcid = input("input mojang email : ")
20 | mcpw = input("input mojang pw : ")
21 |
22 | auth = authentication.AuthenticationToken()
23 | auth.authenticate(mcid, mcpw) # input mojang email and password
24 |
25 | session = mlogin.session() # set session object
26 | session.username = auth.profile.name
27 | session.uuid = auth.profile.id_
28 | session.access_token = auth.access_token
29 |
30 | print("login success : " + session.username)
31 |
32 |
33 | # get profiles
34 | profiles = pml.updateProfiles()
35 | for item in profiles:
36 | print(item.name)
37 |
38 | inputVersion = input("input version : ")
39 |
40 |
41 | # download event handler
42 | # filekind : library , minecraft, index, resource
43 | def downloadEvent(x):
44 | print(x.filekind + " - " + x.filename + " - " + str(x.currentvalue) + "/" + str(x.maxvalue))
45 |
46 |
47 | pml.downloadEventHandler = downloadEvent
48 |
49 | # download profile and create argument
50 | args = pml.startProfile(inputVersion,
51 | xmx_mb=1024,
52 | session=session,
53 |
54 | launcher_name="pml", # option
55 | server_ip="",
56 | jvm_args="",
57 | screen_width=0,
58 | screen_height=0)
59 |
60 |
61 | # start process
62 | with open("args.txt", "w") as f: # for debug
63 | f.write(args)
64 | print(args)
65 |
66 | mc = subprocess.Popen("java " + args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=pml.getGamePath(), shell=True)
67 |
68 | print("launched!")
69 |
70 |
71 | # write output
72 | with mc.stdout as gameLog:
73 | while True:
74 | try:
75 | line = gameLog.readline()
76 | if not line:
77 | break
78 | print(line.decode(sys.getdefaultencoding()))
79 | except:
80 | pass
81 |
82 | if mc.returncode:
83 | print(f"Client returned {mc.returncode}!")
84 |
85 |
--------------------------------------------------------------------------------
/pmlauncher/__init__.py:
--------------------------------------------------------------------------------
1 | # __init__.py
2 |
3 | __all__ = [
4 | 'mdownloader',
5 | 'mevent',
6 | 'minecraft',
7 | 'mlaunchoption',
8 | 'mlaunch',
9 | 'mlibrary',
10 | 'mlogin',
11 | 'mrule',
12 | 'mnative',
13 | 'mprofile',
14 | 'mprofileinfo',
15 |
16 | ]
17 |
--------------------------------------------------------------------------------
/pmlauncher/mdownloader.py:
--------------------------------------------------------------------------------
1 | import os
2 | import requests
3 | from pmlauncher import minecraft, mevent
4 | import json
5 | from shutil import copyfile
6 | import shutil
7 | import hashlib
8 |
9 |
10 | def mkd(path):
11 | if not os.path.isdir(path):
12 | os.makedirs(path)
13 |
14 |
15 | def download(url, path):
16 | dirpath = os.path.dirname(path)
17 | mkd(dirpath)
18 |
19 | response = requests.get(url, stream=True)
20 | if int(response.status_code / 100) is not 2:
21 | return
22 |
23 | with open(path, 'wb') as f:
24 | shutil.copyfileobj(response.raw, f)
25 |
26 |
27 | class mdownload:
28 | def __init__(self, _profile):
29 | self.checkHash = True
30 | self.profile = _profile
31 | self.doFireEvents = True
32 | self.downloadFileChangedEvent = mevent.Event()
33 |
34 | def fireEvent(self, kind, name, max, current):
35 | if not self.doFireEvents:
36 | return
37 |
38 | args = mevent.MDownloadEventArgs()
39 | args.filekind = kind
40 | args.filename = name
41 | args.maxvalue = max
42 | args.currentvalue = current
43 | self.downloadFileChangedEvent(args)
44 |
45 | def checkFileSHA1(self, path, fhash):
46 | if not self.checkHash:
47 | return True
48 | if not fhash:
49 | return True
50 |
51 | f = open(path, "rb")
52 | data = f.read()
53 | f.close()
54 |
55 | return fhash == hashlib.sha1(data).hexdigest()
56 |
57 | def checkFileValidation(self, path, fhash):
58 | return os.path.isfile(path) and self.checkFileSHA1(path, fhash)
59 |
60 | def downloadAll(self, downloadAssets):
61 | self.downloadLibraries()
62 | self.downloadMinecraft()
63 | if downloadAssets:
64 | self.downloadIndex()
65 | self.downloadResources()
66 |
67 | def downloadLibraries(self):
68 | count = len(self.profile.libraries)
69 | for i in range(0, count):
70 | lib = self.profile.libraries[i]
71 | if lib.isRequire and lib.path and lib.url and not self.checkFileValidation(lib.path, lib.hash):
72 | download(lib.url, lib.path)
73 |
74 | self.fireEvent("library", lib.name, count, i + 1)
75 |
76 | def downloadIndex(self):
77 | path = os.path.normpath(minecraft.index + "/" + self.profile.assetId + ".json")
78 | if self.profile.assetUrl and not self.checkFileValidation(path, self.profile.assetHash):
79 | download(self.profile.assetUrl, path)
80 |
81 | self.fireEvent("index", self.profile.assetId, 1, 1)
82 |
83 | def downloadResources(self):
84 | indexPath = os.path.normpath(minecraft.index + "/" + self.profile.assetId + ".json")
85 | if not os.path.isfile(indexPath):
86 | return
87 |
88 | f = open(indexPath, "r")
89 | content = f.read()
90 | f.close()
91 |
92 | index = json.loads(content)
93 |
94 | isVirtual = False
95 | v = index.get("virtual")
96 | if v and v == True:
97 | isVirtual = True
98 |
99 | isMapResource = False
100 | m = index.get("map_to_resources")
101 | if m and m == True:
102 | isMapResource = True
103 |
104 | items = list(index.get("objects").items())
105 | count = len(items)
106 | for i in range(0, count):
107 | key = items[i][0]
108 | value = items[i][1]
109 |
110 | hash = value.get("hash")
111 | hashName = hash[:2] + "/" + hash
112 | hashPath = os.path.normpath(minecraft.assetObject + "/" + hashName)
113 | hashUrl = "http://resources.download.minecraft.net/" + hashName
114 |
115 | if not os.path.isfile(hashPath):
116 | download(hashUrl, hashPath)
117 |
118 | if isVirtual:
119 | resPath = os.path.normpath(minecraft.assetLegacy + "/" + key)
120 |
121 | if not os.path.isfile(resPath):
122 | mkd(os.path.dirname(resPath))
123 | copyfile(hashPath, resPath)
124 |
125 | if isMapResource:
126 | resPath = os.path.normpath(minecraft.resources + "/" + key)
127 |
128 | if not os.path.isfile(resPath):
129 | mkd(os.path.dirname(resPath))
130 | copyfile(hashPath, resPath)
131 |
132 | self.fireEvent("resource", "", count, i + 1)
133 |
134 | def downloadMinecraft(self):
135 | if not self.profile.clientDownloadUrl:
136 | return
137 |
138 | id = self.profile.jar
139 | path = os.path.normpath(minecraft.version + "/" + id + "/" + id + ".jar")
140 | if not self.checkFileValidation(path, self.profile.clientHash):
141 | download(self.profile.clientDownloadUrl, path)
142 |
143 | self.fireEvent("minecraft", id, 1, 1)
144 |
--------------------------------------------------------------------------------
/pmlauncher/mevent.py:
--------------------------------------------------------------------------------
1 | class Event(list):
2 | def __call__(self, *args, **kwargs):
3 | for f in self:
4 | f(*args, **kwargs)
5 |
6 | def __repr__(self):
7 | return "Event(%s)" % list.__repr__(self)
8 |
9 |
10 | class MDownloadEventArgs:
11 | def __init__(self):
12 | self.filekind = ""
13 | self.filename = ""
14 | self.maxvalue = 1
15 | self.currentvalue = 1
16 |
17 |
--------------------------------------------------------------------------------
/pmlauncher/minecraft.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | path = ""
4 | library = ""
5 | version = ""
6 | assets = ""
7 | index = ""
8 | assetObject = ""
9 | assetLegacy = ""
10 | resources = ""
11 | natives = ""
12 |
13 |
14 | def initialize(_path):
15 | global path, library, version, resources, natives
16 |
17 | path = m(_path)
18 | library = m(path + "/libraries")
19 | version = m(path + "/versions")
20 | resources = m(path + "/resources")
21 | natives = m(path + "/natives")
22 | change_assets(path)
23 |
24 |
25 | def change_assets(p):
26 | global assets, assetLegacy, assetObject, index
27 |
28 | assets = os.path.normpath(p + "/assets")
29 | index = os.path.normpath(assets + "/indexes")
30 | assetObject = os.path.normpath(assets + "/objects")
31 | assetLegacy = os.path.normpath(assets + "/virtual/legacy")
32 |
33 |
34 | def m(p):
35 | p = os.path.normpath(p)
36 | if not os.path.isdir(p):
37 | os.makedirs(p)
38 | return p
39 |
40 |
--------------------------------------------------------------------------------
/pmlauncher/mlaunch.py:
--------------------------------------------------------------------------------
1 | from pmlauncher import mnative, minecraft, mrule
2 | import string
3 | import os
4 | import re
5 |
6 | supportversion = "1.4"
7 | pre_compiled = re.compile("\\$\\{(.*?)}")
8 |
9 | def e(t):
10 | if " " in t:
11 | return '"' + t + '"'
12 | else:
13 | return t
14 |
15 |
16 | def ea(t):
17 | if " " in t and "=" in t:
18 | s = t.split("=")
19 | return s[0] + '="' + s[1] + '"'
20 | else:
21 | return t
22 |
23 |
24 | def arg_in(arg, dicts):
25 | args = list()
26 | for item in arg:
27 | if type(item) is str:
28 | m = pre_compiled.search(item) # check ${} str
29 | if m:
30 | arg_key = m.group() # get ${KEY}
31 | arg_value = dicts.get(arg_key[2:-1]) # get dicts value of ${KEY}
32 |
33 | if arg_value:
34 | args.append(pre_compiled.sub(arg_value.replace("\\","\\\\"), item)) # replace ${} of whole str to dicts value
35 | else:
36 | args.append(item) # if value of default arg has space, handle whitespace.
37 | # (ex) -Dos.Version=Windows 10 => -Dos.Version="Windows 10"
38 | else:
39 | args.append(ea(item)) # not ${} str
40 |
41 | return args
42 |
43 |
44 | def arg_str(arg, dicts):
45 | return string.Template(arg).safe_substitute(dicts)
46 |
47 |
48 | class launch:
49 | def __init__(self, option):
50 | self.defaultJavaParameter = " ".join([
51 | "-XX:+UnlockExperimentalVMOptions",
52 | "-XX:+UseG1GC",
53 | "-XX:G1NewSizePercent=20",
54 | "-XX:G1ReservePercent=20",
55 | "-XX:MaxGCPauseMillis=50",
56 | "-XX:G1HeapRegionSize=16M"])
57 | if mrule.osname == "osx":
58 | self.defaultJavaParameter += " -XstartOnFirstThread"
59 |
60 | option.checkValid()
61 | self.launchOption = option
62 |
63 | def createArg(self):
64 | profile = self.launchOption.start_profile
65 |
66 | args = list()
67 |
68 | # common jvm args
69 | if self.launchOption.jvm_arg:
70 | args.append(self.launchOption.jvm_arg)
71 | else:
72 | args.append(self.defaultJavaParameter)
73 |
74 | args.append("-Xmx" + str(self.launchOption.xmx_mb) + "m")
75 |
76 | # specific jvm args
77 | libArgs = list()
78 |
79 | for item in profile.libraries:
80 | if not item.isNative:
81 | libArgs.append(e(item.path))
82 |
83 | libArgs.append(e(os.path.normpath(minecraft.version + "/" + profile.jar + "/" + profile.jar + ".jar")))
84 | libs = os.pathsep.join(libArgs)
85 |
86 | jvmdict = {
87 | "natives_directory" : e(minecraft.natives),
88 | "launcher_name" : "minecraft-launcher",
89 | "launcher_version" : "2",
90 | "classpath" : libs
91 | }
92 |
93 | if profile.jvm_arguments:
94 | args.extend(arg_in(profile.jvm_arguments, jvmdict))
95 | else:
96 | args.append("-Djava.library.path=" + e(minecraft.natives))
97 | args.append("-cp " + libs)
98 |
99 |
100 | args.append(profile.mainclass)
101 |
102 | # game args
103 | gamedict = {
104 | "auth_player_name" : self.launchOption.session.username,
105 | "version_name" : profile.id,
106 | "game_directory" : e(minecraft.path),
107 | "assets_root" : e(minecraft.assets),
108 | "assets_index_name" : profile.assetId,
109 | "auth_uuid" : self.launchOption.session.uuid,
110 | "auth_access_token" : self.launchOption.session.access_token,
111 | "user_properties" : "{}",
112 | "user_type" : "Mojang",
113 | "game_assets" : e(minecraft.assetLegacy),
114 | "auth_session" : self.launchOption.session.access_token
115 | }
116 |
117 | if self.launchOption.launcher_name:
118 | gamedict["version_type"] = self.launchOption.launcher_name
119 | else:
120 | gamedict["version_type"] = profile.type
121 |
122 | if profile.game_arguments: # 1.3
123 | args.extend(arg_in(profile.game_arguments, gamedict))
124 | elif profile.minecraftArguments:
125 | args.append(arg_str(profile.minecraftArguments, gamedict))
126 |
127 | # options
128 | if self.launchOption.server_ip:
129 | args.append("--server " + self.launchOption.server_ip)
130 |
131 | if self.launchOption.screen_width and self.launchOption.screen_height:
132 | args.append("--width " + self.launchOption.screen_width)
133 | args.append("--height " + self.launchOption.screen_height)
134 |
135 | return " ".join(map(str, args))
136 |
137 | def createProcess(self):
138 | mnative.clean_natives()
139 | mnative.extract_natives(self.launchOption.start_profile)
140 |
141 | return self.createArg()
142 |
143 |
144 |
--------------------------------------------------------------------------------
/pmlauncher/mlaunchoption.py:
--------------------------------------------------------------------------------
1 | class launchoption:
2 | def __init__(self):
3 | self.xmx_mb = 1024
4 | self.start_profile = None
5 | self.session = None
6 | self.launcher_name = ""
7 | self.server_ip = ""
8 | self.jvm_arg = ""
9 | self.screen_width = 0
10 | self.screen_height = 0
11 |
12 | def checkValid(self):
13 | exMsg = ""
14 | if not self.xmx_mb:
15 | exMsg = "xmx_mb is too small"
16 | if not self.start_profile:
17 | exMsg = "start_profile was None"
18 | if not self.session:
19 | exMsg = "session was None"
20 | if " " in self.launcher_name:
21 | exMsg = "launcher_name cannot contain space character"
22 |
23 | if exMsg:
24 | raise ValueError(exMsg)
25 |
26 |
--------------------------------------------------------------------------------
/pmlauncher/mlibrary.py:
--------------------------------------------------------------------------------
1 | from pmlauncher import minecraft, mrule
2 | import os
3 |
4 | class mlibrary:
5 | def __init__(self):
6 | self.isNative = False
7 | self.name = ""
8 | self.path = ""
9 | self.url = ""
10 | self.isRequire = True
11 | self.hash = ""
12 |
13 |
14 | checkOSRules = True
15 | defaultLibraryServer = "https://libraries.minecraft.net/"
16 |
17 |
18 | def nameToPath(name, native): # library name to relative path
19 | try:
20 | tmp = name.split(':')
21 | front = tmp[0].replace('.', '/')
22 | back = ""
23 |
24 | for i in range(1, len(tmp)):
25 | if i == len(tmp) - 1:
26 | back += tmp[i]
27 | else:
28 | back += tmp[i] + ":"
29 |
30 | libpath = front + "/" + back.replace(":", "/") + "/" + (back.replace(":", "-"))
31 | if native:
32 | libpath += "-" + native + ".jar"
33 | else:
34 | libpath += ".jar"
35 | return libpath
36 | except Exception:
37 | return ""
38 |
39 |
40 | def createLibrary(name, nativeId, job):
41 | path = job.get("path")
42 | if not path:
43 | path = nameToPath(name, nativeId)
44 |
45 | url = job.get("url")
46 | if not url:
47 | url = defaultLibraryServer + path
48 | elif not url.split('/')[-1]:
49 | url += path
50 |
51 | library = mlibrary()
52 | library.hash = job.get("sha1")
53 | library.name = name
54 | library.path = os.path.normpath(minecraft.library + "/" + path)
55 | library.url = url
56 | if nativeId:
57 | library.isNative = True
58 | else:
59 | library.isNative = False
60 |
61 | return library
62 |
63 | def parselist(json):
64 | list = []
65 |
66 | for item in json:
67 | name = item.get("name")
68 | if name is None:
69 | continue
70 |
71 | # check rules
72 | rules = item.get("rules")
73 | if checkOSRules and rules:
74 | isRequire = mrule.checkAllowOS(rules)
75 |
76 | if not isRequire:
77 | continue
78 |
79 | # forge library
80 | downloads = item.get("downloads")
81 | if not downloads: # downloads == null
82 | natives = item.get("natives")
83 |
84 | nativeId = None
85 | if natives is not None: # natives != null
86 | nativeId = natives.get(mrule.osname)
87 |
88 | list.append(createLibrary(name, nativeId, item))
89 | continue
90 |
91 | # native library
92 | classif = downloads.get("classifiers")
93 | if classif:
94 | native_id = None
95 | native_obj = item.get("natives")
96 | if native_obj:
97 | native_id = native_obj.get(mrule.osname)
98 |
99 | if native_id and classif.get(native_id):
100 | native_id = native_id.replace("${arch}", mrule.arch)
101 | job = classif.get(native_id)
102 | list.append(createLibrary(name, native_id, job))
103 |
104 | # common library
105 | arti = downloads.get("artifact")
106 | if arti:
107 | list.append(createLibrary(name, "", arti))
108 |
109 | return list
110 |
111 |
--------------------------------------------------------------------------------
/pmlauncher/mlogin.py:
--------------------------------------------------------------------------------
1 | class session:
2 | def __init__(self):
3 | self.username = ""
4 | self.access_token = ""
5 | self.uuid = ""
6 |
--------------------------------------------------------------------------------
/pmlauncher/mnative.py:
--------------------------------------------------------------------------------
1 | from pmlauncher import minecraft
2 | import zipfile
3 | import os
4 |
5 |
6 | def extract_natives(profile):
7 | for item in profile.libraries:
8 | if item.isNative:
9 | try:
10 | lib = zipfile.ZipFile(item.path)
11 | lib.extractall(minecraft.natives)
12 | except Exception as e:
13 | print(e)
14 |
15 |
16 | def clean_natives():
17 | for item in os.listdir(minecraft.natives):
18 | path = os.path.join(minecraft.natives, item)
19 |
20 | if os.path.isfile(path):
21 | try:
22 | os.remove(path)
23 | except Exception as e:
24 | print(e)
25 |
26 |
--------------------------------------------------------------------------------
/pmlauncher/mprofile.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 | from pmlauncher import minecraft, mlibrary, mrule
4 | import os
5 |
6 |
7 | def n(t):
8 | if t is None:
9 | return ""
10 | else:
11 | return t
12 |
13 |
14 | def arg_parse(arr):
15 | strlist = list()
16 | for item in arr:
17 | if type(item) == dict:
18 | allow = True
19 |
20 | rule = item.get("rules")
21 | if rule:
22 | allow = mrule.checkAllowOS(rule)
23 |
24 | value = item.get("value")
25 |
26 | if allow and value:
27 | if type(value) == list:
28 | strlist.extend(value)
29 | else:
30 | strlist.append(value)
31 | else:
32 | strlist.append(item)
33 |
34 | return strlist
35 |
36 |
37 |
38 | class profile:
39 | def __init__(self, info):
40 | self.id = ""
41 | self.assetId = ""
42 | self.assetUrl = ""
43 | self.assetHash = ""
44 | self.jvm_arguments = []
45 | self.game_arguments = []
46 | self.libraries = []
47 | self.clientDownloadUrl = ""
48 | self.clientHash = ""
49 | self.parent_profile_id = ""
50 | self.is_inherited = False
51 | self.jar = ""
52 | self.mainclass = ""
53 | self.minecraftArguments = ""
54 | self.releaseTime = ""
55 | self.type = ""
56 | self.parse(info)
57 |
58 |
59 |
60 | def parse(self, info):
61 | if info.isweb:
62 | json = requests.get(info.path).text
63 | else:
64 | f = open(info.path)
65 | json = f.read()
66 | f.close()
67 |
68 | return self.parseFromJson(json)
69 |
70 | def parseFromJson(self, content):
71 | d = json.loads(content)
72 |
73 | self.id = d.get("id")
74 |
75 | assetIndex = d.get("assetIndex")
76 | if assetIndex:
77 | self.assetId = n(assetIndex.get("id"))
78 | self.assetUrl = n(assetIndex.get("url"))
79 | self.assetHash = n(assetIndex.get("sha1"))
80 |
81 | downloads = d.get("downloads")
82 | if downloads:
83 | client = downloads.get("client")
84 | if client:
85 | self.clientDownloadUrl = client["url"]
86 | self.clientHash = client["sha1"]
87 |
88 | self.libraries = mlibrary.parselist(d.get("libraries"))
89 | self.mainclass = n(d.get("mainClass"))
90 |
91 | self.minecraftArguments = d.get("minecraftArguments")
92 | arg = d.get("arguments")
93 | if arg:
94 | if arg.get("game"):
95 | self.game_arguments = arg_parse(arg.get("game"))
96 | if arg.get("jvm"):
97 | self.jvm_arguments = arg_parse(arg.get("jvm"))
98 |
99 | self.releaseTime = n(d.get("releaseTime"))
100 | self.type = n(d.get("type"))
101 |
102 | inherits = d.get("inheritsFrom")
103 | if inherits:
104 | self.is_inherited = True
105 | self.parent_profile_id = inherits
106 | else:
107 | self.jar = self.id
108 |
109 | profilePath = os.path.normpath(minecraft.version + "/" + self.id)
110 |
111 | if not os.path.isdir(profilePath):
112 | os.makedirs(profilePath)
113 |
114 | f = open(os.path.normpath(profilePath + "/" + self.id + ".json"), "w")
115 | f.write(content)
116 | f.close()
117 |
118 |
119 | def inhert(parent, child):
120 | # Overload : assetId, assetUrl, assetHash, clientDownloadUrl, clientHash, mainClass, minecraftArguments
121 | # Combine : libraries, game_arguments, jvm_arguments
122 |
123 | if not child.assetId:
124 | child.assetId = parent.assetId
125 |
126 | if not child.assetUrl:
127 | child.assetUrl = parent.assetUrl
128 |
129 | if not child.assetHash:
130 | child.assetHash = parent.assetHash
131 |
132 | if not child.clientDownloadUrl:
133 | child.clientDownloadUrl = parent.clientDownloadUrl
134 |
135 | if not child.clientHash:
136 | child.clientHash = parent.clientHash
137 |
138 | if not child.mainclass:
139 | child.mainclass = parent.mainclass
140 |
141 | if not child.minecraftArguments:
142 | child.minecraftArguments = parent.minecraftArguments
143 |
144 | if parent.libraries:
145 | if child.libraries:
146 | child.libraries.extend(parent.libraries)
147 | else:
148 | child.libraries = parent.libraries
149 |
150 | if parent.game_arguments:
151 | if child.game_arguments:
152 | child.game_arguments.extend(parent.game_arguments)
153 | else:
154 | child.game_arguments = parent.game_arguments
155 |
156 | if parent.jvm_arguments:
157 | if child.jvm_arguments:
158 | child.jvm_arguments.extend(parent.jvm_arguments)
159 | else:
160 | child.jvm_arguments = parent.jvm_arguments
161 |
162 |
163 | def get_profile(infos, version):
164 | start_profile = None
165 |
166 | for item in infos:
167 | if item.name == version:
168 | start_profile = profile(item)
169 | break
170 |
171 | if start_profile == None:
172 | raise ValueError("cannot find profile named " + version)
173 |
174 | if start_profile.is_inherited:
175 | parent_profile = get_profile(infos, start_profile.parent_profile_id)
176 | inhert(parent_profile, start_profile)
177 |
178 | return start_profile
179 |
--------------------------------------------------------------------------------
/pmlauncher/mprofileinfo.py:
--------------------------------------------------------------------------------
1 | import os
2 | from pmlauncher import minecraft
3 | import json
4 | import requests
5 |
6 |
7 | class mprofileinfo:
8 | def __init__(self):
9 | self.isweb = True
10 | self.name = ""
11 | self.path = ""
12 |
13 |
14 | def getProfilesFromLocal():
15 | files = os.listdir(minecraft.version)
16 | arr = list()
17 |
18 | if not files:
19 | return arr
20 |
21 | for item in files:
22 | filepath = os.path.normpath(minecraft.version + "/" + item + "/" + item + ".json")
23 | if os.path.isfile(filepath):
24 | profile = mprofileinfo()
25 | profile.isweb = False
26 | profile.name = item
27 | profile.path = filepath
28 | arr.append(profile)
29 |
30 | return arr
31 |
32 |
33 | def getProfilesFromWeb():
34 | result = list()
35 | url = "https://launchermeta.mojang.com/mc/game/version_manifest.json"
36 | jarr = json.loads(requests.get(url).text)
37 | for item in jarr.get("versions"):
38 | profile = mprofileinfo()
39 | profile.isweb = True
40 | profile.name = item.get("id")
41 | profile.path = item.get("url")
42 | result.append(profile)
43 |
44 | return result
45 |
46 |
47 | def getProfiles():
48 | arr = getProfilesFromLocal()
49 | for item1 in getProfilesFromWeb():
50 | exist = False
51 | for item2 in arr:
52 | if item1.name == item2.name:
53 | exist = True
54 | break
55 | if not exist:
56 | arr.append(item1)
57 | return arr
--------------------------------------------------------------------------------
/pmlauncher/mrule.py:
--------------------------------------------------------------------------------
1 | import platform
2 | import os
3 |
4 | checkOSRules = True
5 |
6 | is64bit = platform.machine().endswith('64')
7 | if is64bit:
8 | arch = "64"
9 | else:
10 | arch = "32"
11 |
12 | osversion = platform.release()
13 | osname = ""
14 | sysname = platform.system()
15 | if sysname == "Linux":
16 | osname = "linux"
17 | elif sysname == "Darwin":
18 | osname = "osx"
19 | else:
20 | osname = "windows"
21 | #osname = "osx" # fake os to debug
22 |
23 |
24 | def checkAllowOS(arr):
25 | require = True
26 |
27 | for job in arr:
28 | action = True # allow / disallow
29 | containCurrentOS = True
30 |
31 | for key, value in job.items():
32 |
33 | if key == "action":
34 | if value == "allow":
35 | action = True
36 | else:
37 | action = False
38 |
39 | elif key == "os":
40 | containCurrentOS = check_os_contains(value.items())
41 |
42 | elif key == "features":
43 | return False
44 |
45 | if not action and containCurrentOS: # disallow os
46 | require = False
47 | elif action and containCurrentOS: # allow os
48 | require = True
49 | elif action and not containCurrentOS: #
50 | require = False
51 |
52 | return require
53 |
54 |
55 | def check_os_contains(arr):
56 | for osKey, osValue in arr:
57 | if osKey == "name" and osValue == osname:
58 | return True
59 | return False
60 |
--------------------------------------------------------------------------------
/pmlauncher/pml.py:
--------------------------------------------------------------------------------
1 | from . import *
2 |
3 | profiles = None
4 | downloadEventHandler = None
5 |
6 |
7 | def getGamePath():
8 | return minecraft.path
9 |
10 |
11 | def initialize(path):
12 | minecraft.initialize(path)
13 |
14 |
15 | def updateProfiles():
16 | global profiles
17 | profiles = mprofileinfo.getProfiles()
18 | return profiles
19 |
20 |
21 | def getProfile(name):
22 | global profiles
23 | if profiles is None:
24 | updateProfiles()
25 |
26 | return mprofile.get_profile(profiles, name)
27 |
28 |
29 | def downloadProfile(profile, downloadAssets = True):
30 | if type(profile) is not mprofile.profile:
31 | raise ValueError("profile must be mprofile.profile type")
32 |
33 | downloader = mdownloader.mdownload(profile)
34 | if downloadEventHandler:
35 | downloader.downloadFileChangedEvent.append(downloadEventHandler)
36 | downloader.downloadAll(downloadAssets)
37 |
38 |
39 | def startProfile(name, **option):
40 | profile = getProfile(name)
41 | downloadProfile(profile)
42 |
43 | l = mlaunchoption.launchoption()
44 | if option.get("launchoption"):
45 | l = option.get("launchoption")
46 | else:
47 | l.__dict__.update(option)
48 |
49 | l.start_profile = profile
50 | launch = mlaunch.launch(l)
51 | return launch.createProcess()
52 |
--------------------------------------------------------------------------------
/pycraft/put_pycraft_here.txt:
--------------------------------------------------------------------------------
1 | need file :
2 |
3 | networking directory
4 | __init.py__
5 | authentication.py
6 | compat.py
7 | exceptions.py
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file=README.md
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(
4 | name="pmlauncher",
5 | version="0.0.9",
6 | url="https://github.com/AlphaBs/pml",
7 | license="non commercial",
8 | author="ksi123456ab",
9 | author_email="ksi123456ab@naver.com",
10 | description="crossplatform Python Minecraft Launcher support forge and all version",
11 | packages=find_packages(exclude=['main.py']),
12 | long_description=open('README.md').read(),
13 | zip_safe=False,
14 | install_requires=['requests'],
15 | python_requires='>=3',
16 | keywords=["minecraft", "launcher"],
17 | classifiers=[
18 | "Programming Language :: Python :: 3.0",
19 | "Programming Language :: Python :: 3.1",
20 | "Programming Language :: Python :: 3.2",
21 | "Programming Language :: Python :: 3.3",
22 | "Programming Language :: Python :: 3.4",
23 | "Programming Language :: Python :: 3.5",
24 | "Programming Language :: Python :: 3.6",
25 | "Programming Language :: Python :: 3.7",
26 | "Programming Language :: Python :: 3.8",
27 | "Topic :: Games/Entertainment"
28 | ]
29 | )
30 |
31 | # python setup.py sdist
32 | # twine upload --skip-existing dist/*
33 |
--------------------------------------------------------------------------------