33import os
44import sys
55import requests
6+ import json
7+ import traceback
8+ import shutil
9+ import distutils
10+ import configparser
611
712from PyQt5 .QtGui import QIcon
813from PyQt5 .QtWidgets import QWidget , QComboBox , QLabel , QPushButton , QCheckBox , QApplication
914
15+
1016class 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.\n Culling 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.\n Getting class md5" )
157+ classMD5 = self .getClassMD5 ()
158+ print ("MD5 Retrieved.\n Culling vanilla classes" )
159+ self .cull ("bin/minecraft" , classMD5 )
160+ print ("Vanilla classes removed.\n Repacking 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.\n Moving 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.\n Be 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
77259app = QApplication ([])
78260exportWin = exportWindow ()
0 commit comments