Skip to content

Commit d6583d0

Browse files
committed
Update a0.6-pre-3
1 parent 785ecc8 commit d6583d0

File tree

9 files changed

+394
-57
lines changed

9 files changed

+394
-57
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This list is based off of the old beta launcher.
1414
- [x] Modpack manager.
1515
- [x] Fixed login.
1616
- [x] Discord integration.
17+
- [x] MultiMC instance support.
1718
- [ ] Mod installer system similar to Nexus Mod Manager.
1819
- [ ] Stupidly lightweight.
1920

@@ -50,4 +51,4 @@ Then use pyinstaller on the launcher:
5051
instead of a colon (`:`) in the `--add-data` arguments.
5152
- The compiled executable will be put in the dist folder when it is done.
5253
- To have a console for the launcher, remove the `-w` in the compile arguments when compiling.
53-
- The final file is pretty big. (~80mb)
54+
- The final file is pretty big. (~40mb)

changelog.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ The beginning of something awesome.
6464
\- Python library "appdirs" no longer used. Shaves about 10kb off the file size. /shrug
6565

6666
### a0.6-pre2
67-
ETA unknown. Likely about 1-2 weeks after pre-1.
67+
Some nice things ive been working on.
6868

6969
\#\+ A proper wiki. (WIP)
7070
\#\+\+ Online repo of mods, texturepacks and themes that can be installed.
@@ -73,6 +73,7 @@ ETA unknown. Likely about 1-2 weeks after pre-1.
7373
\* Fixed instance settings not loading or saving correctly.
7474

7575
### a0.6-pre2.1
76+
So many bugs. SO MANY MIXINS.
7677

7778
~ Made it so that more things log to console.
7879
\+ EasyMineLauncher is now used to launch minecraft. This uses LaunchWrapper, which allows modders to use mixins.
@@ -87,20 +88,25 @@ ETA unknown. Likely about 1-2 weeks after pre-1.
8788
### Upcoming a0.6-pre3
8889
What pre2 was meant to be.
8990

91+
\+ Links in the blog section are now clickable. They will open in your default browser.
9092
\+ Automatic resource downloads when installing instances/modpacks. Will only download missing files. It will not replace modified files.
91-
\+ Support for a1.2.6, b1.6.6.
92-
\+ Automatic "dumb" mod installing. (just copies contents to mc.jar)
93-
\+ The launcher will have a working modpack exporter window.
94-
\+ A "create instance" button for creating vanilla instances.
93+
\+ A working modpack exporter window. Launch PyMCL with `--export` OR `-e`.
94+
\+ MultiMC instance support. This is extremely experimental and may not work with some modpacks.
95+
\* Fixed PyMCL not deleting META-INF when needed.
96+
\* Fixed random crashes when closing the instance manager for good.
9597

96-
### Upcoming a0.6
98+
### Upcoming a0.6-pre4
9799
Woo!!!11 Automate EVERYTHING!
98100

101+
\+ Automated instance creation.
99102
\#\+\+ Online mod repository with tons of converted mods.
100103
\+ Revamped instance manager.
101-
\+ Automatic "semi-smart" mod installing. Most likely only gonna support optional classes and folders though.
104+
\+ Automatic "dumb" mod installing. (just copies contents to mc.jar)
102105
\+ Automatic launcher updates?
103106

107+
## Upcoming a0.6-pre5
108+
109+
\+ Automatic "semi-smart" mod installing. Most likely only gonna support optional classes and folders though.
104110

105111
### Upcoming b1.0+
106112

compile_unix.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ chmod a+x venv/bin/activate
1616
source venv/bin/activate
1717

1818
echo Adding dependencies.
19-
pip install pyqt5 requests appdirs pyinstaller pypresence
19+
pip install pyqt5 requests appdirs pyinstaller pypresence mitmproxy
2020

2121
echo Using pyinstaller.
2222
pyinstaller -y -F -i "favicon.ico" --add-data "background.png":"." --add-data "logo.png":"." --add-data "favicon.ico":"." --add-data "blogbackground.png":"." --add-data "blog.html":"." --add-data "refresh.png":"." --add-data "venv/Lib/site-packages/mitmproxy/addons/onboardingapp":"mitmproxy/addons/onboardingapp" --add-data "EasyMineLauncher.jar":"." pymcl.py

config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
}
3232
"""
3333

34-
VER = "v0.6 Alpha Pre 2"
34+
VER = "v0.6 Alpha Pre 3"
3535

3636
# Sets minecraft install dir. DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING.
3737
# Touching this can cause unintended file deletion/overwrites/etc.

exportlauncher.py

Lines changed: 196 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,30 @@
33
import os
44
import sys
55
import requests
6+
import json
7+
import traceback
8+
import shutil
9+
import distutils
10+
import configparser
611

712
from PyQt5.QtGui import QIcon
813
from PyQt5.QtWidgets import QWidget, QComboBox, QLabel, QPushButton, QCheckBox, QApplication
914

15+
1016
class exportWindow(QWidget):
1117
currentInstance = ""
18+
currentInstanceVersion = None
19+
doSoundRemoval = False
20+
makePyMCLModpack = False
21+
doClassRemoval = False
22+
doLWJGLRemoval = False
1223

1324
# Same drill. Does background things.
1425
def __init__(self, parent=None):
1526
super().__init__(parent)
1627
screen_resolution = app.desktop().screenGeometry()
17-
self.title = config.NAME + " " + config.VER + " Modpack Installer"
18-
config.ICON = utils.loadImage("favicon.ico", self.currentInstance)
28+
self.title = config.NAME + " " + config.VER + " Modpack Exporter"
29+
config.ICON = utils.loadImage("favicon.ico", "")
1930
self.setWindowIcon(QIcon(config.ICON))
2031
self.left = screen_resolution.width() / 2 - 450
2132
self.top = screen_resolution.height() / 2 - 220
@@ -39,40 +50,211 @@ def createButtons(self):
3950
item = self.instanceSelect.findText(self.currentInstance)
4051
self.instanceSelect.setCurrentIndex(item)
4152
self.currentInstance = self.instanceSelect.currentText()
53+
self.instanceSelect.activated[str].connect(self.setInstance)
54+
self.instanceSelect.move(5, 5)
4255

4356
self.createModpackButton = QPushButton(self)
4457
self.createModpackButton.clicked.connect(self.createModpack)
4558
self.createModpackButton.setText("Export Modpack")
46-
self.createModpackButton.move(50, 50)
59+
self.createModpackButton.move(5, 175)
60+
self.createLabel = QLabel(self, text="Program will stop responding for a few seconds. Keep an eye on the console.")
61+
self.createLabel.move(5, 200)
4762

4863
def createCheckBoxes(self):
49-
self.doDefaultSoundRemoval = QCheckBox(self)
50-
self.doDefaultSoundRemoval.move(100, 20)
51-
self.doDefaultSoundRemoval.clicked.connect(lambda: print(self.doDefaultSoundRemoval.isChecked()))
64+
self.doSoundRemovalCheckbox = QCheckBox(self)
65+
self.doSoundRemovalCheckbox.move(5, 30)
66+
self.doSoundRemovalCheckbox.clicked.connect(self.updateSoundRemoval)
67+
self.label1 = QLabel(self, text="Do vanilla resources removal. Keeps any changed resource files.")
68+
self.label1.move(30, 30)
69+
70+
self.doClassRemovalCheckbox = QCheckBox(self)
71+
self.doClassRemovalCheckbox.move(5, 50)
72+
self.doClassRemovalCheckbox.clicked.connect(self.updateClassRemoval)
73+
self.label2 = QLabel(self, text="Do vanilla class removal. Keeps any changed class files.")
74+
self.label2.move(30, 50)
75+
76+
self.doLWJGLRemovalCheckbox = QCheckBox(self)
77+
self.doLWJGLRemovalCheckbox.move(5, 70)
78+
self.doLWJGLRemovalCheckbox.clicked.connect(self.updateLWJGLRemoval)
79+
self.label3 = QLabel(self, text="Do LWJGL removal.")
80+
self.label3.move(30, 70)
81+
82+
self.makePyMCLModpackCheckbox = QCheckBox(self)
83+
self.makePyMCLModpackCheckbox.move(5, 100)
84+
self.makePyMCLModpackCheckbox.clicked.connect(self.updateMakePyMCLModpack)
85+
self.label4 = QLabel(self, text="Make PyMCL modpack.")
86+
self.label4.move(30, 100)
87+
88+
def setInstance(self, instance):
89+
self.currentInstance = instance
90+
try:
91+
with open(config.MC_DIR + "/instances/" + self.currentInstance + "/.minecraft/modpack.json") as file:
92+
self.currentInstanceVersion = json.loads(file.read())["mcver"]
93+
except:
94+
self.currentInstanceVersion = None
95+
96+
def updateSoundRemoval(self):
97+
self.doSoundRemoval = self.doSoundRemovalCheckbox.isChecked()
98+
if self.doLWJGLRemoval and self.doSoundRemoval and self.doClassRemoval:
99+
self.makePyMCLModpack = True
100+
self.makePyMCLModpackCheckbox.setChecked(True)
101+
else:
102+
self.makePyMCLModpack = False
103+
self.makePyMCLModpackCheckbox.setChecked(False)
104+
105+
def updateClassRemoval(self):
106+
self.doClassRemoval = self.doClassRemovalCheckbox.isChecked()
107+
if self.doLWJGLRemoval and self.doSoundRemoval and self.doClassRemoval:
108+
self.makePyMCLModpack = True
109+
self.makePyMCLModpackCheckbox.setChecked(True)
110+
else:
111+
self.makePyMCLModpack = False
112+
self.makePyMCLModpackCheckbox.setChecked(False)
113+
114+
def updateLWJGLRemoval(self):
115+
self.doLWJGLRemoval = self.doLWJGLRemovalCheckbox.isChecked()
116+
if self.doLWJGLRemoval and self.doSoundRemoval and self.doClassRemoval:
117+
self.makePyMCLModpack = True
118+
self.makePyMCLModpackCheckbox.setChecked(True)
119+
else:
120+
self.makePyMCLModpack = False
121+
self.makePyMCLModpackCheckbox.setChecked(False)
122+
123+
124+
def updateMakePyMCLModpack(self):
125+
isChecked = self.makePyMCLModpackCheckbox.isChecked()
126+
self.makePyMCLModpack = isChecked
127+
self.doLWJGLRemovalCheckbox.setChecked(isChecked)
128+
self.doLWJGLRemoval = isChecked
129+
self.doClassRemovalCheckbox.setChecked(isChecked)
130+
self.doClassRemoval = isChecked
131+
self.doSoundRemovalCheckbox.setChecked(isChecked)
132+
self.doSoundRemoval = isChecked
52133

53134
def createModpack(self):
54-
soundsMD5 = self.getSoundXML()
55-
print(soundsMD5)
135+
print("Copying instance to ~/tmp")
136+
shutil.copytree(config.MC_DIR + "/instances/" + self.currentInstance, config.MC_DIR + "/tmp/" + self.currentInstance)
137+
print("Copied.")
138+
binpath = config.MC_DIR + "/tmp/" + self.currentInstance + "/.minecraft/bin/"
139+
mcpath = config.MC_DIR + "/tmp/" + self.currentInstance + "/.minecraft/"
140+
141+
if self.doSoundRemoval or self.makePyMCLModpack:
142+
print("Getting sound MD5")
143+
try:
144+
soundsMD5 = self.getSoundMD5()
145+
print("MD5 retrieved.\nCulling vanilla resources")
146+
self.cull("resources", soundsMD5)
147+
print("Vanilla resources removed.")
148+
except:
149+
traceback.print_exc()
150+
print("An error occurred when trying to remove vanilla resources.")
151+
152+
if self.doClassRemoval or self.makePyMCLModpack:
153+
print("Extracting minecraft.jar")
154+
try:
155+
shutil.unpack_archive(binpath + "minecraft.jar", binpath + "minecraft", "zip")
156+
print("Extracted.\nGetting class md5")
157+
classMD5 = self.getClassMD5()
158+
print("MD5 Retrieved.\nCulling vanilla classes")
159+
self.cull("bin/minecraft", classMD5)
160+
print("Vanilla classes removed.\nRepacking jar")
161+
shutil.make_archive(binpath + "minecraft", "zip", binpath + "minecraft")
162+
try:
163+
os.unlink(binpath + "minecraft.jar")
164+
except:
165+
traceback.print_exc()
166+
os.rename(binpath + "minecraft.zip", binpath + "minecraft.jar")
167+
shutil.rmtree(binpath + "minecraft")
168+
except:
169+
traceback.print_exc()
170+
print("An error occurred when trying to remove vanilla resources.")
171+
172+
if self.doLWJGLRemoval or self.makePyMCLModpack:
173+
print("Removing LWJGL files.")
174+
try:
175+
os.unlink(binpath + "lwjgl.jar")
176+
except:
177+
pass
178+
try:
179+
os.unlink(binpath + "lwjgl_util.jar")
180+
except:
181+
pass
182+
try:
183+
os.unlink(binpath + "jinput.jar")
184+
except:
185+
pass
186+
try:
187+
os.unlink(binpath + "license.txt")
188+
except:
189+
pass
190+
try:
191+
shutil.rmtree(binpath + "natives")
192+
except:
193+
pass
194+
print("LWJGL files removed, if any existed.")
195+
print("Creating modpack.")
196+
shutil.make_archive(config.MC_DIR + "/tmp/" + self.currentInstance, "zip", config.MC_DIR + "/tmp/" + self.currentInstance)
197+
print("Modpack created.\nMoving to export folder.")
198+
199+
utils.areYouThere(config.MC_DIR + "/modpackzips/export/")
200+
if os.path.exists(config.MC_DIR + "/modpackzips/export/" + self.currentInstance + ".zip"):
201+
os.unlink(config.MC_DIR + "/modpackzips/export/" + self.currentInstance + ".zip")
202+
203+
shutil.move(config.MC_DIR + "/tmp/" + self.currentInstance + ".zip", config.MC_DIR + "/modpackzips/export")
204+
205+
print("Modpack created.\nBe sure to make a modpack.json file for the modpack!")
206+
56207

57208
# Screw XML. It's terrible. I chose to use it because I only had to add a single 10 character line to my minecraft resources php file. Fight me.
58209
@staticmethod
59-
def getSoundXML():
60-
md5Array = []
210+
def getSoundMD5():
211+
md5Array = {}
61212
with requests.get("http://resourceproxy.pymcl.net/MinecraftResources") as response:
62213
trip = False
63214
for string in response.text.split("<Key>"):
64215
if trip:
65-
md5Array.append([string.split("</Key>")[0], string.split("<MD5>")[1].split("</MD5>")[0]])
216+
md5Array[string.split("</Key>")[0]] = string.split("<MD5>")[1].split("</MD5>")[0]
66217
else:
67218
trip = True
68219

69220
return md5Array
70221

71-
def md5Recursive(self, pathininstance):
72-
for root, dirs, files in os.walk(config.MC_DIR + "/" + self.currentInstance + "/" + pathininstance):
222+
def getClassMD5(self):
223+
224+
print("https://files.pymcl.net/client/" + self.currentInstanceVersion + "/classmd5.json")
225+
try:
226+
with requests.get("https://files.pymcl.net/client/" + self.currentInstanceVersion + "/classmd5.json") as response:
227+
classMD5 = json.loads(response.content)
228+
except:
229+
traceback.print_exc()
230+
classMD5 = None
231+
232+
return classMD5
233+
234+
def cull(self, pathininstance, md5List):
235+
base = (config.MC_DIR + "/tmp/" + self.currentInstance + "/.minecraft/" + pathininstance).replace("\\", "/") + "/"
236+
print(base)
237+
for root, dirs, files in os.walk(base):
238+
root = root.replace("\\", "/") + "/"
239+
240+
root = root.replace("//", "/")
73241
for file in files:
74-
utils.md5(root + "/" + file)
242+
try:
243+
if utils.md5(root + file) == md5List[root.replace(base, "") + file]:
244+
os.unlink(root + file)
245+
except KeyError:
246+
pass
247+
248+
249+
def closeEvent(self, QCloseEvent):
250+
shutil.rmtree(config.MC_DIR + "/tmp")
251+
75252

253+
try:
254+
shutil.rmtree(config.MC_DIR + "/tmp")
255+
except:
256+
pass
257+
utils.areYouThere(config.MC_DIR + "/tmp")
76258

77259
app = QApplication([])
78260
exportWin = exportWindow()

mainlauncher.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from PyQt5.QtCore import Qt
1717
from PyQt5.QtGui import QIcon, QFont
18-
from PyQt5.QtWidgets import QPushButton, QWidget, QComboBox, QLineEdit, QLabel, QMessageBox, QDialog, QCheckBox, QVBoxLayout, QGroupBox, QTextEdit, QTabWidget, QScrollArea, QFileDialog, QApplication
18+
from PyQt5.QtWidgets import QPushButton, QWidget, QComboBox, QLineEdit, QLabel, QMessageBox, QDialog, QCheckBox, QVBoxLayout, QGroupBox, QTextBrowser, QTabWidget, QScrollArea, QFileDialog, QApplication, QTextEdit
1919

2020

2121
class mainWindow(QWidget):
@@ -181,10 +181,11 @@ def createLogo(self):
181181

182182
# Doesnt actually anymore, but I liked the name, so it stays.
183183
def createTheInternet(self):
184-
self.theInternet = QTextEdit(self)
184+
self.theInternet = QTextBrowser(self)
185185
with open(config.BLOG, "r", encoding="utf-8") as file:
186186
content = file.read()
187187
self.theInternet.setHtml(content.replace("&background&", urllib.parse.quote(config.BLOG_BACKGROUND.replace("\\", "/"))))
188+
self.theInternet.setOpenExternalLinks(True)
188189
self.theInternet.show()
189190
self.theInternet.setReadOnly(True)
190191

@@ -744,12 +745,12 @@ def closeEvent(self, event, *args, **kwargs):
744745
# Wouldnt want to try and launch a non-existant instance now, do we?
745746
if not os.path.exists(config.MC_DIR + "/instances/" + mainWin.currentInstance):
746747
try:
747-
mainWin.currentInstance = os.listdir(config.MC_DIR + "/instances")[0]
748+
mainWin.currentInstance = os.listdir(config.MC_DIR + "/instances")[1]
748749
except:
749750
mainWin.currentInstance = ""
750751

751-
mainWin.setInstance(mainWin, mainWin.currentInstance)
752-
mainWin.createDropdowns(mainWin)
752+
mainWin.createDropdowns()
753+
mainWin.setInstance(mainWin.currentInstance)
753754

754755

755756
class installWindow(QDialog):

0 commit comments

Comments
 (0)