4S<$Bzz`oxK-vxk`kYalWB zXiN#7L;<^r8mWC%jNqze3a%*09eCp=9 z=izcsfO`Tnw#cTlrAJ{sLf0~@__=YGe9(+w#@xkwrIN;rbA_;&fJ`QJTS*n$NDQmB zSRu{9z^F9iu_Q6D{~K~BWYLQ_rW%H%c&FP=$$+CM+o#WscZjl+g%iG`u3SL_BHqxu zQ4kWd1AzTs`6CCuqmHN)WnmupF^uj(AYc_8k_U@>2O#7iC~$&;k>t7@q#znF9Q_3T z4-CeGL3r>Mw*p+uG?xQx3B&UMNT5h^WxSyn-q7`n9AP5CgUCQQF`n~wT$AD(3h`Uq z_lVvxgBMxcG^oBdn~J^56$R620QLe`4 >!$Af@P@n
Hq4Ta@ zcZ~InXpML)FRtw{9#1AJoQ7Y>o&J5Oqy1d0XSc>=zK(3Ktw6 H==eq{yV&zf*zoEC& zXskHa?4u3+ja;ix67sM%TJ~ S6^Ffb_D2N%R6=j z3|0L$yx7#F#4XiLJ~NO1i|OUdwYp~A+}wRTZcYoYC6{^nlUBxm@DkoxYyraN^Tkl{ z?PRz6@z3`k-p$tJ9`1YvP6$(|1yzW?dSWnH3^ * zMKm&tT7REvn3u?gdaONpnu%EQHm%$X$BgWJs*8<{^q6-Ta@o0f-Z0(tP_DhKDLvp< zb1hwpo$P%D9{8NVF1Ob8T6n>(>Jh7g|E2V$h~+S|N#ek=M4o);REGM^=?@kC5fLE0 zNlAN(O87OK(AELT@x*kg39m-kDwD`c2NPlM(*w18) ;+sOd7Z@uP9Rc53G{ zsbsnl_ec-s{6_+ZH~P_ca2OH9IX$*wGv(2GtXmV0Ses{^lw*7+d#0a;#?pd^6PHS5 zcM5KXOgjU|Ig>=K9NrZ8?6LY&)&QpUqPJMPm(T*tqFWx_nmgO+G~0>G4W&lNz5@d@ z?80rY!hVpGQv>av?z<-Om1L{px!~>8r2nL*h;VQEKltt|vNyxM)0)2VL*|YjPv8WX z=pDAxR`(OVHs4|Y@zH~j(w$Tmm20&(oy)h_IJY+If|Y@@Lw6k8D;RI0i8Y^NZ2mU* zjhgVZTh^iww%t$RyozPriXs-~Wt%CIJHwWEk9|Rj2 zMH^crU=f |&Q<>vaGCIR+=<2OEmX|H&ZMy;dvDB#ANC}N?Ao=8!DJh= zg8Y$+2zYDIp@fyfS|usgs(Ik)>zd0q2P^$KV@T|JtEjv2eHTFuEL&g7a4MDXW#!7v z+%TggpYC87L$8H7U%_>y`ZGs0ryw53xH|QLiAMqxwIp>>RH;{pi{!@aHLd#f5WbrE zaPQvzp|GfY`GXkl>v3lqA<#rjXuXNc=wU_Dzd=()DtxB$`GbpJC+{X%#?40M9`Y7f zZ;TwT(ARSUx5)kf3X$XL&Q8mwdCGV(bHn!9i2(`lo!M@`Cz4z#YHL$U1u>e10QVS} zkKS5Z;xy`;evvZjm*mwkiI+w~F~!1ae3qqjzs5%*<)iA`V 3`bQninfj0TV#y zjdfbjm5?7~qfUagK189xm&g{ryGpwww>(DH1LwuCms(%`NcuO{t;HWDa4NJXL38FW zN5QPut<^S_?WRR~cO>YlB7Xd*-=_G(lWvET3_F)yCA7)Dj9YNRgcIatKM5(-Sva${ zL d~6Z%?gZHw^KXrH=KqI*ie_tqA@v=(amY88-3pj z$7IOAE!r%LfRZbmFOlDg-ulf+87cn6S0DXW9NW2C+Kkuly6IpciV>P}q=P^}SUk_k z09U3JwjH*!PU*qAAoQsVqps=*$&u!+v-VoE-^gaxkUTYexAf@oV(<|ld7esy{DaW3 zj7tIL^B4cOSnCfmqtlJV1+gV~frL~LVe<0M{yDpXgda2j#|TCOA%6EW-?ZUoRvk CHNQa{RR-SXhYLxl3Ea$8=C6HoatiAej?;bwxERhy02x|BNGIb~n~jGX)8l#X zPC0eUdvty3 m>Lc^xlnxoZaYXPb)*1%)ga0{&@+oDc z)HrNg>v|unRkf^pv`M|qzuAjSni44&l1JC-KUf9 n=SIPDk^qFtrB(vG5C~pyXZfPM!34Hp1pvR>=|z(f65=Iy5`Ky%Fi4*S{$INP g`tE-|C}#n5(jz6G^3Is?|DOOViW&+Pa;Aa*2S`Bwc>n+a literal 0 HcmV?d00001 diff --git a/Images/github.png b/Images/github.png new file mode 100644 index 0000000000000000000000000000000000000000..89a55a1a986b793219af85d3ffe568bc2e8a4e53 GIT binary patch literal 8528 zcmXXM2T&7TGYP%--kXFfMIcBhA{}YcM5+n~krqG*5HLYLdKHuwih$CjC`geO5ke6H z2&kbq0U>HYL7MQNGymM&-ORpycl-8j- gUbMF@O^rb3|E{v`svH2q5NzfW3IZef|J`8nyC!-7NEc>qWlXn50|CphkK9*& z4+4pU%#E%%pk}`pN9MaZ79=h;NPB;~z+nEPJKn&lHD0%DNbpK)69Y3;@^xC0I2)98 zwOWc5lCd>fh8Y0~syrH!hZz{~X1hzW(*u_oeSD}j#AVCc@M($uj{x67kJ`?$YzN~lr!9-wX2qYgr7KSW}jn^?)NIDaV z=k~Jk((>{qC`WKm_$g2dNJXEcb%sEC<7dT?HI%qTW)fx)hWO`tD<}zAmfT=_X)XE_ z5&?OrMm%$@R|IpC*o*#R9$|*@Pw~}p`916?8WdyxEnJMjq&S#!hNtKmz7@ZS*Ow`m z(p!~V=3Tz!)w=OS+MqllZi=s(E&~z^x|ZeqcAyUbA3ngKVj~+Eg0GM`59ONJnj|Jn zJ-+emV0iku=JZvFJ<@q*nEpZIP0;eXQJ7?8WvwDa6w;tcT$GJ4!=%Vx)`1LyHrbah z@V!AO1*-< Wgx}691 z4mJmM4olxq9N{XxXOPoxn)}WU{}@W&n5&Qr-giP-( hJ0vH5+mgjMP`i695cdEx&*2k<|=1A<%KQ4I!zj~KyP?Tq_E%mjq&l-ydN-k z(whH_@NkNkiwo1wgM?oMvumt}FQ*bnGn{3sDdW%jceLWoQmImKEIijSk2Q4JK+$q% z# ;($|}P)o3sImuXGo0X`nw)Pf3!&TN6lH8q^;e4$-VA zt5qquH%vvJvG2Q#0g1bm2`utP0sVp%rODXWj8+{=ozkulKNVZ{AE<)T+Y4>)R1jVe zbYg6$;Y>DSs};c+*RQ2OLd160e@8LcUWWXJH>I!Ia_1l654%!q18Ja)eKh#HDf$v9 zA^rfIyDYf2hmt0)*BqU{YMsQUigyAuj vTZ4gx+wJS1$5Egu(Xf0D`^A zVO7c~FKslH{_AZKTkiOMybh(A?)Yr#YPHp7hTS6zA^C=QXet&z@7?2YwaFuC zJlHE+O-v%HKT7-(QQ3}mT7AO;9lzyx-a33*LtR}&8i$0{xhGlt-p#v2t>B3|291~V z5Dgt3yAVjlx!?z5>A*kai5Yh6dbw{>N2UW00YQTFyu86tE}wR 4-omuXZc^;XVm zC UA;M2wIH9$LSCRYKcD^08v7dA{Xf1kD7`GGQC>8@T22g9 zRM~9;Wy+Zd ;qXtD0Txr5^YbZE=x2MWYQE9v0pB=bt=5y`IgwCS7~=5$5_!_s+dhux@oLuVg55 zT3m1}cf7_-e2)e(R+&n39Ca1W24}pn>iQ%G^UQ!g&AdVXdRIy~DiLKq948(76nr0h zW`0V>!C#N~E~A$d7g^qwi_g>K7wprk5kEb-H_m|{#;+S?g=sb3cqySwt Z}(VAn`_NPwqB5WQb-T%R19_ipOvO_Cb~wxvd~hW%j}a0KI8gg6fT9CE _$E_IC0$kX{&V_LLQl&T7$@`vgFDr(Hg?V%cw9ms% z+8F;NYiRQ)fQ9{W^|kDdj+n DJ!a94Ndh*lUdbqraFZhTloWh-gaPB+dHnXvENA0Rqlm=utswBaycevniKHG#{ z5aBF@7`ON&s{1&M(Jn@mhE@R4TVT>DJ6|I8ki~LCrMVPre5GAUMF#;sItt-^JhSYK zR8&!T-;Dl0*i)Y+Icp6hyD=OtQY#!*mSQN-h6+com*mrN6u zcqi>pGA)6m;nI;0rSApJ+kS0C4zmpiuZC85NG)D&g7`}JM0arB@WHt+q)Key*Hxh$ z)wHYiN?e$FY%;#p;URKymq6N4n `?d@VnY!8|nnou)t4r45lu*4b`$Sj{n-v z_2qtA^L;tc@M51H-UBay=LlE~`44&UTlmYaxQa}o73IzvIa5u_z>Yo80IpgvD6-P4 zK`eQklhLNrq)Nddon({I^IBYwpUt@vNCny*8%$WS_35ws1d_+kkm~8!T-V_s?G%^D z4uJ1>9v8C4{1V(XiYh_L4=VREz$)9sKJGmD_i_7VCiCpPnHXaWcIH8nmQ6$=bZi_>H&!CG%IH_Lzvjj zd+f~VaZT1SS1R$WC9{s #Ka`aAo}>+r?#qiWUY$iO8e563^-y*BPQmT~y@A9OG(a0ze+op7&a zY14uCP3za>CHz$33{V2(%LfGsgH3K} >8`&xG#}t?s%#wMPTbhuVYr6neu8q zlLV1rSbveJBId11i1v2kHeb1ko4dH{IGH@~R#{Cv@uAShtdv$F7o&bs9K{*0mEu*0 zY;BnPXXwN6p){27DT2#)I<9+36HNF@NdB?*8kuTAB+lIpG^qIaFp2w!yXh?`T=df< z({t9`4zVUvuyAwlF~$t(`_Unbc`GT9fqfzij^4OWAn{GJ7>!6?VuOu`st=l6ftBk( z!W3iqiXXm*MZf9u^froD29;3eM}r=iV$R0yn7-`)m5!0j6JG0xNz9Wm7%uQELXneF z)0*qJe@fnQnJ`?wFIoh9G0*I)lUc0t85W)0=kA?*5A$of^&KF+P8p!@Ye=fnXZ(Bu z#JEpjx+wou?XUC`b|r!jOOZ~Sq|}$G6YBUoC4~`wRgm0ilY=*AC*K%@vbn5(O*fnY zZgNMFs&KnzWn2j1>FAb<^S;uI6%Z|SqBAOHn7C5X`fZW%ezrRC&vb(v0LTy(JWW9! z=ngawx$ypuz(@Jc@?GXCQ`)zbG`dT96-?4AX0Tl^c=b~V(P%H6ic{nA-5aZh(hoi~ z1OyB|{hk*0f}dAgG@%|Mx;xP_{N2aEG|G2tpYcw1D0;r+)zxUox@!PVgFqVO>6P4c znSgjbL);BGR|Q)zX0aTM4xKw4GTKlAe13-qsF$4`MjV`k0nt~y5WE@T<_g~4EC6Xx z1t|pnC;Pt1HKw`dtM!dkkX2tJ6@8xH?lVf7O9`nt`aXECo_g?2it~!V`DQcdC>)YN zZFZOt6cH8?sASL??fvEQ8d-6mj*_MYeAWy=|HgaTnc}!25fpWIJOT7PgSa!ST8QRd z1X7MA^&G*QYGo@W0<8`!^6-Hwnq)D?36*idZ!!Zll-vSQ(se+KZ5h6pL8Oh8^S>A9 z=AgODB2(;F~qG(vflM$X&jNO|~k*4~i~X8COLFeND# z7~IQIV%{5l0rqox#thX5n42h44ddY~JEhMG8raeT{+lfZ-V68`E~QaN;r@BMliGi| zz)Fg{s?Q!h_r`#s_Y)6_WM(MszghvR-yeNp_mA+RBdEa+y!l=WK<5ZbjAp2 lafQ|4_aRD9+XW8?8 ulnBBYj|TF_cPM(^XpHL-$_*_qAw45 zw286Dpu{M@#n5`1EeV@+oG=iL{P%iDj%v5D{x6;t6HG%KwJkE%A5{Etenmz#(!dm@i{w=ty( zmBz%wxWQK4e~N;dxbx`TIA>B4aasp}K9_(Nwj&??D9`8B7Kklo{Qc`oKVpun%oQH= z`5t3HHW!gH--FPTFd&c~9CoUUdNBozv1<>-hWC%})wO&q)CP1E)QDQ_(RWBnWlpsb z2`<{at4BH(3!vGG98X`m$k-Ray)9XhHu!$ZW-izs0SSu$3m)Aa=oo_9uwN9~Er ze@RVC8gGFlvEjw$^d^BNxn_l*-{X$Y;W?A^*%e^HIk1PG*9n88 z8VtTa%z2A3vqM`Px-(X3SP@8@Z@I S8!^2jD1UW3Q4(#ECbrTKgJG96dzoI z%h|G{Wf=PwrumH0n@jIt?3ujx{(jeW`HT%U0r8%HmTqYh<-db-uWpd=6W4QvT;v-i zE2TF#<`#jKWVPF>#B(v5zz#Y8Vb@qrjw9{Na+R{uK;>)?(C=_99A_o0lCQImegoAL zJI?#YtF6S?Bc0U~nzU?@3URb%hmM5N^8yxsH-pae`AMb+?$B@!Cq%0dltB7E<7q_G zTq=vT;h79y^LMvV6F18sLdEBRyr$`GdUe*}X+`xa)%*l0)tzDDoEH{&kQPU&0ixs@ zresmZ@ntCe5_Pz8lThp@QtAUA*sG3ZyylmUV86YQ(_VnWDF&n6%~{tk`g=*lQWeP40Nv zogoAV&8;OU{W#r*8Cs9v{ hRE=ih(s)b z `A0iK%x(qLwx!IP6UBHR zy#5G>vmMu@03Dg;l&MUfzMH?EAMvN|CT;GePp$nAtZL2GRfsU1-aPDnu$se%ZS N%^liqzP*k8=CcRS@L9u)ap^0!TjfRhd8F)fb8-__@is2b7w+r|TT+H2HA zHDXUbT&v(P{NB?e&<$F63G>tsXGLeXFj|>{lbYcPzv`~*b|sp`@aWGEZD@Sq6h9WV z)L-e%=N=mx-&s!UXYA<&5I^tnpd%oqV|I`9Y|tiicku7O7uQH%PWun^r5NQAJm`Ic zXGGo6hi}cl?cr9myn&3D=x%Y1UnmxN(52RRUr*)o(Pr2hnIXWVmm8)&m=2faASH60 zyROD9DWvdzAI`MMf@^}qtrF(!D`_+~RoDD8YmKM(B|!&e;l}e~-~&}+gVNgD9!7tb z)28m3d^`3M>TADL= sUYLCX*OFd-N- ri^c^DNFr zx(h;xQl+)yS!?S|fbqp$;S3a|OlsejpH-a~xJFZlHUt~X?VQF+{q7prn#?rI+d}Rd zUqSf`{0!*)5@b^6In#_SD4~8YVGO%D3)@NJV@~;f6ee>yB#7$v1 HOos{r!~7m;Eh%!S+)?!Ub3s{l zk8WAv`=}B*cg^4im9QtCm7;Oap!U>WMz2zGZ40Pdy#VSox>yD|?K>7%WBzslkj={- zgaV*))26AZq3pZ)CPHv~N8i{jP$THH9~SL7S>F4q25Af+Uv|ioWob|$dUz(Y)oxjA zK TdCxxTbvn&k1|k>1oZD3cFXHGkSh8mebj2fYK*M zNMZgY#Wn4hlud>3()TKaTr?ZebCc7pmh-I1J#>ImSzO+i^jhN yJt3+_25Tx)qj>#Vjur~SwBtpv^F@~^f|e5(vJOUXyHyq z{n`0El-|_{A%6s;`<`jcS1QePlc@_DP|XnSa2;JM2{_Qfng!8U8Haw{@$O0)=!&u4 zajS-h0z5aCY^fTY9v)_S4%0YpHqKNJ+17p>5T&mCyK`#9*C;l-v2^uj^41jop*LLo zZmkXai$@_{t=@^_+Eq(O#w3$v9XTC;1MYaf@R0O6AYt#s=G|+Sc@H~@SuRYYeXqDi z4%tu6+--8R)vz|VHfw-6cE`r!Y=V0n?5#z-Ccz;qWWn#Uwhyj<&M+)@k4CG#9@ z<`%ajfP!In%NoefHL6m*5HqtaRJtq{a%DhUiF8_I1Gc#Q^U}v|tDC0i_FEk_=u#-X z7!a9hGxj+uos6b6^MUAcVs#50`;Ey1gKK2nRW+q@HFyB7{O)``qyr zAJ9PtZll6w`oSI_GETgU1~Iu)e~&KrR9zL#Ws*0s^1J`p2=jQrvZ|6i5juii+6#|0 z9}nPAStCcSk$)stE7woH(UL9rd{0;VxY7VF$LA1#=l+AZpn5g&wB{+S>-Dhh0w8_5 zL3p6aQCd6uv}x?5gerlm({@2an~%yBOqJGrUePltH$Fnsy&m|IU41G5Fd7IEH8UV#>@vF~7=0a}d_!8W{q0Z*3P?le}F%m-(cZs>I|MMwXl zT9w>u&UyC!ula#Yl&CA$WKYu~^Diw;;_S`Xd+rw45a!@J78#3ku#@2b$cJpJ%?scc zROfEPo|%AFfJ(s|{0P44%&>2bV_4_)WKm=W@}Sr6@AC(8v>z7mSUL3m2Lh>HA^0x> zl-Cv+#l!A+yU2(;J~WB#hW{;&-DJ_RZ3U5If5qM8SMs)>bYBP{>|Gk2Ek4`^YL9X( z1Fb+-<42>nxe#6=1e%|}vRR!PN6E8eR|bkp#@zAh72@!&Yr9~62DEw1bNd$7>XT8a zf#ccdx#>$f0@0reJ4ZUCKK+OBusNx!3%`Us `!6IiFRqe0OTtVS%XlzQ=QpQrsy3j3-bF}4I2&L@`#Wm;WE#U^T&l9 I>#LMNOyU0R@VL4@aZ{VdRPJkZe!LaZ2W ztMMABC)LS)dc1EgFRz*f;=v}SHQT=qQGKo$Nr34*K{@q&(#z)psthSsE84t=Zcmk~ zbFat-m|otmECu+58mOjZ-Cb3(Et&}_GFGO6k}^~vzSmZEs!XpOs{hMCpZB-TsXLp> zrwZ6w+MOUP?XPa>*s=Ej&5Rc*t}EdCm}0DqOIQ1Kr|ze@ao6s9+*PF9wbU?@2pPHT z^%8lo|1xkz&W99DAa%63R6E}xrUF4Icl?sA;wEI_?{U&TzOqueQ}Cshbn4;V7wGOF z@HvB9(X<#ZDK$qGzrzW5(o00W!u?dmukUgQq$ $NhOF*-1T1NsK=!Apif}bP9kv#s3A~49Iq{=L;&NITtpm<;n zwMIs#7u4~2yHbU!MMYXAiz`VeuN{YclvtEdUYZ;-m64+5EH%}a9Bv?RE*;I$^B0~* znCXzpsLdeLPoG|8x*&+y{Q9kLCq&S<0+W~Y$AJgvja4C4SyA;E$Zt_f!TJlU2I$uo zuAV2}tf@2Kjo!J?uD5ar$TfOs5gRV(xvwb=ltG~x?OCUtkxDbJBh7!fsxl=SG;ieI zN=Wd6tgaCNWosB|=6K%K7KaQkMjjX}r^Hd0P@DC1^uanyk(z%rnk;)-l8^^s%l;44 z7_v5T+5K<%a?ySQYvhY&5j!SITNjJvq{sd>WIRv29fsSSEQtPM#$5-5&j#EBw?$Rd zGTTT@vIfcY-2pfehSTAzzgk2peo5p9y>HUzSi0M$r+H&|)Atfl)ELd1$NG{hax`pQ zoha&q3pm&}nt%MW-WS)McIYtqq$IymyNswVE_TDxZEzE^B=piDzWNgJrZ_0jPBsVR zgWJKr1B&RzzD?KdB&T3=zBpY$n9}Bp-%p6B;hW3X*2qW#>4~xGTZa}WYwJg3x-NMz zr@#t}S3B}xuUx=G{`P$##AiW78>pvV(;i+m`i(#WW_ J2rLUW zR2>z0Gbz6jb*8C;hn++uXHr+|NVd$vPC{2#DQ&}2 <5cb{Q`MtDXr@^VIvTuZM@&Cp%eg})diqj$Tg^NqR^Emtd; ztK}u9uZ0K$^ 8FVF$E6(>NZfl}AfX1oL z%~dI|g?&;3uvWTjVBSw&Ni7Z@KeHZa@Sh2KMXZrE38Zf!9n??-!{6+?99N1UnNGXW zXUahlLwoXPAULbPp_|}7vg56qubt}?z@);1A+H0vPJ!bAlmE^FP%0Q>$!=?8r@g>L zy^9=qZ>qiB&iUIlgAfgt7^9sGDP3Y#TzV?Gg_;<3v=*i*3KUxol + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap discord_icon { + get { + object obj = ResourceManager.GetObject("discord_icon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap github_icon { + get { + object obj = ResourceManager.GetObject("github_icon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + ////// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/Properties/Resources.resx b/Properties/Resources.resx index 25cd251..f38c0ae 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -121,6 +121,12 @@..\Images\background.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + +..\Images\discord.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + +..\Images\github.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a +..\Images\logo.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/ServerHandler/Servers.cs b/ServerHandler/Servers.cs index e3dd19d..d9eb80b 100644 --- a/ServerHandler/Servers.cs +++ b/ServerHandler/Servers.cs @@ -12,10 +12,10 @@ // ============================================================================ using Synix_Control_Panel.Database; using Synix_Control_Panel.SynixEngine; +using static Synix_Control_Panel.SynixEngine.Core; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Runtime.InteropServices; -using static Synix_Control_Panel.SynixEngine.Core; namespace Synix_Control_Panel.ServerHandler { @@ -40,9 +40,12 @@ public static class Servers public static async Task Start(GameServer server, ActionlogCallback, StartContext context = StartContext.Manual) { - if (!IsSystemSafeToStart()) return; try { + // 1. HARDWARE CHECKS (Backgrounded to prevent WMI/PerfCounter UI Freezes) + bool isSystemSafe = await Task.Run(() => IsSystemSafeToStart()); + if (!isSystemSafe) return; + if (!Core.Instance.PassResourceGuard(out string guardMsg)) { logCallback?.Invoke(guardMsg, Color.Orange); @@ -50,7 +53,6 @@ public static async Task Start(GameServer server, Action logCallb return; } - // 1. PRE-FLIGHT (Backup & Update) if (server.BackupOnStart && context != StartContext.CrashRecovery) { await Task.Run(() => Core.Instance.ExecuteBackup(server, context)); @@ -61,166 +63,167 @@ public static async Task Start(GameServer server, Action logCallb await Task.Run(() => Core.Instance.UpdateServerAndReport(server, "UPDATE", true)); } - // 2. TEMPLATE VALIDATION + // Safely update the DataGridView UI state on the main thread server.Status = StatusManager.GetStatus(ServerState.Starting); - var dbEntry = GameDatabase.GetGame(server.Game); - if (dbEntry == null) - { - logCallback?.Invoke("[🚨 ERROR] Game template not found.", Color.Red); - return; - } + MainGUI.Instance?.Invoke((Action)(() => MainGUI.Instance.UpdateGrid())); - // 3. PATH SETUP - string fullExePath = Path.Combine(server.InstallPath, dbEntry.ExeName); - string binDir = Path.GetDirectoryName(fullExePath) ?? ""; + ProcessStartInfo? psi = null; + string finalArgs = ""; - if (!File.Exists(fullExePath)) + // 2. HEAVY DISK & STRING PROCESSING (Backgrounded to prevent lag) + await Task.Run(() => { - logCallback?.Invoke($"[🚨 ERROR] Executable missing: {fullExePath}", Color.Red); - server.Status = StatusManager.GetStatus(ServerState.Stopped); - return; - } - - // 4. DYNAMIC IDENTITY & SEARCH - string targetId = dbEntry.AppID; - string invokedId = targetId; + var dbEntry = GameDatabase.GetGame(server.Game); + if (dbEntry == null) + { + logCallback?.Invoke("[🚨 ERROR] Game template not found.", Color.Red); + return; + } - // 1. FAST CHECK: Look in the two exact places it belongs first (0ms operation) - string rootAppIdPath = Path.Combine(server.InstallPath, "steam_appid.txt"); - string binAppIdPath = Path.Combine(binDir, "steam_appid.txt"); - string appidPath = rootAppIdPath; // Default fallback + string fullExePath = Path.Combine(server.InstallPath, dbEntry.ExeName); + string binDir = Path.GetDirectoryName(fullExePath) ?? ""; - if (File.Exists(rootAppIdPath)) - { - appidPath = rootAppIdPath; - } - else if (File.Exists(binAppIdPath)) - { - appidPath = binAppIdPath; - } - else - { - // 2. LAST RESORT: Only run the heavy recursive scan if it's completely missing - try + if (!File.Exists(fullExePath)) { - var scanner = Directory.EnumerateFiles(server.InstallPath, "steam_appid.txt", new EnumerationOptions - { - RecurseSubdirectories = true, - IgnoreInaccessible = true, - MaxRecursionDepth = 5, // Limit to 5 folders deep so it doesn't hang on 200k files - AttributesToSkip = FileAttributes.ReparsePoint - }); - - appidPath = scanner.FirstOrDefault() ?? rootAppIdPath; + logCallback?.Invoke($"[🚨 ERROR] Executable missing: {fullExePath}", Color.Red); + MainGUI.Instance?.Invoke((Action)(() => server.Status = StatusManager.GetStatus(ServerState.Stopped))); + return; } - catch + + string targetId = dbEntry.AppID; + string invokedId = targetId; + + string rootAppIdPath = Path.Combine(server.InstallPath, "steam_appid.txt"); + string binAppIdPath = Path.Combine(binDir, "steam_appid.txt"); + string appidPath = rootAppIdPath; + + if (File.Exists(rootAppIdPath)) { appidPath = rootAppIdPath; } - } + else if (File.Exists(binAppIdPath)) + { + appidPath = binAppIdPath; + } + else + { + try + { + var scanner = Directory.EnumerateFiles(server.InstallPath, "steam_appid.txt", new EnumerationOptions + { + RecurseSubdirectories = true, + IgnoreInaccessible = true, + MaxRecursionDepth = 5, + AttributesToSkip = FileAttributes.ReparsePoint + }); + + appidPath = scanner.FirstOrDefault() ?? rootAppIdPath; + } + catch + { + appidPath = rootAppIdPath; + } + } - // 🎯 THE INVOKE: Pull the ID from the file for {steamAppID} - if (File.Exists(appidPath)) - { - try + if (File.Exists(appidPath)) { - string fileContent = File.ReadAllText(appidPath).Trim(); + try + { + string fileContent = File.ReadAllText(appidPath).Trim(); - // Safety: If SteamCMD adds weird invisible characters or newlines, strictly grab the first line - fileContent = fileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim() ?? ""; + fileContent = fileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim() ?? ""; - if (!string.IsNullOrWhiteSpace(fileContent)) - { - invokedId = fileContent; + if (!string.IsNullOrWhiteSpace(fileContent)) + { + invokedId = fileContent; + } } + catch (Exception ex) { logCallback?.Invoke($"[⚠️ WARNING] File Read Error: {ex.Message}", Color.OrangeRed); } } - catch (Exception ex) { logCallback?.Invoke($"[⚠️ WARNING] File Read Error: {ex.Message}", Color.OrangeRed); } - } - // 🛠️ 6. ARGUMENT REPLACEMENT - string cleanIdentity = Core.Instance.GetSafeName(server.ServerName); - - string args = dbEntry.RequiredArgs - .Replace("{app_port}", server.AppPort?.ToString() ?? "0") - .Replace("{seed}", string.IsNullOrWhiteSpace(server.WorldSeed) ? "12345" : server.WorldSeed) - .Replace("{map}", server.WorldName) - .Replace("{steamAppID}", invokedId) - .Replace("{appid}", targetId) - .Replace("{port}", server.Port.ToString()) - .Replace("{query}", server.QueryPort.ToString()) - .Replace("{MaxPlayers}", server.MaxPlayers.ToString()) - .Replace("{pass}", server.Password ?? "") - .Replace("{adminpass}", server.AdminPassword ?? "") - .Replace("{ServerName}", server.ServerName) - .Replace("{InstallPath}", server.InstallPath) - .Replace("{Identity}", cleanIdentity); - - // 🎯 RCON LOGIC RESTORED - if (args.Contains("{rcon}")) - { - string formattedRcon = server.EnableRcon && !string.IsNullOrWhiteSpace(dbEntry.RconSyntax) - ? dbEntry.RconSyntax.Replace("{rcon_port}", server.RconPort.ToString()).Replace("{rcon_pass}", server.RconPassword ?? "") - : ""; - args = args.Replace("{rcon}", formattedRcon); - } + string cleanIdentity = Core.Instance.GetSafeName(server.ServerName); + + string args = dbEntry.RequiredArgs + .Replace("{app_port}", server.AppPort?.ToString() ?? "0") + .Replace("{seed}", string.IsNullOrWhiteSpace(server.WorldSeed) ? "12345" : server.WorldSeed) + .Replace("{map}", server.WorldName) + .Replace("{steamAppID}", invokedId) + .Replace("{appid}", targetId) + .Replace("{port}", server.Port.ToString()) + .Replace("{query}", server.QueryPort.ToString()) + .Replace("{MaxPlayers}", server.MaxPlayers.ToString()) + .Replace("{pass}", server.Password ?? "") + .Replace("{adminpass}", server.AdminPassword ?? "") + .Replace("{ServerName}", server.ServerName) + .Replace("{InstallPath}", server.InstallPath) + .Replace("{Identity}", cleanIdentity); + + if (args.Contains("{rcon}")) + { + string formattedRcon = server.EnableRcon && !string.IsNullOrWhiteSpace(dbEntry.RconSyntax) + ? dbEntry.RconSyntax.Replace("{rcon_port}", server.RconPort.ToString()).Replace("{rcon_pass}", server.RconPassword ?? "") + : ""; + args = args.Replace("{rcon}", formattedRcon); + } - // 🎯 GAME MODE TRANSLATION RESTORED - if (args.Contains("{mode}") && !string.IsNullOrWhiteSpace(server.GameMode)) - { - string translatedMode = (server.GameMode == "PVE" && (server.Game.Contains("ARK") || server.Game == "Atlas" || server.Game == "Rust")) - ? "True" : (server.GameMode == "PVP" && (server.Game.Contains("ARK") || server.Game == "Atlas" || server.Game == "Rust")) - ? "False" : server.GameMode; - args = args.Replace("{mode}", translatedMode); - } + if (args.Contains("{mode}") && !string.IsNullOrWhiteSpace(server.GameMode)) + { + string translatedMode = (server.GameMode == "PVE" && (server.Game.Contains("ARK") || server.Game == "Atlas" || server.Game == "Rust")) + ? "True" : (server.GameMode == "PVP" && (server.Game.Contains("ARK") || server.Game == "Atlas" || server.Game == "Rust")) + ? "False" : server.GameMode; + args = args.Replace("{mode}", translatedMode); + } - // 🛡️ SECURITY GUARD: Validate ExtraArgs before appending - if (!string.IsNullOrWhiteSpace(server.ExtraArgs)) - { - if (!IsGameServerConfigSafe(server.ExtraArgs)) + if (!string.IsNullOrWhiteSpace(server.ExtraArgs)) { - logCallback?.Invoke("[🚨 SECURITY] Illegal characters detected in the extra arguments. Aborting startup.", Color.Red); - server.Status = StatusManager.GetStatus(ServerState.Stopped); - return; + if (!IsGameServerConfigSafe(server.ExtraArgs)) + { + logCallback?.Invoke("[🚨 SECURITY] Illegal characters detected in the extra arguments. Aborting startup.", Color.Red); + MainGUI.Instance?.Invoke((Action)(() => server.Status = StatusManager.GetStatus(ServerState.Stopped))); + return; + } + + args = $"{args} \"{server.ExtraArgs.Trim()}\""; } - args = $"{args} \"{server.ExtraArgs.Trim()}\""; - } + args = args.Replace(" ", " ").Trim(); - args = args.Replace(" ", " ").Trim(); + if (!IsStringSafe(args)) + { + logCallback?.Invoke("[🚨 SECURITY] Illegal characters detected. Aborting startup.", Color.Red); + MainGUI.Instance?.Invoke((Action)(() => server.Status = StatusManager.GetStatus(ServerState.Stopped))); + return; + } - if (!IsStringSafe(args)) - { - logCallback?.Invoke("[🚨 SECURITY] Illegal characters detected. Aborting startup.", Color.Red); - server.Status = StatusManager.GetStatus(ServerState.Stopped); - return; - } + // Package the final validated strings into process parameters + finalArgs = args; + psi = new ProcessStartInfo + { + FileName = fullExePath, + Arguments = finalArgs, + WorkingDirectory = binDir, + UseShellExecute = false, + CreateNoWindow = false + }; - // 🚀 7. CONFIGURE PROCESS - ProcessStartInfo psi = new() - { - FileName = fullExePath, - Arguments = args, - WorkingDirectory = binDir, - UseShellExecute = false, - CreateNoWindow = false - }; + psi.EnvironmentVariables["SteamAppId"] = invokedId; + psi.EnvironmentVariables["SteamGameId"] = invokedId; + }); - // 🎯 MEMORY INJECTION - psi.EnvironmentVariables["SteamAppId"] = invokedId; - psi.EnvironmentVariables["SteamGameId"] = invokedId; + // If the background task failed early (missing exe, bad string), safely stop execution + if (psi == null) return; - logCallback?.Invoke($"[ARGUMENT] {args}", Color.Cyan); + // 3. LAUNCH PROCESS (Back on the UI thread, instantaneous) + logCallback?.Invoke($"[ARGUMENT] {finalArgs}", Color.Cyan); - // 🚀 8. EXECUTION & MONITORING Process? proc = Process.Start(psi); if (proc != null) { server.RunningProcess = proc; server.PID = proc.Id; - if (server.StartTime == null) server.StartTime = DateTime.Now; + server.StartTime = DateTime.Now; - // 🎯 DISCORD ALERT: Server Online (Clean alert) _ = Core.Instance.SendDiscordAlert(server, "SERVER STARTING", $"{server.ServerName} process has been initiated.", Color.Cyan); proc.EnableRaisingEvents = true; @@ -228,7 +231,6 @@ public static async Task Start(GameServer server, Action logCallb { if (server.Status == StatusManager.GetStatus(ServerState.Running)) { - // Watchdog handles the single Discord crash notification await Core.Instance.ExecuteStartSequence(server, "WATCHDOG"); } else @@ -256,7 +258,6 @@ public static async Task Stop(GameServer server, Action logCallba return; } - // 🎯 DISCORD ALERT: Manual Shutdown if (isManual) { _ = Core.Instance.SendDiscordAlert(server, "MANUAL SHUTDOWN", @@ -277,7 +278,7 @@ public static async Task Stop(GameServer server, Action logCallba if (cleanExit) { - logCallback?.Invoke($"[STOP] {server.ServerName} saved and closed cleanly.", Color.Lime); + logCallback?.Invoke($"[SYNIX] {server.ServerName} saved and closed cleanly.", Color.Lime); FinalizeStoppedState(server); return; } diff --git a/SynixEngine/version.txt b/SynixEngine/version.txt index d0f65cd..835ea23 100644 --- a/SynixEngine/version.txt +++ b/SynixEngine/version.txt @@ -1 +1 @@ -1.0.16 \ No newline at end of file +1.0.17 \ No newline at end of file From cbb7edde38c6dee0aab43fb8133124b593261bdf Mon Sep 17 00:00:00 2001 From: Jason <6037701+ubidzz@users.noreply.github.com> Date: Tue, 26 May 2026 16:41:34 -0400 Subject: [PATCH 4/8] Update README.md update --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a3ef076..b374b1d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ [](https://www.virustotal.com/gui/file/c3a62c98e52bacccb57bc4e9b342feef20d2be49de4f91bfca164f7e6487d0b8?nocache=1) [](https://github.com/microsoft/winget-pkgs/tree/master/manifests/u/ubidzz/Synix) [](https://www.paypal.com/donate/?hosted_button_id=FAHU6EH6BX9J8) + **Synix Control Panel** is an elite, engine-driven management suite designed to provide a centralized "Brain" for game server hosting. By moving beyond simple batch scripts, Synix automates deployment, process health, networking diagnostics, and hardware stewardship within a **Zero-Admin (No UAC)** environment. From f8a11d6f338cd1a9fa5a7d857d16a39ba7274a3e Mon Sep 17 00:00:00 2001 From: ubidzz <6037701+ubidzz@users.noreply.github.com> Date: Wed, 27 May 2026 01:10:12 -0400 Subject: [PATCH 5/8] fixed the watchdog and maintenance stop and start server bug. Added game icon in the server DataGridView. --- Design/GridStyler.cs | 1 + FileFolderHandler/FileHandler.cs | 13 +++ MainGUI.Designer.cs | 137 +++++++++++++++-------------- MainGUI.cs | 31 +++++++ MainGUI.resx | 3 + ServerHandler/GameServer.cs | 2 + ServerHandler/ServerSettingsGUI.cs | 16 ++++ SynixEngine/Actions.cs | 114 ++++++++++++++---------- SynixEngine/IconManager.cs | 74 ++++++++++++++++ 9 files changed, 279 insertions(+), 112 deletions(-) create mode 100644 SynixEngine/IconManager.cs diff --git a/Design/GridStyler.cs b/Design/GridStyler.cs index 2152508..0e26c7b 100644 --- a/Design/GridStyler.cs +++ b/Design/GridStyler.cs @@ -36,6 +36,7 @@ public static void DarkTheme(DataGridView dgv) dgv.AutoGenerateColumns = false; // Map Columns to Class Properties + if (dgv.Columns.Contains("colIcon")) dgv.Columns["colIcon"].DataPropertyName = ""; if (dgv.Columns.Contains("colName")) dgv.Columns["colName"].DataPropertyName = "ServerName"; if (dgv.Columns.Contains("colGame")) dgv.Columns["colGame"].DataPropertyName = "Game"; if (dgv.Columns.Contains("colPort")) dgv.Columns["colPort"].DataPropertyName = "Port"; diff --git a/FileFolderHandler/FileHandler.cs b/FileFolderHandler/FileHandler.cs index c479831..cc45aab 100644 --- a/FileFolderHandler/FileHandler.cs +++ b/FileFolderHandler/FileHandler.cs @@ -56,6 +56,7 @@ public static void LoadServers() MainGUI.serverList.Clear(); foreach (var server in loadedServers) { + // 1. Grab the hardcoded data from the switch statement in your screenshot var masterData = GameDatabase.GetGame(server.Game); if (masterData != null) { @@ -63,6 +64,18 @@ public static void LoadServers() server.ExeName = masterData.ExeName; server.RequiredArgs = masterData.RequiredArgs; server.Maps = masterData.Maps.ToList(); + + // 2. Smash the JSON path and Hardcoded ExeName together + string fullExePath = Path.Combine(server.InstallPath, server.ExeName); + + // 3. Extract the icon + string iconPath = Synix_Control_Panel.SynixEngine.Core.GetLocalServerIcon(server.AppID, fullExePath); + + // 4. Attach it permanently to the object + if (File.Exists(iconPath)) + { + server.DisplayIcon = System.Drawing.Image.FromFile(iconPath); + } } MainGUI.serverList.Add(server); } diff --git a/MainGUI.Designer.cs b/MainGUI.Designer.cs index a6ba285..3e9075d 100644 --- a/MainGUI.Designer.cs +++ b/MainGUI.Designer.cs @@ -34,13 +34,6 @@ private void InitializeComponent() System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainGUI)); dataGridView1 = new DataGridView(); - colGame = new DataGridViewTextBoxColumn(); - colName = new DataGridViewTextBoxColumn(); - colPort = new DataGridViewTextBoxColumn(); - colQueryPort = new DataGridViewTextBoxColumn(); - colPlayerCount = new DataGridViewTextBoxColumn(); - colUptime = new DataGridViewTextBoxColumn(); - colStatus = new DataGridViewTextBoxColumn(); rtbLog = new RichTextBox(); btnStart = new Button(); btnStop = new Button(); @@ -80,6 +73,14 @@ private void InitializeComponent() btnMinimize = new Button(); btnDiscord = new Button(); btnGithub = new Button(); + DisplayIcon = new DataGridViewTextBoxColumn(); + colGame = new DataGridViewTextBoxColumn(); + colName = new DataGridViewTextBoxColumn(); + colPort = new DataGridViewTextBoxColumn(); + colQueryPort = new DataGridViewTextBoxColumn(); + colPlayerCount = new DataGridViewTextBoxColumn(); + colUptime = new DataGridViewTextBoxColumn(); + colStatus = new DataGridViewTextBoxColumn(); ((System.ComponentModel.ISupportInitialize)dataGridView1).BeginInit(); ((System.ComponentModel.ISupportInitialize)logo).BeginInit(); ((System.ComponentModel.ISupportInitialize)chartHeartbeat).BeginInit(); @@ -92,7 +93,7 @@ private void InitializeComponent() dataGridView1.AllowUserToDeleteRows = false; dataGridView1.BorderStyle = BorderStyle.None; dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; - dataGridView1.Columns.AddRange(new DataGridViewColumn[] { colGame, colName, colPort, colQueryPort, colPlayerCount, colUptime, colStatus }); + dataGridView1.Columns.AddRange(new DataGridViewColumn[] { DisplayIcon, colGame, colName, colPort, colQueryPort, colPlayerCount, colUptime, colStatus }); dataGridView1.Location = new Point(12, 171); dataGridView1.MultiSelect = false; dataGridView1.Name = "dataGridView1"; @@ -103,62 +104,6 @@ private void InitializeComponent() dataGridView1.CellFormatting += dataGridView1_CellFormatting; dataGridView1.CellPainting += dataGridView1_CellPainting; // - // colGame - // - colGame.DataPropertyName = "Game"; - colGame.HeaderText = "Game"; - colGame.Name = "colGame"; - colGame.ReadOnly = true; - colGame.Width = 200; - // - // colName - // - colName.DataPropertyName = "ServerName"; - colName.HeaderText = "Server Name"; - colName.Name = "colName"; - colName.ReadOnly = true; - colName.Width = 280; - // - // colPort - // - colPort.DataPropertyName = "Port"; - colPort.HeaderText = "Port"; - colPort.Name = "colPort"; - colPort.ReadOnly = true; - colPort.Width = 80; - // - // colQueryPort - // - colQueryPort.DataPropertyName = "QueryPort"; - colQueryPort.HeaderText = "Query Port"; - colQueryPort.Name = "colQueryPort"; - colQueryPort.ReadOnly = true; - colQueryPort.Width = 80; - // - // colPlayerCount - // - colPlayerCount.DataPropertyName = "PlayerCount"; - colPlayerCount.HeaderText = "Players"; - colPlayerCount.Name = "colPlayerCount"; - colPlayerCount.ReadOnly = true; - colPlayerCount.Width = 70; - // - // colUptime - // - colUptime.DataPropertyName = "Uptime"; - colUptime.HeaderText = "UPTIME"; - colUptime.Name = "colUptime"; - colUptime.ReadOnly = true; - colUptime.Width = 80; - // - // colStatus - // - colStatus.DataPropertyName = "Status"; - colStatus.HeaderText = "Status"; - colStatus.Name = "colStatus"; - colStatus.ReadOnly = true; - colStatus.Width = 90; - // // rtbLog // rtbLog.BackColor = SystemColors.ActiveCaptionText; @@ -519,6 +464,69 @@ private void InitializeComponent() btnGithub.UseVisualStyleBackColor = true; btnGithub.Click += btnGithub_Click; // + // DisplayIcon + // + DisplayIcon.HeaderText = ""; + DisplayIcon.Name = "DisplayIcon"; + DisplayIcon.ReadOnly = true; + DisplayIcon.Width = 15; + // + // colGame + // + colGame.DataPropertyName = "Game"; + colGame.HeaderText = "Game"; + colGame.Name = "colGame"; + colGame.ReadOnly = true; + colGame.Width = 175; + // + // colName + // + colName.DataPropertyName = "ServerName"; + colName.HeaderText = "Server Name"; + colName.Name = "colName"; + colName.ReadOnly = true; + colName.Width = 255; + // + // colPort + // + colPort.DataPropertyName = "Port"; + colPort.HeaderText = "Port"; + colPort.Name = "colPort"; + colPort.ReadOnly = true; + colPort.Width = 80; + // + // colQueryPort + // + colQueryPort.DataPropertyName = "QueryPort"; + colQueryPort.HeaderText = "Query Port"; + colQueryPort.Name = "colQueryPort"; + colQueryPort.ReadOnly = true; + colQueryPort.Width = 80; + // + // colPlayerCount + // + colPlayerCount.DataPropertyName = "PlayerCount"; + colPlayerCount.HeaderText = "Players"; + colPlayerCount.Name = "colPlayerCount"; + colPlayerCount.ReadOnly = true; + colPlayerCount.Width = 70; + // + // colUptime + // + colUptime.DataPropertyName = "Uptime"; + colUptime.HeaderText = "UPTIME"; + colUptime.Name = "colUptime"; + colUptime.ReadOnly = true; + colUptime.Width = 80; + // + // colStatus + // + colStatus.DataPropertyName = "Status"; + colStatus.HeaderText = "Status"; + colStatus.Name = "colStatus"; + colStatus.ReadOnly = true; + colStatus.Width = 90; + // // MainGUI // AutoScaleDimensions = new SizeF(7F, 17F); @@ -607,6 +615,7 @@ private void InitializeComponent() private Button btnMinimize; private Button btnDiscord; private Button btnGithub; + private DataGridViewTextBoxColumn DisplayIcon; private DataGridViewTextBoxColumn colGame; private DataGridViewTextBoxColumn colName; private DataGridViewTextBoxColumn colPort; diff --git a/MainGUI.cs b/MainGUI.cs index 1b04aa1..bdc4def 100644 --- a/MainGUI.cs +++ b/MainGUI.cs @@ -38,6 +38,7 @@ public partial class MainGUI : Form private static Font regularFont = new Font("Segoe UI", 9, FontStyle.Regular); private bool isPrivacyLoading = false; private System.Windows.Forms.Timer? versionTimer; + public static Dictionary ServerIconsCache = new Dictionary (); public const int WM_NCLBUTTONDOWN = 0xA1; public const int HT_CAPTION = 0x2; @@ -57,7 +58,30 @@ public MainGUI() GridStyler.DarkTheme(dataGridView1); GridStyler.ApplyRoundedCorners(dataGridView1, 10); UIStyleHelper.InitializeToggles(this); + dataGridView1.DataSource = serverList; + dataGridView1.DataError += dataGridView1_DataError; + if (!dataGridView1.Columns.Contains("IconCol")) + { + DataGridViewImageColumn iconCol = new DataGridViewImageColumn(); + iconCol.Name = "IconCol"; + iconCol.HeaderText = ""; + iconCol.DataPropertyName = "DisplayIcon"; + iconCol.ImageLayout = DataGridViewImageCellLayout.Zoom; + iconCol.Width = 35; + + iconCol.DefaultCellStyle.Padding = new Padding(4); + + dataGridView1.Columns.Insert(0, iconCol); + + dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; + dataGridView1.RowTemplate.Height = 35; + foreach (DataGridViewRow row in dataGridView1.Rows) + { + row.Height = 35; + } + } + typeof(DataGridView).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty, null, dataGridView1, new object[] { true }); GridStyler.ApplyTransparentTheme(dataGridView1); GridStyler.StyleCloseButton(btnClose); @@ -75,6 +99,13 @@ public MainGUI() _ = VersionCheck(); } + private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e) + { + // If the grid throws a fit because the image is temporarily missing during an edit, + // this tells it to ignore the error and not draw the ugly Red X. + e.ThrowException = false; + } + private void tmrResourceUpdates_Tick(object sender, EventArgs e) { CheckRunningStatus(); diff --git a/MainGUI.resx b/MainGUI.resx index e26e0f3..e46bbc9 100644 --- a/MainGUI.resx +++ b/MainGUI.resx @@ -117,6 +117,9 @@ + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + True +diff --git a/ServerHandler/GameServer.cs b/ServerHandler/GameServer.cs index 0688cf2..8a93d2b 100644 --- a/ServerHandler/GameServer.cs +++ b/ServerHandler/GameServer.cs @@ -21,6 +21,8 @@ public class GameInfo { public string Game { get; init; } = string.Empty; [JsonIgnore] + public System.Drawing.Image DisplayIcon { get; set; } + [JsonIgnore] public bool HasAnnouncedOnline { get; set; } = false; [JsonIgnore] public bool NeedsConfigWarning { get; internal set; } diff --git a/ServerHandler/ServerSettingsGUI.cs b/ServerHandler/ServerSettingsGUI.cs index 2e6d569..e6991d5 100644 --- a/ServerHandler/ServerSettingsGUI.cs +++ b/ServerHandler/ServerSettingsGUI.cs @@ -356,6 +356,22 @@ private void btnSave_Click(object sender, EventArgs e) } } else MainGUI.serverList.Add(NewServer); + + var masterData = GameDatabase.GetGame(NewServer.Game); + if (masterData != null) + { + NewServer.AppID = masterData.AppID; + NewServer.ExeName = masterData.ExeName; + + string fullExePath = System.IO.Path.Combine(NewServer.InstallPath, NewServer.ExeName); + string iconPath = Synix_Control_Panel.SynixEngine.Core.GetLocalServerIcon(NewServer.AppID, fullExePath); + + if (System.IO.File.Exists(iconPath)) + { + NewServer.DisplayIcon = System.Drawing.Image.FromFile(iconPath); + } + } + FileHandler.SaveServers(); this.DialogResult = DialogResult.OK; this.Close(); } catch (Exception ex) { MessageBox.Show(ex.Message); } diff --git a/SynixEngine/Actions.cs b/SynixEngine/Actions.cs index 8f7804d..331638f 100644 --- a/SynixEngine/Actions.cs +++ b/SynixEngine/Actions.cs @@ -21,6 +21,8 @@ namespace Synix_Control_Panel.SynixEngine { public partial class Core { + private static readonly HashSet True _activeSequences = new HashSet (); + public async Task StopServerAndReport(GameServer server, bool isManual = true) { server.Status = StatusManager.GetStatus(ServerState.Stopping); @@ -372,70 +374,86 @@ public void EditServerAndReport(GameServer server) public async Task ExecuteStartSequence(GameServer server, string status = "") { - bool stopServer = false; - StartContext currentContext = StartContext.Manual; - - if (!PassResourceGuard(out string guardMsg)) + lock (_activeSequences) { - Log(guardMsg, System.Drawing.Color.Red, true); - MessageBox.Show(guardMsg, "System Resource Exhaustion", - System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Warning); - return; + if (_activeSequences.Contains(server.ServerName)) + { + return; + } + _activeSequences.Add(server.ServerName); } - if (!ValidateIntegrityAndReport(server)) return; - if (ShouldBlockForConfig(server)) return; - - if (status == "RESTART") - { - Log($"[SYNIX] Starting restart sequence for {server.ServerName}...", Color.Cyan); - stopServer = true; - } - else if (status == "MAINTENANCE") - { - Log($"[🛠 MAINTENANCE] Scheduled restart sequence for {server.ServerName}.", Color.Cyan, true); - stopServer = true; - currentContext = StartContext.Scheduled; - } - else if (status == "WATCHDOG") + try { - server.Status = StatusManager.GetStatus(ServerState.Crashed); - string reason = !server.RunningProcess?.Responding ?? false ? "FREEZE" : "CRASH/CLOSE"; - Log($"[🛡️ WATCHDOG] {reason} detected on {server.ServerName}. Initializing recovery...", Color.Orange); + bool stopServer = false; + StartContext currentContext = StartContext.Manual; - _ = SendDiscordAlert(server, "🚨 CRASH DETECTED", - $"{server.ServerName} has terminated. Synix is attempting an automatic restart.", - Color.Red); + if (!PassResourceGuard(out string guardMsg)) + { + Log(guardMsg, System.Drawing.Color.Red, true); + MessageBox.Show(guardMsg, "System Resource Exhaustion", + System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Warning); + return; + } - stopServer = true; - currentContext = StartContext.CrashRecovery; - Core.Instance.UpdateGridStatus(); - } + if (!ValidateIntegrityAndReport(server)) return; + if (ShouldBlockForConfig(server)) return; - if (stopServer && server.Status != StatusManager.GetStatus(ServerState.Stopping)) - { - Log($"[SYNIX] Stoping the {server.ServerName} server.", Color.Cyan, true); + if (status == "RESTART") + { + Log($"[SYNIX] Starting restart sequence for {server.ServerName}...", Color.Cyan); + stopServer = true; + } + else if (status == "MAINTENANCE") + { + Log($"[🛠 MAINTENANCE] Scheduled restart sequence for {server.ServerName}.", Color.Cyan, true); + stopServer = true; + currentContext = StartContext.Scheduled; + } + else if (status == "WATCHDOG") + { + server.Status = StatusManager.GetStatus(ServerState.Crashed); + string reason = !server.RunningProcess?.Responding ?? false ? "FREEZE" : "CRASH/CLOSE"; + Log($"[🛡️ WATCHDOG] {reason} detected on {server.ServerName}. Initializing recovery...", Color.Orange); - await StopServerAndReport(server); - } + _ = SendDiscordAlert(server, "🚨 CRASH DETECTED", + $"{server.ServerName} has terminated. Synix is attempting an automatic restart.", + Color.Red); - await Task.Delay(1000); + stopServer = true; + currentContext = StartContext.CrashRecovery; + Core.Instance.UpdateGridStatus(); + } - if (server.Status == StatusManager.GetStatus(ServerState.Stopped)) - { - Log($"[SYNIX] Starting the {server.ServerName} server.", Color.Cyan, true); - if (!PassSpamLock(server, out string lockMsg, "Start")) { Log(lockMsg, System.Drawing.Color.Orange); return; } + if (stopServer && server.PID != null) + { + Log($"[SYNIX] Stoping the {server.ServerName} server.", Color.Cyan, true); + await StopServerAndReport(server); + } - await Servers.Start(server, (msg, Color) => MainGUI.Instance?.Invoke((Action)(() => Log(msg, Color))), currentContext); + if (server.Status == StatusManager.GetStatus(ServerState.Stopped)) + { + Log($"[SYNIX] Starting the {server.ServerName} server.", Color.Cyan, true); + if (!PassSpamLock(server, out string lockMsg, "Start")) { Log(lockMsg, System.Drawing.Color.Orange); return; } + + await Servers.Start(server, (msg, Color) => MainGUI.Instance?.Invoke((Action)(() => Log(msg, Color))), currentContext); + } + else + { + if (server.Status != StatusManager.GetStatus(ServerState.Starting)) + { + Log($"[🚨 CRITICAL] Restart failed: {server.ServerName} is still stuck!", Color.Red); + } + } + stopServer = false; } - else + finally { - if (server.Status != StatusManager.GetStatus(ServerState.Starting)) + lock (_activeSequences) { - Log($"[🚨 CRITICAL] Restart failed: {server.ServerName} is still stuck!", Color.Red); + _activeSequences.Remove(server.ServerName); } } - stopServer = false; } public void RunUniversalHealthCheck() diff --git a/SynixEngine/IconManager.cs b/SynixEngine/IconManager.cs new file mode 100644 index 0000000..45f34b5 --- /dev/null +++ b/SynixEngine/IconManager.cs @@ -0,0 +1,74 @@ +// ============================================================================ +// PROJECT: Synix Game Server Control Panel +// AUTHOR: Jason Turner (ubidzz) +// COPYRIGHT: © 2026 All Rights Reserved. +// +// LEGAL NOTICE: +// This source code is proprietary and confidential. +// 1. Permission is granted for PERSONAL, NON-COMMERCIAL use only. +// 2. You may modify this code for your own use, but you may NOT redistribute, +// rebrand, or sell this code or derivative works without written consent. +// 3. The "Synix" brand and logic remain the property of Jason Turner. +// ============================================================================ +using Synix_Control_Panel.FileFolderHandler; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; + +namespace Synix_Control_Panel.SynixEngine +{ + public partial class Core + { + private static Dictionary _iconPathCache = new Dictionary (); + private const string SynixRoot = @"C:\Synix\SynixData"; + + public static string GetLocalServerIcon(string Appid, string serverPath) + { + // 1. Check in-memory session cache first + if (_iconPathCache.TryGetValue(Appid, out string memoryPath)) + { + return memoryPath; + } + + // 2. Setup the output path in C:\Synix\GameIcons + string iconFolder = Path.Combine(SynixRoot, "GameIcons"); + FolderHandler.Create(iconFolder); + string localIconPath = Path.Combine(iconFolder, $"{Appid}.png"); + + // 3. If already extracted in a past session, return it + if (File.Exists(localIconPath)) + { + _iconPathCache[Appid] = localIconPath; + return localIconPath; + } + + // 4. Extract directly using the full path to the executable + if (File.Exists(serverPath)) + { + try + { + using (Icon extractedIcon = Icon.ExtractAssociatedIcon(serverPath)) + { + if (extractedIcon != null) + { + using (Bitmap bitmap = extractedIcon.ToBitmap()) + { + bitmap.Save(localIconPath, System.Drawing.Imaging.ImageFormat.Png); + _iconPathCache[Appid] = localIconPath; + return localIconPath; + } + } + } + } + catch + { + // Fall through if file is locked, in use, or lacks permissions + } + } + + // 5. Hard Fallback if file doesn't exist or extraction fails + return Path.Combine(SynixRoot, "GameIcons", "default_server.png"); + } + } +} \ No newline at end of file From 1e20e4ee98f4d4c34ae60cb16ff6671e924a7ebc Mon Sep 17 00:00:00 2001 From: ubidzz <6037701+ubidzz@users.noreply.github.com> Date: Wed, 27 May 2026 13:16:06 -0400 Subject: [PATCH 6/8] Fixed the server maintenance that triggers on what the user set and was missing to check days. --- MainGUI.cs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/MainGUI.cs b/MainGUI.cs index bdc4def..249e464 100644 --- a/MainGUI.cs +++ b/MainGUI.cs @@ -53,10 +53,6 @@ public MainGUI() InitializeComponent(); Instance = this; FileHandler.LoadServers(); - _ = Core.Instance; - - GridStyler.DarkTheme(dataGridView1); - GridStyler.ApplyRoundedCorners(dataGridView1, 10); UIStyleHelper.InitializeToggles(this); dataGridView1.DataSource = serverList; @@ -69,11 +65,9 @@ public MainGUI() iconCol.DataPropertyName = "DisplayIcon"; iconCol.ImageLayout = DataGridViewImageCellLayout.Zoom; iconCol.Width = 35; - iconCol.DefaultCellStyle.Padding = new Padding(4); dataGridView1.Columns.Insert(0, iconCol); - dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; dataGridView1.RowTemplate.Height = 35; foreach (DataGridViewRow row in dataGridView1.Rows) @@ -82,6 +76,8 @@ public MainGUI() } } + GridStyler.DarkTheme(dataGridView1); + GridStyler.ApplyRoundedCorners(dataGridView1, 10); typeof(DataGridView).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty, null, dataGridView1, new object[] { true }); GridStyler.ApplyTransparentTheme(dataGridView1); GridStyler.StyleCloseButton(btnClose); @@ -89,20 +85,18 @@ public MainGUI() GridStyler.StyleIconButton(btnDiscord, Properties.Resources.discord_icon, Color.FromArgb(88, 101, 242)); GridStyler.StyleIconButton(btnGithub, Properties.Resources.github_icon, Color.FromArgb(200, 200, 200)); - Instance = this; chkPrivacyMode.Text = "Privacy Mode"; chkPrivacyMode.Checked = Properties.Settings.Default.PrivacyMode; this.Region = System.Drawing.Region.FromHrgn(CreateRoundRectRgn(0, 0, this.Width, this.Height, 15, 15)); isPrivacyLoading = chkPrivacyMode.Checked; _ = LoadNetworkInfo(); - InitializeVersionCheckTimer(); + _ = Core.Instance; _ = VersionCheck(); + InitializeVersionCheckTimer(); } private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e) { - // If the grid throws a fit because the image is temporarily missing during an edit, - // this tells it to ignore the error and not draw the ugly Red X. e.ThrowException = false; } @@ -141,9 +135,14 @@ private void tmrResourceUpdates_Tick(object sender, EventArgs e) if (needsTimeCheck) { string currentExactTime = DateTime.Now.ToString("HH:mm:ss"); + int currentDayIndex = (int)DateTime.Now.DayOfWeek; + foreach (var server in serverList) { - if (server.IsScheduledRestartEnabled && currentExactTime == (server.RestartTime + ":00")) + if (server.IsScheduledRestartEnabled && + server.RestartDays != null && + server.RestartDays[currentDayIndex] && + currentExactTime == (server.RestartTime + ":00")) { _ = Core.Instance.ExecuteStartSequence(server, "MAINTENANCE"); } From 39013980431c55ed4eebc5ff7499fb01fbe8ad22 Mon Sep 17 00:00:00 2001 From: ubidzz <6037701+ubidzz@users.noreply.github.com> Date: Wed, 27 May 2026 14:08:03 -0400 Subject: [PATCH 7/8] disabling dataGridView1 AutoGenerateColumns so that it is not adding everything into it from the json file. --- MainGUI.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MainGUI.cs b/MainGUI.cs index 249e464..59655c8 100644 --- a/MainGUI.cs +++ b/MainGUI.cs @@ -54,9 +54,9 @@ public MainGUI() Instance = this; FileHandler.LoadServers(); UIStyleHelper.InitializeToggles(this); - + dataGridView1.AutoGenerateColumns = false; dataGridView1.DataSource = serverList; - dataGridView1.DataError += dataGridView1_DataError; + //dataGridView1.DataError += dataGridView1_DataError; if (!dataGridView1.Columns.Contains("IconCol")) { DataGridViewImageColumn iconCol = new DataGridViewImageColumn(); @@ -65,7 +65,7 @@ public MainGUI() iconCol.DataPropertyName = "DisplayIcon"; iconCol.ImageLayout = DataGridViewImageCellLayout.Zoom; iconCol.Width = 35; - iconCol.DefaultCellStyle.Padding = new Padding(4); + iconCol.DefaultCellStyle.Padding = new Padding(5); dataGridView1.Columns.Insert(0, iconCol); dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; From 62fca8d6932082bf14fa09ac872f7d02618156a4 Mon Sep 17 00:00:00 2001 From: ubidzz <6037701+ubidzz@users.noreply.github.com> Date: Wed, 27 May 2026 14:15:48 -0400 Subject: [PATCH 8/8] update --- MainGUI.Designer.cs | 4 ++-- MainGUI.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MainGUI.Designer.cs b/MainGUI.Designer.cs index 3e9075d..ca05429 100644 --- a/MainGUI.Designer.cs +++ b/MainGUI.Designer.cs @@ -469,7 +469,7 @@ private void InitializeComponent() DisplayIcon.HeaderText = ""; DisplayIcon.Name = "DisplayIcon"; DisplayIcon.ReadOnly = true; - DisplayIcon.Width = 15; + DisplayIcon.Width = 5; // // colGame // @@ -485,7 +485,7 @@ private void InitializeComponent() colName.HeaderText = "Server Name"; colName.Name = "colName"; colName.ReadOnly = true; - colName.Width = 255; + colName.Width = 265; // // colPort // diff --git a/MainGUI.cs b/MainGUI.cs index 59655c8..7ad30dc 100644 --- a/MainGUI.cs +++ b/MainGUI.cs @@ -65,7 +65,7 @@ public MainGUI() iconCol.DataPropertyName = "DisplayIcon"; iconCol.ImageLayout = DataGridViewImageCellLayout.Zoom; iconCol.Width = 35; - iconCol.DefaultCellStyle.Padding = new Padding(5); + iconCol.DefaultCellStyle.Padding = new Padding(6); dataGridView1.Columns.Insert(0, iconCol); dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;