From e8813fe153362b990d4d5af0d3b6cc3f386f1ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+CiiLu@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:50:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Cleanroom=20=E8=87=AA=E5=8A=A8=E5=AE=89?= =?UTF-8?q?=E8=A3=85=20(#4272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Zkitefly <64117916+zkitefly@users.noreply.github.com> Co-authored-by: Glavo --- HMCL/image/cleanroom.png | Bin 0 -> 40127 bytes .../hmcl/game/HMCLGameRepository.java | 2 + .../hmcl/setting/VersionIconType.java | 3 +- .../org/jackhuang/hmcl/ui/InstallerItem.java | 18 ++-- .../hmcl/ui/construct/TaskListPane.java | 4 + .../hmcl/ui/download/InstallersPage.java | 3 + .../hmcl/ui/download/VersionsPage.java | 3 + .../hmcl/ui/versions/DownloadPage.java | 3 + .../hmcl/ui/versions/InstallerListPage.java | 2 +- .../hmcl/ui/versions/ModListPageSkin.java | 6 ++ .../hmcl/ui/versions/VersionIconDialog.java | 1 + .../main/resources/assets/img/cleanroom.png | Bin 0 -> 1454 bytes .../resources/assets/img/cleanroom@2x.png | Bin 0 -> 2732 bytes .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 2 + .../download/BMCLAPIDownloadProvider.java | 7 ++ .../hmcl/download/LibraryAnalyzer.java | 4 +- .../hmcl/download/MojangDownloadProvider.java | 5 + .../cleanroom/CleanroomInstallTask.java | 93 ++++++++++++++++++ .../cleanroom/CleanroomRemoteVersion.java | 38 +++++++ .../cleanroom/CleanroomVersionList.java | 69 +++++++++++++ .../hmcl/launch/DefaultLauncher.java | 10 +- .../org/jackhuang/hmcl/mod/ModLoaderType.java | 1 + .../mod/mcbbs/McbbsModpackExportTask.java | 2 + 25 files changed, 267 insertions(+), 11 deletions(-) create mode 100644 HMCL/image/cleanroom.png create mode 100644 HMCL/src/main/resources/assets/img/cleanroom.png create mode 100644 HMCL/src/main/resources/assets/img/cleanroom@2x.png create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomRemoteVersion.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomVersionList.java diff --git a/HMCL/image/cleanroom.png b/HMCL/image/cleanroom.png new file mode 100644 index 0000000000000000000000000000000000000000..39754085d0bcabfb48a03624f85b02cd45664b2f GIT binary patch literal 40127 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%0A^F9V}B7^{y_xC7{ReSB~pGw!- zzk(lbEfQV+sNp&i%Zdn-Q7&lKJ9Hy+MH~dA^~t4gCT@o@WhB*s^Ry|&VQ-l26@70$ z=XTr8m}lSU`$)rCIi=<$orq_w6M6u%PXfM)V$>VWZx_Bo{82VTgo=Cp1#v^LPUw%T z#9cLuV0<$!Gnzm{5|?+>t_Dt<1Th!#4N5LU&h$y(Rn^Wg3uiUTwJHX-IoOPqxwwFn z?s^J4eS5{TOA|&dn8mD+1$HekdLFoee>Iv3l*gDsOT&bL4t&3#cvkVYtEJ&`E@c%< z*RqW$K%Tj4IW#z$Qj23SOW%)dN5{#=&OnWD{;>+Zk<=UY2rs!}fo8_w7?J!s%)h3| z+3(kNr(JBSoO|QbBuY2Kmtu3j2LGAT5#@Dq8YBgd+`rG;m!&a_hqtBj`~`Q@bNda)Bp)+ zE_Xj2n<$LhGHr1r(ZJaG2NyRk@0ZA}T+ezZl&N|LhJsG=47|!hjAATIfjY~BO;lKi zJyHXeO1?aF$FmJ_2ZItT1J$0e-4rO>eKG9OtB+<-3QVs>}8B0Zx4V{*lLr zk%*TtB4h2e8_k7$o{Cy>>fMbr=c>OUNr_LxKwG%+b#LsqOj*!Wjht`UU#ZhlYeih+ zmS(*w^ePfP%ah?MYqm*;L^>o5eOMsr_!HhoVN^EwqM|pJPZ7ozDJgRZzo1s3$r*+Z z6{|;}o>$jkH&SppYa1HS_AF1+j|*Mp&3OL!a{nUXyFf^f@QSKu!LY61-Ui2@EF#qL z(-3c?j0BfE8!PreVQlSi0)}8q5i@K};2$NbYW=}ix0y`P8Moc4N+&c7tojGuuK^Ah zNiPhL*ULA;s88_1@)mz(I`r}9AYm_c(p~kbp=FrnI%gBB!;x`8TNm!>HRQ?!eSIV~ zUAWavm+nYjBYAP-iBLQ^>XXFpJc;vZ|CtpUncW+Hs{mi($#DS%Dwja_aW0ny<@S5R zbh6*rU(iN)f=?V7;}a_EWn7VRvMD^y=X!>JiRZi0$gzFKlGL2{I4s#7>7&`2 ztvAs;Ut<~K%~ARdVu>pohSRuI2y~*;2^h--VLjA6vaVc)tNG-x@uO|}u%YpW9<{4^ zHb?|5%l8^z8P2UN`RrN!wYRMSje&(DgnOS;JIYrAAE%|Zg}`Hs7+hO{Bk}qjuAo3w z_jM>3!L6?}Cc>?qUz+t?Im#bRz-LMVuYPF;CF+mjv`fyESc^;MNNN_>nh=i)DN-Nr z*;9c(H{|+jgkox6_z(xGkrl?NX}jj3h8iE{r)=YGA5PWh7|l*oF1@U343=(P-{lZ#Cm*83rTgZtS_X+OWh#&=)g?C z<7%)K1Wj~a(q>eYaDSH^Ep^onc{-2FI7RV_v34juRg1;W?3AkQVZD>q*PU|zncs`% z)#2fm0o&!;ugDK;#;pCHLZ!%T$m2GG`2PD@MKVctwF#rd@GrI{egZ-@l6$RcGUt&`l+s5UH&gX_Mi3$7x_ zBQ7#B1Fc^@9j*Mj5js@6;|cSqTPXEw!C^Zb@ZL;baFOJNwh_ON{Vp~4WS`8Va*?y# zJVv692XOcNz7>PUbq-se}aI+yg`b0ySDlxMz5&xhaU z>`b#D=3-W5tce==r@^pVE~BC-Fm@=v!)_FMgFnNUo7|czy_Jx)JTNO;NsI&TcuQ*G z`v%xp3s$PhBX$>&Ga+RiF*%{2^!(*y7Xq|1|HHvqXvH~Uqzxcm;@)JENVq}TWsso z+bk*b$Cs-`3G)xKc+I=Qph09B6m5Q;q$NApZcL9zj@KTUFh#6?3kwr-hU7Fv&WVqLxDqo5QF?wL~5bXicH7+P8=8oAs~{sXTP;9Ndzu zlQSOt>r%um8|wir1Z-d=;sN?`KkvmfkLQJGZqG8&1m?WhULn?>E(~Kx=FS{!R0e;u zfmSyW?olCR|ETEI$0ndl%Wdkp47L;5Iq$W6Oh-~WU10&fFKZ%v+j?lKnPIN;t<~hf z^B}728JwJ|DyuxM8JJB{eD#$_Hfi9jGE5t2-=Ddh(yJP@28=yl;@7|ZB@knJR4wC) zWovA_K3>G>4;lhpNW4SRKONlmP=O9IB$LL8-`)=K)?uD~WFf~_(dLI^zi)`?Of7IFiEHj5!xP+I8G^Z8U~!cd&`*&22&sN8t-Orl=KH2~JJpJLS~)gyovH7Td;1E7#z~-^qWA zDU}>uWRVVZ8PzyL6dOeEeKwXEiNZbC?ARlAj_k-`zq3AQ>#m-H*h3I>5v0(h<#>b) z+Mcoig}_QmP-1z$7_Sx2tW>SlqZ4x&BCWSj(<t|om6QPonkjvv0Lh=6fN3x)r%QsbW%Z~i;Zd9{D6oaVXF#lyYGzWZT6K-v- z=b%lZ^i;oA%7&tB+aeKc$glI0cQ5x)ZIq|;E}k6rqeWk3MdH?)D=h!rJCC8C;&Y1E zzWgbVn&3ThzH{W?<@#JLzcf9GrQW!VY0?pL_srYja-k{P3^k%>eDM5M;gfnC z+YuB^Yul-}0xLh1(sp%E9xhX$?cG@P8z5N1SpDn#?A9N*K2q-?OavZ%abn?Y^o#Eu zn6sZ!pN4y@IRzGJ`{w4UT}<^g0d56_146W4qOPAHbc>xpvLA*^AOSkDnKLaSa3M2x zr#dS(d9+iNp=4 z^G0*U>?zoPq=D2*eOlgMUW}Dn!zfZ*i#R)FgK8Efbk=ge=I6WgDPdzM1AHv%k4pbMW@I_~`bTOO(^?{fkw&x*J|a{v>GWcN zHk$H^B@q=|+Laef)gkg#l*~*vHJsNs0)`Onu*cf#7zm4F4_RluF*x6P-?hI}vHtyo zJH{x5d@PBDBO|L1d$zx{mpbc0uX5C~-OGiBF+yU_kn4)+t!)L-Pt~BxFTI@7c#eWq zvOl90-Px;&WqcfIyE`r0-&`5ltt@Wws)N|Qf-}BJB8S_(>$YUXf8QM2q+{O1HY>$u zSGXx>QiZ<28;v($pWUbhpSdLXz4eNe=ocLXjk)?ph3JI8AE=M-Mt<~)^-i@vIT_v> z`Aanc+UB%P)HYA_;Vg4eOpa7Zho+>DrJ;E$)C+mpb+R=eLcN78#=RW8)7hTHn-~3g zt=qEN$PBlc3pFb^l34du+DBnUPuOQxVH!3T^)Qve@BM;lrQPrRRqIt2refWi1Y%GJ z-whJBuzvW_STw`YyT%f6Tg;jre~a1O`l#Fan>&4)H!nx*r-0*oZk(@8jV{u$FLiZv ziURG9O!7Dsp3YYex4tPmDxmzZuaL$$ubKI40YkeoNL;(ftoU}lB&;GAc{f|z(MG^M zHKD}uY}IsGcaw&LV>;nxdnl67zf?e%2O1u%MZngodN!np0ZYPIrj$-}a(86N1}8(5 z3*prI_6ZkVH{C?bKAgg|v2H$db!zo3t8>3y}9KXJ4H#@UtNl*;-L8XRlTR|r> zEy77bB>_pvPOQ4q3%0CC|I0Bn@2J;PPx33QXd43da7bLZi*TIq(;Qr`jqy)BF|at* zyMG{1ODT>Bv){oMmyPsnX1k1qF2apD9R%zcf?y70K{%;V{3-Ofi|qRgQmxE z?c<*Aqa5U&7zk9=z0kh_!r^+wi)~y%?Gy>`njTak|CP~dQzpkvvsv$maD^4p)0#M^ z&qm5z#f%|O^V&S;-ri@5={M6BWju;YG(ah}OGI*iw2OuV&xpaBGqWQ#~O58f6xlBwuPai^?|2 zSN?#OdA8931VnQ!eBp@5;$7iGy{uOC!exx9d6U3f0jpcK3q`6=pFXD(ZV}*)#fv@u zLgUR%ElbNzKMm7hkp#7(`vs%B&33{#@|+oTmoWT@nXjLJZ!nOiHYg5nb6`Q0d-z@_ z_d8VDE(nnn+KiZ|(0zbCd}P|Ag%<5p%$GhnQH-YLG8Y9k=Nb#r9zCKz9gpyEa7mae z`@DBkAl&RmCkPUJS)tS(ju|g%YmKa@&~3^c*k6t*qk88OE<=7Yktny>`<+2ryi0;(`4AQK0GRUtnP{a^u6dshb5~X%B}@v6 zG;1(u?PyGRO${&}R)@MTaf z(WU_9Oxu|d%%LBu<^7A07fbz0bP}m0V;hr6g_N+Gq8_C--^lrV(=FC?O_f+(m z2^1DZkzXnZ!YEs3Wa~aEE-8_S4C2x#CMs==%SWhNa0%8ls%vGmnHMgS6^~a!xfZbf zThyO3cUUXCvp@XNw!PrexIX9<-+kSxV)|+?QObo_?Tjp?pKEBtg~)Y$x-qMzj+~Sl zBR#SYs@w+g_dHVBfJxehPBKKXZ{ut3qvXgD*4_S5A$)7aqDDxxUBbl z@m~LOf9K}&8h>|(#tPmz(;Ke`5gL2ZxYb$O z8uW(q)4(C8bDk+GaiTYR6nl~KYrPMN=3l06bAk?pJ+d(*roLdP0&VxQCO&cdgO8*f z{dC=}T7YX`w*DvrsN>ywHJ2R#oTiyotKUG||o`T9DA<6z}Rz7yY2_4l{#Y`EKUB2Cw~X=Q3w*M=O}R#k4w zf!{hqP$Pzr!Af;5P<6ICqoG)2nX@zS(ce_gi@jr>d@4lqv|`Ct&LN&Z!4v&go0@VL z12Egfe&K}OG)YlDoQ1Vf^4(J@#4F#QEWj0ozU%8Ek(9`fVQv+uMQZrla^(yjMD=2? z`xA~tGWBPq%R}XAo$XK~`tLT`-_YDv6n)ve7V&rWOg=MbaSAhab)t1Pk9F-KAYRtS zO-?PEjh%^@iONwK1G8jiSG)(_6@nLUK81=k<9wQX+NZ66_}uvS+Y`ZB5Lf9OoL<1L3Tj;JX&{VHN_xP`SOKw(>v7~qE5 z9Nc&?E}Tb&kT_zvlo0s1aPf)Y_qYS$G3z9D46S8DX7w;n_-qeZcpQ!ilO)jx3GL{1 zQoKHqRKD2RNcTR}O+Wss9iL}Hta(!?j*0)H6jO3`DrIP*@X@o0$*Z(50%7|0{+4QT z8JX2C+;=5Qzjt<-L(ofkj#JnWL1;#kD84aLRUpcg5~V#X$~rn;9!fz$iD))9uiE4BRBYl<=Xkb-iNiwd2c=>6XeDgG6r4Y5@ zrHeHkB@7o7A=kj)*wf;u`5Iod`C}t_;2w^c(7D=F<-1*cvm)TMKqA&I&=TJ%q<%=c~n`73>iZHdG=8CiaT>y^t(489Z_H52>bMjwP zGglTn8Tn=M>IomqFfA6-6ilOo*R)jM^N9^8@bG%RU^k$G~~ zNa;&kjn?uVaE-CS`gyc&Z3YF{Cn|MRv-tf4Rh zej#&@ff7PFOz;dJ(OTwX+vLnHhf>+RlBwK4B+S3xSxN*=cf{z9eUMo5BC?edoHOfI z7~tSu+Al=ro|>lQLxC?Op2h0fD)VO8_bokAv*r^n6I|yLsS6|YWY?f7t(>IT0L8w< zoGUbFJXK#KeG|Zm+x(-u(S#Y8?6K^G#!KLTnn>SsfnpDJ z<@8Dv8$4S*rl@Vot5KV~+U*0G!D!1B`1pfi1-VjI17g&gnQMBvR{WzugKm~CJLjZA zS8AJMh;UwYdmC@ppITwloNw?G^-Xl_BbZaOy>C9(_*Pcz$`EI@DUVsf$)BEvWxW6S zLU!e0($1Xv>AdC-4e}Sipx^~x)op+_w%GInlbh0DKTKj*x38Q)XEdLZ&EY;<;`SiH zZcvkurgjw8GFJo})C?9obbt?H8PHZy+9i;c5YE5y=d{Q$4;tflYPF51@yC+V1&3#G zf>ph)#W@a0Z4hy>=GJ@G7wn&jSh%+8_>_e2v5hm)+iw7EnT|FCr~Zxf1c`FU|+nVv#3Km)x;PP09o1e&y)YDK-uf*4w>e} z1|JsQ38+)PTm92vQ!A&2HobnXEh=iACW`X#x%w0V&|ti2io60$W}y~u3Fm^cm6DOL}Ju$GuhZMBx%QvS%9oK4kNwxcsYs6Y$(9~kH6V|8dvwa6)5I^I zh&#m>Gi9e_1op@`E&iQAEQ{%;2XxS3-2~uUQR(lQs0e)5z`2dqY6ayx`EPm)#bngi z*E7dd{k)ySXyI8YWOnfyFe-_A?4Cl=YL|0 zGla~WIg%$ccS8(>)JOY+>%WH#G@iVhe`EdobNt^Qq{dQt#vkx*S_ZzG67Z(^B+f~ z(EW_OvBWgsnv51*w9CQvx+yaA9kgb`pR34ROB3}?xLbWBWex{!#hiasl@;Jck8H52 z&|{A?KRY&S5xrs)@U13!@ALcHUo=tqtC;RqL*Co3O$fRMY?WR*@2rlokXwa&$^}jqcGgx*MdV z84V+(Ly)dXNjD6nyIZ8dRSV;! z`gZv38s#eeix5H)u4ecio0|CP-4O;M9q02ronu4nRA}?+dZM0Nk(bDT{D?8fmt!%* z-i8>2S=In%OMCj&nqd~I2~y2kyuiDi{u5aW9!5n9@??$nNLb|eQbrPf5c&Zn zuqDFytLWp`GYa>W=Da89Ko%qyL2L&&9N^w1&$(T9XwteEI-ZK=l<**=5e`@;C=De#RQmMky^9crSlYmG~?g!ipj-;HnrXCza>!-1g%ad zbE{XUGBV^$B@N9`J1V%f+a$mPs`3^BoO?5AII@nHk-)(wS z`2`WsDTN$08KB;Av35?ky`uy7F*sy4eN5SdPe~HQt!%7?uP&jt=qaFQNged-3GViR zhW59dlAda!)$S$WH8+T2*?}wxtBERsPi^_g$6^cJ?O-=^t9LeY*77w5hOQ(&>VhKY z$&A(l47+IqT0CI=8E6B*{@37t_G-hD~~2V5!RBW{W%dSSWw^Rrf6IHS{H-sVM{i{T{ia)v=}xlq|Ll-4v(L(d{i z+jHzP%QY9|To&oAv|KQkAH~eR8s$=kHm*l?9^gPoBh)0+)i~5sXgR0wsRLTE+@@0L zYC1ETooo%@86G8ya9UNf(n{Pd8Pi0iLqeIB6&iQ&oh9;tz~A|;I$gCq1&0j&XZr@8 zYVTE)L_USi`1!WrthPB^CMT-L%#9H2X&4wV~)7EUZDa_P#ClAq|e#?)<;jH`JeU^*JO#r(R|yO8Oe}C z6L51F$j}11OYnn2&rw?d1{;B^wu-g^N+{$s9o!i+Y@~Cua!7^XA3t()E68uYR5vz` z5}^@A<3qPk>lkv|zyy%7c^lzqEuPHHc^MXN({_2kPBa6M9SL$xVWXHl2F%}!bzdm& z%nh!y;#9r@6hf_a!>?gdDB9~0Zw*D;1`Y2n`s)Wnp9iuJFy>JCAS zC0TDI6`we$aBNc$wUMO|g?53<*;0n2ToYN&=HX7ScP?LqNDbmf{QoFr)e0>&=aEvo zQDbIAb5j?b^e+!j_Iw)@_xr+=)Egn416sa)+d6(7qXn>0Zihxd?aSRJXHtv41KwtL zwE`)4Vnn_Q@n)U*h%^bo&tUUPH)0bzo1zQWkUyOpQ_=V#`&VA7bc8WQumf00WzP|N z=dk-)mZz?7+uKjaYW<%NGPgIFIQu+kO?r_%<}-y!krkgn*7|=d5QH<3hZud$P=c#4&-!QgT31tbh;9Xz zsynAio*L8RM?b`G9DK8n3KJZ&Nw(%(&!?ifAR647se7>mWiBPKaHlNR+wTkRjHI?z z^U5ZT{yQMXvqBIU;>gMQTlDVNQKaU3s=b&-X2Rb@Ct%9?M@9eRv06T%6Yn8>0oHsmrnBsxQ`AoexGL37+pDc;v(cfdJNYG3XK!cZD5}A|C3_=4?dOXN4 zlZLTMhQfYOM0J&DK9+mz&^v!gK3Wu^@TL_3ELx3v7uxa|rPi}}?s21np*SYFyUTfwc+C>W zOe!x?8ZB3PUMffMvFP$ShXvbaNWD}B*zpAVUT-%{ z%5~X1HHI>TLG3-S2#)&qnm#cmQuD4oPbdb)Dz;(pMx2QX`u-L30)*$NuWIP!H#cuZ zz$k&7Cgm`y@AXDz0rEWJ`^p}qC2R8>o$&8^kb1AQJiR-vJGZ#u;n%ZoQIeO8`h9vrJ%=DGGk4_DUiA;xmNaV@Z8+Z01<^QjpKZw)bgY zPs2hrX&isS3%iQ^*;uP75x+J2TMV5n^Ho9d=mdrQ#K?PghSOr>d#t0p{P@=v1v7o`+pyj1YxFjP_}f=|P2G02CAi?D9QmnlC0ZiU1x&;G3RM zw4>(gJE`jsM0ewNnrGA!Ar$Aqf9XNL1$X?)k43E;Y*&i08N!DdK5(INn_7mp7_!?n z8**Vin_$P^KCSQ0NG;j3P9ic~f|Qp&qc)BRdEA!|ex zG!a2=5e_1Xb8v8AeGG7SCq%XUY>Phs+mBYZb#;?@)NPiP!Z|tvzsyr5B>^_Ap7EXB z?jSII0|Q=9aBfTNIGfiK7vWjZ_sRr%XN-IufQL87o$$)c4>MR!+X8=~n8G%lE7i}W zw{apH+^aeXiv->KLy z05KsM3G9Z`ww}4M-I7|(-O47FFKakT1eR?F5%>na?dyD>eZ0NBBZRu$UtC?;&iS%| zi9(dUt)0}F_@eZr)7D*%!&Ix00bM~D6QEx@@u<^fFGNS(tRbtq<}K|H4?lo{*?n9nB(v)^of6P!9KrR$Wry>G#RFdoN{RHIZ~7QJRfDg6h5CmuS&t} z>x8jpLSePS@IV+#(3q`ysHQ4awjLG9kKc8-trb9Jy_8z^Ny3>Zy-b%gLJIGyDR>(64c1<7BcZR9hs_r{iq$O8=SV zI}N>=>PpGy7eYgACd0~`;obK;R$B*)=JaY{61k%!{NN9qLbY(xXNu=V+vZqfnPh@B zy_UXO>o_Zmolbw^&YHKXk|uk1$@Io=dAT$`BAdAUk*SL^Mp|i-k);(3?nfnXr{6ve z>zzy-QUvlRAsRKzhTWKflj@lz0NDknCiBG&G=dM0UP{Wj>IBL|3?nk80F$Gqtr00YaBc1vLP33h#F z6~&*B?y|fH6j|~1#jE~jT|WhSEu??z$(`IGUXXAW==aMad+HkEGHWCI_^;FF(p9|Z z7KiE4GA9%i^>r+9+zd90)hPHReM1M?@iSlU?lgYZ0M;- zE;iBiX`_hSrWM)t&#*sXF`=Pq^(T6TYE!LlC*RxNL{2PNt+xR`ha7K|X(4nN3X(p~ z(qp-o$TFdBl>5}KuOw^kIMHfB6~tP|^DVmh%nAi?_tsvOY8-E8tt@|uX+A0{-`H}4rt-!;+lFT63(ToqG2R}4C zl$(l_AU>0?=pu7?f@y?NiB znGIU_+i;}n{dUt&%L%2*k|g(vqevzW;|+Rn%05lJUOCK>lHAE`&c=eC2^Zp`O*xh} zk8aFE+GI-X@P2fry>fRG#ki;S`!l;ZOs#L}XrxBF;*;YXw`#YtHT2!t(O3BLvCc{I z6Wg5?MDJc()?K>`ukz6V(^%w%S6#5K@1t$lN97?iDvZnK-@%4ho}Aarm7fnKqu^2Wa|Dci z<1TvL434gUE5tfXlx}oOIp3y9*$!yTTwdrg7m-o#LlL)Q2?{_npmbh;&ofyo5t4GI zL22vcd9`K-=&{7nL7yZwDn$x&mzUMTM+6CUr>#vgBiQ2HNAKFYob(G7LCM4S^V^=r z#mVW(pDk6glI=}g$ild;RL~$Ow;cLe=sCBe0GfAgQVAwfG)Yz`%cAuylS1lPI{$LC zH7HzjPPNNBse87l46KJy*-@tGO~i+2hK?2NVd_T&p43z0;o=@g3q&BZ(ZSXfmX<{` z8uZ=AP3Al%(ZxJ+L-Ub=P@_hkm<5B7K&}QAG>WMxE*Jrws&t5p20_DUYr(`6zhkq@ zWpd@=fD@ySm__Z_o-|wE9*l`6<;!7i)re)O>QBv-Q(olM_9q>iv4zkx(Ja?Yt5uF+ zZdf_zQM5U~-o1YOXMe*YIX(`TaG%dALxU0e=6~Y`4+Q4oVm-GV@y^IQ^-g?&5C@`n!=Z&%GBS~@f zHGXpe;6#hex7vkRasB1@8HE;4CbU-0>dR(cu5zl|y8Z!@x9bQRXz z9lvDuMo-BEo2_j~!ke}Z%cO>9{cC`?m@CHp=U^OOG`f7GA$EBnb0l%z%V`ATPFu~G z@Nl)GvpvNzBV;~VPJZYG?-b#OK`#GYcx*-hm(2Cb_ev-%Eb>$bQrwZzu2Q^klwB{$ zh(t!5XB3{_Q;bB5liEc6&Fv>zjL1z_j&WxVl`XCoky6ME!(mlyO*Cv2;olxc$zicx zAIQ6jC?kbHiTJkN=^F6e-7IJ?@E(ntqTA|>d)h>*dga ziS`HVr*2L$R1XWBQP`PwcI)y#3Hq@W{U4CbTWFRM8``_ZIZq7J& z#840oG{ykQ$;qcAaWX+Cg4{QcAH9#TT!L9+33gr!R1vL4!*!IL{f;lRAOTp$4Ysc~ zyjJX8@LX2EyVfhT*Sor#T$QC_$}&NtNn)v`o71+;LbDAEY*7bTFDOw;Eq<{SwM z2x0Dw6a zS?Y+oK90j=s%ozc2QbJ{=DWM-CvmUIVvsV+qq@9H1AwI=#spvovF9Q zyD5Y%RJ%w2>k|(txRpba9@O_OEgjtv1V#%-ex!-O-UoaRDlH%?hI-?S(u0rpfiDg& zzI+ZazwpXgviWwq~DAbS>Mom1A-wm&&#h0QYC%213rT+a1*1q=a ze2L7Zj`d>_lr;&_O#yy(X$=xOxn6L@-?Dc81ph7WjvdC;*E}``a_v(W=&Ln0ye@6f z!GlnetC0MpS7BxnI)>(x-?U@-YpJ=>pI~JX>)8FeX|n|13*q5xT>rJQN8n?7vIY4a z&b=sbiEN&OPPl>AIGGU^SaUs+j)xG+q_C~lujX&mm35O#?Q9s~Y@}~!=vSjnGA5-tB3_-_!>=OvHL#Dk$vns2PB(-B=m-nph+EL4PKh zIs8k~T&wl6xQh1J3%_;Gx+p75+bhHlIR`3Nf&z7g7wE0e!ME(Q=<|t@X<6?SBltL3 zh1VugdSy4K*J{3Z(FfqR;YX!o`QR$-C*1zX{ES0#cJ-MERr;~NU5>B6_kdkQaUX0& zP96vrO35PJ{t5)E?#1=DC6Ik8%2L+N#K{fTekQ&Hvc(9lr5Db6U!50~*D zBsS`&cTiby@ps}ZXw|1UkkSK1j6nB=NPe)K;h?Q2Z5W8y@j4{rOz3+Z?j^S*hGRnq zcBJ>{)azCG;t8vR!ON1+URgBuz<}kyzg;LA;uIF7 zIN1PB+kFD_MhxJ`&``DHO%-5efX5DL#$5$ZKzO)5ua7wsFvB9}QCu6rPk8Dd=nI|4 zR5GlE8Ah%z&pA2{qhvmhzgj4zmH%IY1}8J%&iy4w-cm~gdzlBgtBBCC2PvG#+o7R_ z8x}@KQh*JVwb0!?RG^|T$u(@r)Dc9?YORRSO0{H$CUt+=xj)eaN3G_V4a_z%E_Er9 z(Wm^~Yb7eS|I`W75b#*-x^`zq>Mkus!&&4pVbuud_m)71D(-p&-lqp_E%j0q9}Z7& zBm{d$^w+4&=>9;w<_Ln0awEksFo@$;Kq^jGXGD&~b}(Mfq@OL&OCn%H9W%vDGO%yv z+tVe7-?(SumoY_w9J=bU05xOTM7gF8nV%nG2KemH@iv^9$5Q0=`%5t}R;2MWoe0ww z-VwXAzJw36x?q4<}w1cfr&lcfJVEs>pK(`Yqh0hyk~Y`^l3@!_Z$hqLvGK=h_k%-ppe zI7;RAA(;R1+0Q?v{KcZDJ|x^3$-o`+BTtOEXF4pY&aqQzJa6_k^)x@ag-N z67~sKI;ypXJE)3&?2-5FT|Su^CVN>!6TVc2FKsuLueb+|2n`to5!R2;PGtvAg?>PF z&^Dk<{oE^OEM6J+K8qc=-)W|C$oKv)Ss5yGs!_ z+au{Zr~XM&$)(XjjwP>a41xhG~uPs$+8dk^hn|yYPXt~^bY+?gEV+^vLQ9zXnK5OAS4bM zbMC2GOk9YTGs!`TO+@*f7?^dEWn%~0ByT?ijYU-rhperXgQd7or$gLC%E8=3&SI78 zO~`z^HzFMz@vBi*$qLe(ixIepGrF5$OSZT*%?tkQ!7`HM<)Xa$84HUkv;W1xHy2|i z>9hr*F)%9Tk_tjm-KoG&tk@?;;x0~fj-k_(tS7^A8RV9pR017;{tRzT)t)bizxLsGmPtiV9uwh(J7-R}U@0=U`ATuVr^oz35~uGaC0h#Lb8O1AwSkf8ghA<4>mV1% z^)o@!Z&ap!{T55}CP_xcF&?P$*dz>m*`w`A5WzkD!m{C6#YhEn^x!DNq2z34ER@@d zz0C(iqf6zm6)f%XREk1}X-?$~L#?eClVW+=&_$LcI@2rI#$JBF7v-K)=UDibZ z15M!G#BROtC77Mjuqshrg66$3ScANdg*}TGSthuELcx5}|H(py`yf-?SJmZHD`Y{D zfl2K^aHAe@i+xOA^r4a)0W!?QRto-EOME?&Y7Qy7uQw>u$q+{KcemZ(IsB+f!0N;c zllf2Wf>9>1UO*woS(R}nH9*HOHSJp0W;FPKw=N>5c7=DPLox$Zc$8PQTk*>C+ zqr@5yRl=jxdVv@tCD@Y_VN`a7vpAa9^A;{5QahTIw$<3%oMA|rS3Rj%{UDi-pqmG? zz+H>h{iUbr$H*IrpE7q#iv=Ge=jl3~h@>u)F?)-x3^~G5b!RZjB=9PDtVdrpb8~T( zlijkj(ed^jUZfTL=|h$x!`Pb%^Lu0&4b{k;8phuQeA)W5NK3tL_HK+Tc6--X;<@Ko z(1)?qHLUfnh(bH!FoO|@&jRbX2SGl_7;fTd&P6e`b7NYnXAw-a&3dgpO(kA;l3py4 zfYZG6B_2d98fZf}!TIaZD!Jqg5Cq;!lTKMR9%$p^;#z`C(MKWEmb3xtqf2F8Tq<8W z0I)MKs)I=MW`w5o!@uwM!SOhv4ICF@?wT}^;+-h0psfAf2|_xx3ciPop|%olZn91m zloYH;*OA*a7i_Vz^HD#BRKZ2v)GUkL1y+_AE7$R&ef!y5yIz5CLTWz?MRj-o1AuDq zj=Xp2z-hI)P^4Tu=%w;kQuvSH`S`2x~DrA2ISE1>?Wc1~TTTP#$;a{Hg z$+;{>u`=-V&rJop2%%X{AFg+J*8VL0;d4=<#8e;>%>a%L!p4OLZzr8?8!TZ^CGZ9x zG*Luc9)h~Z57Ic9r;G@t@EPfn#871n&kpUd)u!sR|6X{zsh_jquS95X$bO1v1y!>X z=y(ah4$&GZ0)km%2ng_cpLgNCi%E_mSN&2Q8oDth0?tSgx=~b2TWPAYMC^Efv%F7L zyxppO-Ld~?SQ&YJl4N4e1f~%L^{#w&QGXT(9DAb=aDp#0suweAFZ7@)czM9MY#|{f zojb~a&n6fP;~*7(Jl~#)gUO8X6VYN53euRC3xY}89p_4c zOKeYj(tjw^_RSF_#aSUnkf2{8AZ<-er_=ncq6DsF@q$dvC$fcpbN#)RFX#B z&;948$$;lZMKrxN0mUKph zbL*AtNQS^}I^*XI$bTtq=R!GLPp9BQ=GMfp(S6_=ocs%w>$UoreTn;61Zt{)0M_;Q zK2}jw`nU>B-I%DQ&Iy96>Vex7K+5$}wf?wum<29@XkQaC&&WU}j|3dzENqC;X7*#V zdC4Unby><2*gj-BHqcPqPvHSieCkgv^l=#}?o!s3*1$s>JCsFazoHtb> z;04w(pT5?SJHg&{SP*w1@FLYy))Q39$uxxEh&jQfA1GB%B3oeWYb-OX|3S(mhwZEFey!9#fn#l9tQJH-zz8E)>-172-(9Y4!2_z_izjmV;_h%!t!KZ<9RYv{ zTMtChgHKVjz+kdnKs{FH43M`GWmV=5xgAy_xeU;W0m;X;HR$O2{#ARMs>kP4b@te2 zZ0|Xu1VYJlL+z z?4v>sA^z^iiA?5NF(EXG+o7C>2f2f49*T}hk)vg{zmMJm&V!mck$3S+7-TDkmnV%kfJ zJ6JM!NL`s3Sx$r^U!(-HR^Ofd5-lG(uOR(F!d+~gFpw7zsUVedil=0 zT9cUGs&{(`2A+~_;<^T0-A>grF~n=5tBu$7tp@+*y)0{yXzUVimq)CtG<@NH`21Ps|Fy+kWyiH+&N#y)?zS^2X1;Hu z5t#Q>l8CW+sQb}Bu z-@${obTw*ghTeVq$jMy->G^D%xiu_)89ArR_9De!Ubnll{tQc-DJHdn8GB}w=&cL} zWRyD77W4)hO>a-AU-w+>zZmzDw(B=K7oF0t^zEYAxJxVAI|YlGwTkk2V0#`DCdIj7 zwgCOPzM&W@l+-25aF;VF<2$q4N7DD5{S}=Hy$Cp(-Ll>I;JEH!7yX|ZR}a37%i&ff zJ^lI6>Vxx@)wkSX1i@IGS9veVhxzvY`4AvKl+OokBN8`R0g|@}}fS;h2gn zbq0ED(UTbR@5@2F?l{l0|7r_&17hl$Et^LRz{AGsh%b1FB?g>Y6n~KP1Bff##?1Y& z{ghrSr7I%Bgj1fW)k(7fe zChKrwwksq+S(n2g|Ex8r7|Ig<(taXTj!= z&d#x;>F(nGNG(D95kTneGOC+6a5Ano{NF8(YlN*?r~bEKc*HJQ#`b#1)0;bqe+TQV z66QQtQxiZVd1-ILDppHxdRLBEL7Q8Ry@=g4uIH zW2q;u2eko(?x&Z3hZN2=rL$MJpB56i;$m>zM|XT6wVY{#<)hd8>1=Tzdy_O!kpDyq zE~MDSnDQG4YOkn4qaz+3P-b))N*e++?hKKyTs!-SA95@7cVhS)93uGpg1M3DJFYdH z^mv<&}d((SaE$Ub0Gym7Ph)&FAv zkNS5tIJ^}^+uiNLFJIWt%flV3rK#u}+CcARkYOD0hF`52J}@{)VIu zZ$2yOlyo7Z-k6R2_qU%jI^@Buo69}jRcCiXfwPA;_}<8E_f0$45HXH-X!N$gspa-X#095{Cnw3dE&D>3P)?=*+r#AIO9iOnWwf$8({Yosnk;vd<1UJtM>>8(VLX3x0&r=-o;VXE% z5P1Pyy84UVU3KkM;fX(<%o+-b18=m+!s`O}0?mSE zG>DZ;py61i@Z8O5CN|C3uIJHdAa|@JObB9WS_92x_@FYOO0rf1WAks8VUnKR9nL1! zEJx+lt|1WHPyJ)DR-Mpw@u(8J!wb&HxODH`nG>GpfHY{3ff_PgZA)Q`_a2&ehEqhQ9A| z-<#qdtD@V@v9~xb+UkLDeYGR!KtGcD+ZEt*LJPv9G~MFHcY3`1^G=^RG=yBVXOJmj zOS%FJPRhrokWw!{z^6$MdrRX{Bb`x_tdvnbEQyBSI9)>QaB?M5Yl@%o+uLKhjL^98 zrNup-p{3W%AUIL~RZhmp8a*>n{jmj|v#C!6Y~w~7i_?hWa^L2ApPKJftgcKN#0OwWWezPlOj*3>hQ7q z*rkxh-Qhdlo%|;$W#pv_!}MG!ofQoa-)-KgB64vIrEyn)YqAFO!O5S;}aIBYtVeuYr$$fA2h7ti2&G-l_6pMF@!Z9H`Bh4${ z2)yb+m7KxD(r(KlnI6BDWZp90eyCfK)O}0I&o3k%&u)%gh5p>0a1ZV&IAZJgnRb^^#yfC8awTEQe8JONLVPb|N z`|*w6)7abMb(y~>>r6h554{L|Q<3V+i_JYYS z9IZNHQuW1X0K;PfN8H46VcCjYkCHv-?5EqQP(wMC%)XuuOTkK7Gq}^J$T768)dOOgCpU|A6H3Zwm}`kaLRWA}4~x zBs!W#53!e`wy=eheorp_%;oR;4N9FU{(g3!a`ojCl7yvIR}#4A>F5Q~M6mK!lGK1@ zp-9VuOjA7Xhx#96FQ2Ws-=c2~IAQ-CtFj7iFX_eFq+!z(!z+COnOOtLdRQf`0@UEb z(Dz^HT`w&<?=yZ|&xjG2|y|k_r+w75j!f&fc zKPq(p4Cd+nLzX(7IbbXbVQ}ZyLe(ies&X_V4N+(Q8510-$ieAuvgPg(128E;L zLq6WO^VO~6wu$^~=L>&n#-OrhH^OET4OZ!3kQZ4;NFv-WQ+pfajMSuKK|$rLkPo*B z@?7tB3~CIQ(9i#!4&Iw&KM$)5KsLYdBLK3bR58kE>Az$VX8+>m-Fjp*zYQ2>5S}N% z(TqE>sfGyPuRRjis#QH##9z1C)h{xR;ZcHVF#WM{Jou0;$cM?yJj*@H1t4yAJ98k}58YsFV?*CbwKk1tKI<}=^ktI|-tSz3XGqF55s@r`H~I9=A;zgjUORhR%{ zlo>|^n=W#@2X56wd%I5`bFuF(oz1eEJFBA<$Aq?-@U z1Pi35^XkE;wd?Va{;Kjy6gwSL6Cq#9w+NIgwY4Y15SnImhOiM18KbN3Bt^+=ZKylH zbj-L#c~>cXGb~<`SE2d&G+g51Pl`>3CWNT)J%T{^G@29d()jk;kozXaNztnzBLL+< z^kScV(-I{PqX&zCbfnVM598+1S_OWO?%AV;~uX9n5ISWTa| zaW`u%1TgeLq-Zxk#?gio-?AkpCT@783ei4$_FX8&aJOBBe!w2z5uj8YZo7wMWuQj~ zqh-&r$I%!f22vN-D9A@XIZ_IIAlAHm{D+{(U3g38px#e2>Xfj!vGH1~f@B1>vKM&@ zfkCui77I~xotYRJO{w3$T_;bF=fsbcuA4ffPqBKAcQupDr@ju7jWL?|ny;V5ET(oX z1Vw@~ql`yl0B24+g{1buyKt&w-)V=D(2I-ypY)$Jm|vpy*&YjGS@UH56XfAReEH)} z!f8@WFHa+&cs!NLm6%)Zu`)7Ulbg2WPsrmOQI6#Cy9@_B-w%5VOAop|;Ja<-+m}R| zHd6_q1_D1ZD<=pI4-L^&=&;@nYrczMf<7>U@sD6}Z0bbbGih=0dh5q2D|FLxmEnyW zECRPC5?b42N-th#)s5S-zifHj5PsxP#nwpWS|@2-$Q7F z)?iQ_tbaBzFfco9*Z9QzD#GmxBnrT@i>?tmj-xBohv=Enjg`U;qc2X z5PjUL*zG^b4CgfYmJ#ktsfGoz2!s31qX@{MpB0j96y)W_X%po2Bl@?8-$m>XNfHtA zuY{(*d%TWT7+@L2P|`u}uql~cF@oQk#74h*xs00<{P(Gc)$OdbD)Ok{?#$o%Q=pRJJ;hTZ7bZ`*{4VAZdq`_(0}s^}lf-V;KwbM*O!i8NDJiddL{f~bP` zfp11+BqP|Lpy}O@=cZ47M}PXqCSK6C`vJecP0-$6l@ay9T)#h8E(^6_(w9+Cz>FE* z-MYKl4Y0Mftwa4C7dyDKkAz z&8OK~6<$WAS`Zhe#dj82;ko)IG=M0Eî`yye8kFW0{C;z*jJ}*YvBms|`uj)MT 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 literal 0 HcmV?d00001 diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 612cbe046..8b44701e8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -314,6 +314,8 @@ public class HMCLGameRepository extends DefaultGameRepository { return VersionIconType.FABRIC.getIcon(); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FORGE)) return VersionIconType.FORGE.getIcon(); + else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM)) + return VersionIconType.CLEANROOM.getIcon(); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE)) return VersionIconType.NEO_FORGE.getIcon(); else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.QUILT)) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java index 02dacdaea..51c178130 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionIconType.java @@ -34,7 +34,8 @@ public enum VersionIconType { NEO_FORGE("/assets/img/neoforge.png"), FURNACE("/assets/img/furnace.png"), QUILT("/assets/img/quilt.png"), - APRIL_FOOLS("/assets/img/april_fools.png"); + APRIL_FOOLS("/assets/img/april_fools.png"), + CLEANROOM("/assets/img/cleanroom.png"); // Please append new items at last diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index a57f27bbc..f6ecc715c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -138,6 +138,9 @@ public class InstallerItem extends Control { case "forge": iconType = VersionIconType.FORGE; break; + case "cleanroom": + iconType = VersionIconType.CLEANROOM; + break; case "liteloader": iconType = VersionIconType.CHICKEN; break; @@ -232,6 +235,7 @@ public class InstallerItem extends Control { InstallerItem fabric = new InstallerItem(FABRIC, style); InstallerItem fabricApi = new InstallerItem(FABRIC_API, style); InstallerItem forge = new InstallerItem(FORGE, style); + InstallerItem cleanroom = new InstallerItem(CLEANROOM, style); InstallerItem neoForge = new InstallerItem(NEO_FORGE, style); InstallerItem liteLoader = new InstallerItem(LITELOADER, style); InstallerItem optiFine = new InstallerItem(OPTIFINE, style); @@ -239,11 +243,11 @@ public class InstallerItem extends Control { InstallerItem quiltApi = new InstallerItem(QUILT_API, style); Map> incompatibleMap = new HashMap<>(); - mutualIncompatible(incompatibleMap, forge, fabric, quilt, neoForge); - addIncompatibles(incompatibleMap, liteLoader, fabric, quilt, neoForge); - addIncompatibles(incompatibleMap, optiFine, fabric, quilt, neoForge); - addIncompatibles(incompatibleMap, fabricApi, forge, quiltApi, neoForge, liteLoader, optiFine); - addIncompatibles(incompatibleMap, quiltApi, forge, fabric, fabricApi, neoForge, liteLoader, optiFine); + mutualIncompatible(incompatibleMap, forge, fabric, quilt, neoForge, cleanroom); + addIncompatibles(incompatibleMap, liteLoader, fabric, quilt, neoForge, cleanroom); + addIncompatibles(incompatibleMap, optiFine, fabric, quilt, neoForge, cleanroom); + addIncompatibles(incompatibleMap, fabricApi, forge, quiltApi, neoForge, liteLoader, optiFine, cleanroom); + addIncompatibles(incompatibleMap, quiltApi, forge, fabric, fabricApi, neoForge, liteLoader, optiFine, cleanroom); for (Map.Entry> entry : incompatibleMap.entrySet()) { InstallerItem item = entry.getKey(); @@ -277,7 +281,7 @@ public class InstallerItem extends Control { game.versionProperty.set(new InstalledState(gameVersion, false, false)); } - InstallerItem[] all = {game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi}; + InstallerItem[] all = {game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi, cleanroom}; for (InstallerItem item : all) { if (!item.resolvedStateProperty.isBound()) { @@ -293,6 +297,8 @@ public class InstallerItem extends Control { if (gameVersion == null) { this.libraries = all; + } else if (gameVersion.equals("1.12.2")) { + this.libraries = new InstallerItem[]{game, forge, cleanroom, liteLoader, optiFine}; } else if (GameVersionNumber.compare(gameVersion, "1.13") < 0) { this.libraries = new InstallerItem[]{game, forge, liteLoader, optiFine}; } else { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index 9c5311d53..6a0d930ac 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -33,6 +33,7 @@ import javafx.scene.control.ListCell; import javafx.scene.control.ProgressIndicator; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; +import org.jackhuang.hmcl.download.cleanroom.CleanroomInstallTask; import org.jackhuang.hmcl.download.fabric.FabricAPIInstallTask; import org.jackhuang.hmcl.download.fabric.FabricInstallTask; import org.jackhuang.hmcl.download.forge.ForgeNewInstallTask; @@ -164,6 +165,8 @@ public final class TaskListPane extends StackPane { if (task.getInheritedStage() != null && task.getInheritedStage().startsWith("hmcl.install.game")) return; task.setName(i18n("install.installer.install", i18n("install.installer.game"))); + } else if (task instanceof CleanroomInstallTask) { + task.setName(i18n("install.installer.install", i18n("install.installer.cleanroom"))); } else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) { task.setName(i18n("install.installer.install", i18n("install.installer.forge"))); } else if (task instanceof NeoForgeInstallTask || task instanceof NeoForgeOldInstallTask) { @@ -439,6 +442,7 @@ public final class TaskListPane extends StackPane { case "hmcl.install.libraries": message = i18n("libraries.download"); break; case "hmcl.install.game": message = i18n("install.installer.install", i18n("install.installer.game") + " " + stageValue); break; case "hmcl.install.forge": message = i18n("install.installer.install", i18n("install.installer.forge") + " " + stageValue); break; + case "hmcl.install.cleanroom": message = i18n("install.installer.install", i18n("install.installer.cleanroom") + " " + stageValue); break; case "hmcl.install.neoforge": message = i18n("install.installer.install", i18n("install.installer.neoforge") + " " + stageValue); break; case "hmcl.install.liteloader": message = i18n("install.installer.install", i18n("install.installer.liteloader") + " " + stageValue); break; case "hmcl.install.optifine": message = i18n("install.installer.install", i18n("install.installer.optifine") + " " + stageValue); break; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java index 69f254d28..8dad9704d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java @@ -130,6 +130,9 @@ public class InstallersPage extends AbstractInstallersPage { case NEO_FORGE: loaderName = i18n("install.installer.neoforge"); break; + case CLEANROOM: + loaderName = i18n("install.installer.cleanroom"); + break; case FABRIC: loaderName = i18n("install.installer.fabric"); break; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index 360dae621..030d66c4d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -31,6 +31,7 @@ import javafx.scene.layout.*; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.download.cleanroom.CleanroomRemoteVersion; import org.jackhuang.hmcl.download.fabric.FabricAPIRemoteVersion; import org.jackhuang.hmcl.download.fabric.FabricRemoteVersion; import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion; @@ -220,6 +221,8 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab iconType = VersionIconType.OPTIFINE; else if (remoteVersion instanceof ForgeRemoteVersion) iconType = VersionIconType.FORGE; + else if (remoteVersion instanceof CleanroomRemoteVersion) + iconType = VersionIconType.CLEANROOM; else if (remoteVersion instanceof NeoForgeRemoteVersion) iconType = VersionIconType.NEO_FORGE; else if (remoteVersion instanceof FabricRemoteVersion || remoteVersion instanceof FabricAPIRemoteVersion) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index e756141f1..441b1780d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -407,6 +407,9 @@ public class DownloadPage extends Control implements DecoratorPage { case FORGE: content.getTags().add(i18n("install.installer.forge")); break; + case CLEANROOM: + content.getTags().add(i18n("install.installer.cleanroom")); + break; case NEO_FORGED: content.getTags().add(i18n("install.installer.neoforge")); break; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java index 1620490a3..9405e3800 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/InstallerListPage.java @@ -76,7 +76,7 @@ public class InstallerListPage extends ListPageBase implements Ve InstallerItem.InstallerItemGroup group = new InstallerItem.InstallerItemGroup(gameVersion, InstallerItem.Style.LIST_ITEM); - // Conventional libraries: game, fabric, forge, neoforge, liteloader, optifine + // Conventional libraries: game, fabric, forge, cleanroom, neoforge, liteloader, optifine for (InstallerItem item : group.getLibraries()) { String libraryId = item.getLibraryId(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 340d2ec0d..4e7a12489 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -424,6 +424,9 @@ class ModListPageSkin extends SkinBase { case FORGE: loaderName = i18n("install.installer.forge"); break; + case CLEANROOM: + loaderName = i18n("install.installer.cleanroom"); + break; case NEO_FORGED: loaderName = i18n("install.installer.neoforge"); break; @@ -550,6 +553,9 @@ class ModListPageSkin extends SkinBase { case FORGE: content.getTags().add(i18n("install.installer.forge")); break; + case CLEANROOM: + content.getTags().add(i18n("install.installer.cleanroom")); + break; case NEO_FORGED: content.getTags().add(i18n("install.installer.neoforge")); break; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java index a394ec03a..c00ac7c0e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionIconDialog.java @@ -65,6 +65,7 @@ public class VersionIconDialog extends DialogPane { createIcon(VersionIconType.CRAFT_TABLE), createIcon(VersionIconType.FABRIC), createIcon(VersionIconType.FORGE), + createIcon(VersionIconType.CLEANROOM), createIcon(VersionIconType.NEO_FORGE), createIcon(VersionIconType.FURNACE), createIcon(VersionIconType.QUILT) diff --git a/HMCL/src/main/resources/assets/img/cleanroom.png b/HMCL/src/main/resources/assets/img/cleanroom.png new file mode 100644 index 0000000000000000000000000000000000000000..29ce0cc91b0a9ea84c4e59a380974685e81fd987 GIT binary patch literal 1454 zcmZ8heK^x=82)W6vy)gKrukNBbYf^e&XKal+A7~nGl#VyCf^;AG@oNsJ4&HADwU5$ zUkpVY+Hi%6O)J#WIg(2uSxlVYs`Jlzuj_vA`+45yx!>n{|9Epf+=vn7#OR^>^&F{0BbswfQX_i znP5^U!NotpJ32HWkscQUc+kV65-i+Ye2j1wRu=ZU;qNQKnuZX6QYeXJ4#2?x2B0A@ z00Js92;CoAISmG)@+}VqwG{*kXn{-x5zbNlmX83nas`7>_W#HKyA)6X0G=A43RYmP zZLpB?@2<)YSowFSbD&l(DCh(IR_Wzn52_zsR#>d^w1#cG&b!PW1HMEciQw$xPrT;W z&)V#=e!2R_NOuVn|BDTlm(fWhtg}p>eYa)XF?bFEW;r^%GiO<&j48$G!e(kQ@6%bs zd1{(&c+>K3ULZ=dY%M%Ze6Widn%rDn#+jgeYHmKO|2c!`OYt3&w-^SBxj6t(>Za!6 zL2mM!hwNq@Ae&{^U=k@?2+RI@B2xb=B%S0k0+)Bsd;N~q+5%$%+y%z&|iQ~>(Yb=D$sRtf7TIN-|9H_r^62&GMVk>*GC1q0m#cVi<6R%aip@sXVJI$pn9YWVvQ& z(zA)S?ET@9$5KpkG6HxfR}|}M{~h3D;}G2!njdQ_V%5KhyD`v2;rnqEciEZ`zZAC_ zqyyxR9%u^Enav*ehV<{kaZo7MEeW1giu?;&6iuf)J70gDJF9AOz_GYBn0_Mv-=XFjVIn_t1iIVM5OHR{kW_V3{IM5=CAug`vpeo=8G;^Tv*)dibn?pjpVP@P{* z<$?Q0u{soAPQX=Woq0SUUO_Q%l0{En8Yc^YzQ5cUqe7 zWCmPRIM9V?Uhiego$XxO!QWwy@Nrcdec#F`RPX@?EI|hQe93pp4SO802BW%UbwXav~k|cDS{<^{jMywAYUI zYGrOSBJYWOP_SR4qTeN0re-Hus?xgzIE_h*e4G^V*NtE_n@Ij-onoIg4_AIqLSBn< zX*=VJTw4F(#9Yb4&USH|OlHh$O-V?|Tv$k&WwCSOs{Cm*LX}TXDccLEJ7Rm08{ID! z`<7#1LY=|Mqlfw8nVH+O12ST#+)uK1vF~bWslNE|4j;iw7d;WyOI5o+Hh-CxLYp( literal 0 HcmV?d00001 diff --git a/HMCL/src/main/resources/assets/img/cleanroom@2x.png b/HMCL/src/main/resources/assets/img/cleanroom@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cda6dc0366a5f20c76438b5e5ccd9484b101f88b GIT binary patch literal 2732 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 D`;xvH literal 0 HcmV?d00001 diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 6a3f62335..f67404f5b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -701,6 +701,7 @@ install.failed.optifine_forge_1.17=For Minecraft 1.17.1, Forge is only compatibl install.failed.version_mismatch=This loader requires the game version %s, but the installed one is %s. install.installer.change_version=%s Incompatible install.installer.choose=Choose Your %s Version +install.installer.cleanroom=Cleanroom install.installer.depend=Requires %s install.installer.do_not_install=Do not install install.installer.fabric=Fabric diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 7c9b71ffa..109ca04da 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -510,6 +510,7 @@ install.failed.optifine_forge_1.17=對於 Minecraft 1.17.1 版本,僅 OptiFine install.failed.version_mismatch=該載入器需要的遊戲版本為 %s,但實際的遊戲版本為 %s。 install.installer.change_version=%s 與目前遊戲不相容,請更換版本 install.installer.choose=選取 %s 版本 +install.installer.cleanroom=Cleanroom install.installer.depend=需要先安裝 %s install.installer.do_not_install=不安裝 install.installer.fabric=Fabric diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index abdc7cb1f..77ae0a55f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -520,6 +520,7 @@ install.failed.optifine_forge_1.17=对于 Minecraft 1.17.1 版本,仅 OptiFine install.failed.version_mismatch=该组件需要的游戏版本为 %s,但实际的游戏版本为 %s。 install.installer.change_version=%s 与当前游戏不兼容,请更换版本 install.installer.choose=选择 %s 版本 +install.installer.cleanroom=Cleanroom install.installer.depend=需要先安装 %s install.installer.do_not_install=不安装 install.installer.fabric=Fabric @@ -1053,6 +1054,7 @@ settings.advanced.custom_commands.hint=自定义命令被调用时将包含如 \ · $INST_JAVA: 游戏运行使用的 Java 路径;\n\ \ · $INST_FORGE: 若安装了 Forge,将会存在本环境变量;\n\ \ · $INST_NEOFORGE: 若安装了 NeoForge,将会存在本环境变量;\n\ + \ · $INST_CLEANROOM: 若安装了 Cleanroom,将会存在本环境变量;\n\ \ · $INST_LITELOADER: 若安装了 LiteLoader,将会存在本环境变量;\n\ \ · $INST_OPTIFINE: 若安装了 OptiFine,将会存在本环境变量;\n\ \ · $INST_FABRIC: 若安装了 Fabric,将会存在本环境变量;\n\ diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java index 19dad29d7..44729a983 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.download; +import org.jackhuang.hmcl.download.cleanroom.CleanroomVersionList; import org.jackhuang.hmcl.download.fabric.FabricAPIVersionList; import org.jackhuang.hmcl.download.fabric.FabricVersionList; import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList; @@ -43,6 +44,7 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider { private final FabricVersionList fabric; private final FabricAPIVersionList fabricApi; private final ForgeBMCLVersionList forge; + private final CleanroomVersionList cleanroom; private final NeoForgeBMCLVersionList neoforge; private final LiteLoaderBMCLVersionList liteLoader; private final OptiFineBMCLVersionList optifine; @@ -56,6 +58,7 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider { this.fabric = new FabricVersionList(this); this.fabricApi = new FabricAPIVersionList(this); this.forge = new ForgeBMCLVersionList(apiRoot); + this.cleanroom = new CleanroomVersionList(this); this.neoforge = new NeoForgeBMCLVersionList(apiRoot); this.liteLoader = new LiteLoaderBMCLVersionList(this); this.optifine = new OptiFineBMCLVersionList(apiRoot); @@ -78,6 +81,8 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider { pair("https://maven.fabricmc.net", apiRoot + "/maven"), pair("https://authlib-injector.yushi.moe", apiRoot + "/mirrors/authlib-injector"), pair("https://repo1.maven.org/maven2", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public"), + pair("https://repo.maven.apache.org/maven2", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public"), + pair("https://hmcl-dev.github.io/metadata/cleanroom", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/cleanroom"), pair("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto") // // https://github.com/mcmod-info-mirror/mcim-rust-api // pair("https://api.modrinth.com", "https://mod.mcimirror.top/modrinth"), @@ -113,6 +118,8 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider { return fabricApi; case "forge": return forge; + case "cleanroom": + return cleanroom; case "neoforge": return neoforge; case "liteloader": diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java index cdb5fc7fe..c4dbfbef6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/LibraryAnalyzer.java @@ -122,7 +122,7 @@ public final class LibraryAnalyzer implements Iterable[0-9.]+)(-([0-9.]+))?$"); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java index 86fc3e032..2c5a5dba4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/MojangDownloadProvider.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.download; +import org.jackhuang.hmcl.download.cleanroom.CleanroomVersionList; import org.jackhuang.hmcl.download.fabric.FabricAPIVersionList; import org.jackhuang.hmcl.download.fabric.FabricVersionList; import org.jackhuang.hmcl.download.forge.ForgeVersionList; @@ -37,6 +38,7 @@ public class MojangDownloadProvider implements DownloadProvider { private final FabricAPIVersionList fabricApi; private final ForgeVersionList forge; private final NeoForgeOfficialVersionList neoforge; + private final CleanroomVersionList cleanroom; private final LiteLoaderVersionList liteLoader; private final OptiFineBMCLVersionList optifine; private final QuiltVersionList quilt; @@ -51,6 +53,7 @@ public class MojangDownloadProvider implements DownloadProvider { this.fabricApi = new FabricAPIVersionList(this); this.forge = new ForgeVersionList(this); this.neoforge = new NeoForgeOfficialVersionList(this); + this.cleanroom = new CleanroomVersionList(this); this.liteLoader = new LiteLoaderVersionList(this); this.optifine = new OptiFineBMCLVersionList(apiRoot); this.quilt = new QuiltVersionList(this); @@ -78,6 +81,8 @@ public class MojangDownloadProvider implements DownloadProvider { return fabricApi; case "forge": return forge; + case "cleanroom": + return cleanroom; case "neoforge": return neoforge; case "liteloader": diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java new file mode 100644 index 000000000..de2ce3de9 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java @@ -0,0 +1,93 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.cleanroom; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.download.UnsupportedInstallationException; +import org.jackhuang.hmcl.download.VersionMismatchException; +import org.jackhuang.hmcl.download.forge.ForgeNewInstallTask; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; + +public final class CleanroomInstallTask extends Task { + + private final DefaultDependencyManager dependencyManager; + private final Version version; + private Path installer; + private final CleanroomRemoteVersion remote; + private FileDownloadTask dependent; + private Task task; + + public CleanroomInstallTask(DefaultDependencyManager dependencyManager, Version version, CleanroomRemoteVersion remoteVersion) { + this.dependencyManager = dependencyManager; + this.version = version; + this.remote = remoteVersion; + setSignificance(TaskSignificance.MODERATE); + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void preExecute() throws Exception { + installer = Files.createTempFile("cleanroom-installer", ".jar"); + + dependent = new FileDownloadTask( + dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()), + installer, null); + dependent.setCacheRepository(dependencyManager.getCacheRepository()); + dependent.setCaching(true); + dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); + } + + @Override + public boolean doPostExecute() { + return true; + } + + @Override + public void postExecute() throws Exception { + Files.deleteIfExists(installer); + setResult(task.getResult()); + } + + @Override + public Collection> getDependents() { + return Collections.singleton(dependent); + } + + @Override + public Collection> getDependencies() { + return Collections.singleton(task); + } + + @Override + public void execute() throws IOException, VersionMismatchException, UnsupportedInstallationException { + task = new ForgeNewInstallTask(dependencyManager, version, remote.getSelfVersion(), installer).thenApplyAsync((version) -> version.setId(LibraryAnalyzer.LibraryType.CLEANROOM.getPatchId())); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomRemoteVersion.java new file mode 100644 index 000000000..6d6bfddbd --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomRemoteVersion.java @@ -0,0 +1,38 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.cleanroom; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.LibraryAnalyzer; +import org.jackhuang.hmcl.download.RemoteVersion; +import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.task.Task; + +import java.time.Instant; +import java.util.List; + +public class CleanroomRemoteVersion extends RemoteVersion { + public CleanroomRemoteVersion(String gameVersion, String selfVersion, Instant releaseDate, List url) { + super(LibraryAnalyzer.LibraryType.CLEANROOM.getPatchId(), gameVersion, selfVersion, releaseDate, url); + } + + @Override + public Task getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) { + return new CleanroomInstallTask(dependencyManager, baseVersion, this); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomVersionList.java new file mode 100644 index 000000000..34b2a62e3 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomVersionList.java @@ -0,0 +1,69 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.cleanroom; + +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.VersionList; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; + +import java.time.Instant; +import java.util.Collections; + +public final class CleanroomVersionList extends VersionList { + private final DownloadProvider downloadProvider; + private static final String LOADER_LIST_URL = "https://hmcl-dev.github.io/metadata/cleanroom/index.json"; + private static final String INSTALLER_URL = "https://hmcl-dev.github.io/metadata/cleanroom/files/cleanroom-%s-installer.jar"; + + public CleanroomVersionList(DownloadProvider downloadProvider) { + this.downloadProvider = downloadProvider; + } + + @Override + public boolean hasType() { + return false; + } + + @Override + public Task refreshAsync() { + return Task.allOf( + new GetTask(downloadProvider.injectURLWithCandidates(LOADER_LIST_URL)).thenGetJsonAsync(ReleaseResult[].class) + ).thenAcceptAsync(results -> { + lock.writeLock().lock(); + + try { + versions.clear(); + for (ReleaseResult version : results.get(0)) { + versions.put("1.12.2", new CleanroomRemoteVersion( + "1.12.2", version.name, Instant.parse(version.created_at), + Collections.singletonList( + String.format(INSTALLER_URL, version.name) + ) + )); + } + } finally { + lock.writeLock().unlock(); + } + }); + } + + private static class ReleaseResult { + String name; + String created_at; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index 815c25ddc..621ff68c2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -26,7 +26,6 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.Unzipper; -import org.jackhuang.hmcl.util.platform.Bits; import org.jackhuang.hmcl.util.platform.*; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; @@ -41,8 +40,8 @@ import java.util.*; import java.util.function.Supplier; import static org.jackhuang.hmcl.util.Lang.mapOf; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; /** * @author huangyuhui @@ -247,6 +246,10 @@ public class DefaultLauncher extends Launcher { Set classpath = repository.getClasspath(version); + if (analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM)) { + classpath.removeIf(c -> c.contains("2.9.4-nightly-20150209")); + } + File jar = repository.getVersionJar(version); if (!jar.exists() || !jar.isFile()) throw new IOException("Minecraft jar does not exist"); @@ -551,6 +554,9 @@ public class DefaultLauncher extends Launcher { if (analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) { env.put("INST_FORGE", "1"); } + if (analyzer.has(LibraryAnalyzer.LibraryType.CLEANROOM)) { + env.put("INST_CLEANROOM", "1"); + } if (analyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE)) { env.put("INST_NEOFORGE", "1"); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java index 97ec524b9..f4b8feacd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.mod; public enum ModLoaderType { UNKNOWN, FORGE, + CLEANROOM, NEO_FORGED, FABRIC, QUILT, diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackExportTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackExportTask.java index ee31a96d7..fabe37aec 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackExportTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackExportTask.java @@ -91,6 +91,8 @@ public class McbbsModpackExportTask extends Task { addons.add(new McbbsModpackManifest.Addon(MINECRAFT.getPatchId(), gameVersion)); analyzer.getVersion(FORGE).ifPresent(forgeVersion -> addons.add(new McbbsModpackManifest.Addon(FORGE.getPatchId(), forgeVersion))); + analyzer.getVersion(CLEANROOM).ifPresent(cleanroomVersion -> + addons.add(new McbbsModpackManifest.Addon(CLEANROOM.getPatchId(), cleanroomVersion))); analyzer.getVersion(NEO_FORGE).ifPresent(neoForgeVersion -> addons.add(new McbbsModpackManifest.Addon(NEO_FORGE.getPatchId(), neoForgeVersion))); analyzer.getVersion(LITELOADER).ifPresent(liteLoaderVersion ->