Compare commits
723 Commits
1.1.207.7
...
v1.0.21-ma
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bd62d31298 | ||
![]() |
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 | ||
![]() |
97da740e7e | ||
![]() |
42bb24ca6a | ||
![]() |
2f3b6cdda7 | ||
![]() |
525b496774 | ||
![]() |
562bb77dda |
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
|
||||||
|
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,4 +1,13 @@
|
|||||||
# Torch 1.1.205.478
|
# 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
|
||||||
* 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.
|
||||||
* Features
|
* Features
|
||||||
|
@@ -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**
|
||||||
|
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>
|
32
README.md
32
README.md
@@ -1,31 +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
|
||||||
|
|
||||||
|
### Fork Difference
|
||||||
|
* .NET 6.0 runtime
|
||||||
|
* Additional options & features
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
# 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.
|
||||||
|
|
||||||
# Installation Guide
|
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon. (https://www.patreon.com/TorchSE)
|
||||||
|
|
||||||
### Automatic (recommended)
|
|
||||||
* Unzip Torch to its own folder, run Torch.Server.exe and enter 'y' in the prompt for automatic updates. Torch will automatically download the Space Engineers files and generate all of the configs/folders necessary.
|
|
||||||
|
|
||||||
### Manual (for hosting companies or the paranoid)
|
|
||||||
* Install the Space Engineers DS and then unzip the Torch files into the server's DedicatedServer64 directory. It will automatically detect the manual install and disable automatic updates.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
# 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.207.7")]
|
[assembly: AssemblyTitle("Torch Client")]
|
||||||
[assembly: AssemblyFileVersion("1.0.207.7")]
|
[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,91 +1,196 @@
|
|||||||
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()
|
|
||||||
{
|
|
||||||
LoadInstance(Torch.Config.InstancePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
SelectWorld(DedicatedConfig.LoadWorld ?? DedicatedConfig.Worlds.First().WorldPath, false);
|
||||||
if (string.IsNullOrEmpty(DedicatedConfig.LoadWorld))
|
|
||||||
|
InstanceLoaded?.Invoke(DedicatedConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
LoadWorldMods(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)
|
||||||
private void LoadWorldMods(bool modsOnly = true)
|
|
||||||
{
|
{
|
||||||
if (DedicatedConfig.LoadWorld == null)
|
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)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(DedicatedConfig.LoadWorld))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var sandboxPath = Path.Combine(DedicatedConfig.LoadWorld, "Sandbox.sbc");
|
var sandboxPath = Path.Combine(DedicatedConfig.LoadWorld, "Sandbox.sbc");
|
||||||
@@ -93,49 +198,66 @@ namespace Torch.Server.Managers
|
|||||||
if (!File.Exists(sandboxPath))
|
if (!File.Exists(sandboxPath))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
try
|
||||||
if (checkpoint == null)
|
|
||||||
{
|
{
|
||||||
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
|
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
|
||||||
return;
|
if (checkpoint == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {Torch.Config.InstancePath})");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mods = new MtObservableList<ModItemInfo>();
|
||||||
|
foreach (var mod in checkpoint.Mods)
|
||||||
|
mods.Add(new ModItemInfo(mod));
|
||||||
|
DedicatedConfig.Mods = mods;
|
||||||
|
|
||||||
|
Log.Debug("Loaded mod list from world");
|
||||||
|
|
||||||
|
if (!modsOnly)
|
||||||
|
DedicatedConfig.SessionSettings = new SessionSettingsViewModel(checkpoint.Settings);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error($"Error loading mod list from world, verify that your mod list is accurate. '{DedicatedConfig.LoadWorld}'.");
|
||||||
|
Log.Error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
foreach (var mod in checkpoint.Mods)
|
|
||||||
sb.AppendLine(mod.PublishedFileId.ToString());
|
|
||||||
|
|
||||||
DedicatedConfig.Mods = sb.ToString();
|
|
||||||
|
|
||||||
Log.Info("Loaded mod list from world");
|
|
||||||
|
|
||||||
if (!modsOnly)
|
|
||||||
DedicatedConfig.SessionSettings = new SessionSettingsViewModel(checkpoint.Settings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,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,295 +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.Warn($"Waiting for process {pid} to exit.");
|
|
||||||
waitProc.WaitForExit();
|
|
||||||
_log.Info("Continuing in 5 seconds.");
|
|
||||||
Thread.Sleep(5000);
|
|
||||||
}
|
|
||||||
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.207.7")]
|
[assembly: AssemblyTitle("Torch Server")]
|
||||||
[assembly: AssemblyFileVersion("1.1.207.7")]
|
[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 {
|
||||||
|
2
Torch.Server/Properties/Settings.Designer.cs
generated
2
Torch.Server/Properties/Settings.Designer.cs
generated
@@ -12,7 +12,7 @@ namespace Torch.Server.Properties {
|
|||||||
|
|
||||||
|
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.0.0")]
|
||||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||||
|
|
||||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user