From 30f44645f363a89bbde79e99e7f1348d46445600 Mon Sep 17 00:00:00 2001 From: hypevhs Date: Fri, 24 Apr 2026 23:10:31 -0500 Subject: [PATCH] feat!: rewrite literally everything --- Docs/VRCSDKSettings.webp | Bin 0 -> 15616 bytes Docs/VRCSdkSettings | Bin 88198 -> 0 bytes NOTES.md | 70 ++++ .../package.json | 4 +- .../Editor/Base.cs | 105 ------ .../Editor/CannyPopup.cs | 14 +- .../Editor/LaunchConfiguration.cs | 118 +++++++ .../Editor/LaunchConfiguration.cs.meta | 3 + ...=> LinuxVRChatSdkPatch.Base.Editor.asmdef} | 2 +- ...nuxVRChatSdkPatch.Base.Editor.asmdef.meta} | 2 +- .../Editor/LinuxVrcEditorPrefs.cs | 35 ++ ...se.cs.meta => LinuxVrcEditorPrefs.cs.meta} | 0 .../Editor/Locators.meta | 3 + .../Editor/Locators/OfficialCompatToolData.cs | 83 +++++ .../Locators/OfficialCompatToolData.cs.meta | 3 + .../Editor/Locators/ProtonLocator.cs | 303 ++++++++++++++++++ .../Editor/Locators/ProtonLocator.cs.meta | 3 + .../Editor/Locators/SteamLocator.cs | 100 ++++++ .../Editor/Locators/SteamLocator.cs.meta | 3 + .../Editor/Locators/VrcLocator.cs | 188 +++++++++++ .../Editor/Locators/VrcLocator.cs.meta | 3 + .../Editor/Locators/XdgBaseDirectory.cs | 30 ++ .../Editor/Locators/XdgBaseDirectory.cs.meta | 3 + .../Editor/Menus.cs | 50 +++ .../Editor/Menus.cs.meta | 3 + .../Editor/Patch.cs | 13 +- .../Editor/Patches.meta | 3 + .../Editor/Patches/PatchGetLocalLowPath.cs | 36 +++ .../Patches/PatchGetLocalLowPath.cs.meta | 3 + .../PatchLoadRegistryVRCInstallPath.cs | 36 +++ .../PatchLoadRegistryVRCInstallPath.cs.meta | 3 + .../Editor/Patches/PatchVRCSdkControlPanel.cs | 82 +++++ .../PatchVRCSdkControlPanel.cs.meta} | 0 .../Editor/UI.cs | 85 ----- .../package.json | 4 +- ... LinuxVRChatSdkPatch.Worlds.Editor.asmdef} | 2 +- ...xVRChatSdkPatch.Worlds.Editor.asmdef.meta} | 0 .../Editor/Patch.cs | 13 +- .../Editor/Patches.meta | 3 + .../Editor/Patches/PatchBuildAndTestWorld.cs | 102 ++++++ .../PatchBuildAndTestWorld.cs.meta} | 0 .../Editor/World.cs | 119 ------- .../package.json | 4 +- README.md | 58 ++-- source.json | 22 +- 45 files changed, 1355 insertions(+), 361 deletions(-) create mode 100644 Docs/VRCSDKSettings.webp delete mode 100644 Docs/VRCSdkSettings create mode 100644 NOTES.md delete mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Base.cs create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LaunchConfiguration.cs create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LaunchConfiguration.cs.meta rename Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/{LinuxVRChatSDKPatch-Base.Editor.asmdef => LinuxVRChatSdkPatch.Base.Editor.asmdef} (86%) rename Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/{LinuxVRChatSDKPatch-Base.Editor.asmdef.meta => LinuxVRChatSdkPatch.Base.Editor.asmdef.meta} (76%) create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVrcEditorPrefs.cs rename Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/{Base.cs.meta => LinuxVrcEditorPrefs.cs.meta} (100%) create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators.meta create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/OfficialCompatToolData.cs create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/OfficialCompatToolData.cs.meta create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/ProtonLocator.cs create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/ProtonLocator.cs.meta create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/SteamLocator.cs create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/SteamLocator.cs.meta create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/VrcLocator.cs create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/VrcLocator.cs.meta create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/XdgBaseDirectory.cs create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/XdgBaseDirectory.cs.meta create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Menus.cs create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Menus.cs.meta create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches.meta create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchGetLocalLowPath.cs create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchGetLocalLowPath.cs.meta create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchLoadRegistryVRCInstallPath.cs create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchLoadRegistryVRCInstallPath.cs.meta create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchVRCSdkControlPanel.cs rename Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/{UI.cs.meta => Patches/PatchVRCSdkControlPanel.cs.meta} (100%) delete mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/UI.cs rename Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/{LinuxVRChatSDKPatch-Worlds.Editor.asmdef => LinuxVRChatSdkPatch.Worlds.Editor.asmdef} (87%) rename Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/{LinuxVRChatSDKPatch-Worlds.Editor.asmdef.meta => LinuxVRChatSdkPatch.Worlds.Editor.asmdef.meta} (100%) create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patches.meta create mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patches/PatchBuildAndTestWorld.cs rename Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/{World.cs.meta => Patches/PatchBuildAndTestWorld.cs.meta} (100%) delete mode 100644 Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/World.cs diff --git a/Docs/VRCSDKSettings.webp b/Docs/VRCSDKSettings.webp new file mode 100644 index 0000000000000000000000000000000000000000..86b21d2760c015bd25194f12caa5264713550959 GIT binary patch literal 15616 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 aj#P`C!Nb0S8vve^Z~zS`;|u@*0000DwVN&g literal 0 HcmV?d00001 diff --git a/Docs/VRCSdkSettings b/Docs/VRCSdkSettings deleted file mode 100644 index 5f3e32067703eb4fa33104174d62207842eee8c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88198 zcmXt8V^pPW7tWe&*W}5zYqD+IHYVG)ZQHi3$u%`;GQa2j@%^Z^x~p@}y7#m9r5&Ll zCyoGz3kL!Mf*>g&sssW81_c5Fnh662oV=mn0|y|m4icKqARvgt|9yd`&>-T0fDnU7 ziVCTCWMB4JChLm3GJ1u2+t2MNRk6v}*vYn543R;lc=?WtHrnHJ$JLp z#rC(S>d&^bHZ!)v_s!E><)PY2UGGZWu5AJyH%`tGxl5gsBkn%V^G2jyyz`iQF|Qo5 zwHGc5ZM)5$z|RHQ&yn!Xgn;l1f}FK<8DUOC<)c&i?<0N0Fb^y#HfKsKAIoJ7tp+a) z2CqLNYHpA1+|(3wLW3>6FWVa|w9R21FFgtlOF9nm9lnOS4Q;4O^>yAIJlhB7#w&vi zMpN^SKCBa*5Ze`J$MgK%&KDDe0u>oF{d6nhuI3QS>&DRgC|b-=Xr7pdeqA`{j60J& zVm1bII&}-<>)f2=+|)^U1YJa+)`G%)F3NsQxgbi~>ug(9dMJb&5ZFSx7}>4^(0#SitPQjZD(Vf#TvKwZ?(T(!K3;8x#noMBVKT@qzOU| z4U5%Um?RQsbJ+_wD{8qosi6fGZODNkWa36MW}vJ3HF~CD;gIu;;w}+)4tFV;Bo!YE zYrxQ=$jn`0<*uto9)w|0PjDfEnInp-9CS2tae6~3{hWp<9y_SKEy}xUl+jxEm*;oE z{*?Gt43UnBMb?m*_sD59yfKfFlKw;0q~FTSQ1nHt4Te8TM2e?5K*mkJPb?e`E2+-q zIg}+%CN|t=bGpDq4Ia`|L08!Y9g3<*OHhotiqs`L!3@BP`m0RA7NoN7h;1J?QJ%pB zp)Xp?(4b%`x;l^~*HcNvG3rZ#7$s4|R5_|#04G4mkU4PBU&)6x!&gJ8V}fBK3>OJufF)ek@Hob!sIYnEODwY|K`WL5_oBVA{|c zz+4V4rPW%nsP?zp7OQwwichQrM@(mf(Si?#N5YhGs2M^rW|6P!eN#$tD8AGyqaBVx z{?S})l5cwu8!*;h%G0yXiWB<?-^-8=qjWi(K>QM!#!M?$pZg{5BUJ(N=AS7IN7im%eVF*`c9&U+ zjAsZ`k}K(T^!SMrqNq}e>2va|_8<>NTO!J;pBIr{L~;~yJD4UAMfG0o#v4{u*2kq= z#gqxnnmpe^-&0>^JepjW^`TQk-jnEP-3qkPNyh@qc=#4N;x1Xvz!M8T?9zdv1v)q( z+`O?8mlPgDyeA3*yt;85 zGOQwzx&YNJlqOGEJ*3*F{}W^A&1IVqKwa#_DMQ#+?yySi0Wtm)!jgE3g<1XwF^Mt> zjsAs0cZwnO+H^9V;B+yJaq>x$k^=221bbZ8AfuaLLA;`YZ*Ge?i6m-2ZK4a9j!EKZ zLkXnnw}iaqk|ZguHUoI!tNs`ZDpSlUH2jbx#CVpn`S21iD~f4aIDLzxQFZgl%K%nG zbQn6+ULU~_GE%c16}B@KNVqhwzGY;bc1*KDmt+I+0VFP=Uk4@5GUb7ZkK{tbRq1rJ zF=K{ylKt}&L|#)8WJfg+;^Z|=OoYAFWb*Z>meY7Rc;-%d_#g{x^tarheij#NiP#V1 zR!t9z2W$ENc)Og10^P!^*=9muVTLs6|D;7o=4b2&l~`n@2K9+@4Ji{G&zdVxD$K_%an2g zL2%ejcZy~FGDsfum|T;v+7S$rdk~5Y17S>yi#N3`!39l!>>!{B_L@1G=36&%Ckc6Nm3EwYX zMwV=r4NRH2GtqdJiYgwDqG@={MIXK^|49<|@(|fvQU3j4HREnUKzVY#Z)T^kKHj&wWQpwBjFJgs zy3mWCfK9B?lY@P36?xqfLB&oh&+my z6+CWc!)S-T9Q7AmE~bqJu~Lc|f6K4-K+LcqSBn^P+>^O2!-^&*n5MN(8FD!%J(
vK%-z=ir` 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₀NizfXs<5yWW5dA?1vy3} zMn=SyuB_*Ts#75W&fM8dujt3}YSpqwJl_uc%)+Dve3o6b|mswUVPL)+vClikG? z-@G0OQK6u5DidULg<2%Mfvy21&K4x(rz^ycU4dF!dCB7+lL<1e?m7<>9LSW79@vty>hZDn(tabx|p63+gw3*vLvX z!P;or6m+4`K@$ac8`Z79(9fyt2Z<8|9Xeqfk)*&(cDCrvy~ zC96)L?2*(!-f3d%I5t)Q65GC9OS8kxqAu@(E|AcuJ}|ub!idvz_0OS)?#PP4v8_h9 z-O6!1(JB)fs5{M24#kO>@6y$9;wIqFzG}r$OjpT`+Z(qh3(t zHE)Ra>6~JHYjiWqsg=6r5Bt`~eUu81Q-1`L8D5g*f`KilyGvH_f&R75RH}TOUqesF zy31?7&e?O}oqzb3N+ z1Q2HHC5i0#snW7U`RC}T{=SZFO(^H?-Kx`1wXW%Y=ic576AmB(6ICVb?8M4LB(+Wn zda|$I<|p~f*ltHvAvX#-z{`F*TA_9~&a2XAJftWX#x&0w&55vxj7CDF4YL(qUK|tL z&1l%N?*+Mqq~T)ZxcsEpNDX-*15f)xGaS@#bQPn6oecH{x<_Pj_cH8+YH1#aB~@_R zT`Y#_3wDlc7hx*WHQTMBc4dw?^cH6MfeE0R>FA3c$m)bF7d9Jcu`?&_i7r8JV9c$g z!CQZ7XYRpL2Jo!n#^BfC@d<@46UjmMVh)?T2VK-wuds<7+fk_TW&e&>nv$s)=7Q%1 zLQmzTm;ERZAl+6_2-@{vOwQ)f4~}-0)n9fA>Qw&vLM+9lM6wTt-VhK;x7t2X=SGG! zIHtyhiAiBya`0h;fvew_DDhcZBlicm zxkbIu5q2=iSxmagXB@4&kUc*0=av_;t+D&uX%6;QXAj7}O!b+Tnp_{}v>Z3i$pv^i zA-*#GLl{Abch}RfkPD&~12|>u*Y?&aK;XNuQrV@+X~^&$hTsP~<>I#O4Uj<$ceCFV z^v5SOf=B_<9QT^rVP+fN5a^6zQ!+L9kwyNge|vjjoMd+93}Q{>tE?Y|Y|u6Hpy1p| zXNl8@Aw2h7q0_Z%$eXf!>j{2w?r{7d(Z+Ye?I(=jS5T3m!9WR(=TnV>A+C#MuqFWG zOfY)WiG#IC4+VJe93A$B!yvl(t{!U6M@)K|C<0BmzBS{de8mtX#9%Y$;X5}%(z%Fx zj;g+y{1e=*U_;#XIchb_M{IB%!ZDyD1oLvQK>7z_U=)}#(?NX)yz8yMt0p(u0yPHrH?JqO!KRDvH}|g9 z7}W}zcbKza^USu(`w+bvd;ernFAO@gawD~JQ4lyOf{Jcif|K1>N%_bTtwWED#T5P5 zy|zuk>mc2`2SsSM*X^?8c`~#YaJL|-?K_6rUr7lCIKDN=I2r<~iQ2C?M){~ibq-QH z+Q`=P&*Iwk{R-`WSF3|Ms;IZY>}*kQ2`hWFrkpOuXkuAlU;c%i3Jp2k8?KGWOlNpx z($dPL{e)2$T=yoGx8xhi#fEj-1vHn)XFn=sdG9tb_`XlbLmhTm0JfxA>qevaiPjsq z#mIvJOL=&6W*9iYwfn(5(BDdN#iAug(k?J&bw8LcvBqklQFQiSOn0k*^3-~qSx?M( zQZ-XpHT^xz=dmqOf^>pC4L~tg^wUb%*~BjZe+5e(iyAhb#A?zF0alId3z0bt*|)Cb zhP)O}|9~*@EoAJG3O0t}7vXn@^FQ1*NL&65Y&Posu4A-`$LXFd2XAULBX0zr%NgiZ6Xr9yo_E2m7NX%?VTw#irNEr>P-cVl0tpW*P{~ADsOg7U5eIp_Pl%a93R% ztagxXkM{q{5cQ_4`E_J8w7we@a^rtrX@P<@n(e;#p<>t9y~H%pCdoC zXZLPc1`^qBNV$RT#Im4w8n_Bt<#UC^cwyWDXr7TsM-;z!XKGIiBr+>u|P8F_h?BgY2mc5nKx0cgfi z6svWxZu8gdCIiLw@4r?Z8zpZuWOI3EhM!yc@wtHLZ}j#ntR2xKz?u*ipx)jqup!Eg z;=9!o$TPWTmHxs@t=zNc&!d^s$~_2*o*IAotpIfA+wG}4q(*Hpi315XoYz01B1Ar> z6=LwWuU?T=2ffys)G^qlVEk=y1N$fctxt?hVMgnZ2Nc2b(tVHYY>4&8it%!%fJF)gJdd$f8un5At(Tt^B<`JWz!# z<12IdxI!vC{pZtT$Je0cQvE0e7}wtCrC1H~?p83mS=N*ccM*dz*}q($le)Xy$D%tF zltWt02m?NVImAnyA%zwZCxv1JkZz&^13j^ALCE1~E**W&D)9szI7t8aAue<#&ETKv zYKG8sV>e;L82P#Os)?M6R9zniR@P4m)|Gu^0RQZKOUxmhg-??}0^-miggBduib~ND zSOqA3vswtrc`Ph zCplZ_ZuGf$NTqM^zsY$6gf9kES-2Q>T}A!PE8-vF#B z8Uc%-Ts#y$xdKi#9fiq7TyM{}uo~i|v(3DHw!VeBi0l~{=O~Ytiv=`^0m~bUu+a1H z2nm+J3ozbcX$WeUJbHVp3E7@L7dd94_D zbjtb6Nq*)W`R}^p=fX898%T;23qD7sTlJkb0U{ut+SVsrd<`Hd<_J1M_^X`gydtk6 zNgVS%{d@d4T+Af^Fx`tlt62>tmh>8k7`QsyLl~eapj6U&WgFw--g{AEehtQ7runFY zTX?&!m4XzbD%eNTB-1w%w>)oB+Qix=^)8_rr`1 zI1=Pw^hA(A9yunbHtT{)pKgj`0bZmEF+3h{{g?e5cQz>(+enL+C|CAllBgrnt<~pQ zn9OU{bh!lf^2!=|%rOr!29=uLo7_FS^dR!7%jjhfUHad@?UUHeRLOEfPF|HSurxI{ zspaOaq-;>J1(MS}RRBH7H_G7~Q)vAgon7i_Rac(t%J2VlBR-Jq z1KW|^$B@hjx$Gx}`|GF1@?S>N9Xgfk;_>~kk{bH$p=;3tKcXby3P}%s`G4*Gt0!Dld2$CZ`h~lE+kTLhW zlK-)sDqYZ`?US?Lb?%Hy9%+%5xz0F^w-Z{6m7)w)&I|e7_|c?g^K7N=c;2l1;VcWq zkk}mO&_Z^Kt94fSJM-Duga=JJ`$<9$YId|%t#eBRI$cPjk9Sg!r!O{g4n+Wz%_-iG z2TbMtOkRa#D~3$avHKggRMr(Asdr%i&6Obf>Nd3>qal5aplmG7{>K|8%a5eaQ5@&6 zbb2_#o0GbeFhnwM)u&(IlN|r0ZitD&WB5#cV8gx9Pnxn+R!^L-{H9zx+b8OGy6084 zc!&v!VeB+tjM>Lth4t@(7hTTLOe(TJnoFD;S%1W(8<)-D(75)k)w-7V8FPA5{ziZ5 z&wrdpIvmL*6WWQB#AhIq13CS8gG#-I+541Sg2oJsssE2R>?vA2+?XdaQ8kXuL!)PN zFRd=k({ayKQuPrU${?{vXZg9wi&=zl@NN zR5C-9y>N6`B z#1(uVWWJotcrCjA=jFt6lH;#Gp1i{P(M4R}i=-lz^30`StE`)Mj@6_@Z%s*=5BJxs z`Ho4N);CSh`1m{WPu?7%Z6+V^GIUmSZs=T{&L_`2H#L&pd8|KV`m#%Z)|->Rsax`@ zz5k{7-QH{I72ElI;`y-4f>OZwbsNUc6_Q%bg$%!SZ=W^agDXL=72Q*nwKaIwe>8QZ zxQo0yv_~f5BuAV3u)ei*`6DJRs{j-7O*xD86HO~>8+U@s9GQH!&J5o$Oxmq)?X{?S zM7O-O($URQD&z4@zdeot1ANC^?L}_>=owyNp0cs_I=n*}Q`U$hi_cn#e?$3PUan2e zKrw715lEn?B<$pGiU7Vtt}kwRAD=h16_u8juDmWmQqce~Va+LN#Hz8yO^&;zGMb7CHvjbWP$`@! z>aD5tS)=PD_tdktw)P^qEdVP}B2CMfoj=C2YyeZ2v9U2kxdPhlFcMEj{Y4RM(%c>8kQ^p05XLY=pY>>$*Gd|&sB(HAjc!AMK5pT2!vCENJg z_3P9{l7f?eA$`m1nZH#9%%7V9tQ7RD7@eVT{_F@kV&VC+b$?~k{6m#)(kZobo~rl6 z?h!(7o0>zHh_mwz(Fq%n<|%RD)nEGh9-jYzJQ-uL5gv7L9uyS5^!Ee(Xvt{TnzSDo zrIfrI2Y?t$&B%XzAMD*S;f4|QoVEn}kbZsBq~b+t zI%lQg^H_ejs$j>`sh80JIwIPY`2`DBi?RkhFk1!#!g!Lmp+W~jY(E=9$c^aHgE`M5-XLbLq_~yw3!uHA4B39 zw~Vbx4&aZRoYj>02jb80_Ugm>o84XzSQ1Oyix^rtv+imZ#Q=%x6Ae?0zP)JUiDm)!lxpM%O2_!4C#9QH8! zF_KIL$8tN1>WwG}o=8(h*CN1NXa;frJOCLW!bIkPIXIV1w!61ev$>n`7qx6qCxIPq zH}aT*OHJLG+o2kMr|v6vDAJ4KO^9+CbJVAi8W?)m$!O-9+-V+jkvnqqicZSr|8fD2 zq7Hk{Pa!g{<(C)5(c3`0&dyIqTV|Iqa@!7s+;$T7U;26zFDk|c``g*7CCkUA1aHgy zQ-BrEmpS2;;it5;2J2-y3S!_jkv_5t{2Bws|3=8hVlLdGcSkh^V5jm#wotJXX$go> z0Kc`SMvCq)ls3?Oqea<;*d*TLw;(!VVY6bius_yI*1E$W|JsM%*cp@Bntv5zeZ z-cw~|9I;x6nSfoNnVA_}t?**5V_I*&ZbCez_c(E2rW&X?zQ7u?0!ZY~pn2wFg7gY1 zz_D_tG7QWC$ry<2XG4anVWAo1=MxhX8UhFsI+@S*`^o-?N39p6eq8dD75YhmJ zhrK&77#AA~=nonK7*GNau`CkYC~F|VW>jpqC>_QM;>nm)VBoE!gek<+jwuh^1~d{W zyct;M2kI94Spay$U5|$cca|o!o)|C{i)m#tr0uY20^JbPQ9C zgfS^tE!U3V%}K42j!Q0Q9?gFoOlxdBqo8m~3O@5`)U?au=~%@Tdh8tE`-5n1>mw zWzq1LFNg>Ow+Eyz+me{LI1KeYm5Aq-zDu_cb)Vz*SPjlx+s5D+$N|9e{RdA6LSHRj zfNQ+6vO*vb;8MvReSlpb0)IQ1QdaO3FhYa7Pv+#Y2Mlb7G57`Cd+XMh|518v&Jh~W zzvqAc0iiM$vyKqRk#J?*z4JH5-FUQIEfgM@WnhqbJ@X;l1ZPwWJ|o60RPCU=JffP> zRYL-(jF`8T6>petf%9j08D`j4R#24yf#Ql4VUNZ(ienb#V(H^U|E7r2>p-G>}lC)2vPJ~|410j7-v7oaJENv())=s7{fhHVAu5QyU$9!3@hqQKz+ZdP#P?S)%~ z2p$=@UdfoUqv%oUzbnK`nwli6k^^cQ1~+Up8*p=ke;@P#xSh{-L1%GF9K(>Zfv^4j z^J^P${Q}9O|Jq+5e?Tj1)+eN{zP^915ELD7*m2b`-pn?RmjE3CvR#1$6f{J6ChYLR zxy$Wlif)1_1TU{uh1=tvslhilXk#=)lHiGe?F+j8ng3!4L0(8|9ejHe1LYANk~*tw zV=!P~*?~Q8F>4Ds767SFiNCI@BB~y5j6~6-YZRIx8w6f7*eKJTt8I9+@x7d>A7MwI z+b2N@xaw0a3$B2<#4k8-1lK{JJc+_BwGF8Um_R$e1`85*ij4+FfIM071BWYb0|5`K zmnZN;UnRyS5Wb*(#b7dzCP4@4ctGWt>4+pjVj>1#1A-pe(;prR+xdsC#HkR>iC0$@ zEE3@i@&Zhi*J!;B^RQ$g_&$=Z2p>iQcm!ZQO?k|5oif7jCFr1%0#t&HaRS_@OR2=N zZ%9#PgJENR%V!yzJwr8igk3bDO}6rXQrPb*O4G%w8%~mq@6ZcUw5$!t4zD}&s380N zMclX&$rg*4Tl`I9shBl4+zjUV$fS3t6 z!}6w9e-%|}s-%L>CGrOj)(-o_wWu6#$?P0CLF1`0AbZ4NkTtBVK)R8sFHu9FE?@KM zU^6Xb?lhD;RS05CJ_=NV(Fdy{MT2EWZd%34&d@1#Vd#HJH zzeLCpdm2M+sd$pJ%QB}|IWA|^NaW2DtiSiv9gPg7+WNAz=KVH;_|dN0P+y)&^kbwv z`fE|y`&)-k2Q5eT$-L-NgsA|qrc^*#1|M>ruRKaZ8QsRfdSqBFAwN5@7@a3Oi z{CB~Eyn611kBL`kNYy`n^|jIwFHOBJ_;RpRk!744JGAxtZzfBhr#o}8r7fB^zD?EX z)fGePaaE6bqt(RJ2d78elYM`r2e)RBMA`n{nX`+Y#pt!MM9rcyN7y-*s+47ti>o}cZ*wG$#d$t97-)MOF8_iT|fAe6s9UTE@bs#L$4_1tBjL#?$CZo$DF>mBd&c zoZLKgwbHuGqEfyygQb4OTWNRapWe&tVi`wlRev=piF(;|Xn!I3etxi2ZTa#zDU-<4 zJdgO`lc#hjVkJ!Ci z{KE2zA0GV#8$KrKGy|Z{YI>{x_z>onx>d+<5J5Nfs^nZDk_S)fP1Z${gD30x7Nc8i zW@2php^n}A$-ni3&Sh4BSPdZWoOFMgk9cvi9ARaoY+A22Y!KMJXAg+CIH&9RU^qoI z|Ih7)W$!7m0D6Y^BhCbg`gU&oFAlPJ?{e$DGQp(>d~23+uisrhvRi%7sSF8fsA0Z+ z6Zs|o_q6!kuZCamQhI+LQ4~s)b_x7Hm0kHG)a%vcbGfldIFF#HGo}VvYwF=-Yxp?sRz*uI-=3|^4x*5y&I^XS! zFh*&ZOpNSaZJCvA;)yWlUu(psb^_=vvR6?gQVV4r;-|d4kdhAXmYGb%2Yn2a-4i%z znm(QX#+SF>>bz@Vx0s|P0DUR+Vjkhf`Ge8^m&}IzRs59j#x$5WrwuhFFqfg(ev2^0 zO!b5{!#1?LKw~-(<{GNswodilpeAz*?r^BY@eUPqL^Madiu7|ULC_GGY|C_AO-*y_ z#~tGXnp7toKWIm>$P+;wBm`p=bPhms2A+^=$4rk!g0;J6JOENmq>^2Uma;Kt>rk>I zaEi+tXC#vyq29rwL8BpM31kH*A@D08`ik|YnasO^|HIG#Cxw^BPyMO0FQBBHp$*hM zm*m%|gT#k^`Sn6^j(ftAxWiP#DPTi#>yA#BK?PcbX+QQu+|4jYh$_(zAGaG*m))rl zEF&)Kc75~nG9=5k3xRk0>^v`QYt!N;L7flG zZVxVN{b_WDa0>Q=1o$O zZFPTjdAS!oMHX~$PI`Jp@UQ!;`8>FMF=i5UXJlllL*9|@RnY$_3V>ZClXY_1j;V_j zsoEjg2yjkeHv~BNSu}TzjX`0E;SSOQGqn+&#G!(rwMSfC&j(gGV_?l_K{nXk8xXu= zI|A`w$n-3#7;`tCKBPFGJoy0JJ(3beMj%k8hZ>D%AvfjLS2tNqbEc+{{;(HMW9Y}0 zs&r!&iW;$n#bAl9>%}g*O&*jHNDkBZ%Lg zYKIj-MOhj7HSzf*Dg;Lji|LIXmKMg>%#GN`YL1ol!0M?eK$71Ere<~la(|ROE#I6SFJ4O6Iu!5irxebcHRY?&- zviI`(%gpE}k)tiCSM1N~h|eRK36{oS_yD_qMVBAL#g0qTp8B|VM&@y(W z*%s&~;!Obn$HaN!OHi1%H-{!9Y}g-mz5RBgR0*X(^8CMpp;5NVS98JNIQwxt0CFx+ zcK@skyD?~Hk5a^VH+tLO#9)$%@fDOY+z0m@;Yt6bsHTU}*;~un)^)Y_3es&65y;f= zUhI7UnV+vJYP#d*M$FEZMwyGup{Res%2wwTeFg94p>lNX~JsgoU9W<}*U=9_`I3%qLfcu-&eN zyd2-rL0>0WeG{NdE=EQ^7bhpmkO5#KSf0QPha6O@aINrp!m#q}8PoyhGVKiw4PZ>w zh~GN|@LR{#sT&wzPbX)ioe@NtyL;`4QYZz$$-{i{&X`XRjqR2|iBVKGf>O1_VKwWq zhm%k@x}1irD&1z^V`u0PTRc0a{=m@!+7G+;wrz3VKJ<7BW+`kgkZl(+e`AjoLpBuM z7q^U*Xae(N=ZeVKepJffwp|5^p;||Ulx$p!CmdKNkvRAl2VPyo`VB4|WiTFh{`y@A z(@M927&*%p<9g3(M=18qKYu|%E-pSt?vJha93N9X+yqeRqBO18i7A3%KC00}R6?RM z^zSo^y!5Orcx&|A0GlBD6Dm(av)wL0t!N{q>uIz4r>f6QW!DcwmjIy!jcaEC{PGnQ z$lijDqWf@o@5C=CaJwh|^-hIp16A0%6?-+TB6w4wrhD@7AwF?04fv40eLDLhfe~Xk zJ{>fQPfcMf$04UiOwJJ`?S9d_UQ7%J*603yNePLpoE&Ixd8)AUqv5V?G$S(;)FUfk&ExCMtj zTnAy*5IC$rXMqF9>{bHA>Im!(kNjUnn*~Ha>goFH+j?J|+iqcj=sBIlYt195sy9na zNMK=#fQl8QfMNg4%nYpd^|m}o_0qy%;epQtNY(p+5FnGukhouKgdQ!L1h*-qh>%*6 z(LG?wq!Jw4&NyO*YHB=~Z_}aCCsFBzcZ7C)FcZ8(vGi1QEBIw_ve1~&HwWe$ad#nx zhvoPXT!WW>^5mxVNq>S3+AbU&&@MgLIys1EoDmq)|He1NrK)8eLRp02UkMbi zC&^??7W{4lGRb`yidMDZ(ETA0zGo88yj8wG^0zZvAxHwbowd+f&&_K~eT!iY%QxC6 z#OqsHTO)TtQD8)#b`1?R$Y^;#5omc38>;l3GZmllbp7bBR^_{#%OuOUBM|*209CrX zyQg|zq@s8<;sTKVY*%;pHN1~-@kwm;i^Kz`$eHraTkAzhiR@Eloc5We_m<#Pg0`u; zztBz*XM;=Ou7VEu6+lAa67D)7cq|r4L6@s`8C>~&)dI}R$m{~HQUli)Ok0A2^`!xOlr3)T7*Z^S1d9*j< z=1s&+TG{~)L%S7TL5pWkUmtw^z}`GdIJvno>Ej&hs}7e8`hyHOh;Fyxkq0#Ep>7bi zG!JA7@d58)Km$BwPex4J(!5_m2L@1tZ{6j3PJpCNjhLGP(<3CU+og5pFuMUue9$%4 zIr1wQ-lXCBq_Stvpip`eF1r{Ei=c!A*eY&G!2aK0C>?4o*Sc=o^+z% z1y_id1`;VxpT@fd1rknkK!_zDy^Ecq_Tbx%+GALmPW$-aolP9JlYL!@87#qKc7A3U z{~l8M>VDNsZY&-!(0F@89zOmn7=II2421a*UVrq46T?c5FNGhP$<79PJRF3BbVcs1 zns{?a&}HH`aj(}e5B+;Kubn6+R_iB{we+QtKl3=AGbr!9G`e7n14=5E*eJkWdLUtXS*IZ}KHgXu;w>zI ztp@PMt@>>??R$*s0~FzOPw?)!{ssCpQRI$Nd2C&Q4z5q?$K&yd>m_0w@c2C~E~a*j z1GC2mf|Ok3X!;UQAcmDQH64>Cct8sa#G85x;+wSgB<9z$9^BqIEkAnWa~*qwTkig# zAHN=>NKR|}zLlDyb4QBH?G&R(RFt$Z+CicEU{Cfa=!GXAmARVrvK|9&rq>U#QQ`K% zG7HpJm~vyT6{CQwT6}ywNXb>-M$Dy45Pf5~i6+IKzmnNV3eJ_f6Xk_u*q0ZJVO_O5 z44rC53+v=T!yN8t63{D(CH_sZ(`PRYayAMLEQd`W6NKVM6pR+w&OY@l0rt)EZ_W3+ znAhH8Av#pp-5xSM8)67Ml|3*dK%)>3|m(7VA-xF>V;@7*F^>EBzgXDXF zSXNzK{p=aeLUg&Rq}GC2v@$8X@EZ#lJZonz!GotG{)Qi<9c=rJ9=9Ei=R06-1Tctu zdv$7|SnWgYX30l1nw=#1ASS8|Y;C6}#G+gct$n!g@}{N}=Y$n*`-hOf&kXGzC6L&x;701pDZ$ za=x+;`v7vwG>n>xmLe2= z!SDl?ME)8UBTPWgpI@N>5r5m3n~*k&v)S$;4#lymTw%WV1Q+6EGjevDhdM-^^YcVo zaTt56#yXL5bjKR0e9-o;` z*UaBCu5hlZt_~k*5){BJO+8A}g7*o250rqp6gw&JRT6yA#m>3*Y1W5mHwTt~tJ%Z= z8QG_m;}>=<{>JR+rEz6p%}rSfL3wS1X?)A>a`y`jA%>8N!ONQMx)!C0n5d!07af4J zel>-5RmFwRIup17;X2azklTc+$KyUb?8VGSusRw}X2zbeX0o`3=U@ z^tMqLmh1fl^u$v1sAY=zXL2RhvgLy>*H0XXf}b9)_cImZ@s?lojawA9Ya}xcMN~Bu z8bwOK@=!;CINEgMC4w3T55MLf4O)MEd`w%9QG&Eqy=iYk_q5oA4TK<~f!! z7)diT=N3Z#{OFbSVKliM?|^6YBTZo!R?;X|-xKPkqB~jZfuP`i4-ZF}@sG(#I5j|_ z85nTV?pk1s*edU^mXy^pyM(;)414-aX4 zLPBpqVGm zagD)0YqfzGh2M5F%rk2eR-gi$Owkxb*A_6Ho=K#X6GN%if~^mEY$%Py*9eluv5vFl z_7dq3JfTDm=xs6HH?gVdQUy$eL1qE`qga})jTWxuTg>FFiH%D4{m&CA$$#n7wxJ=N zA%!v8^g?7wMDxfA4bkw$jvVY$=#qkMG3tlkd|A7fuQ-JGC_e#00{xrKo>fM+W>lDo ztHnC@F=X=Cx-i7iX<#AZ+2n}*4VyuBKiUg30rH$y)4R8cbBX_Hs5V#3jAWeF-_>`G z=eMLjnPJNP=2?~0xX}f=b1&Z^bbc^~78UU2{^xxxBd0GQjKQ6;fEiCV+?Rt6a=!7H zi!Qb!1ccNKAQWNZAHDB5f9&M>e$*}&{wV}ey_2M;{GjWeAX>14ASJChOu~ z2dP~`J5)Gp9PGfZTVWB1Q?0_Ffb1wo+O(JeEsl-Vw_yOx`Cx4fU_G4hNG^?OMwE92 z3wTzxQ9T5#E`!pmnjcz+1oY6SZx`4F*;1We^n!QiDaL_%wa$<{J-DLfRnKV)N9GGoSN~0C#oHAa7=&wf zn3+I4io_`*2UaAkn;;_{QQruk-aSV|^~1<9tb>&)t`24#ESE1(OAP-D>qQW?d~Du= z$9sTwAUmeFH~P3lt2yqTduYW*hpB`aa6o}=b$XKO-CnJ z{77e6(b6&Xbfe{&q~ycVKc0b+`sjVD46=qaU<`QWn5Mu&QY?Xr*{I#s&aDK$35W+a z+m7P|5Ks8z(1?(qpYNp+jPk$#dbHY(xqy0Co4i`Uv+>TLkpHi5d#d}ni|^Vgx~_ym z2;n9owthVX1DDKXL_|aYQ--eJKF=PUffVJaVV$@-ltkfB8=9eq?kePjFzH76P?ASn zp<4wT3{Fz4d3FgzmX~a#UpC2}&iCmi!XC2_vOF-r(9`tC!2iik)2$!8G#($W=F$~* zX1r;K?fj>v{O)2WvnDU%57(G4I**Y#_UO@|fcd#6Z{2;Z(=s!czGj^E{SkC8HwYDI zkSw5NZ{6O*J#GOBW-<^U2v^Uu5b;i78}&VxC)d>}6oO9^@(P5rKL9HF4{$wcE3h)R z;>?VUi$%4VYjR>b)U-lz;8ewCpaWh5HGLsf{Yzi9aRMuxo0mtiw8FI=I#R}^2MW6f zLkX!FARW1E*>SUBN<$%i^U>& z(bwk?lv!w?TIe~0Zp3jPqfKbV>W+>1DwGl!c0Zi!gV6$e7aBzYi9c=kDZ_x6>2*k@ zn|^RPD&Y@9OorAGr}k?Bor*$nz(mGUs8ngi`d3^CC2~#Le8Z|p7k~`BvWiU@Ljx|D z$+ymHx0NJuDbChqXGK9(_Dw6A@YjRV#_^Zd_% zW38wBvXxI3PoFHM0d5pk{1|{{Ume<@?>)y`3zyut=$ZJvtJH6M&~@T=Nc|3BR%vfL zA|dk%EMZ1V)3rFnfkfeq#Hxno10+be6duzMr5>nvi_@t6;DTYY!uhOT7CmQJf6TS( zBIj2m@mE7^pu^KPi|O?l#OLUEA5f2JXWZhX3MU2KRg6wR;44^)Fo6A)vAI}%Id<0 zz>tvmoj^~99OQ1slhh=Ag|L+&2k+6vAhr%MD>vwH{G8?ZaK`wSSUC87PnwrYZEjE5 zsc9v5)gn*&A+IP=Lr9aBYkul=NNqg6=)ez7*~szX1thl>9jB?sh_)|Gzp=RG&-2C) z=ONAB0l_Ko=K|On8-te{aTW~SnF|ehyaIgk-s(Q?x>>emJv+BGxK2dP z#&i~3)MW2{F|O%m-5ydd2mV^;**gBq-0u~=Wfw9tZ|<4y6pgO6Vij}<#IuA`72dLC zgpC6iwdUrK;Fv`XHA5b6V-|;kSai7~h z!a4yJkh%sM8Ynsi9zf)tBzwH6;U8QQ;JZ})?zfT+Q8}dGbnCiq`8z_M$S6Pu17ZZk zNyLwl{@s59*Rd2(eGLovX8-%#-1dsEVNBsai;Yhi*=0@HGzB$0pU&7@uDN-J*0`PP zUg!*K)Ss7a?A)Z0b(4&a-iL29J`ddZ@X&|Ms)N5qV@4<9en>rxvl7y>OB>id_tRt& znHo+XXc_;^Y$QiCg#VHz<`VWD9(6tUhLS$I@XCwx=~4b9BTWX1nng;{iCgR`E?zaP zqXlX-lYXz?r9V1(#WPcAJ0j!z19t!EY}{3vEH0ZF=#}N`wHUcFXK-6IdQ;^6iHy^s z(gt_Msnc6@7S^7Wjv;lTgjs_vYd`PRZf&8?9;e#`ruSu6{))#$ zZOF0ek=mkqb;F#w@c6{onMk)RQEq=V$B+BPe&lYyH@%=6#TMf{Z(2^+)ho|>*=UpT zmE;R1KcfP(y4*>&3&XjEYy~P6uk7?km0PQR|E3no49-@OSoiJg{}R-vWShV8up+pX zklpoV_MkRVc_RP){C|Vw$D(cHHrQDDc|KVwzGJPZ&DlLX&}2~BpIvq^`=n>~))BMt z%dd|86%lR$FpSmb~rTI2bZ*!8NosR7AvOWn-`s(V8s5cu0R~&_#1I zhcJ`=+yFaU@+MK1xX|HUY~N$2_13f>ZQu(N!p~J2Kigma#zifAbM2eWd2O% zDX$w3c=zaTbM)+FW-5gysn#ES`mMl75Dw$6i9j=Jg9rBU8(yBhaAAkNmTGT++}h3l z=f_^e{t^DcsgNLe$p5W`Yt!6X1wZHP>i8?N{x=K*nGbJb-^kOj)rgvVTevJTHfJOx zaQ}sM7h_Gss#21Qqq+sY+zuIcid5SzC?uHqF#5;23IaXQp6ipaN!ThjSp<#CJ(*>z5^&!x zwF!_RTqCop;K8HHHcy>1*x0gHKX_L&r^vBc|KYxC|V_fl*Szfzu zg)=rN1ogHz|NX}2pkrY0Z!ktuCbVN{XlOJi+z@rd$<*6+nqR?wpTc4k3Mt znlXQ_U_{P7eBi(V7Z>>OKf6L(>ePK+&hTj2xKryB_qjK8iSdBHzzG2`IK`Z){^_Tm(`}n93Ib=jPaq$q$ zFaU9~n*L&LkdM2)Bi23X%YhkkCKHRyd@=gK)g4O@RF;*iHT#_(+(wJH~+@+u@Kl zPMb+jN0YQEr{w)TIYvS{O86}SX zq+t$~rNlOmAY2ENH0IugL@Yp%DmW>lj&mMwEan=Sk?WW%#seA_Fv;>RF2B2%q(Zo( zbA(LE4RKf*WnP)vwKBK-cM6OYWW)$WJYD_oH>hWfT025fv{yT5{CohwdueI(+_Tf) zj=T^s9+j1TRzHk(2Tavf^~0!%hiqiE=zgV*4X$svhA)0L!_tpeGadjuW%$ZS*Mofk z#U)XEgiPfXFyeTeEmtm=48SqMbK*Rhv~(L%&u(R*yml|88=w06Km%a6HO!fUZnQt_ z?;|JWs<3~0>d|cOF1+^^LeM;oIST@%Cr=O?voZ}SJ6c8%9*){Fy!4qa;PwJ>bqLIp zq0H0EYED-zya5HStXwnN6LPf)^-fS60QOJ<22HhNaI>7}$hb1b$+8WHyycv3ehpvj zmp8ck7gTc$Lvidh*2JDiE>0dE`H5fJPRq}gYj_a`ql!+D(gOt@bkB2Dl@C^nvoBl< z5s*>03}yUI`Q7L(iX@fwBMR>OiH{`w{1W_HSeS;UK*x z3wx+&LX1Lk^GK4U5rq0T(E;LfOh!p!x?)LcvL%Mm0AmM16%o5D>}8^`=G2rOW;YW3}rD?JAh`@nWKdDP?W zwrlSutMq<~EcZ+KiwkqGLN(DD3T|1*Q|<)xcfd-J^wcwny%KacUi$P^5A|_ho#YoJ zy6E&L5ZlSgQ&0G0o3t&UxqVxaOd?62L!=ZGs|0?gIDwH3atjjsyIgY}6a@f8H}Xrz z)(|MR1si*XZk>Uo_b3=qi_; z+bk41mP#kp(~~#vQH*DB*ws-^#^{`8|U7_(j5GS=dG&7pEAsH`V?GyBFCES^nG zNF~L4`r0U&C?3QOn-`o@xsFO;)RGSy+F! z;o2Z3cU9xr^X8iJXMFu8&&ukvZP>+J+QJWqrZ)MCi(>m%vExqR28`+XAHBh~Sfz#S zUz8NFWQwTy^yy}!2NDQD+9Rh8Y~V+4C}fa;nbU^eUO?iYqA`6VwS0OY0<9Y&BI!48 z-tV3OYT?jx^tdLBGszB61s~y|NCIZb=GPwxTr;vWBP%OV2l7KPxZJ_Y+Sp*b!4+3} z0G&m;KrM}o!f^~`(?N6QZ%}O6$_ zq$Js$@~nqeQ^t_;;ZcvmKqB03OrvnZLx3HCyYOaO8hJG99H>4TjfT_dz$17JkZWc+ zPA9%KH8r)h!I!?7U21P{hrAM7#hR-?_$r*i2|)A(hGaY-_~c3Jf({f+g2Tr938`k{ zFrqcj;T2OX!OuZ1mAp}6A$X&E{@723MxDXG+xKl@2!lZ^?+Z#14EL3Y~2tjucUg&Y@bf!>v_XUS^*zHN}~t- zjQBe2ec<*+rY->TExSL6Z7J-Ay1y@Eac<+|O&}uz^q}#V^7x@mX?MUG zBj5#$x`yc8O+*gEu%&Vw#?b=xw|XW4#(|H9%G^Dp_Fku#1|CjS)u5)8XTM77)^rYa z5}*R)PvUB$+G&W=p_&uOXT%l4!HU$uJ2F>e04sVGC!1wC)Lrh=NvUxdd+B1VPFt<4 z0yUEVhyM^OVJeSeK?ruc>*aTi@Uh6xy`p2hrpB0^S$)0Bt-X2aAAG)_-F7t|@;Qp- zml^V+Lt=HS)VcFx9mVG^R1Q|#WgGL=erw^p7p`q*&PLDv^> zN!Ur66n1T&JSwtVbgwfHD1)Nei);5O38s)n-x;UP?hg<#X4T7U|B!l&NMcP9(zKGH zz}}+lzH~z+(M2Wm28XjwNa_)u%Z_TE@#X~`uWDW`<$(a6a<9#wA#wOFs#e{q86fkI zB$peTvmFYZ<~i%lOxZAuJT4q>0A_ZLd0R<3bg2o~s>k;WxH~zO!-vyFXyVnnLXe$^ z9Q!#CNnykoK54wMTDk-rgLSakivXp^K#pK1;(03(y&GOLkA}=!U8i#-9C$`)}_(93sB5 z$h-1tL*~k$n66+OQ}1Jh2~Gbn*gjE*KZF1jk&|WdqxCtf&<7VzMcc^BdCCmqw>9B7 zrWMIi$espookKwfTi%*XJq7xUWne{h0B&5d^u~Xi8w0sQ%aAJ>zW_hwrT^_Ij}Gao zx|1H4@zXRs=At+XzQ@9+XxqN~l@skGyZnJFM%RlCzT-7>d~{v@|I3_67P20*_H9}< zEXtb@=chjY;Zr$8w1q8@^-|n$r_QEhPnhLQD&s#I(G8Hq*0o35D|oDp-(j+|k~GRA o(|?e)eS{%@>$vqj)P*b?`B__!nn~zgVZc8-%x%pclUyVJ4_7zFFaQ7m diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..cd51447 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,70 @@ +# VRCSDK Patching Notes + +## How VRCSDK is meant to work + +The Unity EditorPref key `VRC_installedClientPath` is meant to hold a string of a absolute path that points to VRChat.exe. + +This is always set after VRCSDK initializes; if it was not already set, it pulls the value from some key in the registry, which was probably set beforehand by VRChat.exe or launch.exe. + +In the settings page, the user has an option to change `VRC_installedClientPath`. I have no idea what the practical use-case is for doing this. Clicking "Revert to Default" resets it by reading from the registry again. + +The VRCSDK.World "Build and test" button will execute VRChat.exe directly, giving it many arguments that launch the game in offline mode with a `file:///` URI to the built world AssetBundle ending in `.vrcw`. It only falls back to "executing" the equivalent `vrchat://` link directly if VRChat.exe hasn't been found -- that is, `VRC_installedClientPath` is unset because the registry key was missing, or VRChat was recently moved to a different Steam library. + +The VRCSDK fetches avatars in the Content Manager window, by searching directly inside `Environment.SpecialFolder.LocalApplicationData`. This is `AppData/LocalLow` on Windows and `~/.local/share/VRChat` on Linux, but there's no practical reason for the latter to exist. This dir has even been known to interfere with VRCFaceTracking. The AssetBundle can technically work no matter where you save it, but it's best if it's saved to the LocalLow inside VRChat's Proton prefix. + +## Alternatives + +Prior to this doc, we've been manually launching Proton, and the user must select the Proton version by way of searching for the `proton` Python script file to use as an entrypoint. It also did not locate the VRChat game directory, so moving it to a non-default Steam library would break it. + +Now, we use `SteamLocator`, `VrcLocator`, and `ProtonLocator` to find everything, reconciling it with Unity preferences. + +There is still the option to ask Steam to launch it, with `steam -applaunch 438100` or something, but I don't know if it would work. We want it to use offline mode, allow multiple instances, and not interfere with any instance running in online mode. The URI handler below has the same effect as launching it from Steam. The `vrchat://` URIs can work on Linux, but they need a .desktop file. + +## How we patch VRCSDK + +This is an exhaustive list. + +- Fixes the VRCSDK initialization so it can correctly find VRChat.exe. + - We replace the `LoadRegistryVRCInstallPath` method to call our `VrcLocator` instead. +- Prevents the pointless creation (lol) and use of `~/.local/share/VRChat`, instead saving test worlds and avatars to the Proton prefix. + - We replace the method that looks for `LocalLow` to do Not that. +- Fixes the Content Manager tab so it can show your test avatars. +- Fixes the Build and Test button. + - We replace the entire `VRCWorldAssetExporter.RunWorldTestDesktop` method to: + - Translate the path of the saved AssetBundle to a winepath relative to `Z:/`. + - Call Proton instead, with all `STEAM_COMPAT_` env vars set. + - We do not launch it in Steam Linux Runtime, and I hope it doesn't come to that. But it is possible, we'd just read `toolmanifest.vdf` and wrap the command further. +- Adds some UI in VRCSDK Settings tab to select a different Proton to use instead of what's set in Steam. + +## Ideas/Roadmap + +- [ ] Warn if `~/.local/share/VRChat` exists, because the latest VRCFaceTracking.Avalonia release (currently v1.1.1.0) will break if it sees this. +- [ ] Run `xdg-mime query` and offer to set up [the URI handler](#vrchat-uri-handler). +- [ ] Patch UdonSharp exception watcher. +- [ ] MIT license button in Tools menu. + +## Snippets + +### VRChat Offline Mode + +Example command I use to run VRChat in offline mode, supporting multiple clients all loaded into an instance of a world AssetBundle stored locally: + +```bash +STEAM_COMPAT_DATA_PATH=/mnt/steam/steamapps/compatdata/438100/ STEAM_COMPAT_CLIENT_INSTALL_PATH=$HOME/.local/share/Steam/ STEAM_COMPAT_INSTALL_PATH=/mnt/steam/steamapps/common/VRChat/ ~/.local/share/Steam/compatibilitytools.d/GE-Proton10-33-rtsp24-1/proton run /mnt/steam/steamapps/common/VRChat/VRChat.exe '--url=create?roomId=8094722763&hidden=true&name=BuildAndRun&url=file:///Z:/mnt/steam/steamapps/common/VRChat/VRChat_Data/StreamingAssets/Worlds/errorworld.vrcw' --enable-debug-gui --enable-sdk-log-levels --enable-udon-debug-logging --no-vr +``` + +### VRChat URI Handler + +Save to `~/.local/share/applications/vrchat-uri-handler.desktop`, and Firefox will let you click "Launch World" buttons on the website. It'll let you `xdg-open vrchat://` too. + +```desktop file=vrchat-uri-handler.desktop +[Desktop Entry] +Name=URI-vrchat +Comment=URI handler for vrchat:// +Exec=/usr/bin/env steam -applaunch 438100 %U +Terminal=false +Type=Application +Categories=Game; +MimeType=x-scheme-handler/vrchat; +NoDisplay=true +``` diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.avatars/package.json b/Packages/befuddledlabs.linuxvrchatsdkpatch.avatars/package.json index c53d083..0d67ea4 100644 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.avatars/package.json +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.avatars/package.json @@ -7,9 +7,9 @@ "name": "BefuddledLabs" }, "unity": "2022.3", - "description": "Patches the VRChat SDK to work properly on linux.", + "description": "Patches the VRChat Avatars SDK to work properly on Linux.", "vpmDependencies": { - "com.vrchat.avatars": "^3.8.2", + "com.vrchat.avatars": "^3.10.3", "befuddledlabs.linuxvrchatsdkpatch-base": "0.2.2" } } diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Base.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Base.cs deleted file mode 100644 index 0222e4b..0000000 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Base.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using HarmonyLib; -using JetBrains.Annotations; -using UnityEditor; -using VRC.Core; -using VRC.SDKBase.Editor; - -namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor -{ - [HarmonyPatch] - public static class Base - { - [CanBeNull] - public static string GetCompatDataPath() - { - var vrChatPath = SDKClientUtilities.GetSavedVRCInstallPath(); - if (string.IsNullOrWhiteSpace(vrChatPath)) - return null; - - var dir = new FileInfo(vrChatPath).Directory; - if (dir == null) - return null; - - while (!dir.Name.Contains("steamapps", StringComparison.OrdinalIgnoreCase)) - { - dir = dir.Parent; - if (dir == null) - return null; - } - - return dir.FullName + "/compatdata/"; - } - - static Base() - { - // Check if the user has protontricks installed - try - { - var p = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = "protontricks-launch", - Arguments = "-h", // so it doesn't fail - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false - } - }; - - p.Start(); - p.WaitForExit(); - - HasProtonTricks = p.ExitCode == 0; - } - catch - { - HasProtonTricks = false; - } - } - - public static bool HasProtonTricks; - - [CanBeNull] - public static string GetVrcCompatDataPath() - { - return GetCompatDataPath() + "438100/"; - } - - public static string ProtonPath - { - get - { - var savedVrcInstallPath = ""; - if (EditorPrefs.HasKey("LinuxVRC_protonPath")) - savedVrcInstallPath = EditorPrefs.GetString("LinuxVRC_protonPath"); - return savedVrcInstallPath; - } - set => EditorPrefs.SetString("LinuxVRC_protonPath", value); - } - - public static bool ProtonTricksPrefs - { - get - { - var savedProtonTricksPrefs = true; // Default use proton tricks - if (EditorPrefs.HasKey("LinuxVRC_protonTricksPrefs")) - savedProtonTricksPrefs = EditorPrefs.GetBool("LinuxVRC_protonTricksPrefs"); - return savedProtonTricksPrefs; - } - set => EditorPrefs.SetBool("LinuxVRC_protonTricksPrefs", value); - } - - // Thanks Bartkk <3 - [HarmonyPrefix] - [HarmonyPatch(typeof(VRC_SdkBuilder), "GetLocalLowPath")] - public static bool GetLocalLowPathPrefix(ref string __result) - { - __result = GetVrcCompatDataPath() + "pfx/drive_c/users/steamuser/AppData/LocalLow/"; - return false; - } - } -} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/CannyPopup.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/CannyPopup.cs index aee8381..e77a5be 100644 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/CannyPopup.cs +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/CannyPopup.cs @@ -1,3 +1,5 @@ +#nullable enable + using System.Runtime.InteropServices; using UnityEditor; using UnityEngine; @@ -7,23 +9,23 @@ namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor [InitializeOnLoad] public static class CannyPopup { - [MenuItem("VRChat SDK/Utilities/Linux/Clear LinuxVRC_cannyDialog")] - public static void ResetLinuxVRC_cannyDialog() => EditorPrefs.SetBool("LinuxVRC_cannyDialog", false); + [MenuItem("VRChat SDK/Utilities/Linux/Reset Canny popup preference")] + public static void ResetCannyDialog() => LinuxVrcEditorPrefs.CannyDialog = false; static CannyPopup() { - if (EditorPrefs.GetBool("LinuxVRC_cannyDialog", false) || + if (LinuxVrcEditorPrefs.CannyDialog || !RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return; - var result = EditorUtility.DisplayDialog("Linux VRChat Patch", - "Please upvote this canny instead of needing this patch local tests.", + var result = EditorUtility.DisplayDialog("Linux VRChat SDK Patch", + "Please upvote this VRChat Canny, which would obviate the need for these SDK patches.", "Open Canny", "Don't show again"); if (result) Application.OpenURL( "https://feedback.vrchat.com/sdk-bug-reports/p/add-proton-support-to-the-sdk-for-local-tests"); - EditorPrefs.SetBool("LinuxVRC_cannyDialog", true); // + LinuxVrcEditorPrefs.CannyDialog = true; } } } \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LaunchConfiguration.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LaunchConfiguration.cs new file mode 100644 index 0000000..fa430fc --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LaunchConfiguration.cs @@ -0,0 +1,118 @@ +#nullable enable + +using System.IO; +using BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Locators; +using UnityEngine; + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor +{ + /// + /// Represents all Linux-specific settings required to start VRChat using the World SDK Build and Test functionality, + /// determined by resolving all defaults with the user's preferences in Unity and Steam. + /// + public class LaunchConfiguration + { + private LaunchConfiguration(string steamRoot, string protonExecutable, string compatDataPath, + string vrcInstallRoot) + { + SteamRoot = steamRoot; + ProtonExecutable = protonExecutable; + CompatDataPath = compatDataPath; + VrcInstallRoot = vrcInstallRoot; + } + + /// + /// An absolute path to the Steam root directory. + /// + + public string SteamRoot { get; } + + /// + /// An absolute path to a Proton version's proton Python script. + /// + public string ProtonExecutable { get; } + + /// + /// An absolute path to the compatdata/ directory in a Proton prefix. + /// + + public string CompatDataPath { get; } + + /// + /// An absolute path to common/VRChat/ + /// + public string VrcInstallRoot { get; } + + /// + /// Determine the environment taking preferences and locations from Steam and Unity into account by which we should + /// launch VRChat in offline Build and Test mode. + /// + /// + /// The value of the Unity EditorPref for custom Proton version, or if it is unset. + /// + /// + public static LaunchConfiguration? Resolve(string? protonPath) + { + // SteamRoot + var steamRoot = SteamLocator.FindSteamRoot(); + if (steamRoot == null || !SteamLocator.IsValidSteamRoot(steamRoot)) + { + Debug.LogError($"Couldn't find Steam root: \"{steamRoot}\""); + return null; + } + + // ProtonExecutable + if (!ProtonLocator.IsValidCompatToolPath(protonPath)) + { + Debug.Log("Custom Proton install path is unset or invalid, will auto-detect."); + + protonPath = ProtonLocator.GetSteamVdfCompatTool(steamRoot, VrcLocator.VrcAppId); + if (!ProtonLocator.IsValidCompatToolPath(protonPath)) + { + Debug.LogError($"Couldn't find compat tool used for VRChat: {protonPath}"); + return null; + } + } + + if (protonPath == null) + return null; // satisfies compiler + + var protonExecutable = Path.Combine(protonPath, "proton"); + + // CompatDataPath + var compatDataPath = VrcLocator.GetCompatDataPath(); + if (compatDataPath == null || !VrcLocator.IsValidCompatDataPath(compatDataPath)) + { + Debug.LogError($"Could not find VRChat's compatdata: \"{compatDataPath}\""); + return null; + } + + // VrcGameRoot + var vrcExePath = VrcLocator.GetVrcInstallPath(); + if (vrcExePath == null || !VrcLocator.IsValidVrcInstallPath(vrcExePath)) + { + Debug.LogError($"Could not locate VRChat.exe: \"{vrcExePath}\""); + return null; + } + + var vrcInstallRoot = Path.GetDirectoryName(vrcExePath); + // ReSharper disable once InvertIf + if (vrcInstallRoot == null || !Directory.Exists(vrcInstallRoot)) + { + Debug.LogError($"Could not locate VRChat's install directory: \"{vrcInstallRoot}\""); + return null; + } + + // TODON'T: read toolmanifest.vdf "commandline"? nah + return new LaunchConfiguration(steamRoot, protonExecutable, compatDataPath, vrcInstallRoot); + } + + public void DebugPrint() + { + Debug.Log($"Steam root: \"{SteamRoot}\""); + Debug.Log($"Proton executable: \"{ProtonExecutable}\""); + Debug.Log($"Compat data path: \"{CompatDataPath}\""); + Debug.Log($"VRChat install directory: \"{VrcInstallRoot}\""); + } + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LaunchConfiguration.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LaunchConfiguration.cs.meta new file mode 100644 index 0000000..4fe72b1 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LaunchConfiguration.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ecdc0952b219453fa1708d9bf0b8cd95 +timeCreated: 1777001244 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSDKPatch-Base.Editor.asmdef b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSdkPatch.Base.Editor.asmdef similarity index 86% rename from Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSDKPatch-Base.Editor.asmdef rename to Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSdkPatch.Base.Editor.asmdef index f0c71c1..dc981fd 100644 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSDKPatch-Base.Editor.asmdef +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSdkPatch.Base.Editor.asmdef @@ -1,6 +1,6 @@ { "name": "LinuxVRChatSdkPatch.Base.Editor", - "rootNamespace": "", + "rootNamespace": "BefuddledLabs.LinuxVRChatSdkPatch.Base", "references": [ "VRC.SDKBase", "VRC.SDKBase.Editor" diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSDKPatch-Base.Editor.asmdef.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSdkPatch.Base.Editor.asmdef.meta similarity index 76% rename from Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSDKPatch-Base.Editor.asmdef.meta rename to Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSdkPatch.Base.Editor.asmdef.meta index 0f3b41e..aeeba6a 100644 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSDKPatch-Base.Editor.asmdef.meta +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVRChatSdkPatch.Base.Editor.asmdef.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a330f0ccedb2dc0af8ef51b369f732f9 +guid: 6532898edc3e2729dab32e3ae1aa2126 AssemblyDefinitionImporter: externalObjects: {} userData: diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVrcEditorPrefs.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVrcEditorPrefs.cs new file mode 100644 index 0000000..1f74ee4 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVrcEditorPrefs.cs @@ -0,0 +1,35 @@ +#nullable enable + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor +{ + public static class LinuxVrcEditorPrefs + { + public const string PrefsKeyCustomProtonPath = "LinuxVRC_customProtonPath"; + public const string PrefsKeyCannyDialog = "LinuxVRC_cannyDialog"; + + /// + /// An absolute path to the install dir of the user's preferred Proton version, or if it is unset. + /// + public static string? CustomProtonPath + { + get + { + string? val = null; + if (UnityEditor.EditorPrefs.HasKey(PrefsKeyCustomProtonPath)) + val = UnityEditor.EditorPrefs.GetString(PrefsKeyCustomProtonPath); + return val; + } + set => UnityEditor.EditorPrefs.SetString(PrefsKeyCustomProtonPath, value); + } + + /// + /// Whether the user has been shown the popup asking them to vote for the VRCSDK Canny for Linux support. + /// If true, it won't be shown again. + /// + public static bool CannyDialog + { + get => UnityEditor.EditorPrefs.GetBool(PrefsKeyCannyDialog, false); + set => UnityEditor.EditorPrefs.SetBool(PrefsKeyCannyDialog, value); + } + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Base.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVrcEditorPrefs.cs.meta similarity index 100% rename from Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Base.cs.meta rename to Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/LinuxVrcEditorPrefs.cs.meta diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators.meta new file mode 100644 index 0000000..bcfb535 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bebe6d906a3743cfb49bc76d5737a9fc +timeCreated: 1776228928 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/OfficialCompatToolData.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/OfficialCompatToolData.cs new file mode 100644 index 0000000..87baaed --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/OfficialCompatToolData.cs @@ -0,0 +1,83 @@ +#nullable enable + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Locators +{ + /// + /// Data extracted from appcache.vdf on 2026-04-17 using a one-off script using new-vdf-parser[1] + /// [1]: https://crates.io/crates/new-vdf-parser + /// + public static class OfficialCompatToolData + { + private static readonly CompatTool Proton9 = new( + "proton_9", + "2805730", + "proton-9.0-4pin".Split(","), + "Proton 9.0 (Beta)" + ); + + private static readonly CompatTool Proton10 = new( + "proton_10", + "3658110", + "proton-9,proton-9.0-1RC,proton-stable,proton-next,proton_next,proton-7.0-1,proton-7.0-2,proton-7.0-3,proton-7.0-4,proton-7.0-5,proton-7.0-6,proton-8.0-1,proton-8.0-2,proton-8.0-3,proton-8.0-4,proton-8.0-5,proton-8.0RC,proton-9.0-2RC,proton-9.0-3RC,proton-9.0-4RC,proton-10,proton-10.0-beta,proton-10.0-3RC" + .Split(","), + "Proton 10.0" + ); + + private static readonly CompatTool Proton11 = new( + "proton_11", + "4628710", + "proton-11.0-beta".Split(","), + "Proton 11.0" + ); + + private static readonly CompatTool ProtonHotfix = new( + "proton_hotfix", + "2180100", + "proton-hotfix".Split(","), + "Proton Hotfix" + ); + + private static readonly CompatTool ProtonExperimental = new( + "proton_experimental", + "1493710", + "proton-experimental".Split(","), + "Proton - Experimental" + ); + + public static readonly CompatTool[] All = { Proton9, Proton10, Proton11, ProtonHotfix, ProtonExperimental }; + } + + /// + /// Stores information about official compatibility tools that get installed to Steam libraries. + /// + public class CompatTool + { + public CompatTool(string name, string appId, string[] aliases, string installDir) + { + Name = name; + AppId = appId; + Aliases = aliases; + InstallDir = installDir; + } + + /// + /// The name of the tool as it appears in CompatToolMapping. + /// + public string Name { get; } + + /// + /// The Steam AppId of this tool. + /// + public string AppId { get; } + + /// + /// A list of other (possibly older) names that this tool may also appear in CompatToolMapping. + /// + public string[] Aliases { get; } + + /// + /// The name of the directory within a Steam library folder that this tool will be installed in. + /// + public string InstallDir { get; } + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/OfficialCompatToolData.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/OfficialCompatToolData.cs.meta new file mode 100644 index 0000000..857100c --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/OfficialCompatToolData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2b4bb09b50bb418d845db7749b40c637 +timeCreated: 1776485203 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/ProtonLocator.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/ProtonLocator.cs new file mode 100644 index 0000000..1794244 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/ProtonLocator.cs @@ -0,0 +1,303 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEngine; + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Locators +{ + /// + /// Reads through Steam directories and VDF files to find custom and official compatibility tools ("compat tools"; e.g. + /// Proton), the AppIds they are used for, and the Steam user's preferred tool selections. + /// + /// + /// Much of this was taken from VRCX, MIT. + ///
+ /// See also official documentation: + /// + /// Steam compatibility tool interface + /// + ///
+ public static class ProtonLocator + { + /// + /// Determines the compatibility tool (e.g., Proton) for a game having the given , and return + /// an absolute filepath to the tool's install dir. + /// + /// An absolute path to the Steam root directory. + /// The Steam AppId of the game. + /// + /// An absolute filepath to the compat tool's install dir, or if it couldn't be determined + /// or found. + /// + public static string? GetSteamVdfCompatTool(string steamRoot, string appId) + { + var configVdfPath = Path.Combine(steamRoot, "config", "config.vdf"); + if (!File.Exists(configVdfPath)) + { + Debug.LogError($"Couldn't determine current Proton: config.vdf not found: {configVdfPath}"); + return null; + } + + var vdfContent = File.ReadAllText(configVdfPath); + var compatToolMapping = ExtractCompatToolMapping(vdfContent); + + if (!compatToolMapping.TryGetValue(appId, out var compatToolName) || compatToolName == null) + { + Debug.Log("config.vdf doesn't have an entry for VRChat, so we'll look for the default."); + if (!compatToolMapping.TryGetValue("0", out compatToolName) || compatToolName == null) + { + Debug.LogError( + $"Couldn't determine current Proton: config.vdf couldn't be parsed, or doesn't have an entry for the appid {appId} and couldn't find the default compat tool."); + return null; + } + } + + Debug.Log($"Using compat tool name: {compatToolName}"); + + var compatTool = GetCompatToolDirForName(steamRoot, compatToolName); + if (compatTool == null) + { + Debug.LogError( + $"Couldn't determine current Proton: couldn't find compat tool dir named \"{compatToolName}\""); + return null; + } + + Debug.Log($"Found compat tool dir: {compatTool}"); + return compatTool; + } + + /// + /// Determines whether the given filepath refers to an existing compatibility tool install dir. + /// + /// An absolute path to a compatibility tool. + /// if the tool dir is valid, otherwise. + public static bool IsValidCompatToolPath(string? compatToolPath) + { + if (string.IsNullOrEmpty(compatToolPath)) + { + return false; + } + + // compatibilitytool.vdf is only included with compatibilitytools.d ones, not official ones + // thus, toolmanifest.vdf is a better indicator + var toolVdf = Path.Combine(compatToolPath!, "toolmanifest.vdf"); + var fileExists = File.Exists(toolVdf); + Debug.Log($"File.Exists(\"{toolVdf}\") = {fileExists}"); + return fileExists; + } + + /// + /// Locates the Steam root and finds the compatibilitytools.d directory. + /// + /// An absolute path to compatibilitytools.d, or if it or the Steam root can't be found. + public static string? GetCompatibilityToolsDotD() + { + var steamRoot = SteamLocator.FindSteamRoot(); + if (steamRoot == null || !SteamLocator.IsValidSteamRoot(steamRoot)) + { + return null; + } + + var dotD = Path.Combine(steamRoot, "compatibilitytools.d"); + return Directory.Exists(dotD) ? dotD : null; + } + + /// + /// Interprets as a VDF and extracts the mapping between each AppId and its selected + /// compat tool name (empty string if unset/default). + /// + /// + /// Taken from VRCX, MIT. + /// + /// + /// A mapping between Steam AppIds and the name of the selected compat tool. + private static Dictionary ExtractCompatToolMapping(string vdfContent) + { + var compatToolMapping = new Dictionary(); + const string sectionHeader = "\"CompatToolMapping\""; + var sectionStart = vdfContent.IndexOf(sectionHeader, StringComparison.Ordinal); + + if (sectionStart == -1) + { + Debug.LogError("CompatToolMapping not found"); + return compatToolMapping; + } + + var blockStart = vdfContent.IndexOf('{', sectionStart) + 1; + var blockEnd = FindMatchingBracket(vdfContent, blockStart - 1); + + if (blockStart == -1 || blockEnd == -1) + { + Debug.LogError("CompatToolMapping block not found"); + return compatToolMapping; + } + + var blockContent = vdfContent.Substring(blockStart, blockEnd - blockStart); + + // "123" { "crap" "blah" "name" "proton 67" + // captures `123` and `proton 67` + var keyValuePattern = new Regex("\"(\\d+)\"\\s*\\{[^}]*\"name\"\\s*\"([^\"]+)\"", + RegexOptions.Multiline); + + var matches = keyValuePattern.Matches(blockContent); + foreach (Match match in matches) + { + var key = match.Groups[1].Value; + var name = match.Groups[2].Value; + + if (key != "0") + { + compatToolMapping[key] = name; + } + } + + return compatToolMapping; + } + + /// + /// Scan common locations for all compat tool directories and libraries, and return an absolute path to the tool that + /// matches the name or alias . + /// + /// An absolute path to the Steam root directory. + /// + /// The name or alias of the compat tool, as listed in config.vdf or its compatibilitytool.vdf. + /// + /// + /// An absolute path to the compat tool install dir, or if it doesn't match a known + /// official name or can't be found. + /// + private static string? GetCompatToolDirForName(string steamRoot, string compatToolName) + { + var steamRootDotD = Path.Combine(steamRoot, "compatibilitytools.d"); + var foundDotD = FindCompatToolInDotD(steamRootDotD, compatToolName); + + var foundOfficial = FindOfficialCompatToolForName(steamRoot, compatToolName); + + // also look for system-installed protons. + // I guess the majority of /usr paths come from folks getting protons from the AUR. + var systemDotD = Path.Combine("usr", "share", "steam", "compatibilitytools.d"); + var foundSystemDotD = FindCompatToolInDotD(systemDotD, compatToolName); + + Debug.Log( + $"Possibly matching compat tools: custom=\"{foundDotD}\", systemCustom=\"{foundSystemDotD}\", official=\"{foundOfficial}\""); + + // if the same tool is installed in more than one, I have no idea which one takes precedence in Steam. + return foundDotD ?? foundSystemDotD ?? foundOfficial; + } + + /// + /// Given the name of an official Proton version , try to locate its install dir. + /// + /// + /// If the given is the identifier or an alias for an official compatibility tool + /// that we recognize, knowing that it would be installed into a Steam library, check all known Steam libraries for a + /// compat tool whose directory matches the official installDir. + /// + /// An absolute path to the Steam root directory. + /// + /// The name or alias of the compat tool, as listed in config.vdf. + /// + /// + /// An absolute path to the compat tool install dir, or if it doesn't match a known name + /// or isn't installed. + /// + private static string? FindOfficialCompatToolForName(string steamRoot, string compatToolName) + { + // look for official protons like "Proton 9.0 (Beta)" and "Proton 10.0" + var matchingOfficialCompatTool = OfficialCompatToolData.All + .FirstOrDefault(tool => tool.Name == compatToolName || tool.Aliases.Contains(compatToolName)); + + // does it match an official tool name or an alias of one? + if (matchingOfficialCompatTool == null) + return null; + + Debug.Log( + $"Matching \"{compatToolName}\" to official compat tool: {matchingOfficialCompatTool.Name}, checking" + + $"Steam libraries for appid {matchingOfficialCompatTool.AppId}"); + + // is it installed to one of our libraries? + var libraryWithOfficial = SteamLocator.GetLibraryWithAppId(steamRoot, matchingOfficialCompatTool.AppId); + Debug.Log($"Located in library: \"{libraryWithOfficial ?? "sorry, nothing"}\""); + if (libraryWithOfficial == null) + return null; + + var foundOfficial = Path.Combine(libraryWithOfficial, "steamapps", "common", + matchingOfficialCompatTool.InstallDir); + if (Directory.Exists(foundOfficial) && + // sanity check - all compat tools, custom or official, have this file in the installdir + File.Exists(Path.Combine(foundOfficial, "toolmanifest.vdf"))) + { + return foundOfficial; + } + + return null; + } + + /// + /// Given is an absolute filepath to a compatibilitytools.d directory, search + /// through its contents for a tool that matches the given name , and return an + /// absolute filepath to it. + /// + /// + /// An absolute filepath to a compatibilitytools.d directory, whether in a Steam root or system-wide. + /// + /// + /// The name of the compat tool, as listed in its compatibilitytool.vdf. + /// + /// An absolute filepath to the compat tool's install dir, or if it can't be found. + private static string? FindCompatToolInDotD(string toolsDir, string compatToolName) + { + if (!Directory.Exists(toolsDir)) + return null; + if (string.IsNullOrWhiteSpace(compatToolName)) + return null; + + // Several files inside the directory could indicate the compat tool name. But not all are reliable! + // Q: the name of the directory itself? + // A: NOPE - the name is "Proton-stl" but the installdir is "SteamTinkerLaunch" + // Q: the contents of the "version" file? + // A: NOPE - the name "GE-Proton10-33-rtsp23-zerocopy-test" has a version of "GE-Proton10-33-rtsp23-4-2-g2e8f1d695" + // Q: the "internal name" inside "compatibilitytool.vdf"? + // A: I think so! This is as expected in all my Proton versions. + // The file "toolmanifest.vdf" has nothing relevant. + + return (from toolDir in Directory.GetDirectories(toolsDir) + let compatToolVdfPath = Path.Combine(toolDir, "compatibilitytool.vdf") + where File.Exists(compatToolVdfPath) + let compatToolVdfContents = File.ReadAllText(compatToolVdfPath) + // I could parse this file, but why bother lol + where compatToolVdfContents.Contains(compatToolName) + select toolDir).FirstOrDefault(); + } + + // Taken from VRCX, MIT + private static int FindMatchingBracket(string content, int openBracketIndex) + { + var depth = 0; + for (var i = openBracketIndex; i < content.Length; i++) + { + switch (content[i]) + { + case '{': + depth++; + break; + case '}': + { + depth--; + if (depth == 0) + return i; + break; + } + } + } + + Debug.LogError($"No matching bracket found in VDF starting from position {openBracketIndex}"); + return -1; + } + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/ProtonLocator.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/ProtonLocator.cs.meta new file mode 100644 index 0000000..7d4c420 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/ProtonLocator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2b12e60b769a47c3b7a4f364364dae52 +timeCreated: 1776228706 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/SteamLocator.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/SteamLocator.cs new file mode 100644 index 0000000..45a42cb --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/SteamLocator.cs @@ -0,0 +1,100 @@ +#nullable enable + +using System.IO; +using UnityEngine; + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Locators +{ + public static class SteamLocator + { + /// + /// Search common paths for the Steam root directory, and if one is found, return an absolute path to it. + /// + /// An absolute path to the Steam root we found (if any), or otherwise. + public static string? FindSteamRoot() + { + var steamRoots = new[] + { + Path.Combine(XdgBaseDirectory.DataHome, "Steam"), // typically ~/.local/share/Steam + Path.Combine(XdgBaseDirectory.Home, ".steam", "steam"), + Path.Combine(XdgBaseDirectory.Home, ".steam", "root"), + Path.Combine(XdgBaseDirectory.Home, ".steam", "debian-installation") + }; + + foreach (var steamRoot in steamRoots) + { + if (IsValidSteamRoot(steamRoot)) + return steamRoot; + } + + Debug.LogError("Couldn't find any Steam directory containing libraryfolders.vdf"); + return null; + } + + /// + /// Given the path to Steam root, search steamapps/libraryfolders.vdf for the Steam library that a particular + /// is installed to. + /// + /// + /// Taken from VRCX, MIT. + /// + /// An absolute path to the Steam root directory. + /// The Steam AppId of the game. + /// + /// An absolute path to the library folder that should contain the app, or if it wasn't + /// found. + /// + public static string? GetLibraryWithAppId(string steamRoot, string appId) + { + var libraryFoldersVdfPath = Path.Combine(steamRoot, "steamapps", "libraryfolders.vdf"); + + if (!File.Exists(libraryFoldersVdfPath)) + { + Debug.LogWarning( + $"Attempted to search for appid, but was handed something that is probably not libraryfolders.vdf: {libraryFoldersVdfPath}"); + return null; + } + + string? libraryPath = null; + foreach (var line in File.ReadLines(libraryFoldersVdfPath)) + { + // Assumes line will be \t\t"path"\t\t"pathToLibrary" + if (line.Contains("\"path\"")) + { + var parts = line.Split("\t"); + if (parts.Length < 4) + continue; + + libraryPath = parts[4].Replace("\"", ""); + } + + if (!line.Contains($"\"{appId}\"")) + continue; + if (Directory.Exists(libraryPath)) + return libraryPath; + Debug.LogWarning( + $"libraryfolders.vdf references library \"{libraryPath}\", but that path doesn't exist"); + return null; + } + + return null; + } + + /// + /// Returns true if the given path appears to be a Steam root. We'll say a Steam root is a directory that contains a + /// libraryfolders.vdf file. + /// + /// An absolute path to the Steam root directory. + /// if the directory looks like a Steam root, otherwise. + public static bool IsValidSteamRoot(string? steamRoot) + { + if (string.IsNullOrEmpty(steamRoot) || !Directory.Exists(steamRoot)) + { + return false; + } + + var vdfPath = Path.Combine(steamRoot, "steamapps", "libraryfolders.vdf"); + return File.Exists(vdfPath); + } + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/SteamLocator.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/SteamLocator.cs.meta new file mode 100644 index 0000000..052c11c --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/SteamLocator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b7291775f6d14bcaba0059b0f198876a +timeCreated: 1776487344 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/VrcLocator.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/VrcLocator.cs new file mode 100644 index 0000000..21a0bca --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/VrcLocator.cs @@ -0,0 +1,188 @@ +#nullable enable + +using System.IO; +using UnityEditor; +using UnityEngine; +using VRC.Core; + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Locators +{ + /// + /// Locates VRChat.exe using Steam, taking VRCSDK's user-set preferences into account. + /// + /// + /// We must know these VRChat paths: + /// + /// + /// compatdata path, which contains the Proton prefix + /// + /// + /// AppData/LocalLow, located within the prefix + /// + /// + /// VRChat install path, which contains VRChat.exe + /// + /// + /// + /// VRChat.exe + /// + /// + /// + /// These aren't in fixed locations on every machine, but if the path to VRChat.exe is known, we can locate the + /// rest. + ///
+ /// This class provides that will locate Steam, then the VRChat install + /// directory, then discover VRChat.exe. + ///
+ /// VRCSDK has a Unity EditorPref named VRC_installedClientPath which is a variable meant to hold an absolute + /// path to VRChat.exe. Initially, it is empty, but once VRCSDK first loads, it attempts to discover + /// VRChat.exe and set this preference. It will fail on Linux, so we patch it to call our + /// instead. With this, VRC_installedClientPath is initialized + /// correctly, and we can treat it as the source of truth. Being a preference, the user may also choose to set their + /// own custom path to VRChat.exe in VRCSDK Settings. + ///
+ /// Unless specified otherwise, all of these methods take the value of VRC_installedClientPath into account. + ///
+ public static class VrcLocator + { + /// + /// The Steam AppId for VRChat. + /// + public const string VrcAppId = "438100"; + + /// + /// Locates VRChat's AppData/LocalLow directory, and returns it as an absolute filepath. + ///
+ /// This takes into account VRCSDK's VRC_installedClientPath. + ///
+ /// An absolute path to LocalLow if found, otherwise. + public static string? GetLocalLowPath() + { + var compatDataPath = GetCompatDataPath(); + if (compatDataPath == null) + { + return null; + } + + var relativeLocalLow = Path.Combine(compatDataPath, "pfx", + "drive_c", "users", "steamuser", "AppData", "LocalLow"); + relativeLocalLow = Path.GetFullPath(relativeLocalLow); + + return relativeLocalLow; + } + + /// + /// Gets the value for STEAM_COMPAT_DATA_PATH, e.g. ~/.local/share/Steam/steamapps/compatdata/438100/ + ///
+ /// This takes the value of VRCSDK's VRC_installedClientPath into account. + ///
+ /// An absolute path to the compatdata path if found, otherwise. + public static string? GetCompatDataPath() + { + var savedVrcInstallPath = GetVrcInstallPath(); + if (savedVrcInstallPath == null) + { + return null; + } + + // = /steamapps/common/VRChat/VRChat.exe + // to + // = / + var libraryPath = Path.GetFullPath(Path.Combine(savedVrcInstallPath, "..", "..", "..", "..")); + var compatDataPath = Path.Combine(libraryPath, "steamapps", "compatdata", VrcAppId); + compatDataPath = Path.GetFullPath(compatDataPath); + return compatDataPath; + } + + /// + /// Get the known path to VRChat.exe. + ///
+ /// If VRCSDK's editor preference key VRC_installedClientPath is set and the path exists, return that. + /// Otherwise, this tries to locate VRChat.exe via Steam libraryfolders.vdf shenanigans, then updates + /// VRC_installedClientPath if needed. + ///
+ /// An absolute filepath to VRChat.exe if found, otherwise. + public static string? GetVrcInstallPath() + { + var savedVrcExePath = SDKClientUtilities.GetSavedVRCInstallPath(); + if (IsValidVrcInstallPath(savedVrcExePath)) + return savedVrcExePath; + + Debug.Log( + $"VRCSDK's own saved VRC install path doesn't point to VRChat.exe: \"{savedVrcExePath ?? ""}\". We'll try to locate and correct it."); + + var ourVrcExePath = GetVrcInstallPathFromSteam(); + if (!IsValidVrcInstallPath(ourVrcExePath)) + return null; + + Debug.Log($"Updating VRCSDK's saved VRC install path from \"{savedVrcExePath}\" to \"{ourVrcExePath}\""); + // We would ideally call SDKClientUtilities.SetVRCInstallPath() here, but that method refuses to set + // VRC_installedClientPath if is unset. That doesn't make any sense, but whatever. Sidestep it. + // SDKClientUtilities.SetVRCInstallPath(ourVrcExePath); + EditorPrefs.SetString("VRC_installedClientPath", ourVrcExePath); + var readback = SDKClientUtilities.GetSavedVRCInstallPath(); + Debug.Log($"Updated; it's now \"{readback}\""); + return ourVrcExePath; + } + + /// + /// Locates VRChat.exe as installed by Steam. + ///
+ /// Finds Steam's installation path, parses libraryfolders.vdf, finds the library that VRChat is installed in, + /// finds VRChat.exe, and returns its absolute filepath. + ///
+ /// This does not take into account VRCSDK's editor preference key VRC_installedClientPath. + ///
+ /// An absolute filepath to VRChat.exe if found, otherwise. + public static string? GetVrcInstallPathFromSteam() + { + var steamRoot = SteamLocator.FindSteamRoot(); + if (steamRoot == null) + { + return null; + } + + // e.g. ~/.local/share/Steam or /mnt/steam + var libraryPath = SteamLocator.GetLibraryWithAppId(steamRoot, VrcAppId); + if (libraryPath == null) + { + Debug.LogError("Couldn't find any Steam library with VRChat installed."); + return null; + } + + var vrcExePath = Path.Combine(libraryPath, "steamapps", "common", "VRChat", "VRChat.exe"); + + if (IsValidVrcInstallPath(vrcExePath)) + return vrcExePath; + Debug.LogError( + $"Couldn't locate VRChat.exe at path: {vrcExePath}\n" + + "Please try to set it manually in VRChat SDK > Show Control Panel > Settings > VRChat Client."); + return null; + } + + /// + /// Determines whether the given path refers to an existing compatdata directory, that contains a Proton prefix. + /// + /// The path to test. + /// if the prefix is detected, otherwise. + public static bool IsValidCompatDataPath(string? compatDataPath) + { + return !string.IsNullOrEmpty(compatDataPath) && + // Directory.Exists(compatDataPath) && + Directory.Exists(Path.Combine(compatDataPath!, "pfx")); + } + + /// + /// Determines whether the given filepath refers to an existing VRChat.exe file within an installed game + /// directory. + /// + /// The path to test. + /// if VRChat.exe was found, otherwise. + public static bool IsValidVrcInstallPath(string? vrcInstallPath) + { + return !string.IsNullOrEmpty(vrcInstallPath) && + File.Exists(vrcInstallPath) && + vrcInstallPath!.EndsWith("VRChat.exe"); + } + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/VrcLocator.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/VrcLocator.cs.meta new file mode 100644 index 0000000..a7dd198 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/VrcLocator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0674a58840974c958835b9de406f4dc6 +timeCreated: 1775851250 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/XdgBaseDirectory.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/XdgBaseDirectory.cs new file mode 100644 index 0000000..9bce440 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/XdgBaseDirectory.cs @@ -0,0 +1,30 @@ +#nullable enable + +using System; +using System.IO; + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Locators +{ + /// + /// Uses the XDG Base Directory Specification to find various user directories. + /// + /// + /// Pared down from Xdg.Directories library, MIT. + /// + public static class XdgBaseDirectory + { + /// + /// The user's home directory. + /// + public static string Home => Environment.GetEnvironmentVariable("HOME") ?? + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + + // TODO: should also read ${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs + /// + /// The user's data files directory (e.g. ~/.local/share). + /// + public static string DataHome => + Environment.GetEnvironmentVariable("XDG_DATA_HOME") + ?? Path.Combine(Home, ".local", "share"); + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/XdgBaseDirectory.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/XdgBaseDirectory.cs.meta new file mode 100644 index 0000000..20274a9 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Locators/XdgBaseDirectory.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5b66b60fbf7c4a9cb7430541738443af +timeCreated: 1775853241 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Menus.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Menus.cs new file mode 100644 index 0000000..0e049b7 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Menus.cs @@ -0,0 +1,50 @@ +#nullable enable + +using UnityEditor; +using UnityEngine; +using VRC.Core; + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor +{ + public static class Menus + { + /// + /// Print out the LinuxVRC Unity EditorPrefs. + /// + [MenuItem("Tools/Linux VRChat SDK Patch/Debug Print Preferences")] + public static void DebugPrintPreferences() + { + Debug.Log($"{LinuxVrcEditorPrefs.PrefsKeyCannyDialog}: \"{LinuxVrcEditorPrefs.CannyDialog}\""); + Debug.Log($"{LinuxVrcEditorPrefs.PrefsKeyCustomProtonPath}: \"{LinuxVrcEditorPrefs.CustomProtonPath}\""); + } + + /// + /// Print out the VRCSDK Unity EditorPrefs that we care about. + /// + [MenuItem("Tools/Linux VRChat SDK Patch/Debug Print VRCSDK Preferences")] + public static void DebugPrintVrcSdkPreferences() + { + Debug.Log($"{nameof(SDKClientUtilities.GetSavedVRCInstallPath)}: \"{SDKClientUtilities.GetSavedVRCInstallPath()}\""); + const string key = "VRC_installedClientPath"; + Debug.Log($"EditorPrefs {key}: \"{EditorPrefs.GetString(key)}\""); + } + + /// + /// Print out the current launch configuration, as if we were launching Build and Test right now. + /// + /// + [MenuItem("Tools/Linux VRChat SDK Patch/Debug Print Launch Configuration")] + public static void DebugPrintLaunchConfiguration() + { + var launchConfiguration = LaunchConfiguration.Resolve(protonPath: null); + if (launchConfiguration == null) + { + Debug.Log("Couldn't determine launch configuration."); + } + else + { + launchConfiguration.DebugPrint(); + } + } + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Menus.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Menus.cs.meta new file mode 100644 index 0000000..59c0d59 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Menus.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b1418bd26caa4da0a0f7eb21295dc844 +timeCreated: 1777022296 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patch.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patch.cs index d88aeff..ee2583c 100644 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patch.cs +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patch.cs @@ -1,22 +1,27 @@ +#nullable enable + +using System.Linq; using System.Runtime.InteropServices; using HarmonyLib; using UnityEditor; +using UnityEngine; namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor { [InitializeOnLoad] public static class Patch { - internal static Harmony _harmony; - + private const string HarmonyID = "BefuddledLabs.LinuxVRChatSdkPatch.Base"; + private static readonly Harmony BaseHarmony = new(HarmonyID); static Patch() { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return; - _harmony = new Harmony("BefuddledLabs.LinuxVRChatSdkPatch-World"); - _harmony.PatchAll(); + BaseHarmony.PatchAll(); + var count = BaseHarmony.GetPatchedMethods().Count(); + Debug.Log($"{HarmonyID}: Patched {count} methods"); } } } \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches.meta new file mode 100644 index 0000000..ce6b6df --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 86964aac0554435c9ecb49c448e844b6 +timeCreated: 1776105494 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchGetLocalLowPath.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchGetLocalLowPath.cs new file mode 100644 index 0000000..4051c43 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchGetLocalLowPath.cs @@ -0,0 +1,36 @@ +#nullable enable + +using System.Diagnostics.CodeAnalysis; +using BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Locators; +using HarmonyLib; +using UnityEngine; +using VRC.SDK3.Editor.Builder; +using VRC.SDKBase.Editor; + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Patches +{ + /// + /// Patch for , + /// . + /// + [HarmonyPatch] + // ReSharper disable once UnusedType.Global + public class PatchGetLocalLowPath + { + [HarmonyPrefix] + [HarmonyPatch(typeof(VRC_SdkBuilder), nameof(VRC_SdkBuilder.GetLocalLowPath))] + [SuppressMessage("ReSharper", "InconsistentNaming")] + // ReSharper disable once UnusedMember.Global + public static bool GetLocalLowPathPrefix(ref string __result) + { + var ourPath = VrcLocator.GetLocalLowPath(); + if (!string.IsNullOrEmpty(ourPath)) + { + __result = ourPath!; + } + + Debug.Log($"LocalLow Path: \"{ourPath}\""); + return false; // skip original method + } + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchGetLocalLowPath.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchGetLocalLowPath.cs.meta new file mode 100644 index 0000000..3562f61 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchGetLocalLowPath.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 935804d9f66d4b04a935fe405d8e3d90 +timeCreated: 1776105549 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchLoadRegistryVRCInstallPath.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchLoadRegistryVRCInstallPath.cs new file mode 100644 index 0000000..b4beed8 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchLoadRegistryVRCInstallPath.cs @@ -0,0 +1,36 @@ +#nullable enable + +using System.Diagnostics.CodeAnalysis; +using BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Locators; +using HarmonyLib; +using UnityEngine; +using VRC.Core; + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Patches +{ + /// + /// Patch for VRCSDK's "Revert to Default" button in , and + /// the initialization in . + /// + [HarmonyPatch] + // ReSharper disable once InconsistentNaming + // ReSharper disable once UnusedType.Global + public static class PatchLoadRegistryVRCInstallPath + { + [HarmonyPrefix] + [HarmonyPatch(typeof(SDKClientUtilities), nameof(SDKClientUtilities.LoadRegistryVRCInstallPath))] + [SuppressMessage("ReSharper", "InconsistentNaming")] + // ReSharper disable once UnusedMember.Global + public static bool LoadRegistryVRCInstallPathPrefix(ref string __result) + { + var ourPath = VrcLocator.GetVrcInstallPathFromSteam(); + if (!string.IsNullOrEmpty(ourPath)) + { + __result = ourPath!; + } + + Debug.Log($"Found VRChat.exe from Steam: {ourPath!}"); + return false; // skip original method + } + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchLoadRegistryVRCInstallPath.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchLoadRegistryVRCInstallPath.cs.meta new file mode 100644 index 0000000..f7e2171 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchLoadRegistryVRCInstallPath.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0a75db8084de47d8ba2f9771c0b3feff +timeCreated: 1776193198 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchVRCSdkControlPanel.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchVRCSdkControlPanel.cs new file mode 100644 index 0000000..5eda3cd --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchVRCSdkControlPanel.cs @@ -0,0 +1,82 @@ +#nullable enable + +using BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Locators; +using HarmonyLib; +using UnityEditor; +using UnityEngine; + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor.Patches +{ + /// + /// Patch to add our GUI to the VRCSDK control panel. + /// + [HarmonyPatch] + // ReSharper disable once InconsistentNaming + // ReSharper disable once UnusedType.Global + public static class PatchVRCSdkControlPanel + { + [HarmonyPrefix] + [HarmonyPatch(typeof(VRCSdkControlPanel), "OnVRCInstallPathGUI")] + // ReSharper disable once UnusedMember.Global + public static bool OnVRCInstallPathGUIPrefix() + { + // show our section + OnLinuxVRChatSdkPatchGUI(); + + // show "VRChat Client - Installed Client Path" + return true; // run the original + } + + private static void OnLinuxVRChatSdkPatchGUI() + { + EditorGUILayout.LabelField("Linux VRChat SDK Patch", EditorStyles.boldLabel); + OnGUIRowCustomProton(); + EditorGUILayout.Separator(); + } + + private static void OnGUIRowCustomProton() + { + var customProtonPath = LinuxVrcEditorPrefs.CustomProtonPath; + EditorGUILayout.LabelField("Custom Proton: ", customProtonPath ?? ""); + EditorGUILayout.BeginHorizontal(); + GUILayout.Label(""); + + if (GUILayout.Button("Edit")) + { + var initPath = GetInitPath(); + if (!string.IsNullOrEmpty(customProtonPath)) + initPath = customProtonPath; + + customProtonPath = EditorUtility.OpenFolderPanel("Choose Proton directory", initPath, ""); + if (!ProtonLocator.IsValidCompatToolPath(customProtonPath)) + { + const string message = + "This does not look like a Proton path.\n" + + "Please choose a directory that contains a Proton version that you have installed." + + "It should contain files named \"proton\" and \"compatibilitytool.vdf\"."; + EditorUtility.DisplayDialog("Couldn't set custom Proton path", message, "OK"); + } + else + { + LinuxVrcEditorPrefs.CustomProtonPath = customProtonPath; + } + } + + if (GUILayout.Button("Revert to Default")) + { + LinuxVrcEditorPrefs.CustomProtonPath = null; + } + + EditorGUILayout.EndHorizontal(); + } + + private static string GetInitPath() + { + var initPath = ProtonLocator.GetCompatibilityToolsDotD(); + if (initPath != null) + return initPath; + Debug.LogWarning("Could not locate compatibilitytools.d directory"); + return string.Empty; + } + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/UI.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchVRCSdkControlPanel.cs.meta similarity index 100% rename from Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/UI.cs.meta rename to Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/Patches/PatchVRCSdkControlPanel.cs.meta diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/UI.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/UI.cs deleted file mode 100644 index f6e85f1..0000000 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/Editor/UI.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using HarmonyLib; -using UnityEditor; -using UnityEngine; - -namespace BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor -{ - [HarmonyPatch] - public static class UI - { - private static string[] _options = { "Manual", "ProtonTricks" }; - - private static void OnProtonInstallPathGUI() - { - var protonPath = Base.ProtonPath; - EditorGUILayout.LabelField("Proton", EditorStyles.boldLabel); - EditorGUILayout.LabelField("Proton Python File: ", protonPath); - EditorGUILayout.BeginHorizontal(); - GUILayout.Label(""); - - if (GUILayout.Button("Edit")) - { - var initPath = ""; - if (!string.IsNullOrEmpty(protonPath)) - initPath = protonPath; - - protonPath = EditorUtility.OpenFilePanel("Choose Proton Python File (not wine in the proton folder)", - initPath, ""); - Base.ProtonPath = protonPath; - } - - EditorGUILayout.EndHorizontal(); - EditorGUILayout.Separator(); - } - - private static void OnUseProtonTricksGUI() - { - var selectedIndex = Base.ProtonTricksPrefs ? 1 : 0; - if (EditorGUILayout.Popup("VRC/Proton Path Selection", selectedIndex, _options) != selectedIndex) - Base.ProtonTricksPrefs = !Base.ProtonTricksPrefs; - } - - private static CodeInstruction _originalCall; - - private static void OnGUI() - { - if (Base.HasProtonTricks) - OnUseProtonTricksGUI(); - else - { - EditorGUILayout.LabelField( - "If proton tricks is installed, the patch will optionally use that to find your proton install"); - EditorGUILayout.Space(); - } - - if (!Base.ProtonTricksPrefs) - OnProtonInstallPathGUI(); - - if (_originalCall.operand is MethodInfo method) - method.Invoke(null, null); - } - - [HarmonyTranspiler] - [HarmonyPatch(typeof(VRCSdkControlPanel), "ShowSettings")] - public static IEnumerable ShowSettingsTranspiler(IEnumerable instructions) - { - var codes = instructions.ToList(); - for (var i = 0; i < codes.Count; i++) - { - if (codes[i].opcode != OpCodes.Call) continue; - if (!codes[i].operand.ToString().Contains("OnVRCInstallPathGUI")) continue; - - _originalCall = codes[i]; - codes[i] = CodeInstruction.Call(typeof(UI), nameof(OnGUI)); - - break; - } - - return codes.AsEnumerable(); - } - } -} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/package.json b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/package.json index b2b1ade..0107154 100644 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.base/package.json +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.base/package.json @@ -7,8 +7,8 @@ "name": "BefuddledLabs" }, "unity": "2022.3", - "description": "Patches the VRChat SDK to work properly on linux.", + "description": "Patches the VRChat Base SDK to work properly on Linux.", "vpmDependencies": { - "com.vrchat.base": "^3.8.2" + "com.vrchat.base": "^3.10.3" } } diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/LinuxVRChatSDKPatch-Worlds.Editor.asmdef b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/LinuxVRChatSdkPatch.Worlds.Editor.asmdef similarity index 87% rename from Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/LinuxVRChatSDKPatch-Worlds.Editor.asmdef rename to Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/LinuxVRChatSdkPatch.Worlds.Editor.asmdef index 688c22c..b90ce19 100644 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/LinuxVRChatSDKPatch-Worlds.Editor.asmdef +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/LinuxVRChatSdkPatch.Worlds.Editor.asmdef @@ -1,6 +1,6 @@ { "name": "LinuxVRChatSdkPatch.Worlds.Editor", - "rootNamespace": "", + "rootNamespace": "BefuddledLabs.LinuxVRChatSdkPatch.Worlds", "references": [ "VRC.SDKBase", "VRC.SDKBase.Editor", diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/LinuxVRChatSDKPatch-Worlds.Editor.asmdef.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/LinuxVRChatSdkPatch.Worlds.Editor.asmdef.meta similarity index 100% rename from Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/LinuxVRChatSDKPatch-Worlds.Editor.asmdef.meta rename to Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/LinuxVRChatSdkPatch.Worlds.Editor.asmdef.meta diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patch.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patch.cs index 2d890ab..f858f92 100644 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patch.cs +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patch.cs @@ -1,22 +1,27 @@ +#nullable enable + +using System.Linq; using System.Runtime.InteropServices; using HarmonyLib; using UnityEditor; +using UnityEngine; namespace BefuddledLabs.LinuxVRChatSdkPatch.Worlds.Editor { [InitializeOnLoad] public static class Patch { - internal static Harmony _harmony; - + private const string HarmonyID = "BefuddledLabs.LinuxVRChatSdkPatch.Worlds"; + private static readonly Harmony WorldsHarmony = new(HarmonyID); static Patch() { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return; - _harmony = new Harmony("BefuddledLabs.LinuxVRChatSdkPatch-World"); - _harmony.PatchAll(); + WorldsHarmony.PatchAll(); + var count = WorldsHarmony.GetPatchedMethods().Count(); + Debug.Log($"{HarmonyID}: Patched {count} methods"); } } } diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patches.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patches.meta new file mode 100644 index 0000000..56ec26d --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patches.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b43cb29797044d5b94af0eab40a60483 +timeCreated: 1777017016 \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patches/PatchBuildAndTestWorld.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patches/PatchBuildAndTestWorld.cs new file mode 100644 index 0000000..5f7d6a9 --- /dev/null +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patches/PatchBuildAndTestWorld.cs @@ -0,0 +1,102 @@ +#nullable enable + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using BefuddledLabs.LinuxVRChatSdkPatch.Base.Editor; +using HarmonyLib; +using UnityEngine.Networking; +using VRC.Core; +using VRC.SDK3.Editor.Builder; +using VRC.SDKBase.Editor; +using Debug = UnityEngine.Debug; + +namespace BefuddledLabs.LinuxVRChatSdkPatch.Worlds.Editor.Patches +{ + /// + /// Patch for the VRChat Worlds SDK Build and Test functionality. + /// + [HarmonyPatch] + // ReSharper disable once UnusedType.Global + public static class PatchBuildAndTestWorld + { + [HarmonyPrefix] + [HarmonyPatch(typeof(VRCWorldAssetExporter), "RunWorldTestDesktop", typeof(string))] + [SuppressMessage("ReSharper", "InconsistentNaming")] + // ReSharper disable once UnusedMember.Global + public static bool RunWorldTestDesktopPrefix(object[] __args) + { + var bundleFilePath = (string)__args[0]; + + // ORIGINAL METHOD, with patches. Patches are marked clearly. + + // - gather proton, prefix, steam root, and compatdata + var launchConfig = LaunchConfiguration.Resolve(LinuxVrcEditorPrefs.CustomProtonPath); + if (launchConfig == null) + { + Debug.LogError("Couldn't find everything needed; aborting build-and-test."); + return false; + } + // + + // - logging + launchConfig.DebugPrint(); + Debug.Log($"Bundle path: \"{bundleFilePath}\""); + // + + // translate path with winepath, relative to Z:/ + bundleFilePath = ToWinePath(bundleFilePath); + // + var bundleUri = UnityWebRequest.EscapeURL(bundleFilePath).Replace("+", "%20"); + var randomDigits = VRC.Tools.GetRandomDigits(10); + var executable = SDKClientUtilities.GetSavedVRCInstallPath(); + // - remove URL path, this is almost never taken in Windows either + // if (string.IsNullOrEmpty(executable) || !File.Exists(executable)) + // executable = $"vrchat://create?roomId={randomDigits}&hidden=true&name=BuildAndRun&url=file:///{bundleUri}"; + // + var argUrl = $"--url=create?roomId={randomDigits}&hidden=true&name=BuildAndRun&url=file:///{bundleUri}"; + var argsRest = + $"--enable-debug-gui --enable-sdk-log-levels --enable-udon-debug-logging {(VRCSettings.ForceNoVR ? " --no-vr" : "")}{(VRCSettings.WatchWorlds ? " --watch-worlds" : "")}"; + // - set proton as the executable, prefix args with "run" (as in the Steam compatibility tool interface Verb) and path/to/VRChat.exe + var startInfo = new ProcessStartInfo(launchConfig.ProtonExecutable, $"run {executable} {argUrl} {argsRest}") + // + { + WorkingDirectory = Path.GetDirectoryName(executable) ?? "", + // - add these environment variables and skip parsing with system shell + Environment = + { + { "STEAM_COMPAT_DATA_PATH", launchConfig.CompatDataPath }, + { "STEAM_COMPAT_CLIENT_INSTALL_PATH", launchConfig.SteamRoot }, + { "STEAM_COMPAT_INSTALL_PATH", launchConfig.VrcInstallRoot }, + }, + UseShellExecute = false + // + }; + for (var index = 0; index < VRCSettings.NumClients; ++index) + { + Process.Start(startInfo); + Thread.Sleep(3000); + } + + AnalyticsSDK.BuildAndTestLaunched(RuntimeInformation.OSDescription, "Desktop", "world"); + // END ORIGINAL METHOD + + return false; // skips the original + } + + /// + /// Turn /home/you/some/filepath/file.txt into Z:\home\you\some\filepath\file.txt + /// + /// The path to translate. + /// An absolute Windows-style path. + private static string ToWinePath(string path) + { + var fullPath = Path.GetFullPath(path); + var winePath = "Z:" + fullPath.Replace("/", @"\"); + Debug.Log($"Translated path: \"{winePath}\""); + return winePath; + } + } +} \ No newline at end of file diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/World.cs.meta b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patches/PatchBuildAndTestWorld.cs.meta similarity index 100% rename from Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/World.cs.meta rename to Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/Patches/PatchBuildAndTestWorld.cs.meta diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/World.cs b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/World.cs deleted file mode 100644 index 5473b7e..0000000 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/Editor/World.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using HarmonyLib; -using UnityEngine.Networking; -using VRC.Core; -using VRC.SDK3.Editor.Builder; -using VRC.SDKBase.Editor; -using Debug = UnityEngine.Debug; - -namespace BefuddledLabs.LinuxVRChatSdkPatch.Worlds.Editor -{ - [HarmonyPatch] - public static class World - { - public static void InQuotes(this StringBuilder sb, object contents) - { - sb.Append('"'); - sb.Append(contents); - sb.Append('"'); - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(VRCWorldAssetExporter), "RunWorldTestDesktop", typeof(string))] - public static bool RunWorldTestDesktopPrefix(object[] __args) - { - var vrcInstallPath = SDKClientUtilities.GetSavedVRCInstallPath(); - if (string.IsNullOrEmpty(vrcInstallPath) || !File.Exists(vrcInstallPath)) - { - Debug.LogError("couldn't get VRChat path.. You probobly forgot to set it at: " + - "VRChat control panel > Settings > VRChat Client"); - return false; - } - - var useProtonTricks = Base.Editor.Base.HasProtonTricks && Base.Editor.Base.ProtonTricksPrefs; - - var protonInstallPath = Base.Editor.Base.ProtonPath; - var compatDataPath = Base.Editor.Base.GetVrcCompatDataPath(); - - if (!useProtonTricks) // if we are using protontricks we shouldn't check that these paths are valid - { - if (string.IsNullOrEmpty(protonInstallPath) || !File.Exists(protonInstallPath)) - { - Debug.LogError("couldn't get Proton path.. You probobly forgot to set it at: " + - "VRChat control panel > Settings > Proton Python File"); - return false; - } - - if (compatDataPath == null) // Check if we could find the compatdata directory - { - Debug.LogError("Could not find compatdata Path"); - return false; - } - } - - // Making sure that the paths are using forward slashes - var bundleFilePath = ((string)__args[0]).Replace('\\', '/'); - - bundleFilePath = "file:///" + UnityWebRequest.EscapeURL(bundleFilePath).Replace("+", "%20"); - - var args = new StringBuilder(); - if (useProtonTricks) - args.Append("--appid 438100 "); - else - args.Append("run "); - - args.InQuotes(vrcInstallPath); - args.Append(' '); - - // @formatter:off - args.Append("--url=create?roomId="); - args.Append(VRC.Tools.GetRandomDigits(10)); // Random roomId - args.Append("&hidden=true"); - args.Append("&name=BuildAndRun"); - args.Append("&url="); - args.InQuotes(bundleFilePath); - // @formatter:on - - args.Append(" --enable-debug-gui"); - args.Append(" --enable-sdk-log-levels"); - args.Append(" --enable-udon-debug-logging"); - if (VRCSettings.ForceNoVR) - args.Append(" --no-vr"); - if (VRCSettings.WatchWorlds) - args.Append(" --watch-worlds"); - - - var argsPathFixed = - Regex.Replace(args.ToString(), @"file:[/\\]*", - "file:///Z:/"); // The file we have is relative to / and not the "c drive" Z:/ is / - - var launchCommand = Base.Editor.Base.HasProtonTricks && Base.Editor.Base.ProtonTricksPrefs ? "protontricks-launch" : protonInstallPath; - - Debug.Log(launchCommand + " " + argsPathFixed); - - var processStartInfo = - new ProcessStartInfo(launchCommand, argsPathFixed) - { - EnvironmentVariables = - { - { "STEAM_COMPAT_DATA_PATH", compatDataPath }, - { "STEAM_COMPAT_CLIENT_INSTALL_PATH", Environment.GetEnvironmentVariable("HOME") + "/.steam/" } - }, - WorkingDirectory = Path.GetDirectoryName(vrcInstallPath) ?? "", - UseShellExecute = false - }; - for (var index = 0; index < VRCSettings.NumClients; ++index) - { - Process.Start(processStartInfo); - Thread.Sleep(3000); - } - - return false; - } - } -} diff --git a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/package.json b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/package.json index e87cca2..4d3b210 100644 --- a/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/package.json +++ b/Packages/befuddledlabs.linuxvrchatsdkpatch.worlds/package.json @@ -7,9 +7,9 @@ "name": "BefuddledLabs" }, "unity": "2022.3", - "description": "Patches the VRChat SDK to work properly on linux.", + "description": "Patches the VRChat Worlds SDK to work properly on Linux.", "vpmDependencies": { - "com.vrchat.worlds": "^3.8.2", + "com.vrchat.worlds": "^3.10.3", "befuddledlabs.linuxvrchatsdkpatch-base": "0.2.2" } } diff --git a/README.md b/README.md index 8525396..037058e 100644 --- a/README.md +++ b/README.md @@ -4,32 +4,54 @@ > This modifies the VRChat SDK using [Harmony](https://github.com/pardeike/Harmony) to properly work on Linux. \ > This is directly against the VRChat Terms of Service. + + > [!IMPORTANT] -> Please support this [canny issue](https://feedback.vrchat.com/sdk-bug-reports/p/add-proton-support-to-the-sdk-for-local-tests) ([Add Proton support to the SDK for local tests](https://feedback.vrchat.com/sdk-bug-reports/p/add-proton-support-to-the-sdk-for-local-tests)) so that these patches wouldn't be required in the future. +> Please support this [Canny issue - Add Proton support to the SDK for local tests](https://feedback.vrchat.com/sdk-bug-reports/p/add-proton-support-to-the-sdk-for-local-tests) so that these patches wouldn't be required in the future. + +## How to Install + +1. [ALCOM][alcom] \[Recommended\] + 1. Add the **Linux VRChat SDK Patch** package to [ALCOM][alcom] via the listing at [`befuddledlabs.github.io/LinuxVRChatSDKPatch`](https://befuddledlabs.github.io/LinuxVRChatSDKPatch/). + 2. Install the appropriate **Linux VRChat SDK Patch** package for your project: **Worlds** or **Avatars**. +2. Manually + 1. Download the **Base** and either **Worlds** or **Avatars** UnityPackage(s) from [Releases](https://github.com/BefuddledLabs/LinuxVRChatSDKPatch/releases). + +[alcom]: https://github.com/vrc-get/vrc-get + +## How to Use + +![VRCSDKSettings](/Docs/VRCSDKSettings.webp) -## How to install -1. [ALCOM](https://github.com/vrc-get/vrc-get) [Recommended] - 1. Add the `Linux VRChat SDK Patch` package to [ALCOM](https://github.com/vrc-get/vrc-get) via the listing at [`befuddledlabs.github.io/LinuxVRChatSDKPatch`](https://befuddledlabs.github.io/LinuxVRChatSDKPatch/). - 2. Install the appropriate package `Linux VRChat SDK Patch` Worlds or Avatars -2. Manual - 1. Download the Base and either Worlds or Avatars UnityPackage(s) from the [Releases](https://github.com/BefuddledLabs/LinuxVRChatSDKPatch/releases). +Everything should work out of the box. -## How to use -![VRCSdkSettings](/Docs/VRCSdkSettings) +This package detects VRChat's game directory and Proton prefix, your preferred Proton version for VRChat as set in Steam, whether custom or system-wide or official, in any Steam library, and patches VRCSDK functionality to take this into account. -Select the VRChat binary the settings of the VRChat SDK's settings panel. \ -if you have `protontricks` installed the patch will auto detect the proton install for VRChat, \ -if you don't have protontricks you'll also need to select the proton python file in the VRChat SDK's settings panel. +If you want, you can choose a different VRChat.exe or different Proton version to launch by clicking **Edit** in the VRCSDK Settings tab. + +If it isn't working, use the buttons in the **Tools → Linux VRChat SDK Patch** menu to print some logs, then please open a [GitHub issue](https://github.com/BefuddledLabs/LinuxVRChatSDKPatch/issues)! + +## Features + +- Fixes the VRCSDK initialization so it can correctly find VRChat.exe. +- Prevents the pointless creation (lol) and use of `~/.local/share/VRChat`, instead saving test worlds and avatars to the Proton prefix. +- Fixes the Content Manager tab so it can show your test avatars. +- Fixes the Build and Test button, allowing for multiple clients to test a world in offline mode. +- Adds some UI in VRCSDK Settings tab to select a different Proton to use instead of what's set in Steam. +- Show a one-time dialog to ask for votes on the Canny for Linux support. +- Add debugging helpers to the **Tools → Linux VRChat SDK Patch** menu. ## Development +For a technical overview on what these packages do, see [NOTES.md](NOTES.md). + To make modifications to this package: -1. Clone this repository to a non-unity project folder. -2. Create a symbolic link from the package(s) into a Unity project's package folder. -3. The package should be editable via Unity and any external editor. +1. Uninstall the **Linux VRChat SDK Patch** packages from your project, if necessary. +2. Clone this repository to a directory not inside your Unity project. +3. Create a symbolic link from the package(s) into your Unity project's package folder. +4. The package should be editable via Unity and any external editor. ## Acknowledgements -- [*Bartkk*](https://github.com/Bartkk0) - - For making the original [VRCSDKonLinux](https://github.com/Bartkk0/VRCSDKonLinux). - - And sharing their latest patches they hadn't gotten around to releasing. + +[**Bartkk**](https://github.com/Bartkk0) for making the original [VRCSDKonLinux](https://github.com/Bartkk0/VRCSDKonLinux) and sharing their then-unreleased patches. diff --git a/source.json b/source.json index bda080d..c500557 100644 --- a/source.json +++ b/source.json @@ -1,17 +1,17 @@ { - "name":"Linux VRChat SDK Patch Listing", - "id":"befuddledlabs.linuxvrchatsdkpatch", - "url":"https://befuddledlabs.github.io/LinuxVRChatSDKPatch/index.json", - "author":{ - "name":"BefuddledLabs" + "name": "Linux VRChat SDK Patch Listing", + "id": "befuddledlabs.linuxvrchatsdkpatch", + "url": "https://befuddledlabs.github.io/LinuxVRChatSDKPatch/index.json", + "author": { + "name": "BefuddledLabs" }, - "description":"Listing for some Linux patchs to the VRChat SDK", - "infoLink":{ - "url":"https://befuddledlabs.github.io/LinuxVRChatSDKPatch/", - "text":"View on GitHub" + "description": "Listing for some Linux patches to the VRChat SDK", + "infoLink": { + "url": "https://befuddledlabs.github.io/LinuxVRChatSDKPatch/", + "text": "View on GitHub" }, - "bannerUrl":"banner.png", - "githubRepos":[ + "bannerUrl": "banner.png", + "githubRepos": [ "BefuddledLabs/LinuxVRChatSDKPatch" ] }