Compare commits
717 Commits
1.1.213.39
...
v1.0.20-ma
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b1cf5fb638 | ||
![]() |
1d6a2a9a60 | ||
![]() |
455be2393e | ||
![]() |
6131a9003b | ||
![]() |
2a17c4bc09 | ||
![]() |
ccc1df9349 | ||
![]() |
d80c7b9fba | ||
![]() |
64c6ce289c | ||
![]() |
67de82a103 | ||
![]() |
357e0446df | ||
![]() |
f2291a57c3 | ||
![]() |
8b862df6ea | ||
![]() |
1c92f69bd4 | ||
![]() |
70833adb44 | ||
![]() |
ab61674b47 | ||
![]() |
c8ddf691ba | ||
![]() |
76afccd9db | ||
![]() |
4882dc673a | ||
![]() |
8546165e59 | ||
![]() |
4cab214635 | ||
![]() |
9a6253d0ef | ||
![]() |
55eac7ecbf | ||
![]() |
057d126658 | ||
![]() |
8efe8ae398 | ||
![]() |
e2cdd803af | ||
![]() |
b6d8cf07f8 | ||
![]() |
1b23cd564e | ||
![]() |
f6ef662344 | ||
![]() |
58b9c926ae | ||
![]() |
81d11a32fe | ||
![]() |
eb9afd5e36 | ||
![]() |
1de0b63907 | ||
![]() |
f89170c37d | ||
![]() |
73ee686a84 | ||
![]() |
e49910e121 | ||
![]() |
83805cba77 | ||
![]() |
32a61d2feb | ||
![]() |
133e20c758 | ||
![]() |
d7c5aa3fa8 | ||
![]() |
f6d59c75c6 | ||
![]() |
fd8c731713 | ||
![]() |
55a552af60 | ||
![]() |
770d18859b | ||
![]() |
22e60edc1f | ||
![]() |
c97fd07f89 | ||
![]() |
7e87cdd5a0 | ||
![]() |
b67eff406f | ||
![]() |
29a5c09ac7 | ||
![]() |
a1668abcd4 | ||
![]() |
2d8ca29935 | ||
![]() |
4f9af817a5 | ||
![]() |
7bd4beb82d | ||
![]() |
1ec57bc4c9 | ||
![]() |
63f14c984d | ||
![]() |
6c227b4a6e | ||
![]() |
b846112c43 | ||
![]() |
23f9b1507d | ||
![]() |
2a0555ea4f | ||
![]() |
0395d0bc7f | ||
![]() |
f2c03cbc34 | ||
![]() |
147c94fd93 | ||
![]() |
0e2ecf16f3 | ||
![]() |
36eb256553 | ||
![]() |
e90ca41c3c | ||
![]() |
5528231cc2 | ||
![]() |
4d01b976f1 | ||
![]() |
ff12b214cd | ||
![]() |
55c9f5296f | ||
![]() |
61e1ed7a59 | ||
![]() |
218cd30152 | ||
![]() |
bf3b819d02 | ||
![]() |
b56c8f711c | ||
![]() |
c5c6ef1cd2 | ||
![]() |
3e01cecdcc | ||
![]() |
966b2f5756 | ||
![]() |
a5743701ea | ||
![]() |
7ea1a523e1 | ||
![]() |
5445459dc7 | ||
![]() |
f2eed13473 | ||
![]() |
af894e7507 | ||
![]() |
94475f7bd1 | ||
![]() |
b5ca0bf392 | ||
![]() |
fdd8479016 | ||
![]() |
c915b385e3 | ||
![]() |
f451c552ec | ||
![]() |
0aa17a26ac | ||
![]() |
41b16a6d75 | ||
![]() |
b5f53dd6c6 | ||
![]() |
5cc2d6652d | ||
![]() |
6fb6226be5 | ||
![]() |
cee5cb3c4d | ||
![]() |
b6fb470b84 | ||
![]() |
6c74ed3869 | ||
![]() |
2c44860a70 | ||
![]() |
169b543f7d | ||
![]() |
efd15fcd62 | ||
![]() |
b372230eae | ||
![]() |
e5cb3e5ae0 | ||
![]() |
8f30b7605b | ||
![]() |
bec29c5bb4 | ||
![]() |
f84a27d705 | ||
![]() |
19f9388a43 | ||
![]() |
f25a412a47 | ||
![]() |
f7a63f17cc | ||
![]() |
aa5963b29b | ||
![]() |
15c4276a81 | ||
![]() |
04b13cbd6c | ||
![]() |
f6971c715b | ||
![]() |
c3e65a5bdd | ||
![]() |
93167f349d | ||
![]() |
b12acddab3 | ||
![]() |
fb9dbf40da | ||
![]() |
1b8bddabc8 | ||
![]() |
a4edbf3bd9 | ||
![]() |
3d13460302 | ||
![]() |
fad656e837 | ||
![]() |
fc986c87a6 | ||
![]() |
80a820ae3d | ||
![]() |
e9e446f8ab | ||
![]() |
b53194184c | ||
![]() |
bcb921cd9d | ||
![]() |
c1632b6b20 | ||
![]() |
88d10d1955 | ||
![]() |
d6fe6234d7 | ||
![]() |
29fcdc0bf8 | ||
![]() |
48ddc6389f | ||
![]() |
a3d722eb16 | ||
![]() |
b386e53ff5 | ||
![]() |
1dd444759b | ||
![]() |
76ea5fdbc1 | ||
![]() |
035325da22 | ||
![]() |
33b2fa7094 | ||
![]() |
b220243b75 | ||
![]() |
f2a077deed | ||
![]() |
d3de1426b2 | ||
![]() |
86b6b94d25 | ||
![]() |
0a19663c33 | ||
![]() |
d236fd9bd9 | ||
![]() |
bc54b79098 | ||
![]() |
5035fa39b7 | ||
![]() |
2395c33995 | ||
![]() |
92ae252210 | ||
![]() |
5d40cf373d | ||
![]() |
9cbcc3ed85 | ||
![]() |
a1f397f9bf | ||
![]() |
540b17448a | ||
![]() |
21fd997554 | ||
![]() |
23b0318591 | ||
![]() |
5727e3b1b8 | ||
![]() |
28164b491b | ||
![]() |
b02d613a28 | ||
![]() |
c18d6d4795 | ||
![]() |
5780cf7c95 | ||
![]() |
cc91fa3653 | ||
![]() |
52ef0b4d6d | ||
![]() |
ef2be78102 | ||
![]() |
9c2dc69e3c | ||
![]() |
8107bf131b | ||
![]() |
0220f46741 | ||
![]() |
e4ad517cb1 | ||
![]() |
792151a8b7 | ||
![]() |
fb67b2f3d1 | ||
![]() |
30c1f07207 | ||
![]() |
562a4554f3 | ||
![]() |
9f22b63227 | ||
![]() |
c96e7a284a | ||
![]() |
dbcd696936 | ||
![]() |
79368aa6dd | ||
![]() |
d661c893d5 | ||
![]() |
ca99404b42 | ||
![]() |
aec03840cc | ||
![]() |
5630eb14c2 | ||
![]() |
6680b0adb2 | ||
![]() |
ff0d881273 | ||
![]() |
ed5d0ea474 | ||
![]() |
997a3ca31c | ||
![]() |
ef444730b7 | ||
![]() |
7bfc6077b9 | ||
![]() |
624bd5a2a4 | ||
![]() |
1e19f9aedf | ||
![]() |
1b4c5ace61 | ||
![]() |
28037e11df | ||
![]() |
036f21de81 | ||
![]() |
43adecaf99 | ||
![]() |
c58e4dccc0 | ||
![]() |
1a1f8b2235 | ||
![]() |
f1616f6532 | ||
![]() |
a42f5ab273 | ||
![]() |
f15139e907 | ||
![]() |
f4399d441d | ||
![]() |
fd4b49e0d5 | ||
![]() |
496a276524 | ||
![]() |
c831df74d0 | ||
![]() |
e77d53fd41 | ||
![]() |
983aff2091 | ||
![]() |
570bc83a6d | ||
![]() |
8dca8bbe65 | ||
![]() |
0e91b47dba | ||
![]() |
3f7e95b502 | ||
![]() |
4f30a47c07 | ||
![]() |
ecbbd6fbf2 | ||
![]() |
13f5648963 | ||
![]() |
dfb7314207 | ||
![]() |
9293801037 | ||
![]() |
d8ac3b2353 | ||
![]() |
056f0e5614 | ||
![]() |
ada5418413 | ||
![]() |
b79e970e66 | ||
![]() |
6fc02edd1e | ||
![]() |
9a8c03106e | ||
![]() |
4a5e41b747 | ||
![]() |
649dcf4019 | ||
![]() |
ae3d1262f5 | ||
![]() |
7b2b0edbdf | ||
![]() |
353746ec04 | ||
![]() |
2fb9b4173a | ||
![]() |
fe7242f36f | ||
![]() |
8015f8486c | ||
![]() |
92d7f8f578 | ||
![]() |
fc9a9f8e09 | ||
![]() |
f94bfc2850 | ||
![]() |
db283fad15 | ||
![]() |
dd7288b787 | ||
![]() |
a6e41b2a90 | ||
![]() |
0e6ac14b74 | ||
![]() |
97f380fb7b | ||
![]() |
3e5b29c84d | ||
![]() |
5d19cc7cf1 | ||
![]() |
19995eba1a | ||
![]() |
990ed0f6bf | ||
![]() |
dc0a085d86 | ||
![]() |
0a852c49e8 | ||
![]() |
59588517d8 | ||
![]() |
3a7cbe48b8 | ||
![]() |
ed298cdfb0 | ||
![]() |
3925c34610 | ||
![]() |
a6fa112050 | ||
![]() |
d97a6a52a4 | ||
![]() |
121846eeae | ||
![]() |
ebef1edc09 | ||
![]() |
bbaf1384ba | ||
![]() |
7036a4da81 | ||
![]() |
91ceb0aa22 | ||
![]() |
de12327ac2 | ||
![]() |
6f65b54883 | ||
![]() |
c0ffd7e641 | ||
![]() |
b0c1ccf9b4 | ||
![]() |
ed3e1aa846 | ||
![]() |
7f720a1753 | ||
![]() |
6cbcbc6f3f | ||
![]() |
53ae9bc42d | ||
![]() |
9813d6946a | ||
![]() |
c7651c9949 | ||
![]() |
34211c4b3f | ||
![]() |
8bc4c247e6 | ||
![]() |
92406a051a | ||
![]() |
56f7578d13 | ||
![]() |
068b074de0 | ||
![]() |
bb0ee3b861 | ||
![]() |
93bb11c135 | ||
![]() |
2fd8c17525 | ||
![]() |
b974487fc4 | ||
![]() |
8198243425 | ||
![]() |
b4addcc125 | ||
![]() |
2d7893b9de | ||
![]() |
60c0ae1049 | ||
![]() |
aaabfa2d01 | ||
![]() |
5c8a1f3677 | ||
![]() |
b37cd74e56 | ||
![]() |
af9a31f4ad | ||
![]() |
7d8838c0ee | ||
![]() |
2c47cfd60e | ||
![]() |
6422f7c576 | ||
![]() |
369a3217f7 | ||
![]() |
f44bf935d3 | ||
![]() |
0e43eee2f3 | ||
![]() |
c6121ab590 | ||
![]() |
72ceeffdad | ||
![]() |
5eb6e9990c | ||
![]() |
3d3769cf5a | ||
![]() |
2a64151f67 | ||
![]() |
34616607a8 | ||
![]() |
796feb05e6 | ||
![]() |
14ae58ca0c | ||
![]() |
b555f46f56 | ||
![]() |
dc5e7e4cb0 | ||
![]() |
deb0b202bf | ||
![]() |
0a5a70f583 | ||
![]() |
f16e825b57 | ||
![]() |
263a8229fa | ||
![]() |
dc7a27a5e3 | ||
![]() |
651865f28a | ||
![]() |
27493d3b23 | ||
![]() |
295bbd62b8 | ||
![]() |
5cf3ae1203 | ||
![]() |
c650e190fb | ||
![]() |
5cd5873ec3 | ||
![]() |
42a66b04c5 | ||
![]() |
a237185e4c | ||
![]() |
df225a3d2f | ||
![]() |
8c59098c28 | ||
![]() |
bbd45df54d | ||
![]() |
8aa0ccd437 | ||
![]() |
74cdd9d055 | ||
![]() |
704f202ce8 | ||
![]() |
fb3082094a | ||
![]() |
7a71cbf756 | ||
![]() |
53c279fa00 | ||
![]() |
8dc542a31c | ||
![]() |
4dee127ce8 | ||
![]() |
3e341e02c8 | ||
![]() |
a673848089 | ||
![]() |
642186678e | ||
![]() |
e37357aea5 | ||
![]() |
967d8ce068 | ||
![]() |
2bb3aa84a7 | ||
![]() |
82ddd3942b | ||
![]() |
ac672092f1 | ||
![]() |
1c6eec61af | ||
![]() |
dda7864c1a | ||
![]() |
ddc13cccec | ||
![]() |
95ac2392e8 | ||
![]() |
0122f9e989 | ||
![]() |
f265f7e773 | ||
![]() |
61307ce584 | ||
![]() |
8f755a5cfc | ||
![]() |
c3addd05f7 | ||
![]() |
bbcfc9fb07 | ||
![]() |
63ac99f97a | ||
![]() |
6d29bce267 | ||
![]() |
6b039288d5 | ||
![]() |
3f803b8107 | ||
![]() |
814a9def7f | ||
![]() |
8c7891809e | ||
![]() |
30729049b3 | ||
![]() |
1a0f80dce7 | ||
![]() |
6973bc8e7d | ||
![]() |
ae3edd67da | ||
![]() |
b66db19c0a | ||
![]() |
36b931f680 | ||
![]() |
9c63054926 | ||
![]() |
276a4522d6 | ||
![]() |
4e2e58bb4c | ||
![]() |
8b7a07ffe7 | ||
![]() |
ffc6a60ee9 | ||
![]() |
f990d27851 | ||
![]() |
fa1a968b8f | ||
![]() |
3d8bf78213 | ||
![]() |
90479dfea2 | ||
![]() |
471912759a | ||
![]() |
844d4be96a | ||
![]() |
0b76ded5aa | ||
![]() |
76aa95588e | ||
![]() |
6618752b84 | ||
![]() |
af5f39af44 | ||
![]() |
03ef57daeb | ||
![]() |
a059d18195 | ||
![]() |
9f610d5ae8 | ||
![]() |
c9adb2a212 | ||
![]() |
7fd814d595 | ||
![]() |
e7065a7159 | ||
![]() |
8625db7ae4 | ||
![]() |
68e6774e26 | ||
![]() |
a7c6ae7382 | ||
![]() |
9c06049628 | ||
![]() |
34e5f4df49 | ||
![]() |
b7b58f5870 | ||
![]() |
f9d75856d1 | ||
![]() |
66b7adf485 | ||
![]() |
76637b130c | ||
![]() |
9a1a31c424 | ||
![]() |
faef000245 | ||
![]() |
c2035668cd | ||
![]() |
f1201c6259 | ||
![]() |
bdaa674662 | ||
![]() |
3b17eb4750 | ||
![]() |
9221d412ca | ||
![]() |
65bb71aabf | ||
![]() |
dcd0fa86b9 | ||
![]() |
72be1b8dbf | ||
![]() |
1d7b642c50 | ||
![]() |
f7d45ca338 | ||
![]() |
831722dd84 | ||
![]() |
ee3dd0b5bd | ||
![]() |
7480847677 | ||
![]() |
0ff715af1b | ||
a318aa87cf | |||
![]() |
74b00d3ab1 | ||
![]() |
355375e9db | ||
![]() |
016203d2bc | ||
![]() |
b65efa2968 | ||
![]() |
4fc5f10bad | ||
![]() |
f8e9d68ceb | ||
![]() |
65e8d62391 | ||
![]() |
93fa82201a | ||
![]() |
71930182dd | ||
![]() |
8764540d3b | ||
![]() |
6a6676c1cb | ||
![]() |
5bf91f1891 | ||
![]() |
14e16a959f | ||
![]() |
3b72724966 | ||
![]() |
9a0e7809cd | ||
![]() |
94c25a70b3 | ||
![]() |
4901120be4 | ||
![]() |
21e45b5e45 | ||
![]() |
b829e90edb | ||
![]() |
fbf7fa6176 | ||
![]() |
2b413ef609 | ||
![]() |
0f06ee5688 | ||
![]() |
ec065ec329 | ||
![]() |
c9a5472282 | ||
![]() |
b12199c65b | ||
![]() |
b4ac097910 | ||
![]() |
aae4ec97a9 | ||
![]() |
45d931b351 | ||
![]() |
f53c9660fe | ||
![]() |
c889854818 | ||
![]() |
b8b0a0fcce | ||
![]() |
b3f9d7e5c7 | ||
![]() |
94b457d9c0 | ||
![]() |
0c58655708 | ||
![]() |
ba98e0a15a | ||
![]() |
5496ad1198 | ||
![]() |
7bad6149b5 | ||
![]() |
378905268d | ||
![]() |
f56a700fea | ||
![]() |
736176ce27 | ||
![]() |
96d749c512 | ||
![]() |
17514c89ad | ||
![]() |
8b08f2b747 | ||
![]() |
07bb0fc4cf | ||
![]() |
dbea9d83f4 | ||
![]() |
8989ae94a7 | ||
![]() |
4db83e6f65 | ||
![]() |
9286f2e559 | ||
![]() |
045a572058 | ||
![]() |
f68be8e4c9 | ||
![]() |
ec4572c390 | ||
![]() |
b89b61496b | ||
![]() |
63f504feb7 | ||
![]() |
06eca83ff9 | ||
![]() |
ebc8b7a7fd | ||
![]() |
4c34a653bd | ||
![]() |
38d2f1b62e | ||
![]() |
d8915d1893 | ||
![]() |
873acfcb4f | ||
![]() |
030df5029b | ||
![]() |
7404b6bd2d | ||
![]() |
b9e9be227a | ||
![]() |
f2537706e7 | ||
![]() |
a8251d9385 | ||
![]() |
b1edd62c0b | ||
![]() |
c1e315fa40 | ||
![]() |
bfcf96f1ad | ||
![]() |
d257e9e1e8 | ||
![]() |
85e307f8db | ||
![]() |
03fa0a73b6 | ||
![]() |
2f157a6438 | ||
![]() |
cdde72cbe0 | ||
![]() |
b18420ad55 | ||
![]() |
d92daccdbf | ||
![]() |
6b3cc6c421 | ||
![]() |
18af85c4d7 | ||
![]() |
6c6ff18ec3 | ||
![]() |
67e663f023 | ||
![]() |
3f717b304a | ||
![]() |
300af03012 | ||
![]() |
3d8d333f10 | ||
![]() |
d87cc7f1e7 | ||
![]() |
d59ef20f72 | ||
![]() |
b2bf0229ed | ||
![]() |
f03bfd2d7a | ||
![]() |
3dd646d6e9 | ||
![]() |
58ad553b39 | ||
![]() |
83056bacf4 | ||
![]() |
2751eaf399 | ||
![]() |
869ba0d33c | ||
![]() |
aeb29d9a69 | ||
![]() |
979d5914a9 | ||
![]() |
e72f5b7c37 | ||
![]() |
42d3324fc1 | ||
![]() |
e242ed6f1f | ||
![]() |
a2acb9c11c | ||
![]() |
444da941c9 | ||
![]() |
f19fd84f1d | ||
![]() |
b5793d36a8 | ||
![]() |
a71c03124b | ||
![]() |
66c484796d | ||
![]() |
a4927030d7 | ||
![]() |
c32badb750 | ||
![]() |
356eb849f2 | ||
![]() |
04e949ed0c | ||
![]() |
13dc8622c9 | ||
![]() |
a2b9c4724d | ||
![]() |
f326e569a1 | ||
![]() |
c1961dee5f | ||
![]() |
e42a231553 | ||
![]() |
b3d9a64632 | ||
![]() |
47c7c37fa9 | ||
![]() |
17413f81ff | ||
![]() |
725e555733 | ||
![]() |
6e7456605d | ||
![]() |
b652181dda | ||
![]() |
6764d80534 | ||
![]() |
6fbc06081e | ||
![]() |
0328876d50 | ||
![]() |
c5e1dd7c3a | ||
![]() |
714824df97 | ||
![]() |
2cb921087f | ||
![]() |
1ed3144428 | ||
![]() |
ba8fa01ce5 | ||
![]() |
3f6f077833 | ||
![]() |
74d9999202 | ||
![]() |
1be1c938cc | ||
![]() |
930f1d43e0 | ||
![]() |
1e04053026 | ||
![]() |
1e6b3faff8 | ||
![]() |
e6928b6ab1 | ||
![]() |
eb97d0d479 | ||
![]() |
0a75d57cf9 | ||
![]() |
383c9b9a33 | ||
![]() |
d2adbecc44 | ||
![]() |
834395bdc3 | ||
![]() |
18dad5bedf | ||
![]() |
c188367749 | ||
![]() |
5b098c68aa | ||
![]() |
22bd56652d | ||
![]() |
d07caea0f6 | ||
![]() |
897f75c069 | ||
![]() |
8167e04383 | ||
![]() |
72b6d0e7bb | ||
![]() |
039c5d9244 | ||
![]() |
7ea982c903 | ||
![]() |
f0adeddb66 | ||
![]() |
e709b6c321 | ||
![]() |
1b0dcc9808 | ||
![]() |
fe5dfa0ea7 | ||
![]() |
25e6f27854 | ||
![]() |
c07a01a427 | ||
![]() |
11bc7cb60c | ||
![]() |
6e3b7e7a04 | ||
![]() |
ff58cf5b19 | ||
![]() |
25a708a3d4 | ||
![]() |
004dcc19dc | ||
![]() |
ac95f5f89c | ||
![]() |
0fc9b49fba | ||
![]() |
600e73ad43 | ||
![]() |
0bc0b0dc77 | ||
![]() |
86f62e1f37 | ||
![]() |
7850b8368a | ||
![]() |
496bde733f | ||
![]() |
8b98deafca | ||
![]() |
b3ab0cbd74 | ||
![]() |
462eb77e0d | ||
![]() |
d5702d3065 | ||
![]() |
c8377b318e | ||
![]() |
6814a833be | ||
![]() |
98aae10126 | ||
![]() |
0558675132 | ||
![]() |
c8f42e8a48 | ||
![]() |
d30d16b855 | ||
![]() |
178957642c | ||
![]() |
cd77fe74d5 | ||
![]() |
90c91c3ebc | ||
![]() |
11dbf83faf | ||
![]() |
b7fa57c9b7 | ||
![]() |
7a63527d8f | ||
![]() |
4b33bedccd | ||
![]() |
473637ceaf | ||
![]() |
6f5142393b | ||
![]() |
fdc20d4e9d | ||
![]() |
4d0dcede41 | ||
![]() |
4ed262a330 | ||
![]() |
48f0f81f12 | ||
![]() |
7b9f2d680a | ||
![]() |
c69537b173 | ||
![]() |
794a4a23d3 | ||
![]() |
998ff6a13a | ||
![]() |
4ff4a60106 | ||
![]() |
eaaca5b003 | ||
![]() |
388b4731c7 | ||
![]() |
f285d67c87 | ||
![]() |
bc1a612a20 | ||
![]() |
b67879577d | ||
![]() |
2b5b9d44e6 | ||
![]() |
3e48638d8c | ||
![]() |
3307d2d23d | ||
![]() |
62d73cbf96 | ||
![]() |
2004f71290 | ||
![]() |
013dc43c2f | ||
![]() |
716e6cbc04 | ||
![]() |
9e81b6316f | ||
![]() |
d709bf68dd | ||
![]() |
c14b8ed23a | ||
![]() |
7ba6fb5a2e | ||
![]() |
4f1a03811a | ||
![]() |
0946d5a138 | ||
![]() |
6f650c8bbd | ||
![]() |
ad1502e998 | ||
![]() |
bb42dd026c | ||
![]() |
95b6c9dfe5 | ||
![]() |
9b1754a431 | ||
![]() |
0574d59e12 | ||
![]() |
4f7c35dfcf | ||
![]() |
967384ccfe | ||
![]() |
b906a32e23 | ||
![]() |
9b9a4c5ee1 | ||
![]() |
13f3e7ee11 | ||
![]() |
b7f2a62b3c | ||
![]() |
1f4197ce67 | ||
![]() |
f377d044d6 | ||
![]() |
205dd1a201 | ||
![]() |
9c505c4f5d | ||
![]() |
eb7f7f4244 | ||
![]() |
f1fc49d276 | ||
![]() |
d8e2072493 | ||
![]() |
96f813a17b | ||
![]() |
a97542e649 | ||
![]() |
5eceb21ec7 | ||
![]() |
a61b646295 | ||
![]() |
373c476d2d | ||
![]() |
b1145c8926 | ||
![]() |
0810e76474 | ||
![]() |
57acb274c6 | ||
![]() |
e57f885d3b | ||
![]() |
0073e101dd | ||
![]() |
b42d43c0e1 | ||
![]() |
9d8988a2ec | ||
![]() |
aa784c121b | ||
![]() |
a36e8a4065 | ||
![]() |
3fd7b66905 | ||
![]() |
44ebcc5d25 | ||
![]() |
9a68ed6bd0 | ||
![]() |
4f84cd8963 | ||
![]() |
837b56462f | ||
![]() |
9c3a22c556 | ||
![]() |
9471d83a45 | ||
![]() |
52b225d944 | ||
![]() |
c8f0a61209 | ||
![]() |
59c3e9eb54 | ||
![]() |
d20d68b831 | ||
![]() |
cfda1f8eef | ||
![]() |
2fb222125a | ||
![]() |
c7c3c00783 | ||
![]() |
dcc130e2cf | ||
![]() |
491f3d3af4 | ||
![]() |
140000df55 | ||
![]() |
4b2fee7614 | ||
![]() |
2c7b522378 | ||
![]() |
d9ef60d4e8 | ||
![]() |
178fcb8164 | ||
![]() |
3d6806b63a | ||
![]() |
22e3019610 | ||
![]() |
7937cbd122 | ||
![]() |
2bef34ee5b | ||
![]() |
efe7236d31 | ||
![]() |
67dba9c820 | ||
![]() |
52c509aba0 | ||
![]() |
437c7d293e | ||
![]() |
2b6ce4f25b | ||
![]() |
64f123abe9 | ||
![]() |
599a98bceb | ||
![]() |
2cd1b8bd4e | ||
![]() |
c0be9c25da | ||
![]() |
5cea66374f | ||
![]() |
ee1c270c68 | ||
![]() |
4ab08e2faf | ||
![]() |
dd094edb88 | ||
![]() |
56e45236d8 | ||
![]() |
be9a8c5839 | ||
![]() |
8c11baf3b9 | ||
![]() |
6ce679bd83 | ||
![]() |
a4b1b9bb96 | ||
![]() |
91ad78e6a2 | ||
![]() |
4a68d66ab0 | ||
![]() |
4cb50b556f | ||
![]() |
b5f73a99cc | ||
![]() |
e9476a59e8 | ||
![]() |
f48f23c2eb | ||
![]() |
ddf465d8c9 | ||
![]() |
7149287b8e | ||
![]() |
cc709c6bb3 | ||
![]() |
8d101c4c11 | ||
![]() |
64eef6cd8e | ||
![]() |
f8ae3c0dd1 | ||
![]() |
589205edc3 | ||
![]() |
48b212faaf | ||
![]() |
0554dbc608 | ||
![]() |
3564eb805c | ||
![]() |
62a8064edd | ||
![]() |
55ed45190b | ||
![]() |
a6ae96093f | ||
![]() |
5b1afe6d50 | ||
![]() |
60df71a74c | ||
![]() |
a6d5da861f | ||
![]() |
afc10911f7 | ||
![]() |
0686e95c72 | ||
![]() |
234754fd49 | ||
![]() |
efb8d0f226 | ||
![]() |
3f881f7d67 | ||
![]() |
64d38abc99 | ||
![]() |
4a39362702 | ||
![]() |
db2d3794ae | ||
![]() |
526ff6fff0 | ||
![]() |
eebc0e428e | ||
![]() |
80aca514b2 | ||
![]() |
3e8068e82d | ||
![]() |
601fbcd176 | ||
![]() |
40eab15d69 | ||
![]() |
ceb272c0b4 | ||
![]() |
80d4f62694 | ||
![]() |
42f58a8649 | ||
![]() |
6b9af71967 | ||
![]() |
dbd98a09c5 | ||
![]() |
c6a6363163 | ||
![]() |
82815f66e5 | ||
![]() |
0a38eb770d |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,6 +1,9 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
## files generated by popular Visual Studio add-ons.
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
#Rider directory
|
||||||
|
.idea/
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
@@ -252,3 +255,6 @@ ModelManifest.xml
|
|||||||
# FAKE - F# Make
|
# FAKE - F# Make
|
||||||
.fake/
|
.fake/
|
||||||
GameBinaries
|
GameBinaries
|
||||||
|
|
||||||
|
# Generated Files
|
||||||
|
**Gen.cs
|
||||||
|
@@ -1,3 +1,12 @@
|
|||||||
|
# Torch 1.1.229.265
|
||||||
|
* Features
|
||||||
|
- Added more lenient version parsing for plugins (v#.# should work)
|
||||||
|
- Added countdown option to restart command (!restart [seconds])
|
||||||
|
* Fixes
|
||||||
|
- General fixes to work with the latest SE version
|
||||||
|
- Fixed config changes not saving
|
||||||
|
- (hopefully) Fixed issue causing crashes on servers using the Windows Classic theme
|
||||||
|
|
||||||
# Torch 1.1.207.7
|
# Torch 1.1.207.7
|
||||||
* Notes
|
* Notes
|
||||||
- This release makes significant changes to TorchConfig.xml. It has been renamed to Torch.cfg and has different options.
|
- This release makes significant changes to TorchConfig.xml. It has been renamed to Torch.cfg and has different options.
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
# Making a Pull Request
|
# Making a Pull Request
|
||||||
* Fork this repository and make sure your local **master** branch is up to date with the main repository.
|
* Fork this repository and make sure your local **staging** branch is up to date with the main repository.
|
||||||
* Create a new branch for your addition with an appropriate name, e.g. **add-restart-command**
|
* Create a new branch from the **staging** branch for your addition with an appropriate name, e.g. **add-restart-command**
|
||||||
* PRs work by submitting the *entire* branch, so this allows you to continue work without locking up your whole repository.
|
* PRs work by submitting the *entire* branch, so this allows you to continue work without locking up your whole repository.
|
||||||
* Commit your changes to that branch, making sure that you **follow the code guidelines below**.
|
* Commit your changes to that branch, making sure that you **follow the code guidelines below**.
|
||||||
* Submit your branch as a PR to be reviewed.
|
* Submit your branch as a PR to be reviewed, with Torch's **staging** branch as the base.
|
||||||
|
|
||||||
## Naming Conventions
|
## Naming Conventions
|
||||||
* Types: **PascalCase**
|
* Types: **PascalCase**
|
||||||
|
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
FROM mcr.microsoft.com/windows/servercore:ltsc2022
|
||||||
|
|
||||||
|
USER ContainerAdministrator
|
||||||
|
ADD https://aka.ms/highdpimfc2013x64enu vc_redist2013.exe
|
||||||
|
ADD https://aka.ms/vs/16/release/vc_redist.x64.exe vc_redist.exe
|
||||||
|
|
||||||
|
RUN vc_redist2013.exe /passive /norestart
|
||||||
|
RUN vc_redist.exe /passive /norestart
|
||||||
|
RUN del vc_redist2013.exe && del vc_redist.exe
|
||||||
|
|
||||||
|
USER ContainerUser
|
||||||
|
COPY . .
|
||||||
|
ENV TORCH_GAME_PATH="c:\dedi"
|
||||||
|
ENV TORCH_INSTANCE="c:\instance"
|
||||||
|
ENV TORCH_SERVICE="true"
|
||||||
|
ENTRYPOINT ["Torch.Server.exe"]
|
||||||
|
CMD ["-noupdate"]
|
24
NLog.config
24
NLog.config
@@ -1,15 +1,31 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<variable name="logStamp" value="${time} ${pad:padding=-8:inner=[${level:uppercase=true}]}" />
|
||||||
|
<variable name="logContent" value="${message:withException=true}"/>
|
||||||
|
|
||||||
<targets>
|
<targets>
|
||||||
<target xsi:type="File" name="main" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" fileName="Logs\Torch-${shortdate}.log" />
|
<default-wrapper xsi:type="AsyncWrapper" overflowAction="Block" optimizeBufferReuse="true" />
|
||||||
|
<target xsi:type="Null" name="null" formatMessage="false" />
|
||||||
|
<target xsi:type="File" keepFileOpen="true" concurrentWrites="false" name="keen" layout="${var:logStamp} ${logger}: ${var:logContent}"
|
||||||
|
fileName="Logs\Keen-${shortdate}.log" />
|
||||||
|
<target xsi:type="File" keepFileOpen="true" concurrentWrites="false" name="main" layout="${var:logStamp} ${logger}: ${var:logContent}"
|
||||||
|
fileName="Logs\Torch-${shortdate}.log" />
|
||||||
<target xsi:type="File" name="chat" layout="${longdate} ${message}" fileName="Logs\Chat.log" />
|
<target xsi:type="File" name="chat" layout="${longdate} ${message}" fileName="Logs\Chat.log" />
|
||||||
<target xsi:type="ColoredConsole" name="console" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" />
|
<target xsi:type="ColoredConsole" name="console" layout="${var:logStamp} ${logger:shortName=true}: ${var:logContent}" />
|
||||||
|
<target xsi:type="File" name="patch" layout="${var:logContent}" fileName="Logs\patch.log"/>
|
||||||
|
<target xsi:type="LogViewerTarget" name="wpf" layout="[${level:uppercase=true}] ${logger:shortName=true}: ${var:logContent}" />
|
||||||
</targets>
|
</targets>
|
||||||
|
|
||||||
<rules>
|
<rules>
|
||||||
<logger name="*" minlevel="Info" writeTo="main, console" />
|
<!-- Do not define custom rules here. Use NLog-user.config -->
|
||||||
|
<logger name="Keen" minlevel="Warn" writeTo="main"/>
|
||||||
|
<logger name="Keen" minlevel="Info" writeTo="console, wpf"/>
|
||||||
|
<logger name="Keen" minlevel="Debug" writeTo="keen" final="true" />
|
||||||
|
<logger name="Keen" writeTo="null" final="true" />
|
||||||
|
|
||||||
|
<logger name="*" minlevel="Info" writeTo="main, console, wpf" />
|
||||||
<logger name="Chat" minlevel="Info" writeTo="chat" />
|
<logger name="Chat" minlevel="Info" writeTo="chat" />
|
||||||
|
<!--<logger name="Torch.Managers.PatchManager.*" minlevel="Trace" writeTo="patch"/>-->
|
||||||
</rules>
|
</rules>
|
||||||
</nlog>
|
</nlog>
|
23
README.md
23
README.md
@@ -1,30 +1,29 @@
|
|||||||
Discord: [](https://discord.gg/8uHZykr)
|
[](https://discord.gg/trK6sYdcNE)
|
||||||
|
[](https://ci.appveyor.com/project/zznty/torch/branch/master)
|
||||||
|
|
||||||
# What is Torch?
|
# What is Torch?
|
||||||
Torch is the successor to SE Server Extender and gives server admins the tools they need to keep their Space Engineers servers running smoothly. It features a user interface with live management tools and a plugin system so you can run your server exactly how you'd like. Torch is still in early development so there may be bugs and incomplete features.
|
Torch is the successor to SE Server Extender and gives server admins the tools they need to keep their Space Engineers servers running smoothly. It features a user interface with live management tools and a plugin system so you can run your server exactly how you'd like. Torch is still in early development so there may be bugs and incomplete features.
|
||||||
|
|
||||||
# Features
|
## Torch.Server
|
||||||
|
|
||||||
|
### Features
|
||||||
* WPF-based user interface
|
* WPF-based user interface
|
||||||
* Chat: interact with the game chat and run commands without having to join the game.
|
* Chat: interact with the game chat and run commands without having to join the game.
|
||||||
* Entity manager: realtime modification of ingame entities such as stopping grids and changing block settings without having to join the game
|
* Entity manager: realtime modification of ingame entities such as stopping grids and changing block settings without having to join the game
|
||||||
* Organized, easy to use configuration editor
|
* Organized, easy to use configuration editor
|
||||||
* Extensible using the Torch plugin system
|
* Extensible using the Torch plugin system
|
||||||
|
|
||||||
# Installation
|
### Fork Difference
|
||||||
|
* .NET 6.0 runtime
|
||||||
|
* Additional options & features
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
* Get the latest Torch release here: https://github.com/TorchAPI/Torch/releases
|
|
||||||
* Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files.
|
* Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files.
|
||||||
- If you already have a DS installed you can unzip the Torch files into the folder that contains the DedicatedServer64 folder.
|
- If you already have a DS installed you can unzip the Torch files into the folder that contains the DedicatedServer64 folder.
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
To build Torch you must first have a complete SE Dedicated installation somewhere. Before you open the solution, run the Setup batch file and enter the path of that installation's DedicatedServer64 folder. The script will make a symlink to that folder so the Torch solution can find the DLL references it needs.
|
To build Torch you must first have a complete SE Dedicated installation somewhere. Before you open the solution, run the Setup batch file and enter the path of that installation's DedicatedServer64 folder. The script will make a symlink to that folder so the Torch solution can find the DLL references it needs.
|
||||||
|
|
||||||
In both cases you will need to set the InstancePath in TorchConfig.xml to an existing dedicated server instance as Torch can't fully generate it on its own yet.
|
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon. (https://www.patreon.com/TorchSE)
|
||||||
|
|
||||||
# Official Plugins
|
|
||||||
Install plugins by unzipping them into the 'Plugins' folder which should be in the same location as the Torch files. If it doesn't exist you can simply create it.
|
|
||||||
* [Essentials](https://github.com/TorchAPI/Essentials): Adds a slew of chat commands and other tools to help manage your server.
|
|
||||||
* [Concealment](https://github.com/TorchAPI/Concealment): Adds game logic and physics optimizations that significantly improve sim speed.
|
|
||||||
|
|
||||||
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon.
|
|
||||||
[](https://www.patreon.com/bePatron?u=847269)!
|
|
||||||
|
@@ -3,11 +3,11 @@
|
|||||||
@echo off
|
@echo off
|
||||||
set /p path="Please enter the folder location of your SpaceEngineersDedicated.exe: "
|
set /p path="Please enter the folder location of your SpaceEngineersDedicated.exe: "
|
||||||
cd %~dp0
|
cd %~dp0
|
||||||
mklink /D GameBinaries %path%
|
mklink /J GameBinaries "%path%"
|
||||||
if errorlevel 1 goto Error
|
if errorlevel 1 goto Error
|
||||||
echo Done! You can now open the Torch solution without issue.
|
echo Done! You can now open the Torch solution without issue.
|
||||||
goto End
|
goto End
|
||||||
:Error
|
:Error
|
||||||
echo An error occured creating the symlink.
|
echo An error occured creating the symlink.
|
||||||
:End
|
:End
|
||||||
pause
|
pause
|
||||||
|
28
Torch.API/Event/EventHandlerAttribute.cs
Normal file
28
Torch.API/Event/EventHandlerAttribute.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Torch.API.Event
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Attribute indicating that a method should be invoked when the event occurs.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class EventHandlerAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Events are executed from low priority to high priority.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// While this may seem unintuitive this gives the high priority events the final say on changing/canceling events.
|
||||||
|
/// </remarks>
|
||||||
|
public int Priority { get; set; } = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies if this handler should ignore a consumed event.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If <see cref="SkipCancelled"/> is <em>true</em> and the event is cancelled by a lower priority handler this handler won't be invoked.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="IEvent.Cancelled"/>
|
||||||
|
public bool SkipCancelled { get; set; } = false;
|
||||||
|
}
|
||||||
|
}
|
11
Torch.API/Event/IEvent.cs
Normal file
11
Torch.API/Event/IEvent.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Torch.API.Event
|
||||||
|
{
|
||||||
|
public interface IEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An event that has been cancelled will no be processed in the default manner.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="EventHandlerAttribute.SkipCancelled"/>
|
||||||
|
bool Cancelled { get; }
|
||||||
|
}
|
||||||
|
}
|
9
Torch.API/Event/IEventHandler.cs
Normal file
9
Torch.API/Event/IEventHandler.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Torch.API.Event
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface used to tag an event handler. This does <b>not</b> register it with the event manager.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEventHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
28
Torch.API/Event/IEventManager.cs
Normal file
28
Torch.API/Event/IEventManager.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
|
||||||
|
namespace Torch.API.Event
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manager class responsible for registration of event handlers.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEventManager : IManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers all event handler methods contained in the given instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">Instance to register</param>
|
||||||
|
/// <returns><b>true</b> if added, <b>false</b> otherwise</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
bool RegisterHandler(IEventHandler handler);
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters all event handler methods contained in the given instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">Instance to unregister</param>
|
||||||
|
/// <returns><b>true</b> if removed, <b>false</b> otherwise</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
bool UnregisterHandler(IEventHandler handler);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,31 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Torch.API
|
|
||||||
{
|
|
||||||
public interface IChatMessage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The time the message was created.
|
|
||||||
/// </summary>
|
|
||||||
DateTime Timestamp { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The SteamID of the message author.
|
|
||||||
/// </summary>
|
|
||||||
ulong SteamId { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the message author.
|
|
||||||
/// </summary>
|
|
||||||
string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The content of the message.
|
|
||||||
/// </summary>
|
|
||||||
string Message { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,10 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
|
using Torch.API.Session;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
|
using Version = SemanticVersioning.Version;
|
||||||
|
|
||||||
namespace Torch.API
|
namespace Torch.API
|
||||||
{
|
{
|
||||||
@@ -16,34 +19,50 @@ namespace Torch.API
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when the session begins loading.
|
/// Fired when the session begins loading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||||
event Action SessionLoading;
|
event Action SessionLoading;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when the session finishes loading.
|
/// Fired when the session finishes loading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||||
event Action SessionLoaded;
|
event Action SessionLoaded;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fires when the session begins unloading.
|
/// Fires when the session begins unloading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||||
event Action SessionUnloading;
|
event Action SessionUnloading;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when the session finishes unloading.
|
/// Fired when the session finishes unloading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Prefer using the TorchSessionManager.SessionStateChanged event")]
|
||||||
event Action SessionUnloaded;
|
event Action SessionUnloaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the currently running session instance, or null if none exists.
|
||||||
|
/// </summary>
|
||||||
|
ITorchSession CurrentSession { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configuration for the current instance.
|
/// Configuration for the current instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ITorchConfig Config { get; }
|
ITorchConfig Config { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="IMultiplayerManager"/>
|
|
||||||
IMultiplayerManager Multiplayer { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IPluginManager"/>
|
/// <inheritdoc cref="IPluginManager"/>
|
||||||
|
[Obsolete]
|
||||||
IPluginManager Plugins { get; }
|
IPluginManager Plugins { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDependencyManager"/>
|
||||||
|
IDependencyManager Managers { get; }
|
||||||
|
|
||||||
|
[Obsolete("Prefer using Managers.GetManager for global managers")]
|
||||||
|
T GetManager<T>() where T : class, IManager;
|
||||||
|
|
||||||
|
[Obsolete("Prefer using Managers.AddManager for global managers")]
|
||||||
|
bool AddManager<T>(T manager) where T : class, IManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The binary version of the current instance.
|
/// The binary version of the current instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -52,50 +71,69 @@ namespace Torch.API
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoke an action on the game thread.
|
/// Invoke an action on the game thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Invoke(Action action);
|
void Invoke(Action action, [CallerMemberName] string caller = "");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoke an action on the game thread and block until it has completed.
|
/// Invoke an action on the game thread and block until it has completed.
|
||||||
/// If this is called on the game thread the action will execute immediately.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void InvokeBlocking(Action action);
|
/// <param name="action">Action to execute</param>
|
||||||
|
/// <param name="caller">Caller of the invoke function</param>
|
||||||
|
/// <param name="timeoutMs">Timeout before <see cref="TimeoutException"/> is thrown, or -1 to never timeout</param>
|
||||||
|
/// <exception cref="TimeoutException">If the action times out</exception>
|
||||||
|
void InvokeBlocking(Action action, int timeoutMs = -1, [CallerMemberName] string caller = "");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoke an action on the game thread asynchronously.
|
/// Invoke an action on the game thread asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task InvokeAsync(Action action);
|
Task InvokeAsync(Action action, [CallerMemberName] string caller = "");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start the Torch instance.
|
/// Invoke a function on the game thread asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
Task<T> InvokeAsync<T>(Func<T> func, [CallerMemberName] string caller = "");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signals the torch instance to start, then blocks until it's started.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Start();
|
void Start();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stop the Torch instance.
|
/// Signals the torch instance to stop, then blocks until it's stopped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Stop();
|
void Stop();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restart the Torch instance.
|
/// Restart the Torch instance, blocking until the restart has been performed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Restart();
|
void Restart(bool save = true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a save of the game.
|
/// Initializes a save of the game.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="callerId">Id of the player who initiated the save.</param>
|
/// <param name="timeoutMs">timeout before the save is treated as failed, or -1 for no timeout</param>
|
||||||
Task Save(long callerId);
|
/// <param name="exclusive">Only start saving if we aren't already saving</param>
|
||||||
|
/// <returns>Future result of the save, or null if one is in progress and in exclusive mode</returns>
|
||||||
|
Task<GameSaveResult> Save(int timeoutMs = -1, bool exclusive = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize the Torch instance.
|
/// Initialize the Torch instance. Before this <see cref="Start"/> is invalid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Init();
|
void Init();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get an <see cref="IManager"/> that is part of the Torch instance.
|
/// Disposes the Torch instance. After this <see cref="Start"/> is invalid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Manager type</typeparam>
|
void Destroy();
|
||||||
T GetManager<T>() where T : class, IManager;
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current state of the game this instance of torch is controlling.
|
||||||
|
/// </summary>
|
||||||
|
TorchGameState GameState { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised when <see cref="GameState"/> changes.
|
||||||
|
/// </summary>
|
||||||
|
event TorchGameStateChangedDel GameStateChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -103,10 +141,27 @@ namespace Torch.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITorchServer : ITorchBase
|
public interface ITorchServer : ITorchBase
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The current <see cref="ServerState"/>
|
||||||
|
/// </summary>
|
||||||
|
ServerState State { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Path of the dedicated instance folder.
|
/// Path of the dedicated instance folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string InstancePath { get; }
|
string InstancePath { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the dedicated instance.
|
||||||
|
/// </summary>
|
||||||
|
string InstanceName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the server's Init() method has completed.
|
||||||
|
/// </summary>
|
||||||
|
event Action<ITorchServer> Initialized;
|
||||||
|
|
||||||
|
TimeSpan ElapsedPlayTime { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -114,6 +169,6 @@ namespace Torch.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITorchClient : ITorchBase
|
public interface ITorchClient : ITorchBase
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Torch.API;
|
||||||
|
|
||||||
namespace Torch
|
namespace Torch
|
||||||
{
|
{
|
||||||
@@ -8,17 +10,28 @@ namespace Torch
|
|||||||
bool ForceUpdate { get; set; }
|
bool ForceUpdate { get; set; }
|
||||||
bool GetPluginUpdates { get; set; }
|
bool GetPluginUpdates { get; set; }
|
||||||
bool GetTorchUpdates { get; set; }
|
bool GetTorchUpdates { get; set; }
|
||||||
|
[Obsolete("Use Torch.InstanceName instead")]
|
||||||
string InstanceName { get; set; }
|
string InstanceName { get; set; }
|
||||||
|
[Obsolete("Use Torch.InstancePath instead")]
|
||||||
string InstancePath { get; set; }
|
string InstancePath { get; set; }
|
||||||
bool NoGui { get; set; }
|
bool NoGui { get; set; }
|
||||||
bool NoUpdate { get; set; }
|
bool NoUpdate { get; set; }
|
||||||
List<string> Plugins { get; set; }
|
List<Guid> Plugins { get; set; }
|
||||||
|
bool LocalPlugins { get; set; }
|
||||||
bool RestartOnCrash { get; set; }
|
bool RestartOnCrash { get; set; }
|
||||||
bool ShouldUpdatePlugins { get; }
|
bool ShouldUpdatePlugins { get; }
|
||||||
bool ShouldUpdateTorch { get; }
|
bool ShouldUpdateTorch { get; }
|
||||||
int TickTimeout { get; set; }
|
int TickTimeout { get; set; }
|
||||||
string WaitForPID { get; set; }
|
string WaitForPID { get; set; }
|
||||||
|
string ChatName { get; set; }
|
||||||
bool Save(string path = null);
|
string ChatColor { get; set; }
|
||||||
|
string TestPlugin { get; set; }
|
||||||
|
bool DisconnectOnRestart { get; set; }
|
||||||
|
int WindowWidth { get; set; }
|
||||||
|
int WindowHeight { get; set; }
|
||||||
|
int FontSize { get; set; }
|
||||||
|
UGCServiceType UgcServiceType { get; set; }
|
||||||
|
bool EntityManagerEnabled { get; set; }
|
||||||
|
void Save(string path = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
30
Torch.API/Managers/DependencyManagerExtensions.cs
Normal file
30
Torch.API/Managers/DependencyManagerExtensions.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
public static class DependencyManagerExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a single manager from this dependency manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="managerType">The dependency type to remove</param>
|
||||||
|
/// <returns>The manager that was removed, or null if one wasn't removed</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||||
|
public static IManager RemoveManager(this IDependencyManager depManager, Type managerType)
|
||||||
|
{
|
||||||
|
IManager mgr = depManager.GetManager(managerType);
|
||||||
|
return depManager.RemoveManager(mgr) ? mgr : null;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a single manager from this dependency manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The dependency type to remove</typeparam>
|
||||||
|
/// <returns>The manager that was removed, or null if one wasn't removed</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||||
|
public static IManager RemoveManager<T>(this IDependencyManager depManager)
|
||||||
|
{
|
||||||
|
return depManager.RemoveManager(typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
15
Torch.API/Managers/DependencyProviderExtensions.cs
Normal file
15
Torch.API/Managers/DependencyProviderExtensions.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
public static class DependencyProviderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the manager that provides the given type. If there is no such manager, returns null.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of manager</typeparam>
|
||||||
|
/// <returns>manager, or null if none exists</returns>
|
||||||
|
public static T GetManager<T>(this IDependencyProvider depProvider) where T : class, IManager
|
||||||
|
{
|
||||||
|
return (T)depProvider.GetManager(typeof(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
Torch.API/Managers/IChatManagerClient.cs
Normal file
171
Torch.API/Managers/IChatManagerClient.cs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Game.Gui;
|
||||||
|
using Sandbox.Game.Multiplayer;
|
||||||
|
using Torch.Utils;
|
||||||
|
using VRage.Game;
|
||||||
|
using VRage.Network;
|
||||||
|
using VRage.Replication;
|
||||||
|
using VRageMath;
|
||||||
|
using VRageRender;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a scripted or user chat message.
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct TorchChatMessage
|
||||||
|
{
|
||||||
|
private const string DEFAULT_FONT = MyFontEnum.Blue;
|
||||||
|
|
||||||
|
#region Backwards compatibility
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
|
public TorchChatMessage(string author, string message, string font = DEFAULT_FONT)
|
||||||
|
: this(author, message, default, font) { }
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
|
public TorchChatMessage(string author, ulong authorSteamId, string message, ChatChannel channel, long target, string font = DEFAULT_FONT)
|
||||||
|
: this(author, authorSteamId, message, channel, target, default, font) { }
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
|
public TorchChatMessage(ulong authorSteamId, string message, ChatChannel channel, long target, string font = DEFAULT_FONT)
|
||||||
|
: this(authorSteamId, message, channel, target, default, font) { }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new torch chat message with the given author and message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="author">Author's name</param>
|
||||||
|
/// <param name="message">Message</param>
|
||||||
|
/// <param name="font">Font</param>
|
||||||
|
public TorchChatMessage(string author, string message, Color color, string font = DEFAULT_FONT)
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.Now;
|
||||||
|
AuthorSteamId = null;
|
||||||
|
Author = author;
|
||||||
|
Message = message;
|
||||||
|
Channel = ChatChannel.Global;
|
||||||
|
Target = 0;
|
||||||
|
Font = font;
|
||||||
|
Color = color == default ? ColorUtils.TranslateColor(font) : color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new torch chat message with the given author and message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="author">Author's name</param>
|
||||||
|
/// <param name="authorSteamId">Author's steam ID</param>
|
||||||
|
/// <param name="message">Message</param>
|
||||||
|
/// <param name="font">Font</param>
|
||||||
|
public TorchChatMessage(string author, ulong authorSteamId, string message, ChatChannel channel, long target, Color color, string font = DEFAULT_FONT)
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.Now;
|
||||||
|
AuthorSteamId = authorSteamId;
|
||||||
|
Author = author;
|
||||||
|
Message = message;
|
||||||
|
Channel = channel;
|
||||||
|
Target = target;
|
||||||
|
Font = font;
|
||||||
|
Color = color == default ? ColorUtils.TranslateColor(font) : color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new torch chat message with the given author and message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authorSteamId">Author's steam ID</param>
|
||||||
|
/// <param name="message">Message</param>
|
||||||
|
/// <param name="font">Font</param>
|
||||||
|
public TorchChatMessage(ulong authorSteamId, string message, ChatChannel channel, long target, Color color, string font = DEFAULT_FONT)
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.Now;
|
||||||
|
AuthorSteamId = authorSteamId;
|
||||||
|
Author = MyMultiplayer.Static?.GetMemberName(authorSteamId) ?? "Player";
|
||||||
|
Message = message;
|
||||||
|
Channel = channel;
|
||||||
|
Target = target;
|
||||||
|
Font = font;
|
||||||
|
Color = color == default ? ColorUtils.TranslateColor(font) : color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This message's timestamp.
|
||||||
|
/// </summary>
|
||||||
|
public readonly DateTime Timestamp;
|
||||||
|
/// <summary>
|
||||||
|
/// The author's steam ID, if available. Else, null.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ulong? AuthorSteamId;
|
||||||
|
/// <summary>
|
||||||
|
/// The author's name, if available. Else, null.
|
||||||
|
/// </summary>
|
||||||
|
public readonly string Author;
|
||||||
|
/// <summary>
|
||||||
|
/// The message contents.
|
||||||
|
/// </summary>
|
||||||
|
public readonly string Message;
|
||||||
|
/// <summary>
|
||||||
|
/// The chat channel the message is part of.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ChatChannel Channel;
|
||||||
|
/// <summary>
|
||||||
|
/// The intended recipient of the message.
|
||||||
|
/// </summary>
|
||||||
|
public readonly long Target;
|
||||||
|
/// <summary>
|
||||||
|
/// The font, or null if default.
|
||||||
|
/// </summary>
|
||||||
|
public readonly string Font;
|
||||||
|
/// <summary>
|
||||||
|
/// The chat message color.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Color Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback used to indicate that a messaage has been recieved.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
|
||||||
|
public delegate void MessageRecievedDel(TorchChatMessage msg, ref bool consumed);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback used to indicate the user is attempting to send a message locally.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">Message the user is attempting to send</param>
|
||||||
|
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
|
||||||
|
public delegate void MessageSendingDel(string msg, ref bool consumed);
|
||||||
|
|
||||||
|
public interface IChatManagerClient : IManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is raised when a message addressed to us is recieved. <see cref="MessageRecievedDel"/>
|
||||||
|
/// </summary>
|
||||||
|
event MessageRecievedDel MessageRecieved;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is raised when we are attempting to send a message. <see cref="MessageSendingDel"/>
|
||||||
|
/// </summary>
|
||||||
|
event MessageSendingDel MessageSending;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers the <see cref="MessageSending"/> event,
|
||||||
|
/// typically raised by the user entering text into the chat window.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to send</param>
|
||||||
|
void SendMessageAsSelf(string message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a message on the UI given an author name and a message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="author">Author name</param>
|
||||||
|
/// <param name="message">Message content</param>
|
||||||
|
/// <param name="font">font to use</param>
|
||||||
|
void DisplayMessageOnSelf(string author, string message, string font = "Blue" );
|
||||||
|
}
|
||||||
|
}
|
71
Torch.API/Managers/IChatManagerServer.cs
Normal file
71
Torch.API/Managers/IChatManagerServer.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VRage.Collections;
|
||||||
|
using VRage.Game;
|
||||||
|
using VRage.Network;
|
||||||
|
using VRageMath;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback used to indicate the server has recieved a message to process and forward on to others.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authorId">Steam ID of the user sending a message</param>
|
||||||
|
/// <param name="msg">Message the user is attempting to send</param>
|
||||||
|
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
|
||||||
|
public delegate void MessageProcessingDel(TorchChatMessage msg, ref bool consumed);
|
||||||
|
|
||||||
|
public interface IChatManagerServer : IChatManagerClient
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when the server has recieved a message and should process it. <see cref="MessageProcessingDel"/>
|
||||||
|
/// </summary>
|
||||||
|
event MessageProcessingDel MessageProcessing;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a message with the given author and message to the given player, or all players by default.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authorId">Author's steam ID</param>
|
||||||
|
/// <param name="message">The message to send</param>
|
||||||
|
/// <param name="targetSteamId">Player to send the message to, or everyone by default</param>
|
||||||
|
void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0);
|
||||||
|
|
||||||
|
|
||||||
|
[Obsolete("Use the other overload with a Color parameter.")]
|
||||||
|
void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a scripted message with the given author and message to the given player, or all players by default.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="author">Author name</param>
|
||||||
|
/// <param name="message">The message to send</param>
|
||||||
|
/// <param name="color">Name color</param>
|
||||||
|
/// <param name="font">Font to use</param>
|
||||||
|
/// <param name="targetSteamId">Player to send the message to, or everyone by default</param>
|
||||||
|
void SendMessageAsOther(string author, string message, Color color = default, ulong targetSteamId = 0, string font = MyFontEnum.White);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mute user from global chat.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool MuteUser(ulong steamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unmute user from global chat.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool UnmuteUser(ulong steamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Users which are not allowed to chat.
|
||||||
|
/// </summary>
|
||||||
|
HashSetReader<ulong> MutedUsers { get; }
|
||||||
|
}
|
||||||
|
}
|
62
Torch.API/Managers/IDependencyManager.cs
Normal file
62
Torch.API/Managers/IDependencyManager.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages a set of <see cref="IManager"/> and the dependencies between them.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDependencyManager : IDependencyProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the given manager into the dependency system.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method only returns false when there is already a manager registered with a type derived from this given manager,
|
||||||
|
/// or when the given manager is derived from an already existing manager.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="manager">Manager to register</param>
|
||||||
|
/// <exception cref="InvalidOperationException">When adding a new manager to an initialized dependency manager</exception>
|
||||||
|
/// <returns>true if added, false if not</returns>
|
||||||
|
bool AddManager(IManager manager);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears all managers registered with this dependency manager
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||||
|
void ClearManagers();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a single manager from this dependency manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manager">The manager to remove</param>
|
||||||
|
/// <returns>true if successful, false if the manager wasn't found</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">When removing managers from an initialized dependency manager</exception>
|
||||||
|
bool RemoveManager(IManager manager);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sorts the dependency manager, then attaches all its registered managers in <see cref="AttachOrder" />
|
||||||
|
/// </summary>
|
||||||
|
void Attach();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detaches all registered managers in <see cref="DetachOrder"/>
|
||||||
|
/// </summary>
|
||||||
|
void Detach();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The order that managers should be attached in. (Dependencies, then dependents)
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">When trying to determine load order before this dependency manager is initialized</exception>
|
||||||
|
IEnumerable<IManager> AttachOrder { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The order that managers should be detached in. (Dependents, then dependencies)
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">When trying to determine unload order before this dependency manager is initialized</exception>
|
||||||
|
IEnumerable<IManager> DetachOrder { get; }
|
||||||
|
}
|
||||||
|
}
|
18
Torch.API/Managers/IDependencyProvider.cs
Normal file
18
Torch.API/Managers/IDependencyProvider.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
public interface IDependencyProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the manager that provides the given type. If there is no such manager, returns null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type of manager</param>
|
||||||
|
/// <returns>manager, or null if none exists</returns>
|
||||||
|
IManager GetManager(Type type);
|
||||||
|
}
|
||||||
|
}
|
21
Torch.API/Managers/IInstanceManager.cs
Normal file
21
Torch.API/Managers/IInstanceManager.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using VRage.Game;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers;
|
||||||
|
|
||||||
|
public interface IInstanceManager : IManager
|
||||||
|
{
|
||||||
|
IWorld SelectedWorld { get; }
|
||||||
|
void LoadInstance(string path, bool validate = true);
|
||||||
|
void SelectCreatedWorld(string worldPath);
|
||||||
|
void SelectWorld(string worldPath, bool modsOnly = true);
|
||||||
|
void ImportSelectedWorldConfig();
|
||||||
|
void SaveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IWorld
|
||||||
|
{
|
||||||
|
string FolderName { get; }
|
||||||
|
string WorldPath { get; }
|
||||||
|
MyObjectBuilder_SessionSettings KeenSessionSettings { get; }
|
||||||
|
MyObjectBuilder_Checkpoint KeenCheckpoint { get; }
|
||||||
|
}
|
@@ -12,8 +12,13 @@ namespace Torch.API.Managers
|
|||||||
public interface IManager
|
public interface IManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the manager. Called after Torch is initialized.
|
/// Attaches the manager to the session. Called once this manager's dependencies have been attached.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Init();
|
void Attach();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detaches the manager from the session. Called before this manager's dependencies are detached.
|
||||||
|
/// </summary>
|
||||||
|
void Detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,61 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using VRage.Game;
|
|
||||||
using VRage.Game.ModAPI;
|
|
||||||
|
|
||||||
namespace Torch.API.Managers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Delegate for received messages.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">Message data.</param>
|
|
||||||
/// <param name="sendToOthers">Flag to broadcast message to other players.</param>
|
|
||||||
public delegate void MessageReceivedDel(IChatMessage message, ref bool sendToOthers);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// API for multiplayer related functions.
|
|
||||||
/// </summary>
|
|
||||||
public interface IMultiplayerManager : IManager
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when a player joins.
|
|
||||||
/// </summary>
|
|
||||||
event Action<IPlayer> PlayerJoined;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when a player disconnects.
|
|
||||||
/// </summary>
|
|
||||||
event Action<IPlayer> PlayerLeft;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when a chat message is received.
|
|
||||||
/// </summary>
|
|
||||||
event MessageReceivedDel MessageReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send a chat message to all or one specific player.
|
|
||||||
/// </summary>
|
|
||||||
void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Blue);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Kicks the player from the game.
|
|
||||||
/// </summary>
|
|
||||||
void KickPlayer(ulong steamId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bans or unbans a player from the game.
|
|
||||||
/// </summary>
|
|
||||||
void BanPlayer(ulong steamId, bool banned = true);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a player by their Steam64 ID or returns null if the player isn't found.
|
|
||||||
/// </summary>
|
|
||||||
IMyPlayer GetPlayerBySteamId(ulong id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a player by their display name or returns null if the player isn't found.
|
|
||||||
/// </summary>
|
|
||||||
IMyPlayer GetPlayerByName(string name);
|
|
||||||
}
|
|
||||||
}
|
|
41
Torch.API/Managers/IMultiplayerManagerBase.cs
Normal file
41
Torch.API/Managers/IMultiplayerManagerBase.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using VRage.Game;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// API for multiplayer related functions common to servers and clients.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMultiplayerManagerBase : IManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a player joins.
|
||||||
|
/// </summary>
|
||||||
|
event Action<IPlayer> PlayerJoined;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a player disconnects.
|
||||||
|
/// </summary>
|
||||||
|
event Action<IPlayer> PlayerLeft;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a player by their Steam64 ID or returns null if the player isn't found.
|
||||||
|
/// </summary>
|
||||||
|
IMyPlayer GetPlayerBySteamId(ulong id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a player by their display name or returns null if the player isn't found.
|
||||||
|
/// </summary>
|
||||||
|
IMyPlayer GetPlayerByName(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the steam username of a member's steam ID
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamId">steam ID</param>
|
||||||
|
/// <returns>steam username</returns>
|
||||||
|
string GetSteamUsername(ulong steamId);
|
||||||
|
}
|
||||||
|
}
|
12
Torch.API/Managers/IMultiplayerManagerClient.cs
Normal file
12
Torch.API/Managers/IMultiplayerManagerClient.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
public interface IMultiplayerManagerClient : IMultiplayerManagerBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
71
Torch.API/Managers/IMultiplayerManagerServer.cs
Normal file
71
Torch.API/Managers/IMultiplayerManagerServer.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// API for multiplayer functions that exist on servers and lobbies
|
||||||
|
/// </summary>
|
||||||
|
public interface IMultiplayerManagerServer : IMultiplayerManagerBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Kicks the player from the game.
|
||||||
|
/// </summary>
|
||||||
|
void KickPlayer(ulong steamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bans or unbans a player from the game.
|
||||||
|
/// </summary>
|
||||||
|
void BanPlayer(ulong steamId, bool banned = true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Promotes user if possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamId"></param>
|
||||||
|
void PromoteUser(ulong steamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Demotes user if possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamId"></param>
|
||||||
|
void DemoteUser(ulong steamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a user's promote level.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
MyPromoteLevel GetUserPromoteLevel(ulong steamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of the banned SteamID's
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyList<ulong> BannedPlayers { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the player with the given SteamID is banned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamId">The SteamID of the player.</param>
|
||||||
|
/// <returns>True if the player is banned; otherwise false.</returns>
|
||||||
|
bool IsBanned(ulong steamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a player is kicked. Passes with SteamID of kicked player.
|
||||||
|
/// </summary>
|
||||||
|
event Action<ulong> PlayerKicked;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a player is banned or unbanned. Passes SteamID of player, and true if banned, false if unbanned.
|
||||||
|
/// </summary>
|
||||||
|
event Action<ulong, bool> PlayerBanned;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when a player is promoted or demoted. Passes SteamID of player, and new promote level.
|
||||||
|
/// </summary>
|
||||||
|
event Action<ulong, MyPromoteLevel> PlayerPromoted;
|
||||||
|
}
|
||||||
|
}
|
@@ -18,6 +18,12 @@ namespace Torch.API.Managers
|
|||||||
/// Register a network handler.
|
/// Register a network handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void RegisterNetworkHandler(INetworkHandler handler);
|
void RegisterNetworkHandler(INetworkHandler handler);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregister a network handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if the handler was unregistered, false if it wasn't registered to begin with</returns>
|
||||||
|
bool UnregisterNetworkHandler(INetworkHandler handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -33,6 +39,7 @@ namespace Torch.API.Managers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes a network message.
|
/// Processes a network message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <returns>true if the message should be discarded</returns>
|
||||||
bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
|
bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,23 +14,18 @@ namespace Torch.API.Managers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when plugins are loaded.
|
/// Fired when plugins are loaded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action<IList<ITorchPlugin>> PluginsLoaded;
|
event Action<IReadOnlyCollection<ITorchPlugin>> PluginsLoaded;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Collection of loaded plugins.
|
/// Collection of loaded plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IList<ITorchPlugin> Plugins { get; }
|
IReadOnlyDictionary<Guid, ITorchPlugin> Plugins { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates all loaded plugins.
|
/// Updates all loaded plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void UpdatePlugins();
|
void UpdatePlugins();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposes all loaded plugins.
|
|
||||||
/// </summary>
|
|
||||||
void DisposePlugins();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load plugins.
|
/// Load plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices.WindowsRuntime;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@ namespace Torch.API.Plugins
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The version of the plugin.
|
/// The version of the plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Version Version { get; }
|
string Version { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the plugin.
|
/// The name of the plugin.
|
||||||
@@ -34,5 +34,22 @@ namespace Torch.API.Plugins
|
|||||||
/// This is called on the game thread after each tick.
|
/// This is called on the game thread after each tick.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Update();
|
void Update();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin's enabled state. Mainly for UI niceness
|
||||||
|
/// </summary>
|
||||||
|
PluginState State { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PluginState
|
||||||
|
{
|
||||||
|
NotInitialized,
|
||||||
|
DisabledError,
|
||||||
|
DisabledUser,
|
||||||
|
UpdateRequired,
|
||||||
|
UninstallRequested,
|
||||||
|
NotInstalled,
|
||||||
|
MissingDependency,
|
||||||
|
Enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,22 +1,56 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Torch.API.Plugins
|
namespace Torch.API.Plugins
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the given type should be loaded by the plugin manager as a plugin.
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("All plugin meta-information is now defined in the manifest.xml.")]
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public class PluginAttribute : Attribute
|
public class PluginAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The display name of the plugin
|
||||||
|
/// </summary>
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// The version of the plugin
|
||||||
|
/// </summary>
|
||||||
public Version Version { get; }
|
public Version Version { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// The GUID of the plugin
|
||||||
|
/// </summary>
|
||||||
public Guid Guid { get; }
|
public Guid Guid { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new plugin attribute with the given attributes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="version"></param>
|
||||||
|
/// <param name="guid"></param>
|
||||||
public PluginAttribute(string name, string version, string guid)
|
public PluginAttribute(string name, string version, string guid)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Version = Version.Parse(version);
|
Version = Version.Parse(version);
|
||||||
Guid = Guid.Parse(guid);
|
Guid = Guid.Parse(guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new plugin attribute with the given attributes. Version is computed as the version of the assembly containing the given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="versionSupplier">Version is this type's assembly's version</param>
|
||||||
|
/// <param name="guid"></param>
|
||||||
|
public PluginAttribute(string name, Type versionSupplier, string guid)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Version = versionSupplier.Assembly.GetName().Version;
|
||||||
|
Guid = Guid.Parse(guid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,36 +1,17 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
[assembly: AssemblyTitle("Torch API")]
|
||||||
// set of attributes. Change these attribute values to modify the information
|
|
||||||
// associated with an assembly.
|
|
||||||
[assembly: AssemblyTitle("TorchAPI")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
[assembly: AssemblyCompany("")]
|
||||||
[assembly: AssemblyProduct("TorchAPI")]
|
[assembly: AssemblyProduct("Torch")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
|
||||||
// to COM components. If you need to access a type in this assembly from
|
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
|
||||||
[assembly: ComVisible(false)]
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
#if DEBUG
|
||||||
[assembly: Guid("fba5d932-6254-4a1e-baf4-e229fa94e3c2")]
|
[assembly: AssemblyConfiguration("Debug")]
|
||||||
|
#else
|
||||||
// Version information for an assembly consists of the following four values:
|
[assembly: AssemblyConfiguration("Release")]
|
||||||
//
|
#endif
|
||||||
// Major Version
|
|
||||||
// Minor Version
|
|
||||||
// Build Number
|
|
||||||
// Revision
|
|
||||||
//
|
|
||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
|
||||||
// by using the '*' as shown below:
|
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
|
||||||
[assembly: AssemblyVersion("1.0.0.0")]
|
|
||||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
44
Torch.API/Session/GameSaveResult.cs
Normal file
44
Torch.API/Session/GameSaveResult.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.API.Session
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The result of a save operation
|
||||||
|
/// </summary>
|
||||||
|
public enum GameSaveResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Successfully saved
|
||||||
|
/// </summary>
|
||||||
|
Success = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The game wasn't ready to be saved
|
||||||
|
/// </summary>
|
||||||
|
GameNotReady = -1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Failed to take the snapshot of the current world state
|
||||||
|
/// </summary>
|
||||||
|
FailedToTakeSnapshot = -2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Failed to save the snapshot to disk
|
||||||
|
/// </summary>
|
||||||
|
FailedToSaveToDisk = -3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An unknown error occurred
|
||||||
|
/// </summary>
|
||||||
|
UnknownError = -4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The save operation timed out
|
||||||
|
/// </summary>
|
||||||
|
TimedOut = -5
|
||||||
|
}
|
||||||
|
}
|
44
Torch.API/Session/ITorchSession.cs
Normal file
44
Torch.API/Session/ITorchSession.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Game.World;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
|
||||||
|
namespace Torch.API.Session
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the Torch code working with a single game session
|
||||||
|
/// </summary>
|
||||||
|
public interface ITorchSession
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The Torch instance this session is bound to
|
||||||
|
/// </summary>
|
||||||
|
ITorchBase Torch { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Space Engineers game session this session is bound to.
|
||||||
|
/// </summary>
|
||||||
|
MySession KeenSession { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Currently running world
|
||||||
|
/// </summary>
|
||||||
|
IWorld World { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IDependencyManager"/>
|
||||||
|
IDependencyManager Managers { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current state of the session
|
||||||
|
/// </summary>
|
||||||
|
TorchSessionState State { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised when the <see cref="State"/> changes.
|
||||||
|
/// </summary>
|
||||||
|
event TorchSessionStateChangedDel StateChanged;
|
||||||
|
}
|
||||||
|
}
|
77
Torch.API/Session/ITorchSessionManager.cs
Normal file
77
Torch.API/Session/ITorchSessionManager.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using VRage.Game;
|
||||||
|
|
||||||
|
namespace Torch.API.Session
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a manager for the given session if applicable.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is for creating managers that will live inside the session, not the manager that controls sesssions.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="session">The session to construct a bound manager for</param>
|
||||||
|
/// <returns>The manager that will live in the session, or null if none.</returns>
|
||||||
|
public delegate IManager SessionManagerFactoryDel(ITorchSession session);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manages the creation and destruction of <see cref="ITorchSession"/> instances for each <see cref="Sandbox.Game.World.MySession"/> created by Space Engineers.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITorchSessionManager : IManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The currently running session
|
||||||
|
/// </summary>
|
||||||
|
ITorchSession CurrentSession { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when any <see cref="ITorchSession"/> <see cref="ITorchSession.State"/> changes.
|
||||||
|
/// </summary>
|
||||||
|
event TorchSessionStateChangedDel SessionStateChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the given factory as a supplier for session based managers
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="factory">Session based manager supplier</param>
|
||||||
|
/// <returns>true if added, false if already present</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">If the factory is null</exception>
|
||||||
|
bool AddFactory(SessionManagerFactoryDel factory);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove the given factory from the suppliers for session based managers
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="factory">Session based manager supplier</param>
|
||||||
|
/// <returns>true if removed, false if not present</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">If the factory is null</exception>
|
||||||
|
bool RemoveFactory(SessionManagerFactoryDel factory);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a mod to be injected into client's world download.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool AddOverrideMod(ulong modId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a mod from the injected mod list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool RemoveOverrideMod(ulong modId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List over mods that will be injected into client world downloads.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyCollection<MyObjectBuilder_Checkpoint.ModItem> OverrideMods { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised when injected mod list changes.
|
||||||
|
/// </summary>
|
||||||
|
event Action<CollectionChangeEventArgs> OverrideModsChanged;
|
||||||
|
}
|
||||||
|
}
|
38
Torch.API/Session/TorchSessionState.cs
Normal file
38
Torch.API/Session/TorchSessionState.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.API.Session
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the state of a <see cref="ITorchSession"/>
|
||||||
|
/// </summary>
|
||||||
|
public enum TorchSessionState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The session has been created, and is now loading.
|
||||||
|
/// </summary>
|
||||||
|
Loading,
|
||||||
|
/// <summary>
|
||||||
|
/// The session has loaded, and is now running.
|
||||||
|
/// </summary>
|
||||||
|
Loaded,
|
||||||
|
/// <summary>
|
||||||
|
/// The session was running, and is now unloading.
|
||||||
|
/// </summary>
|
||||||
|
Unloading,
|
||||||
|
/// <summary>
|
||||||
|
/// The session was unloading, and is now unloaded and stopped.
|
||||||
|
/// </summary>
|
||||||
|
Unloaded
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback raised when a session's state changes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">The session who had a state change</param>
|
||||||
|
/// <param name="newState">The session's new state</param>
|
||||||
|
public delegate void TorchSessionStateChangedDel(ITorchSession session, TorchSessionState newState);
|
||||||
|
}
|
@@ -1,188 +1,105 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
<TargetFramework>net6-windows</TargetFramework>
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
<AssemblyTitle>Torch API</AssemblyTitle>
|
||||||
<ProjectGuid>{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}</ProjectGuid>
|
<Product>Torch</Product>
|
||||||
<OutputType>Library</OutputType>
|
<Copyright>Copyright © Torch API 2017</Copyright>
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
<RootNamespace>Torch.API</RootNamespace>
|
<OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||||
<AssemblyName>Torch.API</AssemblyName>
|
<UseWpf>True</UseWpf>
|
||||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<TargetFrameworkProfile />
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<Configurations>Debug;Release</Configurations>
|
||||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
<Platforms>AnyCPU</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
<PropertyGroup Condition="$(Configuration) == 'Release'">
|
||||||
<OutputPath>bin\x64\Release\</OutputPath>
|
<NoWarn>1591</NoWarn>
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
|
||||||
<DocumentationFile>bin\x64\Release\Torch.API.xml</DocumentationFile>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="NLog" Version="5.0.0-rc2" />
|
||||||
|
<PackageReference Include="SemanticVersioning" Version="2.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath>
|
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
|
||||||
<Reference Include="NLog">
|
|
||||||
<HintPath>..\packages\NLog.4.4.1\lib\net45\NLog.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="PresentationCore" />
|
|
||||||
<Reference Include="PresentationFramework" />
|
|
||||||
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Sandbox.Game, Version=0.1.6305.30774, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="Sandbox.Game, Version=0.1.6305.30774, Culture=neutral, processorArchitecture=AMD64">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
|
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Sandbox.Graphics, Version=0.1.6305.30761, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="Sandbox.Graphics, Version=0.1.6305.30761, Culture=neutral, processorArchitecture=AMD64">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\Sandbox.Graphics.dll</HintPath>
|
<HintPath>..\GameBinaries\Sandbox.Graphics.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SpaceEngineers.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="SpaceEngineers.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\SpaceEngineers.Game.dll</HintPath>
|
<HintPath>..\GameBinaries\SpaceEngineers.Game.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SpaceEngineers.ObjectBuilders, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="SpaceEngineers.ObjectBuilders, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\SpaceEngineers.ObjectBuilders.dll</HintPath>
|
<HintPath>..\GameBinaries\SpaceEngineers.ObjectBuilders.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
|
||||||
<Reference Include="SpaceEngineers.ObjectBuilders.XmlSerializers, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\GameBinaries\SpaceEngineers.ObjectBuilders.XmlSerializers.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SteamSDK, Version=0.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\SteamSDK.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="System.Configuration" />
|
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.IO.Compression" />
|
|
||||||
<Reference Include="System.Runtime.Serialization" />
|
|
||||||
<Reference Include="System.ServiceModel" />
|
|
||||||
<Reference Include="System.Transactions" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
<Reference Include="VRage">
|
<Reference Include="VRage">
|
||||||
<HintPath>..\GameBinaries\VRage.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Audio, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="VRage.Audio, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\VRage.Audio.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Audio.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Dedicated">
|
<Reference Include="VRage.Dedicated">
|
||||||
<HintPath>..\GameBinaries\VRage.Dedicated.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Dedicated.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="VRage.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
|
||||||
<Reference Include="VRage.Game.XmlSerializers, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\GameBinaries\VRage.Game.XmlSerializers.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Input, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="VRage.Input, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\VRage.Input.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Input.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Library">
|
<Reference Include="VRage.Library">
|
||||||
<HintPath>..\GameBinaries\VRage.Library.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Library.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Math, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="VRage.Math, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\VRage.Math.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Math.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
|
||||||
<Reference Include="VRage.Native, Version=0.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\GameBinaries\VRage.Native.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="VRage.OpenVRWrapper, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\VRage.OpenVRWrapper.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Render, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="VRage.Render, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\VRage.Render.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Render.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Render11, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="VRage.Render11, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\VRage.Render11.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Render11.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Scripting, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="VRage.Scripting, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\VRage.Scripting.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Scripting.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="ConnectionState.cs" />
|
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties/AssemblyVersion.cs" />
|
||||||
<Compile Include="IChatMessage.cs" />
|
|
||||||
<Compile Include="ITorchConfig.cs" />
|
|
||||||
<Compile Include="Managers\IManager.cs" />
|
|
||||||
<Compile Include="Managers\IMultiplayerManager.cs" />
|
|
||||||
<Compile Include="IPlayer.cs" />
|
|
||||||
<Compile Include="Managers\INetworkManager.cs" />
|
|
||||||
<Compile Include="Managers\IPluginManager.cs" />
|
|
||||||
<Compile Include="Plugins\ITorchPlugin.cs" />
|
|
||||||
<Compile Include="IServerControls.cs" />
|
|
||||||
<Compile Include="ITorchBase.cs" />
|
|
||||||
<Compile Include="Plugins\IWpfPlugin.cs" />
|
|
||||||
<Compile Include="ModAPI\Ingame\GridExtensions.cs" />
|
|
||||||
<Compile Include="Plugins\PluginAttribute.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
<Compile Include="ServerState.cs" />
|
|
||||||
<Compile Include="ModAPI\TorchAPI.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<None Include="packages.config" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
|
||||||
<Target Name="BeforeBuild">
|
|
||||||
</Target>
|
|
||||||
<Target Name="AfterBuild">
|
|
||||||
</Target>
|
|
||||||
-->
|
|
||||||
</Project>
|
</Project>
|
47
Torch.API/TorchGameState.cs
Normal file
47
Torch.API/TorchGameState.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace Torch.API
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the state of a <see cref="MySandboxGame"/>
|
||||||
|
/// </summary>
|
||||||
|
public enum TorchGameState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The game is currently being created.
|
||||||
|
/// </summary>
|
||||||
|
Creating,
|
||||||
|
/// <summary>
|
||||||
|
/// The game has been created and is ready to begin loading.
|
||||||
|
/// </summary>
|
||||||
|
Created,
|
||||||
|
/// <summary>
|
||||||
|
/// The game is currently loading.
|
||||||
|
/// </summary>
|
||||||
|
Loading,
|
||||||
|
/// <summary>
|
||||||
|
/// The game is fully loaded and ready to start sessions
|
||||||
|
/// </summary>
|
||||||
|
Loaded,
|
||||||
|
/// <summary>
|
||||||
|
/// The game is beginning the unload sequence
|
||||||
|
/// </summary>
|
||||||
|
Unloading,
|
||||||
|
/// <summary>
|
||||||
|
/// The game has been shutdown and is no longer active
|
||||||
|
/// </summary>
|
||||||
|
Unloaded
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback raised when a game's state changes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="game">The game who had a state change</param>
|
||||||
|
/// <param name="newState">The game's new state</param>
|
||||||
|
public delegate void TorchGameStateChangedDel(MySandboxGame game, TorchGameState newState);
|
||||||
|
}
|
8
Torch.API/UGCServiceType.cs
Normal file
8
Torch.API/UGCServiceType.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Torch.API
|
||||||
|
{
|
||||||
|
public enum UGCServiceType
|
||||||
|
{
|
||||||
|
Steam,
|
||||||
|
EOS
|
||||||
|
}
|
||||||
|
}
|
40
Torch.API/Utils/ColorUtils.cs
Normal file
40
Torch.API/Utils/ColorUtils.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Windows.Media;
|
||||||
|
using VRage.Game;
|
||||||
|
using Color = VRageMath.Color;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
public static class ColorUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Convert the old "font" or a RGB hex code to a Color.
|
||||||
|
/// </summary>
|
||||||
|
public static Color TranslateColor(string font)
|
||||||
|
{
|
||||||
|
if (StringUtils.IsFontEnum(font))
|
||||||
|
{
|
||||||
|
// RGB values copied from Fonts.sbc
|
||||||
|
switch (font)
|
||||||
|
{
|
||||||
|
case MyFontEnum.Blue:
|
||||||
|
return new Color(220, 244, 252);
|
||||||
|
case MyFontEnum.Red:
|
||||||
|
return new Color(227, 65, 65);
|
||||||
|
case MyFontEnum.Green:
|
||||||
|
return new Color(101, 182, 193);
|
||||||
|
case MyFontEnum.DarkBlue:
|
||||||
|
return new Color(94, 115, 127);
|
||||||
|
default:
|
||||||
|
return Color.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// VRage color doesn't have its own hex code parser and I don't want to write one
|
||||||
|
var conv = (System.Windows.Media.Color)(ColorConverter.ConvertFromString(font) ??
|
||||||
|
System.Windows.Media.Color.FromRgb(255, 255, 255));
|
||||||
|
return new Color(conv.R, conv.G, conv.B);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
Torch.API/Utils/SemanticVersionConverter.cs
Normal file
21
Torch.API/Utils/SemanticVersionConverter.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Version = SemanticVersioning.Version;
|
||||||
|
|
||||||
|
namespace Torch.API.Utils;
|
||||||
|
|
||||||
|
public class SemanticVersionConverter : JsonConverter<Version>
|
||||||
|
{
|
||||||
|
public override Version? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
Version.TryParse(reader.GetString(), out var ver);
|
||||||
|
return ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(value.ToString());
|
||||||
|
}
|
||||||
|
}
|
73
Torch.API/Utils/StringUtils.cs
Normal file
73
Torch.API/Utils/StringUtils.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Utility methods for strings
|
||||||
|
/// </summary>
|
||||||
|
public static class StringUtils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines a common prefix for the given set of strings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="set">Set of strings</param>
|
||||||
|
/// <returns>Common prefix</returns>
|
||||||
|
public static string CommonPrefix(IEnumerable<string> set)
|
||||||
|
{
|
||||||
|
StringBuilder builder = null;
|
||||||
|
foreach (string other in set)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
builder = new StringBuilder(other);
|
||||||
|
if (builder.Length > other.Length)
|
||||||
|
builder.Remove(other.Length, builder.Length - other.Length);
|
||||||
|
for (var i = 0; i < builder.Length; i++)
|
||||||
|
if (builder[i] != other[i])
|
||||||
|
{
|
||||||
|
builder.Remove(i, builder.Length - i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder?.ToString() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines a common suffix for the given set of strings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="set">Set of strings</param>
|
||||||
|
/// <returns>Common suffix</returns>
|
||||||
|
public static string CommonSuffix(IEnumerable<string> set)
|
||||||
|
{
|
||||||
|
StringBuilder builder = null;
|
||||||
|
foreach (string other in set)
|
||||||
|
{
|
||||||
|
if (builder == null)
|
||||||
|
builder = new StringBuilder(other);
|
||||||
|
if (builder.Length > other.Length)
|
||||||
|
builder.Remove(0, builder.Length - other.Length);
|
||||||
|
for (var i = 0; i < builder.Length; i++)
|
||||||
|
{
|
||||||
|
if (builder[builder.Length - 1 - i] != other[other.Length - 1 - i])
|
||||||
|
{
|
||||||
|
builder.Remove(0, builder.Length - i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder?.ToString() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] FontEnumValues => _fontEnumValues ?? (_fontEnumValues = typeof(VRage.Game.MyFontEnum).GetFields(BindingFlags.Public | BindingFlags.Static).Where(x => x.IsLiteral && !x.IsInitOnly).Select(x => (string)x.GetValue(null)).ToArray());
|
||||||
|
|
||||||
|
private static string[] _fontEnumValues;
|
||||||
|
public static bool IsFontEnum(string str)
|
||||||
|
{
|
||||||
|
return FontEnumValues.Contains(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
Torch.API/WebAPI/JenkinsQuery.cs
Normal file
84
Torch.API/WebAPI/JenkinsQuery.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using Torch.API.Utils;
|
||||||
|
using Version = SemanticVersioning.Version;
|
||||||
|
|
||||||
|
namespace Torch.API.WebAPI
|
||||||
|
{
|
||||||
|
public class JenkinsQuery
|
||||||
|
{
|
||||||
|
private const string BRANCH_QUERY = "http://136.243.80.164:2690/job/Torch/job/{0}/" + API_PATH;
|
||||||
|
private const string ARTIFACT_PATH = "artifact/bin/torch-server.zip";
|
||||||
|
private const string API_PATH = "api/json";
|
||||||
|
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
private static JenkinsQuery _instance;
|
||||||
|
public static JenkinsQuery Instance => _instance ??= new JenkinsQuery();
|
||||||
|
private HttpClient _client;
|
||||||
|
|
||||||
|
private JenkinsQuery()
|
||||||
|
{
|
||||||
|
_client = new HttpClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Job> GetLatestVersion(string branch)
|
||||||
|
{
|
||||||
|
var h = await _client.GetAsync(string.Format(BRANCH_QUERY, branch));
|
||||||
|
if (!h.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Log.Error($"'{branch}' Branch query failed with code {h.StatusCode}");
|
||||||
|
if(h.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
Log.Error("This likely means you're trying to update a branch that is not public on Jenkins. Sorry :(");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var branchResponse = await h.Content.ReadFromJsonAsync<BranchResponse>();
|
||||||
|
|
||||||
|
if (branchResponse is null)
|
||||||
|
{
|
||||||
|
Log.Error("Error reading branch response");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
h = await _client.GetAsync($"{branchResponse.LastStableBuild.Url}{API_PATH}");
|
||||||
|
if (h.IsSuccessStatusCode)
|
||||||
|
return await h.Content.ReadFromJsonAsync<Job>();
|
||||||
|
|
||||||
|
Log.Error($"Job query failed with code {h.StatusCode}");
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DownloadRelease(Job job, string path)
|
||||||
|
{
|
||||||
|
var h = await _client.GetAsync(job.Url + ARTIFACT_PATH);
|
||||||
|
if (!h.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Log.Error($"Job download failed with code {h.StatusCode}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var s = await h.Content.ReadAsStreamAsync();
|
||||||
|
await using var fs = new FileStream(path, FileMode.Create);
|
||||||
|
await s.CopyToAsync(fs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public record BranchResponse(string Name, string Url, Build LastBuild, Build LastStableBuild);
|
||||||
|
|
||||||
|
public record Build(int Number, string Url);
|
||||||
|
|
||||||
|
public record Job(int Number, bool Building, string Description, string Result, string Url,
|
||||||
|
[property: JsonConverter(typeof(SemanticVersionConverter))] Version Version);
|
||||||
|
}
|
104
Torch.API/WebAPI/PluginQuery.cs
Normal file
104
Torch.API/WebAPI/PluginQuery.cs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
|
namespace Torch.API.WebAPI
|
||||||
|
{
|
||||||
|
public class PluginQuery
|
||||||
|
{
|
||||||
|
private const string ALL_QUERY = "https://torchapi.com/api/plugins/";
|
||||||
|
private const string PLUGIN_QUERY = "https://torchapi.com/api/plugins/?guid={0}";
|
||||||
|
private readonly HttpClient _client;
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
private static PluginQuery _instance;
|
||||||
|
public static PluginQuery Instance => _instance ??= new();
|
||||||
|
|
||||||
|
private PluginQuery()
|
||||||
|
{
|
||||||
|
_client = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PluginsResponse> QueryAll()
|
||||||
|
{
|
||||||
|
return (PluginsResponse) await _client.GetFromJsonAsync(ALL_QUERY, typeof(PluginsResponse), CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<PluginItem> QueryOne(Guid guid)
|
||||||
|
{
|
||||||
|
return QueryOne(guid.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PluginItem> QueryOne(string guid)
|
||||||
|
{
|
||||||
|
using var res = await _client.GetAsync(string.Format(PLUGIN_QUERY, guid));
|
||||||
|
if (!res.IsSuccessStatusCode)
|
||||||
|
return null;
|
||||||
|
return await res.Content.ReadFromJsonAsync<PluginItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> DownloadPlugin(Guid guid, string path = null)
|
||||||
|
{
|
||||||
|
return DownloadPlugin(guid.ToString(), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DownloadPlugin(string guid, string path = null)
|
||||||
|
{
|
||||||
|
var item = await QueryOne(guid);
|
||||||
|
if (item is null) return false;
|
||||||
|
return await DownloadPlugin(item, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DownloadPlugin(PluginItem item, string path = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
path ??= Path.Combine(Directory.GetCurrentDirectory(), "Plugins", $"{item.Name}.zip");
|
||||||
|
|
||||||
|
if (item.Versions.Length == 0)
|
||||||
|
{
|
||||||
|
Log.Error($"Selected plugin {item.Name} does not have any versions to download!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var version = item.Versions.FirstOrDefault(v => v.Version == item.LatestVersion);
|
||||||
|
if (version is null)
|
||||||
|
{
|
||||||
|
Log.Error($"Could not find latest version for selected plugin {item.Name}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var s = await _client.GetStreamAsync(version.Url);
|
||||||
|
|
||||||
|
if(File.Exists(path))
|
||||||
|
File.Delete(path);
|
||||||
|
|
||||||
|
await using var f = File.Create(path);
|
||||||
|
await s.CopyToAsync(f);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to download plugin!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record PluginsResponse(PluginItem[] Plugins);
|
||||||
|
|
||||||
|
public record PluginItem(Guid Id, string Name, string Author, string Description, string LatestVersion,
|
||||||
|
VersionItem[] Versions)
|
||||||
|
{
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool Installed { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record VersionItem(string Version, string Note, [property: JsonPropertyName("is_beta")] bool IsBeta,
|
||||||
|
string Url);
|
||||||
|
}
|
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="NLog" version="4.4.1" targetFramework="net461" />
|
|
||||||
</packages>
|
|
@@ -1,12 +1,17 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
[assembly: AssemblyTitle("Torch Server")]
|
[assembly: AssemblyTitle("Torch Client Tests")]
|
||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
|
||||||
[assembly: AssemblyCompany("")]
|
[assembly: AssemblyCompany("")]
|
||||||
[assembly: AssemblyProduct("Torch")]
|
[assembly: AssemblyProduct("Torch")]
|
||||||
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
[assembly: ComVisible(false)]
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
[assembly: AssemblyConfiguration("Debug")]
|
||||||
|
#else
|
||||||
|
[assembly: AssemblyConfiguration("Release")]
|
||||||
|
#endif
|
51
Torch.Client.Tests/Torch.Client.Tests.csproj
Normal file
51
Torch.Client.Tests/Torch.Client.Tests.csproj
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net461</TargetFramework>
|
||||||
|
<NoWarn>1591,0649</NoWarn>
|
||||||
|
<AssemblyTitle>Torch Client Tests</AssemblyTitle>
|
||||||
|
<Product>Torch</Product>
|
||||||
|
<Copyright>Copyright © Torch API 2017</Copyright>
|
||||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<OutputPath>$(SolutionDir)\bin-test\$(Platform)\$(Configuration)\</OutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<DocumentationFile>$(SolutionDir)\bin-test\$(Platform)\$(Configuration)\Torch.Client.Tests.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Mono.TextTransform" Version="1.0.0" />
|
||||||
|
<PackageReference Include="NLog" Version="4.4.12" />
|
||||||
|
<PackageReference Include="xunit" Version="2.2.0" />
|
||||||
|
<PackageReference Include="xunit.abstractions" Version="2.0.1" />
|
||||||
|
<PackageReference Include="xunit.assert" Version="2.2.0" />
|
||||||
|
<PackageReference Include="xunit.core" Version="2.2.0" />
|
||||||
|
<PackageReference Include="xunit.extensibility.core" Version="2.2.0" />
|
||||||
|
<PackageReference Include="xunit.extensibility.execution" Version="2.2.0" />
|
||||||
|
<PackageReference Include="xunit.runner.console" Version="2.2.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties\AssemblyVersion.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Torch.API\Torch.API.csproj" />
|
||||||
|
<ProjectReference Include="..\Torch.Client\Torch.Client.csproj" />
|
||||||
|
<ProjectReference Include="..\Torch.Tests\Torch.Tests.csproj" />
|
||||||
|
<ProjectReference Include="..\Torch\Torch.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="obj\x64\Debug\.NETFramework,Version=v4.6.1.AssemblyAttributes.cs" />
|
||||||
|
<Compile Remove="obj\x64\Release\.NETFramework,Version=v4.6.1.AssemblyAttributes.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
94
Torch.Client.Tests/TorchClientReflectionTest.cs
Normal file
94
Torch.Client.Tests/TorchClientReflectionTest.cs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Torch.Client;
|
||||||
|
using Torch.Tests;
|
||||||
|
using Torch.Utils;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Torch.Client.Tests
|
||||||
|
{
|
||||||
|
public class TorchClientReflectionTest
|
||||||
|
{
|
||||||
|
static TorchClientReflectionTest()
|
||||||
|
{
|
||||||
|
TestUtils.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ReflectionTestManager _manager;
|
||||||
|
|
||||||
|
private static ReflectionTestManager Manager()
|
||||||
|
{
|
||||||
|
if (_manager != null)
|
||||||
|
return _manager;
|
||||||
|
|
||||||
|
return _manager = new ReflectionTestManager().Init(typeof(TorchClient).Assembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Getters => Manager().Getters;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Setters => Manager().Setters;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> MemberInfo => Manager().MemberInfo;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Events => Manager().Events;
|
||||||
|
|
||||||
|
#region Binding
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Getters))]
|
||||||
|
public void TestBindingGetter(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Setters))]
|
||||||
|
public void TestBindingSetter(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Invokers))]
|
||||||
|
public void TestBindingInvoker(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(MemberInfo))]
|
||||||
|
public void TestBindingMemberInfo(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Events))]
|
||||||
|
public void TestBindingEvents(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
((Func<ReflectedEventReplacer>)field.Field.GetValue(null)).Invoke();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
11
Torch.Client.Tests/app.config
Normal file
11
Torch.Client.Tests/app.config
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="protobuf-net" publicKeyToken="257b51d87d2e4d67" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-2.4.0.0" newVersion="2.4.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
32
Torch.Client/Manager/MultiplayerManagerClient.cs
Normal file
32
Torch.Client/Manager/MultiplayerManagerClient.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers;
|
||||||
|
|
||||||
|
namespace Torch.Client.Manager
|
||||||
|
{
|
||||||
|
public class MultiplayerManagerClient : MultiplayerManagerBase, IMultiplayerManagerClient
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MultiplayerManagerClient(ITorchBase torch) : base(torch) { }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
base.Attach();
|
||||||
|
MyMultiplayer.Static.ClientJoined += RaiseClientJoined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
MyMultiplayer.Static.ClientJoined -= RaiseClientJoined;
|
||||||
|
base.Detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
Torch.Client/Manager/MultiplayerManagerLobby.cs
Normal file
95
Torch.Client/Manager/MultiplayerManagerLobby.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Game.World;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
|
namespace Torch.Client.Manager
|
||||||
|
{
|
||||||
|
public class MultiplayerManagerLobby : MultiplayerManagerBase, IMultiplayerManagerServer
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<ulong> BannedPlayers => new List<ulong>();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MultiplayerManagerLobby(ITorchBase torch) : base(torch) { }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void BanPlayer(ulong steamId, bool banned = true) => Torch.Invoke(() => MyMultiplayer.Static.BanClient(steamId, banned));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void PromoteUser(ulong steamId)
|
||||||
|
{
|
||||||
|
Torch.Invoke(() =>
|
||||||
|
{
|
||||||
|
var p = MySession.Static.GetUserPromoteLevel(steamId);
|
||||||
|
if (p < MyPromoteLevel.Admin) //cannot promote to owner by design
|
||||||
|
MySession.Static.SetUserPromoteLevel(steamId, p + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void DemoteUser(ulong steamId)
|
||||||
|
{
|
||||||
|
Torch.Invoke(() =>
|
||||||
|
{
|
||||||
|
var p = MySession.Static.GetUserPromoteLevel(steamId);
|
||||||
|
if (p > MyPromoteLevel.None && p < MyPromoteLevel.Owner) //owner cannot be demoted by design
|
||||||
|
MySession.Static.SetUserPromoteLevel(steamId, p - 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MyPromoteLevel GetUserPromoteLevel(ulong steamId)
|
||||||
|
{
|
||||||
|
return MySession.Static.GetUserPromoteLevel(steamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsBanned(ulong steamId) => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event Action<ulong> PlayerKicked
|
||||||
|
{
|
||||||
|
add => throw new NotImplementedException();
|
||||||
|
remove => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event Action<ulong, bool> PlayerBanned
|
||||||
|
{
|
||||||
|
add => throw new NotImplementedException();
|
||||||
|
remove => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event Action<ulong, MyPromoteLevel> PlayerPromoted
|
||||||
|
{
|
||||||
|
add => throw new NotImplementedException();
|
||||||
|
remove => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
base.Attach();
|
||||||
|
MyMultiplayer.Static.ClientJoined += RaiseClientJoined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
MyMultiplayer.Static.ClientJoined -= RaiseClientJoined;
|
||||||
|
base.Detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,17 +1,180 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using System.Windows.Forms;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using Torch.Utils;
|
||||||
|
using MessageBox = System.Windows.MessageBox;
|
||||||
|
|
||||||
namespace Torch.Client
|
namespace Torch.Client
|
||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
|
public const string SpaceEngineersBinaries = "Bin64";
|
||||||
|
private static string _spaceEngInstallAlias = null;
|
||||||
|
|
||||||
|
public static string SpaceEngineersInstallAlias
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// ReSharper disable once ConvertIfStatementToNullCoalescingExpression
|
||||||
|
if (_spaceEngInstallAlias == null)
|
||||||
|
{
|
||||||
|
// ReSharper disable once AssignNullToNotNullAttribute
|
||||||
|
_spaceEngInstallAlias = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location),
|
||||||
|
"SpaceEngineersAlias");
|
||||||
|
}
|
||||||
|
return _spaceEngInstallAlias;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string _steamSpaceEngineersDirectory = @"steamapps\common\SpaceEngineers\";
|
||||||
|
private const string _spaceEngineersVerifyFile = SpaceEngineersBinaries + @"\SpaceEngineers.exe";
|
||||||
|
|
||||||
|
public const string ConfigName = "Torch.cfg";
|
||||||
|
|
||||||
private static Logger _log = LogManager.GetLogger("Torch");
|
private static Logger _log = LogManager.GetLogger("Torch");
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern void AllocConsole();
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern void FreeConsole();
|
||||||
|
#endif
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
#if DEBUG
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AllocConsole();
|
||||||
|
#endif
|
||||||
|
if (!TorchLauncher.IsTorchWrapped())
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||||
|
|
||||||
|
// Early config: Resolve SE install directory.
|
||||||
|
if (!File.Exists(Path.Combine(SpaceEngineersInstallAlias, _spaceEngineersVerifyFile)))
|
||||||
|
SetupSpaceEngInstallAlias();
|
||||||
|
|
||||||
|
TorchLauncher.Launch(Assembly.GetEntryAssembly().FullName, args,
|
||||||
|
Path.Combine(SpaceEngineersInstallAlias, SpaceEngineersBinaries));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RunClient();
|
||||||
|
#if DEBUG
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
FreeConsole();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetupSpaceEngInstallAlias()
|
||||||
|
{
|
||||||
|
string spaceEngineersDirectory = null;
|
||||||
|
|
||||||
|
// TODO look at Steam/config/Config.VDF? Has alternate directories.
|
||||||
|
var steamDir =
|
||||||
|
Microsoft.Win32.Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\Valve\\Steam", "SteamPath",
|
||||||
|
null) as string;
|
||||||
|
if (steamDir != null)
|
||||||
|
{
|
||||||
|
spaceEngineersDirectory = Path.Combine(steamDir, _steamSpaceEngineersDirectory);
|
||||||
|
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||||
|
if (File.Exists(Path.Combine(spaceEngineersDirectory, _spaceEngineersVerifyFile)))
|
||||||
|
_log.Debug("Found Space Engineers in {0}", spaceEngineersDirectory);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_log.Debug("Couldn't find Space Engineers in {0}", spaceEngineersDirectory);
|
||||||
|
spaceEngineersDirectory = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (spaceEngineersDirectory == null)
|
||||||
|
{
|
||||||
|
var dialog = new System.Windows.Forms.FolderBrowserDialog
|
||||||
|
{
|
||||||
|
Description = "Please select the SpaceEngineers installation folder"
|
||||||
|
};
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (dialog.ShowDialog() != DialogResult.OK)
|
||||||
|
{
|
||||||
|
var ex = new FileNotFoundException(
|
||||||
|
"Unable to find the Space Engineers install directory, aborting");
|
||||||
|
_log.Fatal(ex);
|
||||||
|
LogManager.Flush();
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
spaceEngineersDirectory = dialog.SelectedPath;
|
||||||
|
if (File.Exists(Path.Combine(spaceEngineersDirectory, _spaceEngineersVerifyFile)))
|
||||||
|
break;
|
||||||
|
if (MessageBox.Show(
|
||||||
|
$"Unable to find {0} in {1}. Are you sure it's the Space Engineers install directory?",
|
||||||
|
"Invalid Space Engineers Directory", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||||
|
break;
|
||||||
|
} while (true); // Repeat until they confirm.
|
||||||
|
}
|
||||||
|
if (!JunctionLink(SpaceEngineersInstallAlias, spaceEngineersDirectory))
|
||||||
|
{
|
||||||
|
var ex = new IOException(
|
||||||
|
$"Failed to create junction link {SpaceEngineersInstallAlias} => {spaceEngineersDirectory}. Aborting.");
|
||||||
|
_log.Fatal(ex);
|
||||||
|
LogManager.Flush();
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
string junctionVerify = Path.Combine(SpaceEngineersInstallAlias, _spaceEngineersVerifyFile);
|
||||||
|
if (!File.Exists(junctionVerify))
|
||||||
|
{
|
||||||
|
var ex = new FileNotFoundException(
|
||||||
|
$"Junction link is not working. File {junctionVerify} does not exist");
|
||||||
|
_log.Fatal(ex);
|
||||||
|
LogManager.Flush();
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool JunctionLink(string linkName, string targetDir)
|
||||||
|
{
|
||||||
|
var junctionLinkProc = new ProcessStartInfo("cmd.exe", $"/c mklink /J \"{linkName}\" \"{targetDir}\"")
|
||||||
|
{
|
||||||
|
WorkingDirectory = Directory.GetCurrentDirectory(),
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
StandardOutputEncoding = Encoding.ASCII
|
||||||
|
};
|
||||||
|
Process cmd = Process.Start(junctionLinkProc);
|
||||||
|
// ReSharper disable once PossibleNullReferenceException
|
||||||
|
while (!cmd.HasExited)
|
||||||
|
{
|
||||||
|
string line = cmd.StandardOutput.ReadLine();
|
||||||
|
if (!string.IsNullOrWhiteSpace(line))
|
||||||
|
_log.Info(line);
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
if (cmd.ExitCode != 0)
|
||||||
|
_log.Error("Unable to create junction link {0} => {1}", linkName, targetDir);
|
||||||
|
return cmd.ExitCode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
var ex = (Exception) e.ExceptionObject;
|
||||||
|
_log.Error(ex);
|
||||||
|
LogManager.Flush();
|
||||||
|
MessageBox.Show(ex.StackTrace, ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static void RunClient()
|
||||||
|
{
|
||||||
var client = new TorchClient();
|
var client = new TorchClient();
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -27,11 +190,5 @@ namespace Torch.Client
|
|||||||
|
|
||||||
client.Start();
|
client.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
|
||||||
{
|
|
||||||
var ex = (Exception)e.ExceptionObject;
|
|
||||||
MessageBox.Show(ex.StackTrace, ex.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,17 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("1.0.213.390")]
|
[assembly: AssemblyTitle("Torch Client")]
|
||||||
[assembly: AssemblyFileVersion("1.0.213.390")]
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Torch")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
[assembly: AssemblyConfiguration("Debug")]
|
||||||
|
#else
|
||||||
|
[assembly: AssemblyConfiguration("Release")]
|
||||||
|
#endif
|
@@ -1,16 +0,0 @@
|
|||||||
<#@ template debug="false" hostspecific="false" language="C#" #>
|
|
||||||
<#@ assembly name="System.Core" #>
|
|
||||||
<#@ import namespace="System.Linq" #>
|
|
||||||
<#@ import namespace="System.Text" #>
|
|
||||||
<#@ import namespace="System.Collections.Generic" #>
|
|
||||||
<#@ output extension=".cs" #>
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
<# var dt = DateTime.Now;
|
|
||||||
int major = 1;
|
|
||||||
int minor = 0;
|
|
||||||
int build = dt.DayOfYear;
|
|
||||||
int rev = (int)dt.TimeOfDay.TotalMinutes / 2;
|
|
||||||
#>
|
|
||||||
[assembly: AssemblyVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
|
||||||
[assembly: AssemblyFileVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
|
@@ -1,78 +1,68 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{E36DF745-260B-4956-A2E8-09F08B2E7161}</ProjectGuid>
|
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<TargetFramework>net461</TargetFramework>
|
||||||
<RootNamespace>Torch.Client</RootNamespace>
|
|
||||||
<AssemblyName>Torch.Client</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
<TargetFrameworkProfile />
|
<AssemblyTitle>Torch Client</AssemblyTitle>
|
||||||
|
<Product>Torch</Product>
|
||||||
|
<Copyright>Copyright © Torch API 2017</Copyright>
|
||||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<OutputPath>$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||||
|
<Prefer32Bit>true</Prefer32Bit>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
|
||||||
|
</PostBuildEvent>
|
||||||
|
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
|
||||||
|
</PostBuildEvent>
|
||||||
|
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
|
||||||
|
</PostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
|
||||||
<DebugType>full</DebugType>
|
<DebugType>full</DebugType>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
|
||||||
<Prefer32Bit>true</Prefer32Bit>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||||
<OutputPath>bin\x64\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<DocumentationFile>$(SolutionDir)\bin\$(Platform)\$(Configuration)\Torch.Client.xml</DocumentationFile>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<NoWarn>1591</NoWarn>
|
||||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
|
||||||
<Prefer32Bit>true</Prefer32Bit>
|
|
||||||
<DocumentationFile>bin\x64\Release\Torch.Client.xml</DocumentationFile>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ApplicationIcon>torchicon.ico</ApplicationIcon>
|
<ApplicationIcon>torchicon.ico</ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
|
||||||
|
</PostBuildEvent>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Mono.TextTransform" Version="1.0.0" />
|
||||||
|
<PackageReference Include="NLog" Version="4.4.12" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\NLog.4.4.1\lib\net45\NLog.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Sandbox.Game">
|
<Reference Include="Sandbox.Game">
|
||||||
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
|
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Sandbox.Graphics, Version=0.1.6108.20417, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="Sandbox.Graphics, Version=0.1.6108.20417, Culture=neutral, processorArchitecture=AMD64">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\Sandbox.Graphics.dll</HintPath>
|
<HintPath>..\GameBinaries\Sandbox.Graphics.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="SpaceEngineers.Game">
|
<Reference Include="SpaceEngineers.Game">
|
||||||
<HintPath>..\GameBinaries\SpaceEngineers.Game.dll</HintPath>
|
<HintPath>..\GameBinaries\SpaceEngineers.Game.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
<Reference Include="System.Xaml">
|
<Reference Include="System.Xaml" />
|
||||||
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="VRage">
|
<Reference Include="VRage">
|
||||||
<HintPath>..\GameBinaries\VRage.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
@@ -81,19 +71,23 @@
|
|||||||
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="VRage.Game.XmlSerializers">
|
||||||
|
<HintPath>..\GameBinaries\VRage.Game.XmlSerializers.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="VRage.Input, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="VRage.Input, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\VRage.Input.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Input.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Library">
|
<Reference Include="VRage.Library">
|
||||||
<HintPath>..\GameBinaries\VRage.Library.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Library.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Math, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="VRage.Math, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
|
||||||
<HintPath>..\GameBinaries\VRage.Math.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Math.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Render">
|
<Reference Include="VRage.Render">
|
||||||
<HintPath>..\GameBinaries\VRage.Render.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Render.dll</HintPath>
|
||||||
@@ -103,76 +97,26 @@
|
|||||||
<HintPath>..\GameBinaries\VRage.Render11.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Render11.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="VRage.Steam">
|
||||||
|
<HintPath>..\GameBinaries\VRage.Steam.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="WindowsBase" />
|
<Reference Include="WindowsBase" />
|
||||||
<Reference Include="PresentationCore" />
|
<Reference Include="PresentationCore" />
|
||||||
<Reference Include="PresentationFramework" />
|
<Reference Include="PresentationFramework" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Properties\AssemblyInfo.cs">
|
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties\AssemblyVersion.cs" />
|
||||||
<DependentUpon>AssemblyInfo.tt</DependentUpon>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="Properties\AssemblyInfo1.cs" />
|
|
||||||
<Compile Include="TorchClient.cs" />
|
|
||||||
<Compile Include="TorchConsoleScreen.cs" />
|
|
||||||
<Compile Include="TorchMainMenuScreen.cs" />
|
|
||||||
<Compile Include="TorchSettingsScreen.cs" />
|
|
||||||
<Compile Include="Program.cs" />
|
|
||||||
<Compile Include="Properties\Resources.Designer.cs">
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<DependentUpon>Resources.resx</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="Properties\Settings.Designer.cs">
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>Settings.settings</DependentUpon>
|
|
||||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
|
||||||
</Compile>
|
|
||||||
<EmbeddedResource Include="Properties\Resources.resx">
|
|
||||||
<Generator>ResXFileCodeGenerator</Generator>
|
|
||||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
|
||||||
</EmbeddedResource>
|
|
||||||
<None Include="packages.config" />
|
|
||||||
<None Include="Properties\Settings.settings">
|
|
||||||
<Generator>SettingsSingleFileGenerator</Generator>
|
|
||||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
|
||||||
</None>
|
|
||||||
<AppDesigner Include="Properties\" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
|
<ProjectReference Include="..\Torch.API\Torch.API.csproj" />
|
||||||
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
|
<ProjectReference Include="..\Torch\Torch.csproj" />
|
||||||
<Name>Torch.API</Name>
|
|
||||||
<Private>True</Private>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\Torch\Torch.csproj">
|
|
||||||
<Project>{7E01635C-3B67-472E-BCD6-C5539564F214}</Project>
|
|
||||||
<Name>Torch</Name>
|
|
||||||
<Private>True</Private>
|
|
||||||
</ProjectReference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Resource Include="torchicon.ico" />
|
<Resource Include="torchicon.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="Properties\AssemblyInfo.tt">
|
<Compile Remove="obj\x64\Debug\.NETFramework,Version=v4.6.1.AssemblyAttributes.cs" />
|
||||||
<Generator>TextTemplatingFileGenerator</Generator>
|
<Compile Remove="obj\x64\Release\.NETFramework,Version=v4.6.1.AssemblyAttributes.cs" />
|
||||||
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
<PropertyGroup>
|
|
||||||
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"</PostBuildEvent>
|
|
||||||
</PropertyGroup>
|
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
|
||||||
<Target Name="BeforeBuild">
|
|
||||||
</Target>
|
|
||||||
<Target Name="AfterBuild">
|
|
||||||
</Target>
|
|
||||||
-->
|
|
||||||
</Project>
|
</Project>
|
@@ -1,71 +1,56 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Sandbox;
|
using Sandbox;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
using Sandbox.Engine.Platform;
|
using Sandbox.Engine.Platform;
|
||||||
using Sandbox.Engine.Utils;
|
|
||||||
using Sandbox.Game;
|
using Sandbox.Game;
|
||||||
using Sandbox.ModAPI;
|
|
||||||
using SpaceEngineers.Game;
|
using SpaceEngineers.Game;
|
||||||
|
using VRage.Steam;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.API.Session;
|
||||||
|
using Torch.Client.Manager;
|
||||||
|
using Torch.Client.UI;
|
||||||
|
using Torch.Session;
|
||||||
|
using VRage;
|
||||||
using VRage.FileSystem;
|
using VRage.FileSystem;
|
||||||
|
using VRage.GameServices;
|
||||||
using VRageRender;
|
using VRageRender;
|
||||||
|
using VRageRender.ExternalApp;
|
||||||
|
|
||||||
namespace Torch.Client
|
namespace Torch.Client
|
||||||
{
|
{
|
||||||
public class TorchClient : TorchBase, ITorchClient
|
public class TorchClient : TorchBase, ITorchClient
|
||||||
{
|
{
|
||||||
private MyCommonProgramStartup _startup;
|
protected override uint SteamAppId => 244850;
|
||||||
private IMyRender _renderer;
|
protected override string SteamAppName => "SpaceEngineers";
|
||||||
private const uint APP_ID = 244850;
|
|
||||||
private VRageGameServices _services;
|
public TorchClient()
|
||||||
|
{
|
||||||
|
Config = new TorchClientConfig();
|
||||||
|
var sessionManager = Managers.GetManager<ITorchSessionManager>();
|
||||||
|
sessionManager.AddFactory((x) => MyMultiplayer.Static is MyMultiplayerLobby
|
||||||
|
? new MultiplayerManagerLobby(this)
|
||||||
|
: null);
|
||||||
|
sessionManager.AddFactory((x) => MyMultiplayer.Static is MyMultiplayerClientBase
|
||||||
|
? new MultiplayerManagerClient(this)
|
||||||
|
: null);
|
||||||
|
}
|
||||||
|
|
||||||
public override void Init()
|
public override void Init()
|
||||||
{
|
{
|
||||||
|
Directory.SetCurrentDirectory(Program.SpaceEngineersInstallAlias);
|
||||||
|
MyFileSystem.ExePath = Path.Combine(Program.SpaceEngineersInstallAlias, Program.SpaceEngineersBinaries);
|
||||||
Log.Info("Initializing Torch Client");
|
Log.Info("Initializing Torch Client");
|
||||||
|
Config.InstancePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
SteamAppName);
|
||||||
base.Init();
|
base.Init();
|
||||||
|
OverrideMenus();
|
||||||
if (!File.Exists("steam_appid.txt"))
|
SetRenderWindowTitle($"Space Engineers v{GameVersion} with Torch v{TorchVersion}");
|
||||||
{
|
|
||||||
Directory.SetCurrentDirectory(Path.GetDirectoryName(typeof(VRage.FastResourceLock).Assembly.Location) + "\\..");
|
|
||||||
}
|
|
||||||
|
|
||||||
SpaceEngineersGame.SetupBasicGameInfo();
|
|
||||||
_startup = new MyCommonProgramStartup(RunArgs);
|
|
||||||
if (_startup.PerformReporting())
|
|
||||||
return;
|
|
||||||
|
|
||||||
_startup.PerformAutoconnect();
|
|
||||||
if (!_startup.CheckSingleInstance())
|
|
||||||
return;
|
|
||||||
|
|
||||||
var appDataPath = _startup.GetAppDataPath();
|
|
||||||
MyInitializer.InvokeBeforeRun(APP_ID, MyPerGameSettings.BasicGameInfo.ApplicationName, appDataPath);
|
|
||||||
MyInitializer.InitCheckSum();
|
|
||||||
if (!_startup.Check64Bit())
|
|
||||||
return;
|
|
||||||
|
|
||||||
_startup.DetectSharpDxLeaksBeforeRun();
|
|
||||||
using (var mySteamService = new SteamService(Game.IsDedicated, APP_ID))
|
|
||||||
{
|
|
||||||
_renderer = null;
|
|
||||||
SpaceEngineersGame.SetupPerGameSettings();
|
|
||||||
|
|
||||||
OverrideMenus();
|
|
||||||
|
|
||||||
InitializeRender();
|
|
||||||
|
|
||||||
_services = new VRageGameServices(mySteamService);
|
|
||||||
if (!Game.IsDedicated)
|
|
||||||
MyFileSystem.InitUserSpecific(mySteamService.UserId.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
_startup.DetectSharpDxLeaksAfterRun();
|
|
||||||
MyInitializer.InvokeAfterRun();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OverrideMenus()
|
private void OverrideMenus()
|
||||||
@@ -73,70 +58,35 @@ namespace Torch.Client
|
|||||||
var credits = new MyCreditsDepartment("Torch Developed By")
|
var credits = new MyCreditsDepartment("Torch Developed By")
|
||||||
{
|
{
|
||||||
Persons = new List<MyCreditsPerson>
|
Persons = new List<MyCreditsPerson>
|
||||||
{
|
{
|
||||||
new MyCreditsPerson("THE TORCH TEAM"),
|
new MyCreditsPerson("THE TORCH TEAM"),
|
||||||
new MyCreditsPerson("http://github.com/TorchSE"),
|
new MyCreditsPerson("http://github.com/TorchSE"),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
MyPerGameSettings.Credits.Departments.Insert(0, credits);
|
MyPerGameSettings.Credits.Departments.Insert(0, credits);
|
||||||
|
|
||||||
MyPerGameSettings.GUI.MainMenu = typeof(TorchMainMenuScreen);
|
MyPerGameSettings.GUI.MainMenu = typeof(TorchMainMenuScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Start()
|
private void SetRenderWindowTitle(string title)
|
||||||
{
|
{
|
||||||
using (var spaceEngineersGame = new SpaceEngineersGame(_services, RunArgs))
|
MyRenderThread renderThread = MySandboxGame.Static?.GameRenderComponent?.RenderThread;
|
||||||
{
|
if (renderThread == null)
|
||||||
Log.Info("Starting client");
|
return;
|
||||||
spaceEngineersGame.OnGameLoaded += SpaceEngineersGame_OnGameLoaded;
|
FieldInfo renderWindowField = typeof(MyRenderThread).GetField("m_renderWindow",
|
||||||
spaceEngineersGame.Run();
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
}
|
if (renderWindowField == null)
|
||||||
|
return;
|
||||||
|
var window =
|
||||||
|
renderWindowField.GetValue(MySandboxGame.Static.GameRenderComponent.RenderThread) as
|
||||||
|
System.Windows.Forms.Form;
|
||||||
|
if (window != null)
|
||||||
|
renderThread.Invoke(() => { window.Text = title; });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SpaceEngineersGame_OnGameLoaded(object sender, EventArgs e)
|
public override void Restart()
|
||||||
{
|
{
|
||||||
}
|
throw new NotImplementedException();
|
||||||
|
|
||||||
public override void Stop()
|
|
||||||
{
|
|
||||||
MySandboxGame.ExitThreadSafe();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeRender()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Game.IsDedicated)
|
|
||||||
{
|
|
||||||
_renderer = new MyNullRender();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var graphicsRenderer = MySandboxGame.Config.GraphicsRenderer;
|
|
||||||
if (graphicsRenderer == MySandboxGame.DirectX11RendererKey)
|
|
||||||
{
|
|
||||||
_renderer = new MyDX11Render();
|
|
||||||
if (!_renderer.IsSupported)
|
|
||||||
{
|
|
||||||
MySandboxGame.Log.WriteLine("DirectX 11 renderer not supported. No renderer to revert back to.");
|
|
||||||
_renderer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (_renderer == null)
|
|
||||||
throw new MyRenderException("The current version of the game requires a Dx11 card. \\n For more information please see : http://blog.marekrosa.org/2016/02/space-engineers-news-full-source-code_26.html", MyRenderExceptionEnum.GpuNotSupported);
|
|
||||||
|
|
||||||
MySandboxGame.Config.GraphicsRenderer = graphicsRenderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
MyRenderProxy.Initialize(_renderer);
|
|
||||||
MyRenderProxy.GetRenderProfiler().SetAutocommit(false);
|
|
||||||
MyRenderProxy.GetRenderProfiler().InitMemoryHack("MainEntryPoint");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
MessageBox.Show(ex.Message, "Render Initialization Failed");
|
|
||||||
Environment.Exit(-1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
34
Torch.Client/TorchClientConfig.cs
Normal file
34
Torch.Client/TorchClientConfig.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Client
|
||||||
|
{
|
||||||
|
public class TorchClientConfig : ITorchConfig
|
||||||
|
{
|
||||||
|
// How do we want to handle client side config? It's radically different than the server.
|
||||||
|
public bool GetPluginUpdates { get; set; } = false;
|
||||||
|
public bool GetTorchUpdates { get; set; } = false;
|
||||||
|
public string InstanceName { get; set; } = "TorchClient";
|
||||||
|
public string InstancePath { get; set; }
|
||||||
|
public bool NoUpdate { get; set; } = true;
|
||||||
|
public List<string> Plugins { get; set; }
|
||||||
|
public bool ShouldUpdatePlugins { get; } = false;
|
||||||
|
public bool ShouldUpdateTorch { get; } = false;
|
||||||
|
public int TickTimeout { get; set; }
|
||||||
|
public bool Autostart { get; set; } = false;
|
||||||
|
public bool ForceUpdate { get; set; } = false;
|
||||||
|
public bool NoGui { get; set; } = false;
|
||||||
|
public bool RestartOnCrash { get; set; } = false;
|
||||||
|
public string WaitForPID { get; set; } = null;
|
||||||
|
public string ChatName { get; set; }
|
||||||
|
public string ChatColor { get; set; }
|
||||||
|
|
||||||
|
public bool Save(string path = null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,35 +0,0 @@
|
|||||||
#pragma warning disable 618
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Sandbox.Graphics;
|
|
||||||
using Sandbox.Graphics.GUI;
|
|
||||||
using Sandbox.Gui;
|
|
||||||
using SpaceEngineers.Game.GUI;
|
|
||||||
using VRage.Game;
|
|
||||||
using VRage.Utils;
|
|
||||||
using VRageMath;
|
|
||||||
|
|
||||||
namespace Torch.Client
|
|
||||||
{
|
|
||||||
public class TorchMainMenuScreen : MyGuiScreenMainMenu
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void RecreateControls(bool constructor)
|
|
||||||
{
|
|
||||||
base.RecreateControls(constructor);
|
|
||||||
|
|
||||||
var buttonSize = MyGuiControlButton.GetVisualStyle(MyGuiControlButtonStyleEnum.Default).NormalTexture.MinSizeGui;
|
|
||||||
Vector2 leftButtonPositionOrigin = MyGuiManager.ComputeFullscreenGuiCoordinate(MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_BOTTOM) + new Vector2(buttonSize.X / 2f, 0f);
|
|
||||||
var btn = MakeButton(leftButtonPositionOrigin - 9 * MyGuiConstants.MENU_BUTTONS_POSITION_DELTA, MyStringId.GetOrCompute("Torch"), TorchButtonClicked);
|
|
||||||
Controls.Add(btn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TorchButtonClicked(MyGuiControlButton obj)
|
|
||||||
{
|
|
||||||
MyGuiSandbox.AddScreen(new TorchSettingsScreen());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
56
Torch.Client/UI/TorchMainMenuScreen.cs
Normal file
56
Torch.Client/UI/TorchMainMenuScreen.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma warning disable 618
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
|
using Sandbox.Engine.Utils;
|
||||||
|
using Sandbox.Game;
|
||||||
|
using Sandbox.Game.Gui;
|
||||||
|
using Sandbox.Graphics;
|
||||||
|
using Sandbox.Graphics.GUI;
|
||||||
|
using Sandbox.Gui;
|
||||||
|
using SpaceEngineers.Game.GUI;
|
||||||
|
using Torch.Utils;
|
||||||
|
using VRage.Game;
|
||||||
|
using VRage.Utils;
|
||||||
|
using VRageMath;
|
||||||
|
|
||||||
|
namespace Torch.Client.UI
|
||||||
|
{
|
||||||
|
public class TorchMainMenuScreen : MyGuiScreenMainMenu
|
||||||
|
{
|
||||||
|
#pragma warning disable 169
|
||||||
|
[ReflectedGetter(Name = "m_elementGroup")]
|
||||||
|
private static Func<MyGuiScreenMainMenu, MyGuiControlElementGroup> _elementsGroup;
|
||||||
|
#pragma warning restore 169
|
||||||
|
|
||||||
|
public TorchMainMenuScreen() : this(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TorchMainMenuScreen(bool pauseGame)
|
||||||
|
: base(pauseGame)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void RecreateControls(bool constructor)
|
||||||
|
{
|
||||||
|
base.RecreateControls(constructor);
|
||||||
|
|
||||||
|
Vector2 minSizeGui = MyGuiControlButton.GetVisualStyle(MyGuiControlButtonStyleEnum.Default).NormalTexture.MinSizeGui;
|
||||||
|
Vector2 value = MyGuiManager.ComputeFullscreenGuiCoordinate(MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_BOTTOM, 54, 54) + new Vector2(minSizeGui.X / 2f, 0f) + new Vector2(15f, 0f) / MyGuiConstants.GUI_OPTIMAL_SIZE;
|
||||||
|
|
||||||
|
MyGuiControlButton myGuiControlButton = MakeButton(value - 9 * MyGuiConstants.MENU_BUTTONS_POSITION_DELTA,
|
||||||
|
MyStringId.GetOrCompute("Torch"), TorchButtonClicked, MyCommonTexts.ToolTipExitToWindows);
|
||||||
|
Controls.Add(myGuiControlButton);
|
||||||
|
_elementsGroup.Invoke(this).Add(myGuiControlButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TorchButtonClicked(MyGuiControlButton obj)
|
||||||
|
{
|
||||||
|
MyGuiSandbox.AddScreen(MyGuiSandbox.CreateScreen<TorchNavScreen>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
Torch.Client/UI/TorchNavScreen.cs
Normal file
49
Torch.Client/UI/TorchNavScreen.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Sandbox;
|
||||||
|
using Sandbox.Game.Gui;
|
||||||
|
using Sandbox.Graphics.GUI;
|
||||||
|
using VRage;
|
||||||
|
using VRage.Game;
|
||||||
|
using VRage.Utils;
|
||||||
|
using VRageMath;
|
||||||
|
|
||||||
|
namespace Torch.Client.UI
|
||||||
|
{
|
||||||
|
public class TorchNavScreen : MyGuiScreenBase
|
||||||
|
{
|
||||||
|
private MyGuiControlElementGroup _elementGroup;
|
||||||
|
|
||||||
|
public TorchNavScreen() : base(new Vector2(0.5f, 0.5f), MyGuiConstants.SCREEN_BACKGROUND_COLOR, new Vector2(0.35875f, 0.558333337f))
|
||||||
|
{
|
||||||
|
EnabledBackgroundFade = true;
|
||||||
|
RecreateControls(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RecreateControls(bool constructor)
|
||||||
|
{
|
||||||
|
base.RecreateControls(constructor);
|
||||||
|
_elementGroup = new MyGuiControlElementGroup();
|
||||||
|
_elementGroup.HighlightChanged += ElementGroupHighlightChanged;
|
||||||
|
AddCaption(MyCommonTexts.ScreenCaptionOptions, null, null);
|
||||||
|
var value = new Vector2(0f, -m_size.Value.Y / 2f + 0.146f);
|
||||||
|
var num = 0;
|
||||||
|
var myGuiControlButton = new MyGuiControlButton(value + num++ * MyGuiConstants.MENU_BUTTONS_POSITION_DELTA, MyGuiControlButtonStyleEnum.Default, null, null, MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, null, MyTexts.Get(MyCommonTexts.ScreenOptionsButtonGame), 0.8f, MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER, MyGuiControlHighlightType.WHEN_ACTIVE, delegate(MyGuiControlButton sender)
|
||||||
|
{
|
||||||
|
MyGuiSandbox.AddScreen(MyGuiSandbox.CreateScreen<TorchSettingsScreen>());
|
||||||
|
}, GuiSounds.MouseClick, 1f, null);
|
||||||
|
Controls.Add(myGuiControlButton);
|
||||||
|
_elementGroup.Add(myGuiControlButton);
|
||||||
|
CloseButtonEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ElementGroupHighlightChanged(MyGuiControlElementGroup obj)
|
||||||
|
{
|
||||||
|
foreach (MyGuiControlBase current in _elementGroup)
|
||||||
|
if (current.HasFocus && obj.SelectedElement != current)
|
||||||
|
FocusedControl = obj.SelectedElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetFriendlyName() => "Torch";
|
||||||
|
|
||||||
|
public void OnBackClick(MyGuiControlButton sender) => CloseScreen();
|
||||||
|
}
|
||||||
|
}
|
25
Torch.Client/UI/TorchSettingsScreen.cs
Normal file
25
Torch.Client/UI/TorchSettingsScreen.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Graphics.GUI;
|
||||||
|
using VRageMath;
|
||||||
|
|
||||||
|
namespace Torch.Client.UI
|
||||||
|
{
|
||||||
|
public class TorchSettingsScreen : MyGuiScreenBase
|
||||||
|
{
|
||||||
|
public TorchSettingsScreen() : base(new Vector2(0.5f, 0.5f), MyGuiConstants.SCREEN_BACKGROUND_COLOR,
|
||||||
|
new Vector2(0.35875f, 0.558333337f))
|
||||||
|
{
|
||||||
|
EnabledBackgroundFade = true;
|
||||||
|
RecreateControls(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string GetFriendlyName() => "Torch Settings";
|
||||||
|
|
||||||
|
public void OnBackClick(MyGuiControlButton sender) => CloseScreen();
|
||||||
|
}
|
||||||
|
}
|
11
Torch.Client/app.config
Normal file
11
Torch.Client/app.config
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="protobuf-net" publicKeyToken="257b51d87d2e4d67" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-2.4.0.0" newVersion="2.4.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="NLog" version="4.4.1" targetFramework="net461" />
|
|
||||||
</packages>
|
|
72
Torch.Mod/Messages/DialogMessage.cs
Normal file
72
Torch.Mod/Messages/DialogMessage.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ProtoBuf;
|
||||||
|
using Sandbox.ModAPI;
|
||||||
|
|
||||||
|
namespace Torch.Mod.Messages
|
||||||
|
{
|
||||||
|
/// Dialogs are structured as follows
|
||||||
|
///
|
||||||
|
/// _____________________________________
|
||||||
|
/// | Title |
|
||||||
|
/// --------------------------------------
|
||||||
|
/// | Prefix Subtitle |
|
||||||
|
/// --------------------------------------
|
||||||
|
/// | ________________________________ |
|
||||||
|
/// | | Content | |
|
||||||
|
/// | --------------------------------- |
|
||||||
|
/// | ____________ |
|
||||||
|
/// | | ButtonText | |
|
||||||
|
/// | -------------- |
|
||||||
|
/// --------------------------------------
|
||||||
|
///
|
||||||
|
/// Button has a callback on click option,
|
||||||
|
/// but can't serialize that, so ¯\_(ツ)_/¯
|
||||||
|
[ProtoContract]
|
||||||
|
public class DialogMessage : MessageBase
|
||||||
|
{
|
||||||
|
[ProtoMember(201)]
|
||||||
|
public string Title;
|
||||||
|
[ProtoMember(202)]
|
||||||
|
public string Subtitle;
|
||||||
|
[ProtoMember(203)]
|
||||||
|
public string Prefix;
|
||||||
|
[ProtoMember(204)]
|
||||||
|
public string Content;
|
||||||
|
[ProtoMember(205)]
|
||||||
|
public string ButtonText;
|
||||||
|
|
||||||
|
public DialogMessage()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public DialogMessage(string title, string subtitle, string content)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
Subtitle = subtitle;
|
||||||
|
Content = content;
|
||||||
|
Prefix = String.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DialogMessage(string title = null, string prefix = null, string subtitle = null, string content = null, string buttonText = null)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
Subtitle = subtitle;
|
||||||
|
Prefix = prefix ?? String.Empty;
|
||||||
|
Content = content;
|
||||||
|
ButtonText = buttonText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessClient()
|
||||||
|
{
|
||||||
|
MyAPIGateway.Utilities.ShowMissionScreen(Title, Prefix, Subtitle, Content, null, ButtonText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessServer()
|
||||||
|
{
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
Torch.Mod/Messages/IncomingMessage.cs
Normal file
26
Torch.Mod/Messages/IncomingMessage.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Torch.Mod.Messages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// shim to store incoming message data
|
||||||
|
/// </summary>
|
||||||
|
internal class IncomingMessage : MessageBase
|
||||||
|
{
|
||||||
|
public IncomingMessage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessClient()
|
||||||
|
{
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessServer()
|
||||||
|
{
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
Torch.Mod/Messages/JoinServerMessage.cs
Normal file
57
Torch.Mod/Messages/JoinServerMessage.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using ProtoBuf;
|
||||||
|
using Sandbox.ModAPI;
|
||||||
|
|
||||||
|
namespace Torch.Mod.Messages
|
||||||
|
{
|
||||||
|
[ProtoContract]
|
||||||
|
public class JoinServerMessage : MessageBase
|
||||||
|
{
|
||||||
|
[ProtoMember(201)]
|
||||||
|
public int Delay;
|
||||||
|
[ProtoMember(202)]
|
||||||
|
public string Address;
|
||||||
|
|
||||||
|
private JoinServerMessage()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public JoinServerMessage(string address)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JoinServerMessage(string address, int delay)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
Delay = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessClient()
|
||||||
|
{
|
||||||
|
if (TorchModCore.Debug)
|
||||||
|
{
|
||||||
|
MyAPIGateway.Utilities.ShowMessage("Torch", $"Joining server {Address} with delay {Delay}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Delay <= 0)
|
||||||
|
{
|
||||||
|
MyAPIGateway.Multiplayer.JoinServer(Address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MyAPIGateway.Parallel.StartBackground(() =>
|
||||||
|
{
|
||||||
|
MyAPIGateway.Parallel.Sleep(Delay);
|
||||||
|
MyAPIGateway.Multiplayer.JoinServer(Address);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessServer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
Torch.Mod/Messages/MessageBase.cs
Normal file
52
Torch.Mod/Messages/MessageBase.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ProtoBuf;
|
||||||
|
|
||||||
|
namespace Torch.Mod.Messages
|
||||||
|
{
|
||||||
|
#region Includes
|
||||||
|
[ProtoInclude(1, typeof(DialogMessage))]
|
||||||
|
[ProtoInclude(2, typeof(NotificationMessage))]
|
||||||
|
[ProtoInclude(3, typeof(VoxelResetMessage))]
|
||||||
|
[ProtoInclude(4, typeof(JoinServerMessage))]
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public abstract class MessageBase
|
||||||
|
{
|
||||||
|
[ProtoMember(101)]
|
||||||
|
public ulong SenderId;
|
||||||
|
|
||||||
|
public abstract void ProcessClient();
|
||||||
|
public abstract void ProcessServer();
|
||||||
|
|
||||||
|
//members below not serialized, they're just metadata about the intended target(s) of this message
|
||||||
|
internal MessageTarget TargetType;
|
||||||
|
internal ulong Target;
|
||||||
|
internal ulong[] Ignore;
|
||||||
|
internal byte[] CompressedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MessageTarget
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send to Target
|
||||||
|
/// </summary>
|
||||||
|
Single,
|
||||||
|
/// <summary>
|
||||||
|
/// Send to Server
|
||||||
|
/// </summary>
|
||||||
|
Server,
|
||||||
|
/// <summary>
|
||||||
|
/// Send to all Clients (only valid from server)
|
||||||
|
/// </summary>
|
||||||
|
AllClients,
|
||||||
|
/// <summary>
|
||||||
|
/// Send to all except those steam ID listed in Ignore
|
||||||
|
/// </summary>
|
||||||
|
AllExcept,
|
||||||
|
}
|
||||||
|
}
|
39
Torch.Mod/Messages/NotificationMessage.cs
Normal file
39
Torch.Mod/Messages/NotificationMessage.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using ProtoBuf;
|
||||||
|
using Sandbox.ModAPI;
|
||||||
|
|
||||||
|
namespace Torch.Mod.Messages
|
||||||
|
{
|
||||||
|
[ProtoContract]
|
||||||
|
public class NotificationMessage : MessageBase
|
||||||
|
{
|
||||||
|
[ProtoMember(201)]
|
||||||
|
public string Message;
|
||||||
|
[ProtoMember(202)]
|
||||||
|
public string Font;
|
||||||
|
[ProtoMember(203)]
|
||||||
|
public int DisappearTimeMs;
|
||||||
|
|
||||||
|
public NotificationMessage()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public NotificationMessage(string message, int disappearTimeMs, string font)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
DisappearTimeMs = disappearTimeMs;
|
||||||
|
Font = font;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessClient()
|
||||||
|
{
|
||||||
|
MyAPIGateway.Utilities.ShowNotification(Message, DisappearTimeMs, Font);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessServer()
|
||||||
|
{
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
Torch.Mod/Messages/VoxelResetMessage.cs
Normal file
44
Torch.Mod/Messages/VoxelResetMessage.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using ProtoBuf;
|
||||||
|
using Sandbox.ModAPI;
|
||||||
|
using VRage.ModAPI;
|
||||||
|
using VRage.Voxels;
|
||||||
|
|
||||||
|
namespace Torch.Mod.Messages
|
||||||
|
{
|
||||||
|
[ProtoContract]
|
||||||
|
public class VoxelResetMessage : MessageBase
|
||||||
|
{
|
||||||
|
[ProtoMember(201)]
|
||||||
|
public long[] EntityId;
|
||||||
|
|
||||||
|
public VoxelResetMessage()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public VoxelResetMessage(long[] entityId)
|
||||||
|
{
|
||||||
|
EntityId = entityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessClient()
|
||||||
|
{
|
||||||
|
//MyAPIGateway.Parallel.ForEach(EntityId, id =>
|
||||||
|
foreach (var id in EntityId)
|
||||||
|
{
|
||||||
|
IMyEntity e;
|
||||||
|
if (!MyAPIGateway.Entities.TryGetEntityById(id, out e))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var v = e as IMyVoxelBase;
|
||||||
|
v?.Storage.Reset(MyStorageDataTypeFlags.All);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ProcessServer()
|
||||||
|
{
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
208
Torch.Mod/ModCommunication.cs
Normal file
208
Torch.Mod/ModCommunication.cs
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.ModAPI;
|
||||||
|
using Torch.Mod.Messages;
|
||||||
|
using VRage;
|
||||||
|
using VRage.Collections;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
using VRage.Network;
|
||||||
|
using VRage.Utils;
|
||||||
|
using Task = ParallelTasks.Task;
|
||||||
|
|
||||||
|
namespace Torch.Mod
|
||||||
|
{
|
||||||
|
public static class ModCommunication
|
||||||
|
{
|
||||||
|
public const ushort NET_ID = 4352;
|
||||||
|
private static bool _closing = false;
|
||||||
|
private static BlockingCollection<MessageBase> _processing;
|
||||||
|
private static MyConcurrentPool<IncomingMessage> _messagePool;
|
||||||
|
private static List<IMyPlayer> _playerCache;
|
||||||
|
|
||||||
|
public static void Register()
|
||||||
|
{
|
||||||
|
MyLog.Default.WriteLineAndConsole("TORCH MOD: Registering mod communication.");
|
||||||
|
_processing = new BlockingCollection<MessageBase>(new ConcurrentQueue<MessageBase>());
|
||||||
|
_playerCache = new List<IMyPlayer>();
|
||||||
|
_messagePool = new MyConcurrentPool<IncomingMessage>(8);
|
||||||
|
|
||||||
|
MyAPIGateway.Multiplayer.RegisterMessageHandler(NET_ID, MessageHandler);
|
||||||
|
//background thread to handle de/compression and processing
|
||||||
|
_closing = false;
|
||||||
|
MyAPIGateway.Parallel.StartBackground(DoProcessing);
|
||||||
|
MyLog.Default.WriteLineAndConsole("TORCH MOD: Mod communication registered successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Unregister()
|
||||||
|
{
|
||||||
|
MyLog.Default.WriteLineAndConsole("TORCH MOD: Unregistering mod communication.");
|
||||||
|
MyAPIGateway.Multiplayer?.UnregisterMessageHandler(NET_ID, MessageHandler);
|
||||||
|
_processing?.CompleteAdding();
|
||||||
|
_closing = true;
|
||||||
|
//_task.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MessageHandler(byte[] bytes)
|
||||||
|
{
|
||||||
|
var m = _messagePool.Get();
|
||||||
|
m.CompressedData = bytes;
|
||||||
|
#if TORCH
|
||||||
|
m.SenderId = MyEventContext.Current.Sender.Value;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_processing.Add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DoProcessing()
|
||||||
|
{
|
||||||
|
while (!_closing)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MessageBase m;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m = _processing.Take();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MyLog.Default.WriteLineAndConsole($"Processing message: {m.GetType().Name}");
|
||||||
|
|
||||||
|
if (m is IncomingMessage) //process incoming messages
|
||||||
|
{
|
||||||
|
MessageBase i;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var o = MyCompression.Decompress(m.CompressedData);
|
||||||
|
m.CompressedData = null;
|
||||||
|
_messagePool.Return((IncomingMessage)m);
|
||||||
|
i = MyAPIGateway.Utilities.SerializeFromBinary<MessageBase>(o);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MyLog.Default.WriteLineAndConsole($"TORCH MOD: Failed to deserialize message! {ex}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TorchModCore.Debug)
|
||||||
|
MyAPIGateway.Utilities.ShowMessage("Torch", $"Received message of type {i.GetType().Name}");
|
||||||
|
|
||||||
|
if (MyAPIGateway.Multiplayer.IsServer)
|
||||||
|
i.ProcessServer();
|
||||||
|
else
|
||||||
|
i.ProcessClient();
|
||||||
|
}
|
||||||
|
else //process outgoing messages
|
||||||
|
{
|
||||||
|
if (TorchModCore.Debug)
|
||||||
|
MyAPIGateway.Utilities.ShowMessage("Torch", $"Sending message of type {m.GetType().Name}");
|
||||||
|
|
||||||
|
var b = MyAPIGateway.Utilities.SerializeToBinary(m);
|
||||||
|
m.CompressedData = MyCompression.Compress(b);
|
||||||
|
|
||||||
|
switch (m.TargetType)
|
||||||
|
{
|
||||||
|
case MessageTarget.Single:
|
||||||
|
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, m.Target);
|
||||||
|
break;
|
||||||
|
case MessageTarget.Server:
|
||||||
|
MyAPIGateway.Multiplayer.SendMessageToServer(NET_ID, m.CompressedData);
|
||||||
|
break;
|
||||||
|
case MessageTarget.AllClients:
|
||||||
|
MyAPIGateway.Players.GetPlayers(_playerCache);
|
||||||
|
foreach (var p in _playerCache)
|
||||||
|
{
|
||||||
|
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId)
|
||||||
|
continue;
|
||||||
|
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, p.SteamUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case MessageTarget.AllExcept:
|
||||||
|
MyAPIGateway.Players.GetPlayers(_playerCache);
|
||||||
|
foreach (var p in _playerCache)
|
||||||
|
{
|
||||||
|
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId || m.Ignore.Contains(p.SteamUserId))
|
||||||
|
continue;
|
||||||
|
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, p.SteamUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
_playerCache.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MyLog.Default.WriteLineAndConsole($"TORCH MOD: Exception occurred in communication thread! {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MyLog.Default.WriteLineAndConsole("TORCH MOD: INFO: Communication thread shut down successfully! THIS IS NOT AN ERROR");
|
||||||
|
//exit signal received. Clean everything and GTFO
|
||||||
|
_processing?.Dispose();
|
||||||
|
_processing = null;
|
||||||
|
_messagePool?.Clean();
|
||||||
|
_messagePool = null;
|
||||||
|
_playerCache = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SendMessageTo(MessageBase message, ulong target)
|
||||||
|
{
|
||||||
|
if (!MyAPIGateway.Multiplayer.IsServer)
|
||||||
|
throw new Exception("Only server can send targeted messages");
|
||||||
|
|
||||||
|
if (_closing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
message.Target = target;
|
||||||
|
message.TargetType = MessageTarget.Single;
|
||||||
|
_processing.Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SendMessageToClients(MessageBase message)
|
||||||
|
{
|
||||||
|
if (!MyAPIGateway.Multiplayer.IsServer)
|
||||||
|
throw new Exception("Only server can send targeted messages");
|
||||||
|
|
||||||
|
if (_closing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
message.TargetType = MessageTarget.AllClients;
|
||||||
|
_processing.Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SendMessageExcept(MessageBase message, params ulong[] ignoredUsers)
|
||||||
|
{
|
||||||
|
if (!MyAPIGateway.Multiplayer.IsServer)
|
||||||
|
throw new Exception("Only server can send targeted messages");
|
||||||
|
|
||||||
|
if (_closing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
message.TargetType = MessageTarget.AllExcept;
|
||||||
|
message.Ignore = ignoredUsers;
|
||||||
|
_processing.Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SendMessageToServer(MessageBase message)
|
||||||
|
{
|
||||||
|
if (_closing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
message.TargetType = MessageTarget.Server;
|
||||||
|
_processing.Add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
Torch.Mod/Torch.Mod.projitems
Normal file
21
Torch.Mod/Torch.Mod.projitems
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||||
|
<HasSharedItems>true</HasSharedItems>
|
||||||
|
<SharedGUID>3ce4d2e9-b461-4f19-8233-f87e0dfddd74</SharedGUID>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Label="Configuration">
|
||||||
|
<Import_RootNamespace>Torch.Mod</Import_RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Messages\IncomingMessage.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Messages\JoinServerMessage.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Messages\NotificationMessage.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Messages\DialogMessage.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Messages\MessageBase.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Messages\VoxelResetMessage.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)ModCommunication.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)TorchModCore.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
12
Torch.Mod/Torch.Mod.shproj
Normal file
12
Torch.Mod/Torch.Mod.shproj
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>3ce4d2e9-b461-4f19-8233-f87e0dfddd74</ProjectGuid>
|
||||||
|
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||||
|
<Import Project="Torch.Mod.projitems" Label="Shared" />
|
||||||
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||||
|
</Project>
|
51
Torch.Mod/TorchModCore.cs
Normal file
51
Torch.Mod/TorchModCore.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.ModAPI;
|
||||||
|
using VRage.Game.Components;
|
||||||
|
|
||||||
|
namespace Torch.Mod
|
||||||
|
{
|
||||||
|
[MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)]
|
||||||
|
public class TorchModCore : MySessionComponentBase
|
||||||
|
{
|
||||||
|
public const ulong MOD_ID = 2722000298;
|
||||||
|
private static bool _init;
|
||||||
|
public static bool Debug;
|
||||||
|
|
||||||
|
public override void UpdateAfterSimulation()
|
||||||
|
{
|
||||||
|
if (_init)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_init = true;
|
||||||
|
ModCommunication.Register();
|
||||||
|
MyAPIGateway.Utilities.MessageEntered += Utilities_MessageEntered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Utilities_MessageEntered(string messageText, ref bool sendToOthers)
|
||||||
|
{
|
||||||
|
if (messageText == "@!debug")
|
||||||
|
{
|
||||||
|
Debug = !Debug;
|
||||||
|
MyAPIGateway.Utilities.ShowMessage("Torch", $"Debug: {Debug}");
|
||||||
|
sendToOthers = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UnloadData()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MyAPIGateway.Utilities.MessageEntered -= Utilities_MessageEntered;
|
||||||
|
ModCommunication.Unregister();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//session unloading, don't care
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
Torch.Server.Tests/Properties/AssemblyInfo.cs
Normal file
17
Torch.Server.Tests/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
[assembly: AssemblyTitle("Torch Server Tests")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Torch")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
[assembly: AssemblyConfiguration("Debug")]
|
||||||
|
#else
|
||||||
|
[assembly: AssemblyConfiguration("Release")]
|
||||||
|
#endif
|
39
Torch.Server.Tests/Torch.Server.Tests.csproj
Normal file
39
Torch.Server.Tests/Torch.Server.Tests.csproj
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6-windows</TargetFramework>
|
||||||
|
<NoWarn>1591,0649</NoWarn>
|
||||||
|
<AssemblyTitle>Torch Server Tests</AssemblyTitle>
|
||||||
|
<Product>Torch</Product>
|
||||||
|
<Copyright>Copyright © Torch API 2017</Copyright>
|
||||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<OutputPath>$(SolutionDir)\bin-test\$(Platform)\$(Configuration)\</OutputPath>
|
||||||
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<Configurations>Debug;Release</Configurations>
|
||||||
|
<Platforms>AnyCPU</Platforms>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="$(Configuration) == 'Release'">
|
||||||
|
<DocumentationFile>$(SolutionDir)\bin-test\$(Platform)\$(Configuration)\Torch.Server.Tests.xml</DocumentationFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
|
<PackageReference Include="NLog" Version="5.0.0-rc2" />
|
||||||
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="VRage.Game, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\GameBinaries\VRage.Game.dll</HintPath>
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\Versioning\AssemblyVersion.cs" Link="Properties\AssemblyVersion.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Torch.API\Torch.API.csproj" />
|
||||||
|
<ProjectReference Include="..\Torch.Server\Torch.Server.csproj" />
|
||||||
|
<ProjectReference Include="..\Torch.Tests\Torch.Tests.csproj" />
|
||||||
|
<ProjectReference Include="..\Torch\Torch.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
83
Torch.Server.Tests/TorchServerReflectionTest.cs
Normal file
83
Torch.Server.Tests/TorchServerReflectionTest.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Torch.Tests;
|
||||||
|
using Torch.Utils;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Torch.Server.Tests
|
||||||
|
{
|
||||||
|
#warning Disabled reflection tests because of seemingly random failures
|
||||||
|
public class TorchServerReflectionTest
|
||||||
|
{
|
||||||
|
static TorchServerReflectionTest()
|
||||||
|
{
|
||||||
|
TestUtils.Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ReflectionTestManager _manager;
|
||||||
|
|
||||||
|
private static ReflectionTestManager Manager()
|
||||||
|
{
|
||||||
|
if (_manager != null)
|
||||||
|
return _manager;
|
||||||
|
|
||||||
|
return _manager = new ReflectionTestManager().Init(typeof(TorchServer).Assembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Getters => Manager().Getters;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Setters => Manager().Setters;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> MemberInfo => Manager().MemberInfo;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Events => Manager().Events;
|
||||||
|
|
||||||
|
#region Binding
|
||||||
|
//[Theory]
|
||||||
|
[MemberData(nameof(Getters))]
|
||||||
|
public void TestBindingGetter(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
//[Theory]
|
||||||
|
[MemberData(nameof(Setters))]
|
||||||
|
public void TestBindingSetter(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
//[Theory]
|
||||||
|
[MemberData(nameof(Invokers))]
|
||||||
|
public void TestBindingInvoker(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
//[Theory]
|
||||||
|
[MemberData(nameof(Events))]
|
||||||
|
public void TestBindingEvents(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
((Func<ReflectedEventReplacer>)field.Field.GetValue(null)).Invoke();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
34
Torch.Server.Tests/TorchServerSessionSettingsTest.cs
Normal file
34
Torch.Server.Tests/TorchServerSessionSettingsTest.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Torch.Server.ViewModels;
|
||||||
|
using VRage.Game;
|
||||||
|
using Xunit;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Torch.Server.Tests
|
||||||
|
{
|
||||||
|
public class TorchServerSessionSettingsTest
|
||||||
|
{
|
||||||
|
public static PropertyInfo[] ViewModelProperties = typeof(SessionSettingsViewModel).GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
public static IEnumerable<object[]> ModelFields = typeof(MyObjectBuilder_SessionSettings).GetFields(BindingFlags.Public | BindingFlags.Instance).Select(x => new object[] { x });
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(ModelFields))]
|
||||||
|
public void MissingPropertyTest(FieldInfo modelField)
|
||||||
|
{
|
||||||
|
// Ignore fields that aren't applicable to SE
|
||||||
|
if (modelField.GetCustomAttribute<GameRelationAttribute>()?.RelatedTo == Game.MedievalEngineers)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(modelField.GetCustomAttribute<DisplayAttribute>()?.Name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var match = ViewModelProperties.FirstOrDefault(p => p.Name.Equals(modelField.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
Assert.NotNull(match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Torch.Server.Tests/app.config
Normal file
11
Torch.Server.Tests/app.config
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="protobuf-net" publicKeyToken="257b51d87d2e4d67" culture="neutral"/>
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-2.4.0.0" newVersion="2.4.0.0"/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/></startup></configuration>
|
66
Torch.Server/Commands/WhitelistCommands.cs
Normal file
66
Torch.Server/Commands/WhitelistCommands.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Torch.Commands;
|
||||||
|
|
||||||
|
namespace Torch.Server.Commands
|
||||||
|
{
|
||||||
|
[Category("whitelist")]
|
||||||
|
public class WhitelistCommands : CommandModule
|
||||||
|
{
|
||||||
|
private TorchConfig Config => (TorchConfig)Context.Torch.Config;
|
||||||
|
|
||||||
|
[Command("on", "Enables the whitelist.")]
|
||||||
|
public void On()
|
||||||
|
{
|
||||||
|
if (!Config.EnableWhitelist)
|
||||||
|
{
|
||||||
|
Config.EnableWhitelist = true;
|
||||||
|
Context.Respond("Whitelist enabled.");
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Context.Respond("Whitelist is already enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("off", "Disables the whitelist")]
|
||||||
|
public void Off()
|
||||||
|
{
|
||||||
|
if (Config.EnableWhitelist)
|
||||||
|
{
|
||||||
|
Config.EnableWhitelist = false;
|
||||||
|
Context.Respond("Whitelist disabled.");
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Context.Respond("Whitelist is already disabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("add", "Add a Steam ID to the whitelist.")]
|
||||||
|
public void Add(ulong steamId)
|
||||||
|
{
|
||||||
|
if (!Config.Whitelist.Contains(steamId))
|
||||||
|
{
|
||||||
|
Config.Whitelist.Add(steamId);
|
||||||
|
Context.Respond($"Added {steamId} to the whitelist.");
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Context.Respond($"{steamId} is already whitelisted.");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Command("remove", "Remove a Steam ID from the whitelist.")]
|
||||||
|
public void Remove(ulong steamId)
|
||||||
|
{
|
||||||
|
if (Config.Whitelist.Remove(steamId))
|
||||||
|
{
|
||||||
|
Context.Respond($"Removed {steamId} from the whitelist.");
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Context.Respond($"{steamId} is not whitelisted.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
Torch.Server/FodyWeavers.xml
Normal file
3
Torch.Server/FodyWeavers.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||||
|
<PropertyChanged />
|
||||||
|
</Weavers>
|
74
Torch.Server/FodyWeavers.xsd
Normal file
74
Torch.Server/FodyWeavers.xsd
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
|
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||||
|
<xs:element name="Weavers">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:all>
|
||||||
|
<xs:element name="PropertyChanged" minOccurs="0" maxOccurs="1">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="InjectOnPropertyNameChanged" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if the On_PropertyName_Changed feature is enabled.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="TriggerDependentProperties" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if the Dependent properties feature is enabled.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="EnableIsChangedProperty" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if the IsChanged property feature is enabled.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="EventInvokerNames" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="CheckForEquality" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="CheckForEqualityUsingBaseEquals" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if equality checks should use the Equals method resolved from the base class.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="UseStaticEqualsFromBase" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="SuppressWarnings" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to turn off build warnings from this weaver.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="SuppressOnPropertyNameChangedWarning" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to turn off build warnings about mismatched On_PropertyName_Changed methods.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:all>
|
||||||
|
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:schema>
|
186
Torch.Server/Initializer.cs
Normal file
186
Torch.Server/Initializer.cs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using NLog;
|
||||||
|
using NLog.Targets;
|
||||||
|
using Sandbox.Engine.Utils;
|
||||||
|
using Torch.Utils;
|
||||||
|
using VRage.FileSystem;
|
||||||
|
|
||||||
|
namespace Torch.Server
|
||||||
|
{
|
||||||
|
public class Initializer
|
||||||
|
{
|
||||||
|
internal static Initializer Instance { get; private set; }
|
||||||
|
|
||||||
|
private static readonly Logger Log = LogManager.GetLogger(nameof(Initializer));
|
||||||
|
private bool _init;
|
||||||
|
private const string STEAMCMD_DIR = "steamcmd";
|
||||||
|
private const string STEAMCMD_ZIP = "temp.zip";
|
||||||
|
private static readonly string STEAMCMD_EXE = "steamcmd.exe";
|
||||||
|
private static readonly string RUNSCRIPT_FILE = "runscript.txt";
|
||||||
|
|
||||||
|
private const string RUNSCRIPT = @"force_install_dir ../
|
||||||
|
login anonymous
|
||||||
|
app_update 298740
|
||||||
|
quit";
|
||||||
|
private TorchServer _server;
|
||||||
|
|
||||||
|
internal Persistent<TorchConfig> ConfigPersistent { get; }
|
||||||
|
public TorchConfig Config => ConfigPersistent?.Data;
|
||||||
|
public TorchServer Server => _server;
|
||||||
|
|
||||||
|
public Initializer(string basePath, Persistent<TorchConfig> torchConfig)
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
ConfigPersistent = torchConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Initialize(string[] args)
|
||||||
|
{
|
||||||
|
if (_init)
|
||||||
|
return false;
|
||||||
|
#if DEBUG
|
||||||
|
//enables logging debug messages when built in debug mode. Amazing.
|
||||||
|
LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Debug, "main");
|
||||||
|
LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Debug, "console");
|
||||||
|
LogManager.Configuration.AddRule(LogLevel.Debug, LogLevel.Debug, "wpf");
|
||||||
|
LogManager.ReconfigExistingLoggers();
|
||||||
|
Log.Debug("Debug logging enabled.");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This is what happens when Keen is bad and puts extensions into the System namespace.
|
||||||
|
if (!Enumerable.Contains(args, "-noupdate"))
|
||||||
|
RunSteamCmd();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Config.WaitForPID))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pid = int.Parse(Config.WaitForPID);
|
||||||
|
var waitProc = Process.GetProcessById(pid);
|
||||||
|
Log.Info("Continuing in 5 seconds.");
|
||||||
|
Log.Warn($"Waiting for process {pid} to close");
|
||||||
|
while (!waitProc.HasExited)
|
||||||
|
{
|
||||||
|
Console.Write(".");
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Warn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_init = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run(bool isService, string instanceName, string instancePath)
|
||||||
|
{
|
||||||
|
_server = new TorchServer(Config, instancePath, instanceName);
|
||||||
|
|
||||||
|
if (isService || Config.NoGui)
|
||||||
|
{
|
||||||
|
_server.Init();
|
||||||
|
_server.Start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if !DEBUG
|
||||||
|
if (!Config.IndependentConsole)
|
||||||
|
{
|
||||||
|
Console.SetOut(TextWriter.Null);
|
||||||
|
NativeMethods.FreeConsole();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var gameThread = new Thread(() =>
|
||||||
|
{
|
||||||
|
_server.Init();
|
||||||
|
|
||||||
|
if (Config.Autostart || Config.TempAutostart)
|
||||||
|
{
|
||||||
|
Config.TempAutostart = false;
|
||||||
|
_server.Start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gameThread.Start();
|
||||||
|
|
||||||
|
var ui = new TorchUI(_server);
|
||||||
|
|
||||||
|
SynchronizationContext.SetSynchronizationContext(
|
||||||
|
new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
|
||||||
|
|
||||||
|
ui.ShowDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RunSteamCmd()
|
||||||
|
{
|
||||||
|
var log = LogManager.GetLogger("SteamCMD");
|
||||||
|
|
||||||
|
var path = Environment.GetEnvironmentVariable("TORCH_STEAMCMD") ?? Path.GetFullPath(STEAMCMD_DIR);
|
||||||
|
|
||||||
|
if (!Directory.Exists(path))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
var runScriptPath = Path.Combine(path, RUNSCRIPT_FILE);
|
||||||
|
if (!File.Exists(runScriptPath))
|
||||||
|
File.WriteAllText(runScriptPath, RUNSCRIPT);
|
||||||
|
|
||||||
|
var steamCmdExePath = Path.Combine(path, STEAMCMD_EXE);
|
||||||
|
if (!File.Exists(steamCmdExePath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
log.Info("Downloading SteamCMD.");
|
||||||
|
using (var client = new HttpClient())
|
||||||
|
using (var file = File.Create(STEAMCMD_ZIP))
|
||||||
|
client.GetStreamAsync("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip").Result.CopyTo(file);
|
||||||
|
|
||||||
|
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, path);
|
||||||
|
File.Delete(STEAMCMD_ZIP);
|
||||||
|
log.Info("SteamCMD downloaded successfully!");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.Error(e, "Failed to download SteamCMD, unable to update the DS.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Checking for DS updates.");
|
||||||
|
var steamCmdProc = new ProcessStartInfo(steamCmdExePath, "+runscript runscript.txt")
|
||||||
|
{
|
||||||
|
WorkingDirectory = path,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
StandardOutputEncoding = Encoding.ASCII
|
||||||
|
};
|
||||||
|
var cmd = Process.Start(steamCmdProc);
|
||||||
|
|
||||||
|
// ReSharper disable once PossibleNullReferenceException
|
||||||
|
while (!cmd.HasExited)
|
||||||
|
{
|
||||||
|
log.Info(cmd.StandardOutput.ReadLine());
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
Torch.Server/ListBoxExtensions.cs
Normal file
59
Torch.Server/ListBoxExtensions.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace Torch.Server
|
||||||
|
{
|
||||||
|
public static class ListBoxExtensions
|
||||||
|
{
|
||||||
|
//https://stackoverflow.com/questions/28689125/how-to-autoscroll-listbox-to-bottom-wpf-c
|
||||||
|
public static void ScrollToItem(this ListBox listBox, int index)
|
||||||
|
{
|
||||||
|
// Find a container
|
||||||
|
UIElement container = null;
|
||||||
|
for (int i = index; i > 0; i--)
|
||||||
|
{
|
||||||
|
container = listBox.ItemContainerGenerator.ContainerFromIndex(i) as UIElement;
|
||||||
|
if (container != null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (container == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Find the ScrollContentPresenter
|
||||||
|
ScrollContentPresenter presenter = null;
|
||||||
|
for (Visual vis = container; vis != null && vis != listBox; vis = VisualTreeHelper.GetParent(vis) as Visual)
|
||||||
|
if ((presenter = vis as ScrollContentPresenter) != null)
|
||||||
|
break;
|
||||||
|
if (presenter == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Find the IScrollInfo
|
||||||
|
var scrollInfo =
|
||||||
|
!presenter.CanContentScroll ? presenter :
|
||||||
|
presenter.Content as IScrollInfo ??
|
||||||
|
FirstVisualChild(presenter.Content as ItemsPresenter) as IScrollInfo ??
|
||||||
|
presenter;
|
||||||
|
|
||||||
|
// Find the amount of items that is "Visible" in the ListBox
|
||||||
|
var height = (container as ListBoxItem).ActualHeight;
|
||||||
|
var lbHeight = listBox.ActualHeight;
|
||||||
|
var showCount = (int)Math.Floor(lbHeight / height) - 1;
|
||||||
|
|
||||||
|
//Set the scrollbar
|
||||||
|
if (scrollInfo.CanVerticallyScroll)
|
||||||
|
scrollInfo.SetVerticalOffset(index - showCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DependencyObject FirstVisualChild(Visual visual)
|
||||||
|
{
|
||||||
|
if (visual == null) return null;
|
||||||
|
if (VisualTreeHelper.GetChildrenCount(visual) == 0) return null;
|
||||||
|
return VisualTreeHelper.GetChild(visual, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
Torch.Server/LogViewerTarget.cs
Normal file
51
Torch.Server/LogViewerTarget.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using NLog;
|
||||||
|
using NLog.Targets;
|
||||||
|
using Torch.Server.ViewModels;
|
||||||
|
using Torch.Server.Views;
|
||||||
|
|
||||||
|
namespace Torch.Server
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// NLog target that writes to a <see cref="LogViewerControl"/>.
|
||||||
|
/// </summary>
|
||||||
|
[Target("logViewer")]
|
||||||
|
public sealed class LogViewerTarget : TargetWithLayout
|
||||||
|
{
|
||||||
|
public IList<LogEntry> LogEntries { get; set; }
|
||||||
|
public SynchronizationContext TargetContext { get; set; }
|
||||||
|
private readonly int _maxLines = 1000;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Write(LogEventInfo logEvent)
|
||||||
|
{
|
||||||
|
TargetContext?.Post(_sendOrPostCallback, logEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCallback(object state)
|
||||||
|
{
|
||||||
|
var logEvent = (LogEventInfo) state;
|
||||||
|
LogEntries?.Add(new(logEvent.TimeStamp, Layout.Render(logEvent), LogLevelColors[logEvent.Level]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Dictionary<LogLevel, SolidColorBrush> LogLevelColors = new()
|
||||||
|
{
|
||||||
|
[LogLevel.Trace] = new SolidColorBrush(Colors.DimGray),
|
||||||
|
[LogLevel.Debug] = new SolidColorBrush(Colors.DarkGray),
|
||||||
|
[LogLevel.Info] = new SolidColorBrush(Colors.White),
|
||||||
|
[LogLevel.Warn] = new SolidColorBrush(Colors.Magenta),
|
||||||
|
[LogLevel.Error] = new SolidColorBrush(Colors.Yellow),
|
||||||
|
[LogLevel.Fatal] = new SolidColorBrush(Colors.Red),
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly SendOrPostCallback _sendOrPostCallback;
|
||||||
|
|
||||||
|
public LogViewerTarget()
|
||||||
|
{
|
||||||
|
_sendOrPostCallback = WriteCallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
257
Torch.Server/Managers/EntityControlManager.cs
Normal file
257
Torch.Server/Managers/EntityControlManager.cs
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using NLog;
|
||||||
|
using NLog.Fluent;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.Collections;
|
||||||
|
using Torch.Managers;
|
||||||
|
using Torch.Server.ViewModels.Entities;
|
||||||
|
using Torch.Utils;
|
||||||
|
|
||||||
|
namespace Torch.Server.Managers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manager that lets users bind random view models to entities in Torch's Entity Manager
|
||||||
|
/// </summary>
|
||||||
|
public class EntityControlManager : Manager
|
||||||
|
{
|
||||||
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an entity control manager for the given instance of torch
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="torchInstance">Torch instance</param>
|
||||||
|
internal EntityControlManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class ModelFactory
|
||||||
|
{
|
||||||
|
private readonly ConditionalWeakTable<EntityViewModel, EntityControlViewModel> _models = new ConditionalWeakTable<EntityViewModel, EntityControlViewModel>();
|
||||||
|
|
||||||
|
public abstract Delegate Delegate { get; }
|
||||||
|
|
||||||
|
protected abstract EntityControlViewModel Create(EntityViewModel evm);
|
||||||
|
|
||||||
|
internal IEnumerable<EntityViewModel> Keys => _models.Select(b => b.Key);
|
||||||
|
|
||||||
|
internal EntityControlViewModel GetOrCreate(EntityViewModel evm)
|
||||||
|
{
|
||||||
|
return _models.GetValue(evm, Create);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryGet(EntityViewModel evm, out EntityControlViewModel res)
|
||||||
|
{
|
||||||
|
return _models.TryGetValue(evm, out res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ModelFactory<T> : ModelFactory where T : EntityViewModel
|
||||||
|
{
|
||||||
|
private readonly Func<T, EntityControlViewModel> _factory;
|
||||||
|
public override Delegate Delegate => _factory;
|
||||||
|
|
||||||
|
internal ModelFactory(Func<T, EntityControlViewModel> factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected override EntityControlViewModel Create(EntityViewModel evm)
|
||||||
|
{
|
||||||
|
if (evm is T m)
|
||||||
|
{
|
||||||
|
var result = _factory(m);
|
||||||
|
_log.Trace($"Model factory {_factory.Method} created {result} for {evm}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<ModelFactory> _modelFactories = new List<ModelFactory>();
|
||||||
|
private readonly List<Delegate> _controlFactories = new List<Delegate>();
|
||||||
|
|
||||||
|
private readonly List<WeakReference<EntityViewModel>> _boundEntityViewModels = new List<WeakReference<EntityViewModel>>();
|
||||||
|
private readonly ConditionalWeakTable<EntityViewModel, MtObservableList<EntityControlViewModel>> _boundViewModels = new ConditionalWeakTable<EntityViewModel, MtObservableList<EntityControlViewModel>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This factory will be used to create component models for matching entity models.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntityBaseModel">entity model type to match</typeparam>
|
||||||
|
/// <param name="modelFactory">Method to create component model from entity model.</param>
|
||||||
|
public void RegisterModelFactory<TEntityBaseModel>(Func<TEntityBaseModel, EntityControlViewModel> modelFactory)
|
||||||
|
where TEntityBaseModel : EntityViewModel
|
||||||
|
{
|
||||||
|
if (!typeof(TEntityBaseModel).IsAssignableFrom(modelFactory.Method.GetParameters()[0].ParameterType))
|
||||||
|
throw new ArgumentException("Generic type must match lamda type", nameof(modelFactory));
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
var factory = new ModelFactory<TEntityBaseModel>(modelFactory);
|
||||||
|
_modelFactories.Add(factory);
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
while (i < _boundEntityViewModels.Count)
|
||||||
|
{
|
||||||
|
if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) &&
|
||||||
|
_boundViewModels.TryGetValue(target, out MtObservableList<EntityControlViewModel> components))
|
||||||
|
{
|
||||||
|
if (target is TEntityBaseModel tent)
|
||||||
|
UpdateBinding(target, components);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_boundEntityViewModels.RemoveAtFast(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregisters a factory registered with <see cref="RegisterModelFactory{TEntityBaseModel}"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntityBaseModel">entity model type to match</typeparam>
|
||||||
|
/// <param name="modelFactory">Method to create component model from entity model.</param>
|
||||||
|
public void UnregisterModelFactory<TEntityBaseModel>(Func<TEntityBaseModel, EntityControlViewModel> modelFactory)
|
||||||
|
where TEntityBaseModel : EntityViewModel
|
||||||
|
{
|
||||||
|
if (!typeof(TEntityBaseModel).IsAssignableFrom(modelFactory.Method.GetParameters()[0].ParameterType))
|
||||||
|
throw new ArgumentException("Generic type must match lamda type", nameof(modelFactory));
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _modelFactories.Count; i++)
|
||||||
|
{
|
||||||
|
if (_modelFactories[i].Delegate == (Delegate)modelFactory)
|
||||||
|
{
|
||||||
|
foreach (var entry in _modelFactories[i].Keys)
|
||||||
|
if (_modelFactories[i].TryGet(entry, out EntityControlViewModel ecvm) && ecvm != null
|
||||||
|
&& _boundViewModels.TryGetValue(entry, out var binding))
|
||||||
|
binding.Remove(ecvm);
|
||||||
|
_modelFactories.RemoveAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This factory will be used to create controls for matching view models.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntityComponentModel">component model to match</typeparam>
|
||||||
|
/// <param name="controlFactory">Method to create control from component model</param>
|
||||||
|
public void RegisterControlFactory<TEntityComponentModel>(
|
||||||
|
Func<TEntityComponentModel, Control> controlFactory)
|
||||||
|
where TEntityComponentModel : EntityControlViewModel
|
||||||
|
{
|
||||||
|
if (!typeof(TEntityComponentModel).IsAssignableFrom(controlFactory.Method.GetParameters()[0].ParameterType))
|
||||||
|
throw new ArgumentException("Generic type must match lamda type", nameof(controlFactory));
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
_controlFactories.Add(controlFactory);
|
||||||
|
RefreshControls<TEntityComponentModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///<summary>
|
||||||
|
/// Unregisters a factory registered with <see cref="RegisterControlFactory{TEntityComponentModel}"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntityComponentModel">component model to match</typeparam>
|
||||||
|
/// <param name="controlFactory">Method to create control from component model</param>
|
||||||
|
public void UnregisterControlFactory<TEntityComponentModel>(
|
||||||
|
Func<TEntityComponentModel, Control> controlFactory)
|
||||||
|
where TEntityComponentModel : EntityControlViewModel
|
||||||
|
{
|
||||||
|
if (!typeof(TEntityComponentModel).IsAssignableFrom(controlFactory.Method.GetParameters()[0].ParameterType))
|
||||||
|
throw new ArgumentException("Generic type must match lamda type", nameof(controlFactory));
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
_controlFactories.Remove(controlFactory);
|
||||||
|
RefreshControls<TEntityComponentModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshControls<TEntityComponentModel>() where TEntityComponentModel : EntityControlViewModel
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
while (i < _boundEntityViewModels.Count)
|
||||||
|
{
|
||||||
|
if (_boundEntityViewModels[i].TryGetTarget(out EntityViewModel target) &&
|
||||||
|
_boundViewModels.TryGetValue(target, out MtObservableList<EntityControlViewModel> components))
|
||||||
|
{
|
||||||
|
foreach (EntityControlViewModel component in components)
|
||||||
|
if (component is TEntityComponentModel)
|
||||||
|
component.InvalidateControl();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_boundEntityViewModels.RemoveAtFast(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the models bound to the given entity view model.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">view model to query</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MtObservableList<EntityControlViewModel> BoundModels(EntityViewModel entity)
|
||||||
|
{
|
||||||
|
return _boundViewModels.GetValue(entity, CreateFreshBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a control for the given view model type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">model to create a control for</param>
|
||||||
|
/// <returns>control, or null if none</returns>
|
||||||
|
public Control CreateControl(EntityControlViewModel model)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
foreach (Delegate factory in _controlFactories)
|
||||||
|
if (factory.Method.GetParameters()[0].ParameterType.IsInstanceOfType(model) &&
|
||||||
|
factory.DynamicInvoke(model) is Control result)
|
||||||
|
{
|
||||||
|
_log.Trace($"Control factory {factory.Method} created {result}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
_log.Warn($"No control created for {model}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MtObservableList<EntityControlViewModel> CreateFreshBinding(EntityViewModel key)
|
||||||
|
{
|
||||||
|
var binding = new MtObservableList<EntityControlViewModel>();
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
_boundEntityViewModels.Add(new WeakReference<EntityViewModel>(key));
|
||||||
|
}
|
||||||
|
binding.PropertyChanged += (x, args) =>
|
||||||
|
{
|
||||||
|
if (nameof(binding.IsObserved).Equals(args.PropertyName))
|
||||||
|
UpdateBinding(key, binding);
|
||||||
|
};
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBinding(EntityViewModel key, MtObservableList<EntityControlViewModel> binding)
|
||||||
|
{
|
||||||
|
if (!binding.IsObserved)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
foreach (ModelFactory factory in _modelFactories)
|
||||||
|
{
|
||||||
|
var result = factory.GetOrCreate(key);
|
||||||
|
if (result != null && !binding.Contains(result))
|
||||||
|
binding.Add(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,94 +1,192 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Havok;
|
using Havok;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using Sandbox;
|
||||||
using Sandbox.Engine.Networking;
|
using Sandbox.Engine.Networking;
|
||||||
using Sandbox.Engine.Utils;
|
using Sandbox.Engine.Utils;
|
||||||
|
using Sandbox.Game;
|
||||||
|
using Sandbox.Game.Gui;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
|
using Torch.Collections;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
|
using Torch.Mod;
|
||||||
using Torch.Server.ViewModels;
|
using Torch.Server.ViewModels;
|
||||||
|
using Torch.Utils;
|
||||||
|
using VRage;
|
||||||
using VRage.FileSystem;
|
using VRage.FileSystem;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
|
using VRage.Game.ObjectBuilder;
|
||||||
using VRage.ObjectBuilders;
|
using VRage.ObjectBuilders;
|
||||||
|
using VRage.Plugins;
|
||||||
|
|
||||||
namespace Torch.Server.Managers
|
namespace Torch.Server.Managers
|
||||||
{
|
{
|
||||||
public class InstanceManager : Manager
|
public class InstanceManager : Manager, IInstanceManager
|
||||||
{
|
{
|
||||||
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
|
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
|
||||||
|
|
||||||
|
public event Action<ConfigDedicatedViewModel> InstanceLoaded;
|
||||||
public ConfigDedicatedViewModel DedicatedConfig { get; set; }
|
public ConfigDedicatedViewModel DedicatedConfig { get; set; }
|
||||||
private static readonly Logger Log = LogManager.GetLogger(nameof(InstanceManager));
|
private static readonly Logger Log = LogManager.GetLogger(nameof(InstanceManager));
|
||||||
|
[Dependency]
|
||||||
|
private FilesystemManager _filesystemManager;
|
||||||
|
|
||||||
public InstanceManager(ITorchBase torchInstance) : base(torchInstance)
|
private new ITorchServer Torch { get; }
|
||||||
|
|
||||||
|
public InstanceManager(ITorchServer torchInstance) : base(torchInstance)
|
||||||
{
|
{
|
||||||
|
Torch = torchInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
public IWorld SelectedWorld => DedicatedConfig.SelectedWorld;
|
||||||
public override void Init()
|
|
||||||
{
|
|
||||||
MyFileSystem.ExePath = Path.Combine(Torch.GetManager<FilesystemManager>().TorchDirectory, "DedicatedServer64");
|
|
||||||
MyFileSystem.Init("Content", Torch.Config.InstancePath);
|
|
||||||
//Initializes saves path. Why this isn't in Init() we may never know.
|
|
||||||
MyFileSystem.InitUserSpecific(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadInstance(string path, bool validate = true)
|
public void LoadInstance(string path, bool validate = true)
|
||||||
{
|
{
|
||||||
|
Log.Info($"Loading instance {path}");
|
||||||
|
|
||||||
if (validate)
|
if (validate)
|
||||||
ValidateInstance(path);
|
ValidateInstance(path);
|
||||||
|
|
||||||
MyFileSystem.Reset();
|
MyFileSystem.Reset();
|
||||||
MyFileSystem.ExePath = Path.Combine(Torch.GetManager<FilesystemManager>().TorchDirectory, "DedicatedServer64");
|
|
||||||
MyFileSystem.Init("Content", path);
|
MyFileSystem.Init("Content", path);
|
||||||
//Initializes saves path. Why this isn't in Init() we may never know.
|
//Initializes saves path. Why this isn't in Init() we may never know.
|
||||||
MyFileSystem.InitUserSpecific(null);
|
MyFileSystem.InitUserSpecific(null);
|
||||||
|
|
||||||
var configPath = Path.Combine(path, CONFIG_NAME);
|
// why?....
|
||||||
if (!File.Exists(configPath))
|
// var configPath = Path.Combine(path, CONFIG_NAME);
|
||||||
{
|
// if (!File.Exists(configPath))
|
||||||
Log.Error($"Failed to load dedicated config at {path}");
|
// {
|
||||||
return;
|
// Log.Error($"Failed to load dedicated config at {path}");
|
||||||
}
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
|
|
||||||
config.Load(configPath);
|
// var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
|
||||||
|
// config.Load(configPath);
|
||||||
|
|
||||||
DedicatedConfig = new ConfigDedicatedViewModel(config);
|
DedicatedConfig = new ConfigDedicatedViewModel((MyConfigDedicated<MyObjectBuilder_SessionSettings>) MySandboxGame.ConfigDedicated);
|
||||||
var worldFolders = Directory.EnumerateDirectories(Path.Combine(Torch.Config.InstancePath, "Saves"));
|
|
||||||
|
var worldFolders = Directory.EnumerateDirectories(Path.Combine(Torch.InstancePath, "Saves"));
|
||||||
|
|
||||||
foreach (var f in worldFolders)
|
foreach (var f in worldFolders)
|
||||||
DedicatedConfig.WorldPaths.Add(f);
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(f) && File.Exists(Path.Combine(f, "Sandbox.sbc")))
|
||||||
|
{
|
||||||
|
var worldViewModel = new WorldViewModel(f, DedicatedConfig.LoadWorld == f);
|
||||||
|
DedicatedConfig.Worlds.Add(worldViewModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to load world at path: " + f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (DedicatedConfig.WorldPaths.Count == 0)
|
if (DedicatedConfig.Worlds.Count == 0)
|
||||||
{
|
{
|
||||||
Log.Warn($"No worlds found in the current instance {path}.");
|
Log.Warn($"No worlds found in the current instance {path}.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImportWorldConfig();
|
SelectWorld(DedicatedConfig.LoadWorld ?? DedicatedConfig.Worlds.First().WorldPath, false);
|
||||||
|
|
||||||
/*
|
InstanceLoaded?.Invoke(DedicatedConfig);
|
||||||
if (string.IsNullOrEmpty(DedicatedConfig.LoadWorld))
|
}
|
||||||
|
|
||||||
|
public void SelectCreatedWorld(string worldPath)
|
||||||
|
{
|
||||||
|
WorldViewModel worldViewModel;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Log.Warn("No world specified, importing first available world.");
|
worldViewModel = new(worldPath);
|
||||||
SelectWorld(DedicatedConfig.WorldPaths[0], false);
|
DedicatedConfig.Worlds.Add(worldViewModel);
|
||||||
}*/
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to load world at path: " + worldPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SelectWorld(worldViewModel, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectWorld(string worldPath, bool modsOnly = true)
|
public void SelectWorld(string worldPath, bool modsOnly = true)
|
||||||
{
|
{
|
||||||
DedicatedConfig.LoadWorld = worldPath;
|
DedicatedConfig.LoadWorld = worldPath;
|
||||||
ImportWorldConfig(modsOnly);
|
|
||||||
|
var worldInfo = DedicatedConfig.Worlds.FirstOrDefault(x => x.WorldPath == worldPath);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (worldInfo?.Checkpoint == null)
|
||||||
|
{
|
||||||
|
worldInfo = new WorldViewModel(worldPath);
|
||||||
|
DedicatedConfig.Worlds.Add(worldInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to load world at path: " + worldPath);
|
||||||
|
DedicatedConfig.LoadWorld = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DedicatedConfig.SelectedWorld = worldInfo;
|
||||||
|
if (DedicatedConfig.SelectedWorld?.Checkpoint != null)
|
||||||
|
{
|
||||||
|
DedicatedConfig.Mods.Clear();
|
||||||
|
//remove the Torch mod to avoid running multiple copies of it
|
||||||
|
DedicatedConfig.SelectedWorld.WorldConfiguration.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
||||||
|
foreach (var m in DedicatedConfig.SelectedWorld.WorldConfiguration.Mods)
|
||||||
|
DedicatedConfig.Mods.Add(new ModItemInfo(m));
|
||||||
|
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SelectWorld(WorldViewModel world, bool modsOnly = true)
|
||||||
|
{
|
||||||
|
DedicatedConfig.LoadWorld = world.WorldPath;
|
||||||
|
DedicatedConfig.SelectedWorld = world;
|
||||||
|
if (DedicatedConfig.SelectedWorld?.Checkpoint != null)
|
||||||
|
{
|
||||||
|
DedicatedConfig.Mods.Clear();
|
||||||
|
//remove the Torch mod to avoid running multiple copies of it
|
||||||
|
DedicatedConfig.SelectedWorld.WorldConfiguration.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID);
|
||||||
|
foreach (var m in DedicatedConfig.SelectedWorld.WorldConfiguration.Mods)
|
||||||
|
DedicatedConfig.Mods.Add(new ModItemInfo(m));
|
||||||
|
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ImportSelectedWorldConfig()
|
||||||
|
{
|
||||||
|
ImportWorldConfig(DedicatedConfig.SelectedWorld, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ImportWorldConfig(WorldViewModel world, bool modsOnly = true)
|
||||||
|
{
|
||||||
|
var mods = new MtObservableList<ModItemInfo>();
|
||||||
|
foreach (var mod in world.WorldConfiguration.Mods)
|
||||||
|
mods.Add(new ModItemInfo(mod));
|
||||||
|
DedicatedConfig.Mods = mods;
|
||||||
|
|
||||||
|
|
||||||
|
Log.Debug("Loaded mod list from world");
|
||||||
|
|
||||||
|
if (!modsOnly)
|
||||||
|
DedicatedConfig.SessionSettings = world.WorldConfiguration.Settings;
|
||||||
|
}
|
||||||
|
|
||||||
private void ImportWorldConfig(bool modsOnly = true)
|
private void ImportWorldConfig(bool modsOnly = true)
|
||||||
{
|
{
|
||||||
@@ -105,15 +203,14 @@ namespace Torch.Server.Managers
|
|||||||
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
||||||
if (checkpoint == null)
|
if (checkpoint == null)
|
||||||
{
|
{
|
||||||
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
|
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {Torch.Config.InstancePath})");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var mods = new MtObservableList<ModItemInfo>();
|
||||||
foreach (var mod in checkpoint.Mods)
|
foreach (var mod in checkpoint.Mods)
|
||||||
sb.AppendLine(mod.PublishedFileId.ToString());
|
mods.Add(new ModItemInfo(mod));
|
||||||
|
DedicatedConfig.Mods = mods;
|
||||||
DedicatedConfig.Mods = sb.ToString();
|
|
||||||
|
|
||||||
Log.Debug("Loaded mod list from world");
|
Log.Debug("Loaded mod list from world");
|
||||||
|
|
||||||
@@ -129,28 +226,38 @@ namespace Torch.Server.Managers
|
|||||||
|
|
||||||
public void SaveConfig()
|
public void SaveConfig()
|
||||||
{
|
{
|
||||||
DedicatedConfig.Model.Save();
|
if (!((TorchServer)Torch).HasRun)
|
||||||
Log.Info("Saved dedicated config.");
|
{
|
||||||
|
DedicatedConfig.Save(Path.Combine(Torch.InstancePath, CONFIG_NAME));
|
||||||
|
Log.Info("Saved dedicated config.");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MyObjectBuilderSerializer.DeserializeXML(Path.Combine(DedicatedConfig.LoadWorld, "Sandbox.sbc"), out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
var world = DedicatedConfig.Worlds.FirstOrDefault(x => x.WorldPath == DedicatedConfig.LoadWorld) ?? new WorldViewModel(DedicatedConfig.LoadWorld);
|
||||||
if (checkpoint == null)
|
|
||||||
{
|
world.Checkpoint.SessionName = DedicatedConfig.WorldName;
|
||||||
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
|
world.WorldConfiguration.Settings = DedicatedConfig.SessionSettings;
|
||||||
return;
|
world.WorldConfiguration.Mods.Clear();
|
||||||
}
|
|
||||||
checkpoint.Settings = DedicatedConfig.SessionSettings;
|
foreach (var mod in DedicatedConfig.Mods)
|
||||||
checkpoint.Mods.Clear();
|
{
|
||||||
foreach (var modId in DedicatedConfig.Model.Mods)
|
var savedMod = ModItemUtils.Create(mod.PublishedFileId);
|
||||||
checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId));
|
savedMod.IsDependency = mod.IsDependency;
|
||||||
|
savedMod.Name = mod.Name;
|
||||||
|
savedMod.FriendlyName = mod.FriendlyName;
|
||||||
|
|
||||||
|
world.WorldConfiguration.Mods.Add(savedMod);
|
||||||
|
}
|
||||||
|
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
|
||||||
|
|
||||||
|
world.SaveSandbox();
|
||||||
|
|
||||||
MyLocalCache.SaveCheckpoint(checkpoint, DedicatedConfig.LoadWorld);
|
|
||||||
Log.Info("Saved world config.");
|
Log.Info("Saved world config.");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Log.Error("Failed to write sandbox config, changes will not appear on server");
|
Log.Error("Failed to write sandbox config");
|
||||||
Log.Error(e);
|
Log.Error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,4 +277,84 @@ namespace Torch.Server.Managers
|
|||||||
config.Save(configPath);
|
config.Save(configPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class WorldViewModel : ViewModel, IWorld
|
||||||
|
{
|
||||||
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public string FolderName { get; set; }
|
||||||
|
public string WorldPath { get; }
|
||||||
|
public MyObjectBuilder_SessionSettings KeenSessionSettings => WorldConfiguration.Settings;
|
||||||
|
public MyObjectBuilder_Checkpoint KeenCheckpoint => Checkpoint;
|
||||||
|
public long WorldSizeKB { get; }
|
||||||
|
private string _checkpointPath;
|
||||||
|
private string _worldConfigPath;
|
||||||
|
private CheckpointViewModel _checkpoint;
|
||||||
|
|
||||||
|
public CheckpointViewModel Checkpoint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_checkpoint is null) LoadSandbox();
|
||||||
|
return _checkpoint;
|
||||||
|
}
|
||||||
|
private set => _checkpoint = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldConfigurationViewModel WorldConfiguration { get; private set; }
|
||||||
|
|
||||||
|
public WorldViewModel(string worldPath, bool loadFiles = true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
WorldPath = worldPath;
|
||||||
|
WorldSizeKB = new DirectoryInfo(worldPath).GetFiles().Sum(x => x.Length) / 1024;
|
||||||
|
_checkpointPath = Path.Combine(WorldPath, "Sandbox.sbc");
|
||||||
|
_worldConfigPath = Path.Combine(WorldPath, "Sandbox_config.sbc");
|
||||||
|
FolderName = Path.GetFileName(worldPath);
|
||||||
|
if (loadFiles)
|
||||||
|
LoadSandbox();
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
Log.Error($"World view model failed to load the path: {worldPath} Please ensure this is a valid path.");
|
||||||
|
throw; //rethrow to be handled further up the stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveSandbox()
|
||||||
|
{
|
||||||
|
using (var f = File.Open(_checkpointPath, FileMode.Create))
|
||||||
|
MyObjectBuilderSerializer.SerializeXML(f, Checkpoint);
|
||||||
|
|
||||||
|
using (var f = File.Open(_worldConfigPath, FileMode.Create))
|
||||||
|
MyObjectBuilderSerializer.SerializeXML(f, WorldConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadSandbox()
|
||||||
|
{
|
||||||
|
if (!MyObjectBuilderSerializer.DeserializeXML(_checkpointPath, out MyObjectBuilder_Checkpoint checkpoint))
|
||||||
|
throw new SerializationException("Error reading checkpoint, see keen log for details");
|
||||||
|
Checkpoint = new CheckpointViewModel(checkpoint);
|
||||||
|
|
||||||
|
// migrate old saves
|
||||||
|
if (File.Exists(_worldConfigPath))
|
||||||
|
{
|
||||||
|
if (!MyObjectBuilderSerializer.DeserializeXML(_worldConfigPath, out MyObjectBuilder_WorldConfiguration worldConfig))
|
||||||
|
throw new SerializationException("Error reading settings, see keen log for details");
|
||||||
|
WorldConfiguration = new WorldConfigurationViewModel(worldConfig);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WorldConfiguration = new WorldConfigurationViewModel(new MyObjectBuilder_WorldConfiguration
|
||||||
|
{
|
||||||
|
Mods = checkpoint.Mods,
|
||||||
|
Settings = checkpoint.Settings
|
||||||
|
});
|
||||||
|
|
||||||
|
checkpoint.Mods = null;
|
||||||
|
checkpoint.Settings = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
373
Torch.Server/Managers/MultiplayerManagerDedicated.cs
Normal file
373
Torch.Server/Managers/MultiplayerManagerDedicated.cs
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using NLog.Fluent;
|
||||||
|
using Sandbox;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
|
using Sandbox.Game.Gui;
|
||||||
|
using Sandbox.Game.World;
|
||||||
|
using Steamworks;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers;
|
||||||
|
using Torch.Utils;
|
||||||
|
using Torch.ViewModels;
|
||||||
|
using VRage.Game;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
using VRage.GameServices;
|
||||||
|
using VRage.Network;
|
||||||
|
using VRage.Steam;
|
||||||
|
|
||||||
|
namespace Torch.Server.Managers
|
||||||
|
{
|
||||||
|
public class MultiplayerManagerDedicated : MultiplayerManagerBase, IMultiplayerManagerServer
|
||||||
|
{
|
||||||
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[ReflectedGetter(Name = "m_members")]
|
||||||
|
private static Func<MyDedicatedServerBase, List<ulong>> _members;
|
||||||
|
|
||||||
|
[ReflectedGetter(Name = "m_waitingForGroup")]
|
||||||
|
private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<ulong> BannedPlayers => MySandboxGame.ConfigDedicated.Banned;
|
||||||
|
|
||||||
|
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
|
||||||
|
|
||||||
|
[Dependency]
|
||||||
|
private InstanceManager _instanceManager;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MultiplayerManagerDedicated(ITorchBase torch) : base(torch)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void BanPlayer(ulong steamId, bool banned = true)
|
||||||
|
{
|
||||||
|
Torch.Invoke(() =>
|
||||||
|
{
|
||||||
|
MyMultiplayer.Static.BanClient(steamId, banned);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void PromoteUser(ulong steamId)
|
||||||
|
{
|
||||||
|
Torch.Invoke(() =>
|
||||||
|
{
|
||||||
|
var p = MySession.Static.GetUserPromoteLevel(steamId);
|
||||||
|
if (p < MyPromoteLevel.Admin) //cannot promote to owner by design
|
||||||
|
//MySession.Static.SetUserPromoteLevel(steamId, p + 1);
|
||||||
|
MyGuiScreenPlayers.PromoteImplementation(steamId, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void DemoteUser(ulong steamId)
|
||||||
|
{
|
||||||
|
Torch.Invoke(() =>
|
||||||
|
{
|
||||||
|
var p = MySession.Static.GetUserPromoteLevel(steamId);
|
||||||
|
if (p > MyPromoteLevel.None && p < MyPromoteLevel.Owner) //owner cannot be demoted by design
|
||||||
|
//MySession.Static.SetUserPromoteLevel(steamId, p - 1);
|
||||||
|
MyGuiScreenPlayers.PromoteImplementation(steamId, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MyPromoteLevel GetUserPromoteLevel(ulong steamId)
|
||||||
|
{
|
||||||
|
return MySession.Static.GetUserPromoteLevel(steamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RaiseClientBanned(ulong user, bool banned)
|
||||||
|
{
|
||||||
|
PlayerBanned?.Invoke(user, banned);
|
||||||
|
Torch.Invoke(() =>
|
||||||
|
{
|
||||||
|
if(_gameOwnerIds.TryGetValue(user, out ulong owner))
|
||||||
|
MyMultiplayer.Static.BanClient(owner, banned);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RaiseClientKicked(ulong user)
|
||||||
|
{
|
||||||
|
PlayerKicked?.Invoke(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsBanned(ulong steamId) => _isClientBanned.Invoke(MyMultiplayer.Static, steamId) ||
|
||||||
|
MySandboxGame.ConfigDedicated.Banned.Contains(steamId);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public bool IsProfiling(ulong steamId) => _Profiling.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event Action<ulong> PlayerKicked;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event Action<ulong, bool> PlayerBanned;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event Action<ulong, MyPromoteLevel> PlayerPromoted;
|
||||||
|
|
||||||
|
internal void RaisePromoteChanged(ulong steamId, MyPromoteLevel level)
|
||||||
|
{
|
||||||
|
PlayerPromoted?.Invoke(steamId, level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
base.Attach();
|
||||||
|
if (Torch.Config.UgcServiceType == UGCServiceType.Steam)
|
||||||
|
{
|
||||||
|
_gameServerValidateAuthTicketReplacer = _gameServerValidateAuthTicketFactory.Invoke();
|
||||||
|
_gameServerUserGroupStatusReplacer = _gameServerUserGroupStatusFactory.Invoke();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gameServerValidateAuthTicketReplacer = _eosServerValidateAuthTicketFactory.Invoke();
|
||||||
|
_gameServerUserGroupStatusReplacer = _eosServerUserGroupStatusFactory.Invoke();
|
||||||
|
}
|
||||||
|
_gameServerValidateAuthTicketReplacer.Replace(
|
||||||
|
new Action<ulong, JoinResult, ulong, string>(ValidateAuthTicketResponse), MyGameService.GameServer);
|
||||||
|
_gameServerUserGroupStatusReplacer.Replace(new Action<ulong, ulong, bool, bool>(UserGroupStatusResponse),
|
||||||
|
MyGameService.GameServer);
|
||||||
|
_log.Info("Inserted authentication intercept");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
if (_gameServerValidateAuthTicketReplacer != null && _gameServerValidateAuthTicketReplacer.Replaced)
|
||||||
|
_gameServerValidateAuthTicketReplacer.Restore(MyGameService.GameServer);
|
||||||
|
if (_gameServerUserGroupStatusReplacer != null && _gameServerUserGroupStatusReplacer.Replaced)
|
||||||
|
_gameServerUserGroupStatusReplacer.Restore(MyGameService.GameServer);
|
||||||
|
_log.Info("Removed authentication intercept");
|
||||||
|
base.Detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[ReflectedEventReplace("VRage.Steam.MySteamGameServer, VRage.Steam", "ValidateAuthTicketResponse",
|
||||||
|
typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
|
||||||
|
private static Func<ReflectedEventReplacer> _gameServerValidateAuthTicketFactory;
|
||||||
|
|
||||||
|
[ReflectedEventReplace("VRage.Steam.MySteamGameServer, VRage.Steam", "UserGroupStatusResponse",
|
||||||
|
typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
|
||||||
|
private static Func<ReflectedEventReplacer> _gameServerUserGroupStatusFactory;
|
||||||
|
|
||||||
|
[ReflectedEventReplace("VRage.EOS.MyEOSGameServer, VRage.EOS", "ValidateAuthTicketResponse",
|
||||||
|
typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
|
||||||
|
private static Func<ReflectedEventReplacer> _eosServerValidateAuthTicketFactory;
|
||||||
|
|
||||||
|
[ReflectedEventReplace("VRage.EOS.MyEOSGameServer, VRage.EOS", "UserGroupStatusResponse",
|
||||||
|
typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
|
||||||
|
private static Func<ReflectedEventReplacer> _eosServerUserGroupStatusFactory;
|
||||||
|
|
||||||
|
private ReflectedEventReplacer _gameServerValidateAuthTicketReplacer;
|
||||||
|
private ReflectedEventReplacer _gameServerUserGroupStatusReplacer;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
#region CustomAuth
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase), Name = "ConvertSteamIDFrom64")]
|
||||||
|
private static Func<ulong, string> _convertSteamIDFrom64;
|
||||||
|
|
||||||
|
[ReflectedStaticMethod(Type = typeof(MyGameService), Name = "GetServerAccountType")]
|
||||||
|
private static Func<ulong, MyGameServiceAccountType> _getServerAccountType;
|
||||||
|
|
||||||
|
[ReflectedMethod(Name = "ClientIsProfiling")]
|
||||||
|
private static Func<MyDedicatedServerBase, ulong, bool> _Profiling;
|
||||||
|
|
||||||
|
[ReflectedMethod(Name = "UserAccepted")]
|
||||||
|
private static Action<MyDedicatedServerBase, ulong> _userAcceptedImpl;
|
||||||
|
|
||||||
|
[ReflectedMethod(Name = "UserRejected")]
|
||||||
|
private static Action<MyDedicatedServerBase, ulong, JoinResult> _userRejected;
|
||||||
|
|
||||||
|
[ReflectedMethod(Name = "IsClientBanned")]
|
||||||
|
private static Func<MyMultiplayerBase, ulong, bool> _isClientBanned;
|
||||||
|
|
||||||
|
[ReflectedMethod(Name = "IsClientKicked")]
|
||||||
|
private static Func<MyMultiplayerBase, ulong, bool> _isClientKicked;
|
||||||
|
|
||||||
|
[ReflectedMethod(Name = "RaiseClientKicked")]
|
||||||
|
private static Action<MyMultiplayerBase, ulong> _raiseClientKicked;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private const int _waitListSize = 32;
|
||||||
|
private readonly List<WaitingForGroup> _waitingForGroupLocal = new List<WaitingForGroup>(_waitListSize);
|
||||||
|
|
||||||
|
private struct WaitingForGroup
|
||||||
|
{
|
||||||
|
public readonly ulong SteamId;
|
||||||
|
public readonly JoinResult Response;
|
||||||
|
public readonly ulong SteamOwner;
|
||||||
|
|
||||||
|
public WaitingForGroup(ulong id, JoinResult response, ulong owner)
|
||||||
|
{
|
||||||
|
SteamId = id;
|
||||||
|
Response = response;
|
||||||
|
SteamOwner = owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Largely copied from SE
|
||||||
|
private void ValidateAuthTicketResponse(ulong steamId, JoinResult response, ulong steamOwner, string serviceName)
|
||||||
|
{
|
||||||
|
var state = new MyP2PSessionState();
|
||||||
|
Sandbox.Engine.Networking.MyGameService.Peer2Peer.GetSessionState(steamId, ref state);
|
||||||
|
var ip = new IPAddress(BitConverter.GetBytes(state.RemoteIP).Reverse().ToArray());
|
||||||
|
|
||||||
|
Torch.CurrentSession.KeenSession.PromotedUsers.TryGetValue(steamId, out MyPromoteLevel promoteLevel);
|
||||||
|
|
||||||
|
_log.Debug($"ValidateAuthTicketResponse(user={steamId}, response={response}, owner={steamOwner}, permissions={promoteLevel})");
|
||||||
|
|
||||||
|
_log.Info($"Connection attempt by {steamId} from {ip}");
|
||||||
|
|
||||||
|
if (IsProfiling(steamId))
|
||||||
|
{
|
||||||
|
_log.Warn($"Rejecting user {steamId} for using Profiler/ModSDK!");
|
||||||
|
UserRejected(steamId, JoinResult.ProfilingNotAllowed);
|
||||||
|
}
|
||||||
|
else if (Torch.CurrentSession.KeenSession.OnlineMode == MyOnlineModeEnum.OFFLINE &&
|
||||||
|
promoteLevel < MyPromoteLevel.Admin)
|
||||||
|
{
|
||||||
|
_log.Warn($"Rejecting user {steamId}, world is set to offline and user is not admin.");
|
||||||
|
UserRejected(steamId, JoinResult.TicketCanceled);
|
||||||
|
}
|
||||||
|
else if (MySandboxGame.ConfigDedicated.GroupID == 0uL)
|
||||||
|
RunEvent(new ValidateAuthTicketEvent(steamId, steamOwner, response, 0, true, false));
|
||||||
|
else if (_getServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
|
||||||
|
UserRejected(steamId, JoinResult.GroupIdInvalid);
|
||||||
|
else if (MyGameService.GameServer.RequestGroupStatus(steamId, MySandboxGame.ConfigDedicated.GroupID))
|
||||||
|
lock (_waitingForGroupLocal)
|
||||||
|
{
|
||||||
|
if (_waitingForGroupLocal.Count >= _waitListSize)
|
||||||
|
_waitingForGroupLocal.RemoveAt(0);
|
||||||
|
_waitingForGroupLocal.Add(new WaitingForGroup(steamId, response, steamOwner));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
UserRejected(steamId, JoinResult.SteamServersOffline);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunEvent(ValidateAuthTicketEvent info)
|
||||||
|
{
|
||||||
|
JoinResult internalAuth;
|
||||||
|
|
||||||
|
|
||||||
|
if (IsBanned(info.SteamOwner) || IsBanned(info.SteamID))
|
||||||
|
internalAuth = JoinResult.BannedByAdmins;
|
||||||
|
else if (_isClientKicked(MyMultiplayer.Static, info.SteamID) ||
|
||||||
|
_isClientKicked(MyMultiplayer.Static, info.SteamOwner))
|
||||||
|
internalAuth = JoinResult.KickedRecently;
|
||||||
|
else if (info.SteamResponse == JoinResult.OK)
|
||||||
|
{
|
||||||
|
var config = (TorchConfig) Torch.Config;
|
||||||
|
if (config.EnableWhitelist && !config.Whitelist.Contains(info.SteamID))
|
||||||
|
{
|
||||||
|
_log.Warn($"Rejecting user {info.SteamID} because they are not whitelisted in Torch.cfg.");
|
||||||
|
internalAuth = JoinResult.NotInGroup;
|
||||||
|
}
|
||||||
|
else if (MySandboxGame.ConfigDedicated.Reserved.Contains(info.SteamID))
|
||||||
|
internalAuth = JoinResult.OK;
|
||||||
|
//Admins can bypass member limit
|
||||||
|
else if (MySandboxGame.ConfigDedicated.Administrators.Contains(info.SteamID.ToString()) ||
|
||||||
|
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(info.SteamID)))
|
||||||
|
internalAuth = JoinResult.OK;
|
||||||
|
//Server counts as a client, so subtract 1 from MemberCount
|
||||||
|
else if (MyMultiplayer.Static.MemberLimit > 0 &&
|
||||||
|
MyMultiplayer.Static.MemberCount - 1 >= MyMultiplayer.Static.MemberLimit)
|
||||||
|
internalAuth = JoinResult.ServerFull;
|
||||||
|
else if (MySandboxGame.ConfigDedicated.GroupID == 0uL)
|
||||||
|
internalAuth = JoinResult.OK;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (MySandboxGame.ConfigDedicated.GroupID == info.Group && (info.Member || info.Officer))
|
||||||
|
internalAuth = JoinResult.OK;
|
||||||
|
else
|
||||||
|
internalAuth = JoinResult.NotInGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
internalAuth = info.SteamResponse;
|
||||||
|
|
||||||
|
info.FutureVerdict = Task.FromResult(internalAuth);
|
||||||
|
|
||||||
|
MultiplayerManagerDedicatedEventShim.RaiseValidateAuthTicket(ref info);
|
||||||
|
|
||||||
|
info.FutureVerdict.ContinueWith((task) =>
|
||||||
|
{
|
||||||
|
JoinResult verdict;
|
||||||
|
if (task.IsFaulted)
|
||||||
|
{
|
||||||
|
_log.Error(task.Exception, $"Future validation verdict faulted");
|
||||||
|
verdict = JoinResult.TicketCanceled;
|
||||||
|
}
|
||||||
|
else if (Players.ContainsKey(info.SteamID))
|
||||||
|
{
|
||||||
|
_log.Warn($"Player {info.SteamID} has already joined!");
|
||||||
|
verdict = JoinResult.AlreadyJoined;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
verdict = task.Result;
|
||||||
|
|
||||||
|
Torch.Invoke(() => { CommitVerdict(info.SteamID, verdict); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CommitVerdict(ulong steamId, JoinResult verdict)
|
||||||
|
{
|
||||||
|
if (verdict == JoinResult.OK)
|
||||||
|
UserAccepted(steamId);
|
||||||
|
else
|
||||||
|
UserRejected(steamId, verdict);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
|
||||||
|
{
|
||||||
|
lock (_waitingForGroupLocal)
|
||||||
|
for (var j = 0; j < _waitingForGroupLocal.Count; j++)
|
||||||
|
{
|
||||||
|
var wait = _waitingForGroupLocal[j];
|
||||||
|
if (wait.SteamId == userId)
|
||||||
|
{
|
||||||
|
RunEvent(new ValidateAuthTicketEvent(wait.SteamId, wait.SteamOwner, wait.Response, groupId,
|
||||||
|
member, officer));
|
||||||
|
_waitingForGroupLocal.RemoveAt(j);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UserRejected(ulong steamId, JoinResult reason)
|
||||||
|
{
|
||||||
|
_userRejected.Invoke((MyDedicatedServerBase) MyMultiplayer.Static, steamId, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UserAccepted(ulong steamId)
|
||||||
|
{
|
||||||
|
_userAcceptedImpl.Invoke((MyDedicatedServerBase) MyMultiplayer.Static, steamId);
|
||||||
|
base.RaiseClientJoined(steamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,84 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox;
|
||||||
|
using Torch.API.Event;
|
||||||
|
using Torch.Event;
|
||||||
|
using VRage.Network;
|
||||||
|
|
||||||
|
namespace Torch.Server.Managers
|
||||||
|
{
|
||||||
|
[EventShim]
|
||||||
|
internal static class MultiplayerManagerDedicatedEventShim
|
||||||
|
{
|
||||||
|
private static readonly EventList<ValidateAuthTicketEvent> _eventValidateAuthTicket =
|
||||||
|
new EventList<ValidateAuthTicketEvent>();
|
||||||
|
|
||||||
|
|
||||||
|
internal static void RaiseValidateAuthTicket(ref ValidateAuthTicketEvent info)
|
||||||
|
{
|
||||||
|
_eventValidateAuthTicket?.RaiseEvent(ref info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that occurs when a player tries to connect to a dedicated server.
|
||||||
|
/// Use these values to choose a <see cref="ValidateAuthTicketEvent.FutureVerdict"/>,
|
||||||
|
/// or leave it unset to allow the default logic to handle the request.
|
||||||
|
/// </summary>
|
||||||
|
public struct ValidateAuthTicketEvent : IEvent
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SteamID of the player
|
||||||
|
/// </summary>
|
||||||
|
public readonly ulong SteamID;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SteamID of the game owner
|
||||||
|
/// </summary>
|
||||||
|
public readonly ulong SteamOwner;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The response from steam
|
||||||
|
/// </summary>
|
||||||
|
public readonly JoinResult SteamResponse;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ID of the queried group, or <c>0</c> if no group.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ulong Group;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this person a member of <see cref="Group"/>. If no group this is true.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool Member;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this person an officer of <see cref="Group"/>. If no group this is false.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool Officer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A future verdict on this authorization request. If null, let the default logic choose. If not async use <see cref="Task.FromResult{TResult}(TResult)"/>
|
||||||
|
/// </summary>
|
||||||
|
public Task<JoinResult> FutureVerdict;
|
||||||
|
|
||||||
|
internal ValidateAuthTicketEvent(ulong steamId, ulong steamOwner, JoinResult steamResponse,
|
||||||
|
ulong serverGroup, bool member, bool officer)
|
||||||
|
{
|
||||||
|
SteamID = steamId;
|
||||||
|
SteamOwner = steamOwner;
|
||||||
|
SteamResponse = steamResponse;
|
||||||
|
Group = serverGroup;
|
||||||
|
Member = member;
|
||||||
|
Officer = officer;
|
||||||
|
FutureVerdict = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Cancelled => FutureVerdict != null;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Torch.Managers.PatchManager;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
|
||||||
|
namespace Torch.Server.Managers
|
||||||
|
{
|
||||||
|
[PatchShim]
|
||||||
|
internal static class MultiplayerManagerDedicatedPatchShim
|
||||||
|
{
|
||||||
|
public static void Patch(PatchContext ctx)
|
||||||
|
{
|
||||||
|
ctx.GetPattern(typeof(MyDedicatedServerBase).GetMethod(nameof(MyDedicatedServerBase.BanClient))).Prefixes.Add(typeof(MultiplayerManagerDedicatedPatchShim).GetMethod(nameof(BanPrefix)));
|
||||||
|
ctx.GetPattern(typeof(MyDedicatedServerBase).GetMethod(nameof(MyDedicatedServerBase.KickClient))).Prefixes.Add(typeof(MultiplayerManagerDedicatedPatchShim).GetMethod(nameof(KickPrefix)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void BanPrefix(ulong userId, bool banned)
|
||||||
|
{
|
||||||
|
TorchBase.Instance.CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>().RaiseClientBanned(userId, banned);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void KickPrefix(ulong userId)
|
||||||
|
{
|
||||||
|
TorchBase.Instance.CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>().RaiseClientKicked(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
Torch.Server/Managers/RemoteAPIManager.cs
Normal file
40
Torch.Server/Managers/RemoteAPIManager.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using NLog;
|
||||||
|
using Sandbox;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.Managers;
|
||||||
|
using VRage.Dedicated.RemoteAPI;
|
||||||
|
|
||||||
|
namespace Torch.Server.Managers
|
||||||
|
{
|
||||||
|
public class RemoteAPIManager : Manager
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public RemoteAPIManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
Torch.GameStateChanged += TorchOnGameStateChanged;
|
||||||
|
base.Attach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
Torch.GameStateChanged -= TorchOnGameStateChanged;
|
||||||
|
base.Detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TorchOnGameStateChanged(MySandboxGame game, TorchGameState newstate)
|
||||||
|
{
|
||||||
|
if (newstate == TorchGameState.Loading && MySandboxGame.ConfigDedicated.RemoteApiEnabled && !string.IsNullOrEmpty(MySandboxGame.ConfigDedicated.RemoteSecurityKey))
|
||||||
|
{
|
||||||
|
var myRemoteServer = new MyRemoteServer(MySandboxGame.ConfigDedicated.RemoteApiPort, MySandboxGame.ConfigDedicated.RemoteSecurityKey);
|
||||||
|
LogManager.GetCurrentClassLogger().Info($"Remote API started on port {myRemoteServer.Port}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
Torch.Server/MultiTextWriter.cs
Normal file
49
Torch.Server/MultiTextWriter.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Torch.Server
|
||||||
|
{
|
||||||
|
public class MultiTextWriter : TextWriter
|
||||||
|
{
|
||||||
|
private IEnumerable<TextWriter> writers;
|
||||||
|
public MultiTextWriter(IEnumerable<TextWriter> writers)
|
||||||
|
{
|
||||||
|
this.writers = writers.ToList();
|
||||||
|
}
|
||||||
|
public MultiTextWriter(params TextWriter[] writers)
|
||||||
|
{
|
||||||
|
this.writers = writers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(char value)
|
||||||
|
{
|
||||||
|
foreach (var writer in writers)
|
||||||
|
writer.Write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(string value)
|
||||||
|
{
|
||||||
|
foreach (var writer in writers)
|
||||||
|
writer.Write(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
foreach (var writer in writers)
|
||||||
|
writer.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Close()
|
||||||
|
{
|
||||||
|
foreach (var writer in writers)
|
||||||
|
writer.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Encoding Encoding
|
||||||
|
{
|
||||||
|
get { return Encoding.ASCII; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
Torch.Server/Patches/CheckpointLoadPatch.cs
Normal file
39
Torch.Server/Patches/CheckpointLoadPatch.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers.PatchManager;
|
||||||
|
using Torch.Server.Managers;
|
||||||
|
using Torch.Utils;
|
||||||
|
using VRage.Game;
|
||||||
|
|
||||||
|
namespace Torch.Patches;
|
||||||
|
|
||||||
|
[PatchShim]
|
||||||
|
public static class CheckpointLoadPatch
|
||||||
|
{
|
||||||
|
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(MyLocalCache), "LoadCheckpoint")]
|
||||||
|
private static MethodInfo LoadCheckpointMethod = null!;
|
||||||
|
|
||||||
|
public static void Patch(PatchContext context)
|
||||||
|
{
|
||||||
|
context.GetPattern(LoadCheckpointMethod).AddPrefix();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool Prefix(ref MyObjectBuilder_Checkpoint __result)
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
var world = TorchBase.Instance.Managers.GetManager<InstanceManager>().DedicatedConfig.SelectedWorld;
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
if (world is null)
|
||||||
|
{
|
||||||
|
Log.Error("Selected world is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = world.Checkpoint;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
38
Torch.Server/Patches/PromotePatch.cs
Normal file
38
Torch.Server/Patches/PromotePatch.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox.Game.World;
|
||||||
|
using Torch.Managers.PatchManager;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Server.Managers;
|
||||||
|
|
||||||
|
namespace Torch.Patches
|
||||||
|
{
|
||||||
|
[PatchShim]
|
||||||
|
internal static class PromotePatch
|
||||||
|
{
|
||||||
|
private static Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
private static IMultiplayerManagerServer _backing;
|
||||||
|
|
||||||
|
private static IMultiplayerManagerServer ServerManager => _backing ?? (_backing = TorchBase.Instance?.CurrentSession?.Managers.GetManager<IMultiplayerManagerServer>());
|
||||||
|
|
||||||
|
public static void Patch(PatchContext ctx)
|
||||||
|
{
|
||||||
|
_log.Info("patching promote");
|
||||||
|
ctx.GetPattern(typeof(MySession).GetMethod("OnPromoteLevelSet", BindingFlags.NonPublic | BindingFlags.Static)).Prefixes.Add(typeof(PromotePatch).GetMethod(nameof(PromotePrefix)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PromotePrefix(ulong steamId, MyPromoteLevel level)
|
||||||
|
{
|
||||||
|
if (ServerManager is MultiplayerManagerDedicated d)
|
||||||
|
d.RaisePromoteChanged(steamId, level);
|
||||||
|
else
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
Torch.Server/Patches/ServerResponsePatch.cs
Normal file
44
Torch.Server/Patches/ServerResponsePatch.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Game.World;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers.PatchManager;
|
||||||
|
using Torch.Managers.PatchManager.MSIL;
|
||||||
|
using Torch.Server.Managers;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
|
namespace Torch.Patches
|
||||||
|
{
|
||||||
|
[PatchShim]
|
||||||
|
public static class ServerResponsePatch
|
||||||
|
{
|
||||||
|
private static Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public static void Patch(PatchContext ctx)
|
||||||
|
{
|
||||||
|
var transpiler = typeof(ServerResponsePatch).GetMethod(nameof(Transpile), BindingFlags.Public | BindingFlags.Static);
|
||||||
|
ctx.GetPattern(typeof(MyDedicatedServerBase).GetMethod("Initialize", BindingFlags.NonPublic | BindingFlags.Instance))
|
||||||
|
.Transpilers.Add(transpiler);
|
||||||
|
_log.Info("Patching Steam response polling");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<MsilInstruction> Transpile(IEnumerable<MsilInstruction> instructions)
|
||||||
|
{
|
||||||
|
// Reduce response timeout from 100 seconds to 5 seconds.
|
||||||
|
foreach (var instruction in instructions)
|
||||||
|
{
|
||||||
|
if (instruction.OpCode == OpCodes.Ldc_I4 && instruction.Operand is MsilOperandInline.MsilOperandInt32 inlineI32 && inlineI32.Value == 1000)
|
||||||
|
{
|
||||||
|
_log.Info("Patching Steam response timeout to 5 seconds");
|
||||||
|
inlineI32.Value = 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return instruction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
Torch.Server/Patches/WorldLoadExceptionPatch.cs
Normal file
48
Torch.Server/Patches/WorldLoadExceptionPatch.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox;
|
||||||
|
using Torch.Managers.PatchManager;
|
||||||
|
using Torch.Managers.PatchManager.MSIL;
|
||||||
|
|
||||||
|
namespace Torch.Patches
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Patches MySandboxGame.InitQuickLaunch to rethrow exceptions caught during session load.
|
||||||
|
/// </summary>
|
||||||
|
[PatchShim]
|
||||||
|
public static class WorldLoadExceptionPatch
|
||||||
|
{
|
||||||
|
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
public static void Patch(PatchContext ctx)
|
||||||
|
{
|
||||||
|
ctx.GetPattern(typeof(MySandboxGame).GetMethod("InitQuickLaunch", BindingFlags.Instance | BindingFlags.NonPublic))
|
||||||
|
.Transpilers.Add(typeof(WorldLoadExceptionPatch).GetMethod(nameof(Transpile), BindingFlags.Static | BindingFlags.NonPublic));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<MsilInstruction> Transpile(IEnumerable<MsilInstruction> method)
|
||||||
|
{
|
||||||
|
var msil = method.ToList();
|
||||||
|
for (var i = 0; i < msil.Count; i++)
|
||||||
|
{
|
||||||
|
if (msil[i].TryCatchOperations.All(x => x.Type != MsilTryCatchOperationType.BeginClauseBlock))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (; i < msil.Count; i++)
|
||||||
|
{
|
||||||
|
if (msil[i].OpCode != OpCodes.Leave)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
msil[i] = new MsilInstruction(OpCodes.Rethrow);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,299 +1,105 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Configuration.Install;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using NLog.Targets;
|
||||||
using System.Reflection;
|
using Torch.Utils;
|
||||||
using System.ServiceProcess;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Threading;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
|
||||||
using NLog;
|
|
||||||
using Sandbox.Game.World;
|
|
||||||
using Sandbox.ModAPI;
|
|
||||||
using Torch;
|
|
||||||
using Torch.API;
|
|
||||||
using Torch.Server.Views;
|
|
||||||
using VRage.Game.ModAPI;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Net;
|
|
||||||
using System.Security.Policy;
|
|
||||||
using Torch.Server.Managers;
|
|
||||||
using VRage.FileSystem;
|
|
||||||
using VRageRender;
|
|
||||||
|
|
||||||
namespace Torch.Server
|
namespace Torch.Server
|
||||||
{
|
{
|
||||||
internal static class Program
|
internal static class Program
|
||||||
{
|
{
|
||||||
private static ITorchServer _server;
|
|
||||||
private static Logger _log = LogManager.GetLogger("Torch");
|
|
||||||
private static bool _restartOnCrash;
|
|
||||||
private static TorchConfig _config;
|
|
||||||
private static bool _steamCmdDone;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This method must *NOT* load any types/assemblies from the vanilla game, otherwise automatic updates will fail.
|
|
||||||
/// </summary>
|
|
||||||
[STAThread]
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
var isService = Environment.GetEnvironmentVariable("TORCH_SERVICE")
|
||||||
|
?.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase) ?? false;
|
||||||
|
Target.Register<LogViewerTarget>(nameof(LogViewerTarget));
|
||||||
//Ensures that all the files are downloaded in the Torch directory.
|
//Ensures that all the files are downloaded in the Torch directory.
|
||||||
Directory.SetCurrentDirectory(new FileInfo(typeof(Program).Assembly.Location).Directory.ToString());
|
var workingDir = AppContext.BaseDirectory;
|
||||||
|
var binDir = Path.Combine(Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir, "DedicatedServer64");
|
||||||
foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.old"))
|
Directory.SetCurrentDirectory(Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? workingDir);
|
||||||
File.Delete(file);
|
|
||||||
|
if (!isService && Directory.Exists(binDir))
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
foreach (var file in Directory.GetFiles(binDir, "System.*.dll"))
|
||||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
|
||||||
|
|
||||||
if (!Environment.UserInteractive)
|
|
||||||
{
|
|
||||||
using (var service = new TorchService())
|
|
||||||
{
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
TorchLauncher.Launch(workingDir, binDir);
|
||||||
|
|
||||||
|
// Breaks on Windows Server 2019
|
||||||
|
#if TORCH_SERVICE
|
||||||
|
if (!new ComputerInfo().OSFullName.Contains("Server 2019") && !Environment.UserInteractive)
|
||||||
|
{
|
||||||
|
using (var service = new TorchService(args))
|
||||||
ServiceBase.Run(service);
|
ServiceBase.Run(service);
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
//CommandLine reflection triggers assembly loading, so DS update must be completely separated.
|
var instanceName = Environment.GetEnvironmentVariable("TORCH_INSTANCE") ?? "Instance";
|
||||||
if (!args.Contains("-noupdate"))
|
string instancePath;
|
||||||
|
|
||||||
|
if (Path.IsPathRooted(instanceName))
|
||||||
{
|
{
|
||||||
if (!Directory.Exists("DedicatedServer64"))
|
instancePath = instanceName;
|
||||||
{
|
instanceName = Path.GetDirectoryName(instanceName);
|
||||||
_log.Error("Game libraries not found. Press the Enter key to install the dedicated server.");
|
|
||||||
Console.ReadLine();
|
|
||||||
}
|
|
||||||
RunSteamCmd();
|
|
||||||
}
|
|
||||||
|
|
||||||
InitConfig();
|
|
||||||
|
|
||||||
if (!_config.Parse(args))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_config.WaitForPID))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var pid = int.Parse(_config.WaitForPID);
|
|
||||||
var waitProc = Process.GetProcessById(pid);
|
|
||||||
_log.Info("Continuing in 5 seconds.");
|
|
||||||
Thread.Sleep(5000);
|
|
||||||
if (!waitProc.HasExited)
|
|
||||||
{
|
|
||||||
_log.Warn($"Killing old process {pid}.");
|
|
||||||
waitProc.Kill();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_restartOnCrash = _config.RestartOnCrash;
|
|
||||||
RunServer(_config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void InitConfig()
|
|
||||||
{
|
|
||||||
var configName = "Torch.cfg";
|
|
||||||
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
|
|
||||||
if (File.Exists(configName))
|
|
||||||
{
|
|
||||||
_log.Info($"Loading config {configPath}");
|
|
||||||
_config = TorchConfig.LoadFrom(configPath);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_log.Info($"Generating default config at {configPath}");
|
instancePath = Path.GetFullPath(instanceName);
|
||||||
_config = new TorchConfig { InstancePath = Path.GetFullPath("Instance") };
|
|
||||||
_config.Save(configPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var oldTorchCfg = Path.Combine(workingDir, "Torch.cfg");
|
||||||
|
var torchCfg = Path.Combine(instancePath, "Torch.cfg");
|
||||||
|
|
||||||
|
if (File.Exists(oldTorchCfg))
|
||||||
|
File.Move(oldTorchCfg, torchCfg, true);
|
||||||
|
|
||||||
|
var config = Persistent<TorchConfig>.Load(torchCfg);
|
||||||
|
config.Data.InstanceName = instanceName;
|
||||||
|
config.Data.InstancePath = instancePath;
|
||||||
|
if (!config.Data.Parse(args))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Invalid arguments");
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var handler = new UnhandledExceptionHandler(config.Data, isService);
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += handler.OnUnhandledException;
|
||||||
|
|
||||||
|
var initializer = new Initializer(workingDir, config);
|
||||||
|
if (!initializer.Initialize(args))
|
||||||
|
Environment.Exit(1);
|
||||||
|
|
||||||
|
CopyNative(binDir);
|
||||||
|
initializer.Run(isService, instanceName, instancePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string STEAMCMD_DIR = "steamcmd";
|
private static void CopyNative(string binPath)
|
||||||
private const string STEAMCMD_ZIP = "temp.zip";
|
|
||||||
private static readonly string STEAMCMD_PATH = $"{STEAMCMD_DIR}\\steamcmd.exe";
|
|
||||||
private static readonly string RUNSCRIPT_PATH = $"{STEAMCMD_DIR}\\runscript.txt";
|
|
||||||
private const string RUNSCRIPT = @"force_install_dir ../
|
|
||||||
login anonymous
|
|
||||||
app_update 298740
|
|
||||||
quit";
|
|
||||||
|
|
||||||
public static void RunSteamCmd()
|
|
||||||
{
|
{
|
||||||
if (_steamCmdDone)
|
var apiSource = Path.Combine(binPath, "steam_api64.dll");
|
||||||
return;
|
var apiTarget = Path.Combine(AppContext.BaseDirectory, "steam_api64.dll");
|
||||||
|
if (!File.Exists(apiTarget))
|
||||||
var log = LogManager.GetLogger("SteamCMD");
|
|
||||||
|
|
||||||
if (!Directory.Exists(STEAMCMD_DIR))
|
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(STEAMCMD_DIR);
|
File.Copy(apiSource, apiTarget);
|
||||||
}
|
}
|
||||||
|
else if (File.GetLastWriteTime(apiTarget) < File.GetLastWriteTime(binPath))
|
||||||
if (!File.Exists(RUNSCRIPT_PATH))
|
|
||||||
File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT);
|
|
||||||
|
|
||||||
if (!File.Exists(STEAMCMD_PATH))
|
|
||||||
{
|
{
|
||||||
try
|
File.Delete(apiTarget);
|
||||||
{
|
File.Copy(apiSource, apiTarget);
|
||||||
log.Info("Downloading SteamCMD.");
|
|
||||||
using (var client = new WebClient())
|
|
||||||
client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP);
|
|
||||||
|
|
||||||
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR);
|
|
||||||
File.Delete(STEAMCMD_ZIP);
|
|
||||||
log.Info("SteamCMD downloaded successfully!");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
log.Error("Failed to download SteamCMD, unable to update the DS.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var havokSource = Path.Combine(binPath, "Havok.dll");
|
||||||
|
var havokTarget = Path.Combine(AppContext.BaseDirectory, "Havok.dll");
|
||||||
|
|
||||||
log.Info("Checking for DS updates.");
|
if (!File.Exists(havokTarget))
|
||||||
var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt")
|
|
||||||
{
|
{
|
||||||
WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), STEAMCMD_DIR),
|
File.Copy(havokSource, havokTarget);
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
StandardOutputEncoding = Encoding.ASCII
|
|
||||||
};
|
|
||||||
var cmd = Process.Start(steamCmdProc);
|
|
||||||
|
|
||||||
// ReSharper disable once PossibleNullReferenceException
|
|
||||||
while (!cmd.HasExited)
|
|
||||||
{
|
|
||||||
log.Info(cmd.StandardOutput.ReadLine());
|
|
||||||
Thread.Sleep(100);
|
|
||||||
}
|
}
|
||||||
|
else if (File.GetLastWriteTime(havokTarget) < File.GetLastWriteTime(havokSource))
|
||||||
_steamCmdDone = true;
|
{
|
||||||
}
|
File.Delete(havokTarget);
|
||||||
|
File.Copy(havokSource, havokTarget);
|
||||||
public static void RunServer(TorchConfig config)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (!parser.ParseArguments(args, config))
|
|
||||||
{
|
|
||||||
_log.Error($"Parsing arguments failed: {string.Join(" ", args)}");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(config.Config) && File.Exists(config.Config))
|
|
||||||
{
|
|
||||||
config = ServerConfig.LoadFrom(config.Config);
|
|
||||||
parser.ParseArguments(args, config);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
//RestartOnCrash autostart autosave=15
|
|
||||||
//gamepath ="C:\Program Files\Space Engineers DS" instance="Hydro Survival" instancepath="C:\ProgramData\SpaceEngineersDedicated\Hydro Survival"
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (config.InstallService)
|
|
||||||
{
|
|
||||||
var serviceName = $"\"Torch - {config.InstanceName}\"";
|
|
||||||
// Working on installing the service properly instead of with sc.exe
|
|
||||||
_log.Info($"Installing service '{serviceName}");
|
|
||||||
var exePath = $"\"{Assembly.GetExecutingAssembly().Location}\"";
|
|
||||||
var createInfo = new ServiceCreateInfo
|
|
||||||
{
|
|
||||||
Name = config.InstanceName,
|
|
||||||
BinaryPath = exePath,
|
|
||||||
};
|
|
||||||
_log.Info("Service Installed");
|
|
||||||
|
|
||||||
var runArgs = string.Join(" ", args.Skip(1));
|
|
||||||
_log.Info($"Installing Torch as a service with arguments '{runArgs}'");
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "sc.exe",
|
|
||||||
Arguments = $"create Torch binPath=\"{Assembly.GetExecutingAssembly().Location} {runArgs}\"",
|
|
||||||
CreateNoWindow = true,
|
|
||||||
UseShellExecute = true,
|
|
||||||
Verb = "runas"
|
|
||||||
};
|
|
||||||
Process.Start(startInfo).WaitForExit();
|
|
||||||
_log.Info("Torch service installed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.UninstallService)
|
|
||||||
{
|
|
||||||
_log.Info("Uninstalling Torch service");
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "sc.exe",
|
|
||||||
Arguments = "delete Torch",
|
|
||||||
CreateNoWindow = true,
|
|
||||||
UseShellExecute = true,
|
|
||||||
Verb = "runas"
|
|
||||||
};
|
|
||||||
Process.Start(startInfo).WaitForExit();
|
|
||||||
_log.Info("Torch service uninstalled");
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
_server = new TorchServer(config);
|
|
||||||
|
|
||||||
_server.Init();
|
|
||||||
if (config.NoGui || config.Autostart)
|
|
||||||
{
|
|
||||||
new Thread(() => _server.Start()).Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.NoGui)
|
|
||||||
{
|
|
||||||
var ui = new TorchUI((TorchServer)_server);
|
|
||||||
ui.ShowDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var basePath = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "DedicatedServer64");
|
|
||||||
string asmPath = Path.Combine(basePath, new AssemblyName(args.Name).Name + ".dll");
|
|
||||||
if (File.Exists(asmPath))
|
|
||||||
return Assembly.LoadFrom(asmPath);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
|
||||||
{
|
|
||||||
var ex = (Exception)e.ExceptionObject;
|
|
||||||
_log.Fatal(ex);
|
|
||||||
Console.WriteLine("Exiting in 5 seconds.");
|
|
||||||
Thread.Sleep(5000);
|
|
||||||
if (_restartOnCrash)
|
|
||||||
{
|
|
||||||
var exe = typeof(Program).Assembly.Location;
|
|
||||||
_config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
|
|
||||||
Process.Start(exe, _config.ToString());
|
|
||||||
}
|
|
||||||
//1627 = Function failed during execution.
|
|
||||||
Environment.Exit(1627);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1065
Torch.Server/Properties/Annotations.cs
Normal file
1065
Torch.Server/Properties/Annotations.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,17 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("1.1.213.390")]
|
[assembly: AssemblyTitle("Torch Server")]
|
||||||
[assembly: AssemblyFileVersion("1.1.213.390")]
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("Torch")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
[assembly: AssemblyConfiguration("Debug")]
|
||||||
|
#else
|
||||||
|
[assembly: AssemblyConfiguration("Release")]
|
||||||
|
#endif
|
@@ -1,16 +0,0 @@
|
|||||||
<#@ template debug="false" hostspecific="false" language="C#" #>
|
|
||||||
<#@ assembly name="System.Core" #>
|
|
||||||
<#@ import namespace="System.Linq" #>
|
|
||||||
<#@ import namespace="System.Text" #>
|
|
||||||
<#@ import namespace="System.Collections.Generic" #>
|
|
||||||
<#@ output extension=".cs" #>
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
<# var dt = DateTime.Now;
|
|
||||||
int major = 1;
|
|
||||||
int minor = 1;
|
|
||||||
int build = dt.DayOfYear;
|
|
||||||
int rev = (int)dt.TimeOfDay.TotalMinutes / 2;
|
|
||||||
#>
|
|
||||||
[assembly: AssemblyVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
|
||||||
[assembly: AssemblyFileVersion("<#= major #>.<#= minor #>.<#= build #>.<#= rev #>")]
|
|
2
Torch.Server/Properties/Resources.Designer.cs
generated
2
Torch.Server/Properties/Resources.Designer.cs
generated
@@ -19,7 +19,7 @@ namespace Torch.Server.Properties {
|
|||||||
// class via a tool like ResGen or Visual Studio.
|
// class via a tool like ResGen or Visual Studio.
|
||||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
// with the /str option, or rebuild your VS project.
|
// with the /str option, or rebuild your VS project.
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
internal class Resources {
|
internal class Resources {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user