Compare commits

...

189 Commits

Author SHA1 Message Date
Westin Miller
897f75c069 Merge branch 'new_init' 2017-12-02 12:16:30 -08:00
Westin Miller
8167e04383 Merge branch 'staging' 2017-12-02 12:16:23 -08:00
Westin Miller
72b6d0e7bb Tweaks to versioning scheme
Nuke staging
2017-12-02 12:14:48 -08:00
Westin Miller
039c5d9244 Fields on internal types can be reflected 2017-11-25 23:02:01 -08:00
Westin Miller
7ea982c903 Future methods for loading/unloading sessions.
Fix reflected manager to work with instance methods on internal types.
2017-11-25 17:44:50 -08:00
Westin Miller
f0adeddb66 Split game initialization and starting into a separate thread and file. 2017-11-25 17:25:06 -08:00
Westin Miller
e709b6c321 Tweaked initializer to keep the SpaceEngineersGame instance around. 2017-11-25 15:52:53 -08:00
Westin Miller
1b0dcc9808 Validate auth ticket event exposed to other parts of Torch and plugins 2017-11-24 20:46:41 -08:00
John Gross
fe5dfa0ea7 Another restart fix and invoke tweaks 2017-11-23 10:46:28 -08:00
John Gross
25e6f27854 Fixes for 2017-11-17 major 2017-11-17 12:11:11 -08:00
Westin Miller
c07a01a427 Patcher correctly translates catch clauses 2017-11-11 03:50:25 -08:00
Westin Miller
11bc7cb60c Trace level messages
Collection change events are all deferred, and replaced with reset if it makes sense.
2017-11-09 09:42:48 -08:00
Westin Miller
6e3b7e7a04 Merge branch 'fix-dependent-dll-loading' of https://github.com/susu/Torch into staging 2017-11-06 19:42:31 -08:00
Westin Miller
ff58cf5b19 Merge pull request #161 from TorchAPI/plugin-wpf-controls
Pluggable WPF Controls for the Entity Manager UI
2017-11-06 19:36:24 -08:00
Marton Suranyi
25a708a3d4 PluginManager: load dependent DLLs first
When there are dependencies among a plugin's DLLs,
those dependencies needs a custom resolving.
2017-11-06 20:30:48 +01:00
Marton Suranyi
004dcc19dc PluginManager: make zip plugins working
Let Torch to load the plugin's assembly.
2017-11-06 20:28:15 +01:00
Marton Suranyi
ac95f5f89c PluginLoader: load only files with DLL extension from directories and zips
I had several Xy.dll.xml files there
and it was tried to be loaded as an Assembly.
2017-11-06 20:24:09 +01:00
Westin Miller
0fc9b49fba Didn't work well 2017-11-04 10:19:33 -07:00
Westin Miller
600e73ad43 Post transpilers (happen after prefix and suffix)
More debugging options
More robust inline writing and reading
2017-11-04 10:09:22 -07:00
Westin Miller
0bc0b0dc77 Fix integrity analysis to work with loops 2017-11-04 04:18:47 -07:00
Westin Miller
86f62e1f37 Plugin controls are scroll viewers. 2017-11-03 21:16:49 -07:00
Westin Miller
7850b8368a Fix hard clean
Remove leftover logging
2017-11-03 20:15:11 -07:00
Westin Miller
496bde733f Kill repeated file 2017-11-03 19:30:38 -07:00
Westin Miller
8b98deafca Fixed patch manager to emit try-catch-finally blocks.
Solves issue with PBs not running
2017-11-02 22:49:17 -07:00
Westin Miller
b3ab0cbd74 Fix loading plugins from ZIP files 2017-11-01 19:50:02 -07:00
Westin Miller
462eb77e0d Try to load .exe files when looking for assemblies in test framework 2017-11-01 18:19:22 -07:00
Westin Miller
d5702d3065 Progress report when patching 2017-11-01 17:47:21 -07:00
Westin Miller
c8377b318e EntityControlManager partial rewrite to do lazy updating
Auto commit patches once SE instance has been created
2017-11-01 05:08:17 -07:00
Westin Miller
6814a833be Removed some duplicated code. 2017-11-01 01:03:10 -07:00
Westin Miller
98aae10126 Tools for debugging patch transpilers. 2017-10-31 23:42:25 -07:00
Westin Miller
0558675132 If plugins or Torch call the static ctor of MyEntities too early the game doesn't break.
Freeze isn't fatal when compiled with DEBUG.  (So you can use breakpoints)
2017-10-31 20:55:48 -07:00
Westin Miller
c8f42e8a48 Method to determine if a MtObservableCollection is observed by anyone. 2017-10-31 17:39:45 -07:00
Westin Miller
d30d16b855 Fixed issue with not being on dispatcher thread 2017-10-31 04:43:50 -07:00
Westin Miller
178957642c Revert NLog level back to Info 2017-10-31 04:22:10 -07:00
Westin Miller
cd77fe74d5 Pluggable system for providing WPF controls inside the Entity Manager 2017-10-31 04:18:27 -07:00
Westin Miller
90c91c3ebc Initial version of profiler
Moved reflected manager into separate files
Extracted MtObservableEvent
Added a patch to Keencode that lets us call the static cctor of MyEntities in the wrong spot
2017-10-30 13:48:04 -07:00
Westin Miller
11dbf83faf NLog works again 2017-10-30 13:43:35 -07:00
Westin Miller
b7fa57c9b7 Non-indexed remove causes a full reset (@Xero's issue)
Removed ignore last session option.  Implicitly true now.
Asteroid generation options (fixes #151)
2017-10-29 09:53:20 -07:00
Westin Miller
7a63527d8f Archive before test 2017-10-28 00:06:00 -07:00
Westin Miller
4b33bedccd Disable NLog based KeenLog.
NLog is being derpy
2017-10-27 23:28:15 -07:00
Westin Miller
473637ceaf Fixed issue where constructor overloads killed me 2017-10-27 20:10:19 -07:00
Westin Miller
6f5142393b MT observable collections
(UI sync fix)
2017-10-27 17:52:27 -07:00
Westin Miller
fdc20d4e9d Keen's logs now go to Logs/Keen-*
Various formatting options
2017-10-27 14:13:56 -07:00
Westin Miller
4d0dcede41 Log unhandled exceptions very verbosely 2017-10-26 12:41:03 -07:00
Westin Miller
4ed262a330 Report loader exceptions 2017-10-26 12:16:28 -07:00
Westin Miller
48f0f81f12 Hard clean v2 2017-10-26 10:54:19 -07:00
Westin Miller
7b9f2d680a Hard clean 2017-10-26 10:51:56 -07:00
Westin Miller
c69537b173 Warning guard around some unsafe stuff 2017-10-26 10:11:51 -07:00
Westin Miller
794a4a23d3 VRage.Steam snuck in 2017-10-26 09:55:49 -07:00
Westin Miller
998ff6a13a Keen removed infinario 2017-10-26 09:17:36 -07:00
Westin Miller
4ff4a60106 Merge branch 'event-mgr' into staging 2017-10-22 03:31:46 -07:00
Westin Miller
eaaca5b003 Register aux assemblies when they aren't loaded from a zip 2017-10-21 04:21:04 -07:00
Westin Miller
388b4731c7 Merge pull request #137 from TorchAPI/observable-collections
Refactor observable collections and add read-only proxies for them
2017-10-12 02:23:48 -07:00
Westin Miller
f285d67c87 Fixed synchronization in assembly resolver
Close #145
2017-10-10 03:58:21 -07:00
Westin Miller
bc1a612a20 Add server state to ITorchServer
Fixes #101
2017-10-10 03:54:23 -07:00
Westin Miller
b67879577d Remove example shim pending writing actual shims 2017-10-10 03:49:48 -07:00
Westin Miller
2b5b9d44e6 Fix load order issues with reflected manager and patches 2017-10-10 03:08:41 -07:00
Westin Miller
3e48638d8c Merge branch 'app_domains' into event-mgr 2017-10-10 03:01:18 -07:00
Westin Miller
3307d2d23d Patch manager supports communicating method cancel state
Game state patch split out into a patch shim
Special NS for events
Core and auxillary assembly concepts
Reflected manager stops working for mods/scripts
2017-10-10 02:47:45 -07:00
Westin Miller
62d73cbf96 Moved event stuff to a non-manager NS.
Core assembly concept: Assemblies that will never need to get unloaded (Torch.API, Torch, Torch.Server)
Event shims and patch shims on the core assemblies.
2017-10-09 20:52:22 -07:00
Westin Miller
2004f71290 Patch manager state is static, registration methods are non-static.
Patch manager tracks patch context ownership by assembly internally
2017-10-09 01:06:01 -07:00
Westin Miller
013dc43c2f Tagging interface for event handlers
Support events in the TorchAPI
Static registry, non-static registration methods.
Support unregister by assembly.
2017-10-09 00:42:21 -07:00
Westin Miller
716e6cbc04 Event dispatch and registration service 2017-10-08 05:32:16 -07:00
Westin Miller
9e81b6316f Better debugging for freezing 2017-10-05 12:56:16 -07:00
Westin Miller
d709bf68dd New system for resolving binaries 2017-10-04 21:40:42 -07:00
Westin Miller
c14b8ed23a Fixes #138 2017-10-04 18:57:47 -07:00
John Gross
7ba6fb5a2e Add observable read only list implementation 2017-10-03 11:44:35 -07:00
John Gross
4f1a03811a Refactor property changed implementations 2017-10-02 23:44:06 -07:00
John Gross
0946d5a138 Update README.md 2017-09-28 13:57:19 -07:00
Westin Miller
6f650c8bbd More MSIL utilities 2017-09-23 01:29:06 -07:00
John Gross
ad1502e998 Small tweaks 2017-09-22 20:08:47 -07:00
Westin Miller
bb42dd026c Some more misc Msil stuff 2017-09-22 14:45:07 -07:00
Westin Miller
95b6c9dfe5 Fix so plugins properly register commands. 2017-09-22 14:30:21 -07:00
Westin Miller
9b1754a431 Merge pull request #113 from TorchAPI/session-mgr-cmp
Managers for Clients
2017-09-22 00:40:14 -07:00
Westin Miller
0574d59e12 Merge branch 'staging' into session-mgr-cmp 2017-09-22 00:22:45 -07:00
Westin Miller
4f7c35dfcf Use the correct binaries 2017-09-21 23:41:29 -07:00
Westin Miller
967384ccfe Clean before build 2017-09-21 23:07:37 -07:00
John Gross
b906a32e23 Merge pull request #121 from TorchAPI/plugins
Plugin loader improvements
2017-09-21 22:58:02 -07:00
John Gross
9b9a4c5ee1 Merge branch 'staging' into plugins 2017-09-21 22:56:32 -07:00
Westin Miller
13f3e7ee11 Jenkins only releases from master, and builds in Release only on master.
Packages don't contain PDB files on master builds either.
2017-09-21 22:54:48 -07:00
John Gross
b7f2a62b3c Expose read-only collections in PluginManager instead of full collections 2017-09-21 22:30:48 -07:00
John Gross
1f4197ce67 Merge branch 'staging' into plugins 2017-09-21 20:26:32 -07:00
John Gross
f377d044d6 Observable type improvements 2017-09-21 20:15:18 -07:00
Westin Miller
205dd1a201 Merge pull request #120 from blaho/session-mgr-cmp
Exposed information about banned players
2017-09-20 08:24:28 -07:00
Tomas Blaho
9c505c4f5d banned player list made readonly, lobby fakes support 2017-09-20 17:16:15 +02:00
Tomas Blaho
eb7f7f4244 MultiplayerManagerLobby doesn't implement IMultiplayerManagerServer 2017-09-20 17:05:23 +02:00
Tomas Blaho
f1fc49d276 Merge branch 'staging' into session-mgr-cmp
# Conflicts:
#	Torch.API/Managers/IMultiplayerManager.cs
#	Torch/Managers/MultiplayerManager.cs
2017-09-20 16:13:38 +02:00
Tomas Blaho
d8e2072493 documentation added 2017-09-20 13:03:06 +02:00
Tomas Blaho
96f813a17b IMultiplayerManager: added list of banned steam ID's 2017-09-20 12:34:09 +02:00
John Gross
a97542e649 Add "Open Folder" functionality to plugins tab, crash-safe plugin updating 2017-09-14 21:09:20 -07:00
Westin Miller
5eceb21ec7 Comments 2017-09-12 01:44:13 -07:00
Westin Miller
a61b646295 ReflectedMethodInfo allows non-public type names.
MsilInstructionExtensions to make life easier
2017-09-11 22:32:37 -07:00
Westin Miller
373c476d2d Better guessing on the generic operand type 2017-09-11 20:26:33 -07:00
Westin Miller
b1145c8926 Utility method to invert common load and store instructions 2017-09-11 19:50:07 -07:00
Westin Miller
0810e76474 Once the game is created we can patch it with impunity. 2017-09-11 18:55:09 -07:00
Westin Miller
57acb274c6 Don't crash when modifying constructor
Tweak log level of assembly resolver.
Events relating to game initialization and shutdown.
Plugin manager loads plugins right before the dependency manager is attached.
2017-09-11 18:28:53 -07:00
Westin Miller
e57f885d3b Log errors in ReflectedManager 2017-09-11 05:19:02 -07:00
Westin Miller
0073e101dd Fixed issues with operand replacing, and branch instructions. 2017-09-11 05:18:07 -07:00
Westin Miller
b42d43c0e1 Redirect Keen console to Info, all else to trace. 2017-09-11 05:17:21 -07:00
Westin Miller
9d8988a2ec MyLog logs to the Torch logging system
Fixed issue with labels not being transferred correctly when transpiling.
2017-09-10 17:40:43 -07:00
Westin Miller
aa784c121b Null protection in multiplayer manager detach 2017-09-10 15:17:50 -07:00
Westin Miller
a36e8a4065 Merge branch 'staging' into session-mgr-cmp 2017-09-10 15:11:08 -07:00
John Gross
3fd7b66905 Rewrite plugin loader/updater to support loading from .zip and more complex versions. 2017-09-10 01:44:15 -07:00
John Gross
44ebcc5d25 Merge pull request #110 from TorchAPI/patching
Patch Manager
2017-09-10 04:05:50 -04:00
Westin Miller
9a68ed6bd0 Added patching tests
Fixed error when reverting patches
Made the LoggingILGenerator not break without a logger
2017-09-09 23:30:49 -07:00
Westin Miller
4f84cd8963 Normal logging for patch manager
Fix: RE treating constructors as normal methods
2017-09-09 22:15:42 -07:00
Westin Miller
837b56462f Use reflected manager for patch internals 2017-09-09 00:50:45 -07:00
Westin Miller
9c3a22c556 Method patching framework 2017-09-09 00:37:34 -07:00
Westin Miller
9471d83a45 Merge pull request #108 from TorchAPI/staging
Prepare Release v1.2.0
2017-09-07 19:56:23 -07:00
Westin Miller
52b225d944 Merge branch 'master' into staging 2017-09-07 15:39:04 -07:00
Westin Miller
c8f0a61209 Provide DS config path to Save
This keeps MyFileSystem from getting initialized too early.
2017-09-07 14:54:57 -07:00
John Gross
59c3e9eb54 Merge pull request #107 from TorchAPI/patch-96
Proper scrolling and formatting for the ChatPanel.
2017-09-07 13:39:15 -04:00
Westin Miller
d20d68b831 Proper scrolling and formatting for the ChatPanel.
- Users are blue, server is dark blue.
- Autoscroll when it's scrolled to the bottom, else leave it alone.
2017-09-07 02:54:10 -07:00
Westin Miller
cfda1f8eef Fixes issues with the assembly resolver getting disposed when launched in nogui mode.
This should solve #102, #104.
2017-09-07 02:03:48 -07:00
Westin Miller
2fb222125a Merge pull request #106 from TorchAPI/reflected-internals
Specify internal types by Assembly Qualified Name for reflection
2017-09-06 20:58:29 -07:00
John Gross
c7c3c00783 Merge branch 'staging' into reflected-internals 2017-09-06 23:57:34 -04:00
Westin Miller
dcc130e2cf Fixed bad exit code.
Fixed creds GUID
2017-09-06 20:33:37 -07:00
Westin Miller
491f3d3af4 Auto-release 2017-08-31 19:36:01 -07:00
Westin Miller
140000df55 Test-time reflected event checker
Server UI components work with new system
Events for loading and unloading TorchSessions
Commands report information again.
Catch, log, and rethrow errors that occur in Torch-handled SE events
2017-08-31 16:27:55 -07:00
Westin Miller
4b2fee7614 Offline mode fallbacks for the chat manager. 2017-08-31 16:27:54 -07:00
Westin Miller
2c7b522378 Proper delegate naming 2017-08-31 16:27:54 -07:00
Westin Miller
d9ef60d4e8 Client manager components
- ReflectedManager now supports MemberInfo and event replacement
- New chat manager interfaces for both client and server
- New multiplayer manager interfaces for both client and server
2017-08-31 16:27:54 -07:00
Westin Miller
178fcb8164 Specify internal types by Assembly Qualified Name for reflection 2017-08-31 16:22:38 -07:00
John Gross
3d6806b63a Merge pull request #88 from szczepix/patch-1
When path contains spaces
2017-08-31 18:01:00 -04:00
Szczepan Zaskalski
22e3019610 Fixed batch mklink command argument 2017-08-31 22:25:10 +02:00
Szczepan Zaskalski
7937cbd122 When path contains spaces 2017-08-31 22:18:46 +02:00
John Gross
2bef34ee5b Merge pull request #94 from TorchAPI/jenkins
Unified versions.
2017-08-31 01:37:24 -04:00
Westin Miller
efe7236d31 TorchVersion is read-only 2017-08-30 22:35:50 -07:00
Westin Miller
67dba9c820 PluginAttribute now lets you get the version from the loaded assembly
Documentation in PluginAttribute
2017-08-30 14:13:49 -07:00
Westin Miller
52c509aba0 Salt after revision. 2017-08-30 13:38:43 -07:00
Westin Miller
437c7d293e Make local development versions clearly local dev 2017-08-30 11:19:13 -07:00
Westin Miller
2b6ce4f25b Unified versions. 2017-08-29 19:54:06 -07:00
John Gross
64f123abe9 Specify PR base branch 2017-08-27 09:08:50 -07:00
John Gross
599a98bceb Merge pull request #87 from TorchAPI/server-refactor
Server initialization refactor
2017-08-24 23:12:02 -04:00
John Gross
2cd1b8bd4e Merge branch 'staging' into server-refactor
# Conflicts:
#	Torch.Server/Torch.Server.csproj
#	Torch/TorchBase.cs
2017-08-24 17:44:43 -07:00
John Gross
c0be9c25da Refactor server initialization for service support 2017-08-24 17:30:38 -07:00
John Gross
5cea66374f Merge pull request #84 from TorchAPI/jenkins
Generate Assembly Versions on Build
2017-08-23 16:44:29 -04:00
Westin Miller
ee1c270c68 Merge branch 'staging' into jenkins 2017-08-23 03:02:26 -07:00
Westin Miller
4ab08e2faf Automatically create zip file 2017-08-23 01:17:19 -07:00
Westin Miller
dd094edb88 Using UTC time 2017-08-22 23:25:18 -07:00
Westin Miller
56e45236d8 Create empty generated files to MSBuild doesn't freak. 2017-08-22 23:03:44 -07:00
Westin Miller
be9a8c5839 Transform Templates on Build
Templates for all assemblies
2017-08-22 22:31:24 -07:00
John Gross
8c11baf3b9 Merge pull request #81 from TorchAPI/sessions-manager-71
Session Management System
2017-08-22 11:09:04 -04:00
Westin Miller
6ce679bd83 Proper delegate naming 2017-08-22 08:06:30 -07:00
Westin Miller
a4b1b9bb96 CurrentSession available directly from TorchBase
SteamService is now properly injected for clients, and reports errors
Obsolete tags on the manager properties in TorchBase
2017-08-22 08:06:30 -07:00
Westin Miller
91ad78e6a2 Private session field of proper type
Project Setup
2017-08-22 08:06:30 -07:00
Westin Miller
4a68d66ab0 Session management system
- Managers bound to the session instead of to Torch
- TorchSession automatic creation and destruction
- Automatic manager creation for sessions
2017-08-22 08:06:30 -07:00
John Gross
4cb50b556f Merge pull request #80 from TorchAPI/shutdown-crash-fix
Check if thread is running before suspending
2017-08-22 01:31:02 -04:00
John Gross
b5f73a99cc Use Thread.IsAlive property 2017-08-21 20:39:54 -07:00
John Gross
e9476a59e8 Fix ambiguous reference resolution 2017-08-21 20:15:16 -07:00
John Gross
f48f23c2eb Check if thread is running before suspending 2017-08-21 18:57:35 -07:00
John Michael Gross
ddf465d8c9 Merge pull request #79 from TorchAPI/reflection-manager
Testing Framework
2017-08-21 19:38:29 -04:00
Westin Miller
7149287b8e Let's pretend that typo didn't happen. 2017-08-21 15:44:15 -07:00
Westin Miller
cc709c6bb3 Torch.Client.Tests
Fixed jenkinsfile to create reports directory properly
Removed Any CPU configurations from Torch.sln
2017-08-21 15:38:17 -07:00
Westin Miller
8d101c4c11 Many more things use the new reflection system
Project for Torch.Server tests.
Refactoring some of the torch utility classes into a different namespace.
2017-08-20 23:46:19 -07:00
Westin Miller
64eef6cd8e Typo Number 3 2017-08-20 21:56:41 -07:00
Westin Miller
f8ae3c0dd1 Typo Number 2 2017-08-20 21:55:18 -07:00
Westin Miller
589205edc3 :( 2017-08-20 21:50:07 -07:00
Westin Miller
48b212faaf Reflection unit testing
Jenkins integration (we can only hope)
2017-08-20 21:48:42 -07:00
Westin Miller
0554dbc608 Complete documentation for the reflection manager. 2017-08-20 18:30:52 -07:00
Westin Miller
3564eb805c Reflection manager capable of handling field, property, and method resolution. 2017-08-20 17:56:42 -07:00
John Michael Gross
62a8064edd Update README.md 2017-08-20 17:11:49 -07:00
John Michael Gross
55ed45190b Update README.md 2017-08-20 17:09:58 -07:00
Westin Miller
a6ae96093f CI Archiving 2017-08-19 21:49:57 -07:00
Westin Miller
5b1afe6d50 Patch for uninitialized CI directories. 2017-08-19 21:46:29 -07:00
Westin Miller
60df71a74c Merge pull request #77 from TorchAPI/jenkins
Continuous Integration Support
2017-08-19 21:43:18 -07:00
Westin Miller
a6d5da861f Pipeline DSL 2017-08-19 21:36:42 -07:00
Westin Miller
afc10911f7 More descriptive stages 2017-08-19 21:17:58 -07:00
Westin Miller
0686e95c72 Delete existing link 2017-08-19 20:52:03 -07:00
Westin Miller
234754fd49 Initial continuous integration support 2017-08-19 20:31:08 -07:00
John Michael Gross
efb8d0f226 Merge pull request #76 from TorchAPI/dedi-issue-69
Stronger runtime checks in MultiplayerManager and Reflection
2017-08-19 20:13:27 -07:00
Westin Miller
3f881f7d67 Fixed tag parsing in UpdateManager 2017-08-19 18:22:26 -07:00
Westin Miller
64d38abc99 Typo 2017-08-19 18:20:50 -07:00
Westin Miller
4a39362702 Stronger runtime checks in MultiplayerManager and Reflection
MultiplayerManager now uses Keen's wrapper wrapper (wrapper?) of SteamSDK
Removed rogue file in the csproj
2017-08-19 04:56:41 -07:00
John Michael Gross
db2d3794ae Merge pull request #73 from TorchAPI/dedi-issue-69
Multiplayer Authentication Hook Removal
2017-08-19 01:28:38 -07:00
Westin Miller
526ff6fff0 Provide some semblance of compile time checking. 2017-08-18 22:31:03 -07:00
Westin Miller
eebc0e428e Fix that actually fixes the underlying problem with #69 2017-08-18 21:27:09 -07:00
John Michael Gross
80aca514b2 Merge pull request #72 from TorchAPI/dedi-issue-69
Remove player if already registered
2017-08-18 19:52:28 -07:00
Westin Miller
3e8068e82d Fixes a stupid problem with the DS multiplayer not checking if a
player is already registered.  (#69)
2017-08-18 19:34:41 -07:00
John Michael Gross
601fbcd176 Merge pull request #70 from Equinox-/master
Dependency Resolution and Sorting
2017-08-18 16:24:33 -07:00
Westin Miller
40eab15d69 More explanatory naming for Manager Init (Attach) and Dispose (Detach).
Torch.Client uses the registry to discover Steam's installation directory.
2017-08-18 16:19:59 -07:00
Westin Miller
ceb272c0b4 Moved extension classes to seperate files as Jimmacle requires. 2017-08-18 15:30:36 -07:00
Westin Miller
80d4f62694 Added load order restrictions for DependencyAttribute
Abstracted dependency manager away as an interface
2017-08-18 02:03:53 -07:00
Westin Miller
42f58a8649 Recursive dependency trees
- Now TorchBase and TorchSession can each have seperate managers
- Managers now have a Dispose function that's run on shutdown
2017-08-17 23:52:39 -07:00
Westin Miller
6b9af71967 Automatic dependency resolution
- All managers now use automatic dependency resolution
2017-08-17 18:14:23 -07:00
Westin Miller
dbd98a09c5 Early initialization for Torch Client.
- Assembly resolution
- SE installation directory locating
Dependency manager with automatic sorting and resolution
- Drop in replacement for the system currently in TorchBase
Shared binary directory for all Torch projects
2017-08-17 17:32:08 -07:00
John Michael Gross
c6a6363163 Merge pull request #67 from Mavy87/master
Fix to allow Torch to run as a service.
2017-08-17 09:29:04 -07:00
John Gross
82815f66e5 # 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
    - Fixed crash on servers using the Windows Classic theme
2017-08-17 09:09:51 -07:00
Rene K
0a38eb770d Fix to allow Torch to run as a service. 2017-08-05 13:06:15 +02:00
199 changed files with 13656 additions and 2420 deletions

3
.gitignore vendored
View File

@@ -252,3 +252,6 @@ ModelManifest.xml
# FAKE - F# Make # FAKE - F# Make
.fake/ .fake/
GameBinaries GameBinaries
# Generated Files
**Gen.cs

View File

@@ -1,3 +1,12 @@
# Torch 1.1.229.265
* Features
- Added more lenient version parsing for plugins (v#.# should work)
- Added countdown option to restart command (!restart [seconds])
* Fixes
- General fixes to work with the latest SE version
- Fixed config changes not saving
- (hopefully) Fixed issue causing crashes on servers using the Windows Classic theme
# Torch 1.1.207.7 # Torch 1.1.207.7
* Notes * Notes
- This release makes significant changes to TorchConfig.xml. It has been renamed to Torch.cfg and has different options. - This release makes significant changes to TorchConfig.xml. It has been renamed to Torch.cfg and has different options.

View File

@@ -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**

View File

@@ -0,0 +1,22 @@
pushd
$steamData = "C:/Steam/Data/"
$steamCMDPath = "C:/Steam/steamcmd/"
$steamCMDZip = "C:/Steam/steamcmd.zip"
Add-Type -AssemblyName System.IO.Compression.FileSystem
if (!(Test-Path $steamData)) {
mkdir "$steamData"
}
if (!(Test-Path $steamCMDPath)) {
if (!(Test-Path $steamCMDZip)) {
(New-Object System.Net.WebClient).DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", "$steamCMDZip");
}
[System.IO.Compression.ZipFile]::ExtractToDirectory($steamCMDZip, $steamCMDPath)
}
cd "$steamData"
& "$steamCMDPath/steamcmd.exe" "+login anonymous" "+force_install_dir $steamData" "+app_update 298740" "+quit"
popd

52
Jenkins/release.ps1 Normal file
View File

@@ -0,0 +1,52 @@
param([string] $ApiBase, [string]$tagName, [string]$authinfo, [string[]] $assetPaths)
Add-Type -AssemblyName "System.Web"
$headers = @{
Authorization = "Basic " + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($authinfo))
Accept = "application/vnd.github.v3+json"
}
try
{
Write-Output("Checking if release with tag " + $tagName + " already exists...")
$release = Invoke-RestMethod -Uri ($ApiBase+"releases/tags/$tagName") -Method "GET" -Headers $headers
Write-Output(" Using existing release " + $release.id + " at " + $release.html_url)
} catch {
Write-Output(" Doesn't exist")
$rel_arg = @{
tag_name=$tagName
name="Generated $tagName"
body=""
draft=$TRUE
prerelease=$tagName.Contains("alpha") -or $tagName.Contains("beta")
}
Write-Output("Creating new release " + $tagName + "...")
$release = Invoke-RestMethod -Uri ($ApiBase+"releases") -Method "POST" -Headers $headers -Body (ConvertTo-Json($rel_arg))
Write-Output(" Created new release " + $tagName + " at " + $release.html_url)
}
$assetsApiBase = $release.assets_url
Write-Output("Checking for existing assets...")
$existingAssets = Invoke-RestMethod -Uri ($assetsApiBase) -Method "GET" -Headers $headers
$assetLabels = ($assetPaths | ForEach-Object {[System.IO.Path]::GetFileName($_)})
foreach ($asset in $existingAssets) {
if ($assetLabels -contains $asset.name) {
$uri = $asset.url
Write-Output(" Deleting old asset " + $asset.name + " (id " + $asset.id + "); URI=" + $uri)
$result = Invoke-RestMethod -Uri $uri -Method "DELETE" -Headers $headers
}
}
Write-Output("Uploading assets...")
$uploadUrl = $release.upload_url.Substring(0, $release.upload_url.LastIndexOf('{'))
foreach ($asset in $assetPaths) {
$assetName = [System.IO.Path]::GetFileName($asset)
$assetType = [System.Web.MimeMapping]::GetMimeMapping($asset)
$assetData = [System.IO.File]::ReadAllBytes($asset)
$headerExtra = $headers + @{
"Content-Type" = $assetType
Name = $assetName
}
$uri = $uploadUrl + "?name=" + $assetName
Write-Output(" Uploading " + $asset + " as " + $assetType + "; URI=" + $uri)
$result = Invoke-RestMethod -Uri $uri -Method "POST" -Headers $headerExtra -Body $assetData
Write-Output(" ID=" + $result.id + ", found at=" + $result.browser_download_url)
}

74
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,74 @@
def packageAndArchive(buildMode, packageName, exclude) {
zipFile = "bin\\${packageName}.zip"
packageDir = "bin\\${packageName}\\"
bat "IF EXIST ${zipFile} DEL ${zipFile}"
bat "IF EXIST ${packageDir} RMDIR /S /Q ${packageDir}"
bat "xcopy bin\\x64\\${buildMode} ${packageDir}"
if (exclude.length() > 0) {
bat "del ${packageDir}${exclude}"
}
if (buildMode == "Release") {
bat "del ${packageDir}*.pdb"
}
powershell "Add-Type -Assembly System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::CreateFromDirectory(\"\$PWD\\${packageDir}\", \"\$PWD\\${zipFile}\")"
archiveArtifacts artifacts: zipFile, caseSensitive: false, onlyIfSuccessful: true
}
node {
stage('Checkout') {
checkout scm
bat 'git pull --tags'
}
stage('Acquire SE') {
bat 'powershell -File Jenkins/jenkins-grab-se.ps1'
bat 'IF EXIST GameBinaries RMDIR GameBinaries'
bat 'mklink /J GameBinaries "C:/Steam/Data/DedicatedServer64/"'
}
stage('Acquire NuGet Packages') {
bat 'nuget restore Torch.sln'
}
stage('Build') {
currentBuild.description = bat(returnStdout: true, script: '@powershell -File Versioning/version.ps1').trim()
if (env.BRANCH_NAME == "master") {
buildMode = "Release"
} else {
buildMode = "Debug"
}
bat "IF EXIST \"bin\" rmdir /Q /S \"bin\""
bat "IF EXIST \"bin-test\" rmdir /Q /S \"bin-test\""
bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64 /t:Clean"
bat "\"${tool 'MSBuild'}msbuild\" Torch.sln /p:Configuration=${buildMode} /p:Platform=x64"
}
stage('Archive') {
archiveArtifacts artifacts: "bin/x64/${buildMode}/Torch*", caseSensitive: false, fingerprint: true, onlyIfSuccessful: true
packageAndArchive(buildMode, "torch-server", "Torch.Client*")
packageAndArchive(buildMode, "torch-client", "Torch.Server*")
}
stage('Test') {
bat 'IF NOT EXIST reports MKDIR reports'
bat "\"packages/xunit.runner.console.2.2.0/tools/xunit.console.exe\" \"bin-test/x64/${buildMode}/Torch.Tests.dll\" \"bin-test/x64/${buildMode}/Torch.Server.Tests.dll\" \"bin-test/x64/${buildMode}/Torch.Client.Tests.dll\" -parallel none -xml \"reports/Torch.Tests.xml\""
step([
$class: 'XUnitBuilder',
thresholdMode: 1,
thresholds: [[$class: 'FailedThreshold', failureThreshold: '1']],
tools: [[
$class: 'XUnitDotNetTestType',
deleteOutputFiles: true,
failIfNotNew: true,
pattern: 'reports/*.xml',
skipNoTestFiles: false,
stopProcessingIfError: true
]]
])
}
}

View File

@@ -1,15 +1,25 @@
<?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" /> <target xsi:type="Null" name="null" formatMessage="false" />
<target xsi:type="File" name="keen" layout="${var:logStamp} ${logger}: ${var:logContent}" fileName="Logs\Keen-${shortdate}.log" />
<target xsi:type="File" 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}: ${var:logContent}" />
<target xsi:type="File" name="patch" layout="${var:logContent}" fileName="Logs\patch.log"/>
</targets> </targets>
<rules> <rules>
<logger name="Keen" minlevel="Info" writeTo="console"/>
<logger name="Keen" minlevel="Debug" writeTo="keen" final="true" />
<logger name="Keen" writeTo="null" final="true" />
<logger name="*" minlevel="Info" writeTo="main, console" /> <logger name="*" minlevel="Info" writeTo="main, console" />
<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>

View File

@@ -1,21 +1,26 @@
Discord: [![Discord](https://discordapp.com/api/guilds/230191591640268800/widget.png)](https://discord.gg/8uHZykr) [![Discord](https://discordapp.com/api/guilds/230191591640268800/widget.png)](https://discord.gg/8uHZykr) [![Build Status](http://server.torchapi.net:8080/job/Torch/job/Torch/job/master/badge/icon)](http://server.torchapi.net:8080/job/Torch/job/Torch/job/master/)
# What is Torch? # What is Torch?
Torch is the successor to SE Server Extender and gives server admins the tools they need to keep their Space Engineers servers running smoothly. It features a user interface with live management tools and a plugin system so you can run your server exactly how you'd like. Torch is still in early development so there may be bugs and incomplete features. Torch is the successor to SE Server Extender and gives server admins the tools they need to keep their Space Engineers servers running smoothly. It features a user interface with live management tools and a plugin system so you can run your server exactly how you'd like. Torch is still in early development so there may be bugs and incomplete features.
# Features ## Torch.Server
### Features
* WPF-based user interface * WPF-based user interface
* Chat: interact with the game chat and run commands without having to join the game. * Chat: interact with the game chat and run commands without having to join the game.
* Entity manager: realtime modification of ingame entities such as stopping grids and changing block settings without having to join the game * Entity manager: realtime modification of ingame entities such as stopping grids and changing block settings without having to join the game
* Organized, easy to use configuration editor * Organized, easy to use configuration editor
* Extensible using the Torch plugin system * Extensible using the Torch plugin system
# Installation ### Installation
* Get the latest Torch release here: https://github.com/TorchAPI/Torch/releases * Get the latest Torch release here: https://github.com/TorchAPI/Torch/releases
* Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files. * Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files.
- If you already have a DS installed you can unzip the Torch files into the folder that contains the DedicatedServer64 folder. - If you already have a DS installed you can unzip the Torch files into the folder that contains the DedicatedServer64 folder.
## Torch.Client
* An optional client-side version of Torch. More documentation to come.
# 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.

View File

@@ -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

View 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
View 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; }
}
}

View 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
{
}
}

View 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);
}
}

View File

@@ -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; }
}
}

View File

@@ -1,9 +1,11 @@
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;
namespace Torch.API namespace Torch.API
@@ -33,17 +35,29 @@ namespace Torch.API
/// </summary> /// </summary>
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,26 +66,26 @@ 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. /// If this is called on the game thread the action will execute immediately.
/// </summary> /// </summary>
void InvokeBlocking(Action action); void InvokeBlocking(Action action, [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. /// 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();
@@ -87,15 +101,24 @@ namespace Torch.API
Task Save(long callerId); Task Save(long callerId);
/// <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 Dispose();
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,6 +126,11 @@ 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>
@@ -114,6 +142,6 @@ namespace Torch.API
/// </summary> /// </summary>
public interface ITorchClient : ITorchBase public interface ITorchClient : ITorchBase
{ {
} }
} }

View 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));
}
}
}

View 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));
}
}
}

View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Multiplayer;
using VRage.Game;
using VRage.Network;
using VRage.Replication;
namespace Torch.API.Managers
{
/// <summary>
/// Represents a scripted or user chat message.
/// </summary>
public struct TorchChatMessage
{
/// <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, string font = MyFontEnum.Blue)
{
Timestamp = DateTime.Now;
AuthorSteamId = null;
Author = author;
Message = message;
Font = font;
}
/// <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, string font = MyFontEnum.Blue)
{
Timestamp = DateTime.Now;
AuthorSteamId = authorSteamId;
Author = author;
Message = message;
Font = font;
}
/// <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, string font = MyFontEnum.Blue)
{
Timestamp = DateTime.Now;
AuthorSteamId = authorSteamId;
Author = MyMultiplayer.Static?.GetMemberName(authorSteamId) ?? "Player";
Message = message;
Font = font;
}
/// <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 font, or null if default.
/// </summary>
public readonly string Font;
}
/// <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" );
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.Network;
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);
/// <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="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, string font, ulong targetSteamId = 0);
}
}

View 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; }
}
}

View 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);
}
}

View File

@@ -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();
} }
} }

View File

@@ -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);
}
}

View 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);
}
}

View 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
{
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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>
/// 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);
}
}

View File

@@ -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);
} }
} }

View File

@@ -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>

View File

@@ -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.

View File

@@ -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);
}
} }
} }

View File

@@ -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")]

View File

@@ -0,0 +1,39 @@
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; }
/// <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;
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Torch.API.Managers;
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);
}
}

View 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);
}

View File

@@ -2,8 +2,6 @@
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <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')" /> <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>{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}</ProjectGuid> <ProjectGuid>{FBA5D932-6254-4A1E-BAF4-E229FA94E3C2}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
@@ -12,10 +10,12 @@
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath> <OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
@@ -23,14 +23,14 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath> <OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>bin\x64\Release\Torch.API.xml</DocumentationFile> <DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.API.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<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">
@@ -38,8 +38,8 @@
<HintPath>..\GameBinaries\HavokWrapper.dll</HintPath> <HintPath>..\GameBinaries\HavokWrapper.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="NLog"> <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> <HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="PresentationCore" /> <Reference Include="PresentationCore" />
@@ -156,12 +156,26 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs">
<Link>Properties\AssemblyVersion.cs</Link>
</Compile>
<Compile Include="ConnectionState.cs" /> <Compile Include="ConnectionState.cs" />
<Compile Include="IChatMessage.cs" />
<Compile Include="ITorchConfig.cs" /> <Compile Include="ITorchConfig.cs" />
<Compile Include="Managers\DependencyManagerExtensions.cs" />
<Compile Include="Managers\DependencyProviderExtensions.cs" />
<Compile Include="Event\EventHandlerAttribute.cs" />
<Compile Include="Event\IEvent.cs" />
<Compile Include="Event\IEventHandler.cs" />
<Compile Include="Managers\IChatManagerClient.cs" />
<Compile Include="Managers\IChatManagerServer.cs" />
<Compile Include="Managers\IDependencyManager.cs" />
<Compile Include="Managers\IDependencyProvider.cs" />
<Compile Include="Event\IEventManager.cs" />
<Compile Include="Managers\IManager.cs" /> <Compile Include="Managers\IManager.cs" />
<Compile Include="Managers\IMultiplayerManager.cs" /> <Compile Include="Managers\IMultiplayerManagerClient.cs" />
<Compile Include="Managers\IMultiplayerManagerBase.cs" />
<Compile Include="IPlayer.cs" /> <Compile Include="IPlayer.cs" />
<Compile Include="Managers\IMultiplayerManagerServer.cs" />
<Compile Include="Managers\INetworkManager.cs" /> <Compile Include="Managers\INetworkManager.cs" />
<Compile Include="Managers\IPluginManager.cs" /> <Compile Include="Managers\IPluginManager.cs" />
<Compile Include="Plugins\ITorchPlugin.cs" /> <Compile Include="Plugins\ITorchPlugin.cs" />
@@ -173,16 +187,17 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServerState.cs" /> <Compile Include="ServerState.cs" />
<Compile Include="ModAPI\TorchAPI.cs" /> <Compile Include="ModAPI\TorchAPI.cs" />
<Compile Include="Session\ITorchSession.cs" />
<Compile Include="Session\ITorchSessionManager.cs" />
<Compile Include="Session\TorchSessionState.cs" />
<Compile Include="TorchGameState.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <Import Project="$(SolutionDir)\TransformOnBuild.targets" />
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project> </Project>

View 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);
}

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="NLog" version="4.4.1" targetFramework="net461" /> <package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
<package id="NLog" version="4.4.12" targetFramework="net461" />
</packages> </packages>

View File

@@ -1,12 +1,17 @@
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Torch Client")] [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

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<ProjectGuid>{632E78C0-0DAC-4B71-B411-2F1B333CC310}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Torch.Client.Tests</RootNamespace>
<AssemblyName>Torch.Client.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<NoWarn>1591,0649</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>$(SolutionDir)\bin-test\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>$(SolutionDir)\bin-test\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>$(SolutionDir)\bin-test\x64\Release\Torch.Client.Tests.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<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="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
<Reference Include="xunit.assert, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll</HintPath>
</Reference>
<Reference Include="xunit.core, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll</HintPath>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs">
<Link>Properties\AssemblyVersion.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TorchClientReflectionTest.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
<Name>Torch.API</Name>
</ProjectReference>
<ProjectReference Include="..\Torch.Client\Torch.Client.csproj">
<Project>{e36df745-260b-4956-a2e8-09f08b2e7161}</Project>
<Name>Torch.Client</Name>
</ProjectReference>
<ProjectReference Include="..\Torch.Tests\Torch.Tests.csproj">
<Project>{c3c8b671-6ad1-44aa-a8da-e0c0dc0fedf5}</Project>
<Name>Torch.Tests</Name>
</ProjectReference>
<ProjectReference Include="..\Torch\Torch.csproj">
<Project>{7e01635c-3b67-472e-bcd6-c5539564f214}</Project>
<Name>Torch</Name>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
</Project>

View 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
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
<package id="NLog" version="4.4.12" targetFramework="net461" />
<package id="xunit" version="2.2.0" targetFramework="net461" />
<package id="xunit.abstractions" version="2.0.1" targetFramework="net461" />
<package id="xunit.assert" version="2.2.0" targetFramework="net461" />
<package id="xunit.core" version="2.2.0" targetFramework="net461" />
<package id="xunit.extensibility.core" version="2.2.0" targetFramework="net461" />
<package id="xunit.extensibility.execution" version="2.2.0" targetFramework="net461" />
<package id="xunit.runner.console" version="2.2.0" targetFramework="net461" developmentDependency="true" />
</packages>

View 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();
}
}
}

View File

@@ -0,0 +1,44 @@
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 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 bool IsBanned(ulong steamId) => false;
/// <inheritdoc/>
public override void Attach()
{
base.Attach();
MyMultiplayer.Static.ClientJoined += RaiseClientJoined;
}
/// <inheritdoc/>
public override void Detach()
{
MyMultiplayer.Static.ClientJoined -= RaiseClientJoined;
base.Detach();
}
}
}

View File

@@ -1,17 +1,167 @@
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
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
// Early config: Resolve SE install directory.
if (!File.Exists(Path.Combine(SpaceEngineersInstallAlias, _spaceEngineersVerifyFile)))
SetupSpaceEngInstallAlias();
using (new TorchAssemblyResolver(Path.Combine(SpaceEngineersInstallAlias, SpaceEngineersBinaries)))
{
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);
}
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 +177,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);
}
} }
} }

View File

@@ -1,4 +1,17 @@
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyVersion("1.0.213.390")] [assembly: AssemblyTitle("Torch Client")]
[assembly: AssemblyFileVersion("1.0.213.390")] [assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Torch")]
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
#if DEBUG
[assembly: AssemblyConfiguration("Debug")]
#else
[assembly: AssemblyConfiguration("Release")]
#endif

View File

@@ -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 #>")]

View File

@@ -2,8 +2,6 @@
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <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')" /> <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> <ProjectGuid>{E36DF745-260B-4956-A2E8-09F08B2E7161}</ProjectGuid>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
@@ -15,10 +13,12 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath> <OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
@@ -27,7 +27,7 @@
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath> <OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@@ -35,19 +35,20 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
<DocumentationFile>bin\x64\Release\Torch.Client.xml</DocumentationFile> <DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.Client.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<ApplicationIcon>torchicon.ico</ApplicationIcon> <ApplicationIcon>torchicon.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <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> <HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </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> <SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath> <HintPath>..\GameBinaries\Sandbox.Common.dll</HintPath>
<Private>False</Private>
</Reference> </Reference>
<Reference Include="Sandbox.Game"> <Reference Include="Sandbox.Game">
<HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath> <HintPath>..\GameBinaries\Sandbox.Game.dll</HintPath>
@@ -64,6 +65,7 @@
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
@@ -81,6 +83,10 @@
<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> <SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\VRage.Input.dll</HintPath> <HintPath>..\GameBinaries\VRage.Input.dll</HintPath>
@@ -103,20 +109,24 @@
<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">
<DependentUpon>AssemblyInfo.tt</DependentUpon> <Link>Properties\AssemblyVersion.cs</Link>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile> </Compile>
<Compile Include="Properties\AssemblyInfo1.cs" /> <Compile Include="Manager\MultiplayerManagerClient.cs" />
<Compile Include="Manager\MultiplayerManagerLobby.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TorchClient.cs" /> <Compile Include="TorchClient.cs" />
<Compile Include="TorchClientConfig.cs" />
<Compile Include="TorchConsoleScreen.cs" /> <Compile Include="TorchConsoleScreen.cs" />
<Compile Include="TorchMainMenuScreen.cs" />
<Compile Include="TorchSettingsScreen.cs" /> <Compile Include="TorchSettingsScreen.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">
@@ -129,6 +139,9 @@
<DependentUpon>Settings.settings</DependentUpon> <DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput> <DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile> </Compile>
<Compile Include="UI\TorchMainMenuScreen.cs" />
<Compile Include="UI\TorchNavScreen.cs" />
<Compile Include="UI\TorchSettingsScreen.cs" />
<EmbeddedResource Include="Properties\Resources.resx"> <EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator> <Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput> <LastGenOutput>Resources.Designer.cs</LastGenOutput>
@@ -144,35 +157,25 @@
<ProjectReference Include="..\Torch.API\Torch.API.csproj"> <ProjectReference Include="..\Torch.API\Torch.API.csproj">
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project> <Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
<Name>Torch.API</Name> <Name>Torch.API</Name>
<Private>True</Private> <Private>False</Private>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Torch\Torch.csproj"> <ProjectReference Include="..\Torch\Torch.csproj">
<Project>{7E01635C-3B67-472E-BCD6-C5539564F214}</Project> <Project>{7E01635C-3B67-472E-BCD6-C5539564F214}</Project>
<Name>Torch</Name> <Name>Torch</Name>
<Private>True</Private> <Private>False</Private>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="torchicon.ico" /> <Resource Include="torchicon.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="Properties\AssemblyInfo.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" /> <Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"</PostBuildEvent> <PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"
</PostBuildEvent>
</PropertyGroup> </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>

View File

@@ -1,19 +1,26 @@
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
{ {
@@ -21,51 +28,48 @@ namespace Torch.Client
{ {
private MyCommonProgramStartup _startup; private MyCommonProgramStartup _startup;
private IMyRender _renderer; private IMyRender _renderer;
private const uint APP_ID = 244850;
private VRageGameServices _services; protected override uint SteamAppId => 244850;
protected override string SteamAppName => "Space Engineers";
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");
base.Init();
if (!File.Exists("steam_appid.txt"))
{
Directory.SetCurrentDirectory(Path.GetDirectoryName(typeof(VRage.FastResourceLock).Assembly.Location) + "\\..");
}
SpaceEngineersGame.SetupBasicGameInfo();
_startup = new MyCommonProgramStartup(RunArgs); _startup = new MyCommonProgramStartup(RunArgs);
SpaceEngineersGame.SetupBasicGameInfo();
SpaceEngineersGame.SetupPerGameSettings();
if (_startup.PerformReporting()) if (_startup.PerformReporting())
return; throw new InvalidOperationException("Torch client won't launch when started in error reporting mode");
_startup.PerformAutoconnect(); _startup.PerformAutoconnect();
if (!_startup.CheckSingleInstance()) if (!_startup.CheckSingleInstance())
return; throw new InvalidOperationException("Only one instance of Space Engineers can be running at a time.");
var appDataPath = _startup.GetAppDataPath(); var appDataPath = _startup.GetAppDataPath();
MyInitializer.InvokeBeforeRun(APP_ID, MyPerGameSettings.BasicGameInfo.ApplicationName, appDataPath); Config.InstancePath = appDataPath;
MyInitializer.InitCheckSum(); base.Init();
if (!_startup.Check64Bit()) OverrideMenus();
return; SetRenderWindowTitle($"Space Engineers v{GameVersion} with Torch v{TorchVersion}");
}
_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());
}
public override void Dispose()
{
base.Dispose();
_startup.DetectSharpDxLeaksAfterRun(); _startup.DetectSharpDxLeaksAfterRun();
MyInitializer.InvokeAfterRun();
} }
private void OverrideMenus() private void OverrideMenus()
@@ -83,60 +87,26 @@ namespace Torch.Client
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;
private void SpaceEngineersGame_OnGameLoaded(object sender, EventArgs e) if (window != null)
{ renderThread.Invoke(() =>
}
public override void Stop()
{
MySandboxGame.ExitThreadSafe();
}
private void InitializeRender()
{
try
{
if (Game.IsDedicated)
{ {
_renderer = new MyNullRender(); window.Text = title;
} });
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; public override void Restart()
} {
throw new NotImplementedException();
MyRenderProxy.Initialize(_renderer);
MyRenderProxy.GetRenderProfiler().SetAutocommit(false);
MyRenderProxy.GetRenderProfiler().InitMemoryHack("MainEntryPoint");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Render Initialization Failed");
Environment.Exit(-1);
}
} }
} }
} }

View File

@@ -0,0 +1,32 @@
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 bool Save(string path = null)
{
return true;
}
}
}

View File

@@ -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());
}
}
}

View 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>());
}
}
}

View 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();
}
}

View 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();
}
}

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="NLog" version="4.4.1" targetFramework="net461" /> <package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
<package id="NLog" version="4.4.12" targetFramework="net461" />
</packages> </packages>

View 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

View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<ProjectGuid>{9EFD1D91-2FA2-47ED-B537-D8BC3B0E543E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Torch.Server.Tests</RootNamespace>
<AssemblyName>Torch.Server.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<NoWarn>1591,0649</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>$(SolutionDir)\bin-test\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>$(SolutionDir)\bin-test\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>$(SolutionDir)\bin-test\x64\Release\Torch.Server.Tests.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<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="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>
<Reference Include="xunit.assert, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.assert.2.2.0\lib\netstandard1.1\xunit.assert.dll</HintPath>
</Reference>
<Reference Include="xunit.core, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.core.2.2.0\lib\netstandard1.1\xunit.core.dll</HintPath>
</Reference>
<Reference Include="xunit.execution.desktop, Version=2.2.0.3545, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
<HintPath>..\packages\xunit.extensibility.execution.2.2.0\lib\net452\xunit.execution.desktop.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Versioning\AssemblyVersion.cs">
<Link>Properties\AssemblyVersion.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TorchServerReflectionTest.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj">
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
<Name>Torch.API</Name>
</ProjectReference>
<ProjectReference Include="..\Torch.Server\Torch.Server.csproj">
<Project>{ca50886b-7b22-4cd8-93a0-c06f38d4f77d}</Project>
<Name>Torch.Server</Name>
</ProjectReference>
<ProjectReference Include="..\Torch.Tests\Torch.Tests.csproj">
<Project>{c3c8b671-6ad1-44aa-a8da-e0c0dc0fedf5}</Project>
<Name>Torch.Tests</Name>
</ProjectReference>
<ProjectReference Include="..\Torch\Torch.csproj">
<Project>{7e01635c-3b67-472e-bcd6-c5539564f214}</Project>
<Name>Torch</Name>
<Private>True</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
</Project>

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using Torch.Tests;
using Torch.Utils;
using Xunit;
namespace Torch.Server.Tests
{
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
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
<package id="NLog" version="4.4.12" targetFramework="net461" />
<package id="xunit" version="2.2.0" targetFramework="net461" />
<package id="xunit.abstractions" version="2.0.1" targetFramework="net461" />
<package id="xunit.assert" version="2.2.0" targetFramework="net461" />
<package id="xunit.core" version="2.2.0" targetFramework="net461" />
<package id="xunit.extensibility.core" version="2.2.0" targetFramework="net461" />
<package id="xunit.extensibility.execution" version="2.2.0" targetFramework="net461" />
<package id="xunit.runner.console" version="2.2.0" targetFramework="net461" developmentDependency="true" />
</packages>

196
Torch.Server/Initializer.cs Normal file
View File

@@ -0,0 +1,196 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
using NLog;
using Torch.Utils;
namespace Torch.Server
{
public class Initializer
{
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_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";
private TorchConfig _config;
private TorchServer _server;
private string _basePath;
public TorchConfig Config => _config;
public TorchServer Server => _server;
public Initializer(string basePath)
{
_basePath = basePath;
}
public bool Initialize(string[] args)
{
if (_init)
return false;
#if !DEBUG
AppDomain.CurrentDomain.UnhandledException += HandleException;
#endif
if (!args.Contains("-noupdate"))
RunSteamCmd();
_config = InitConfig();
if (!_config.Parse(args))
return false;
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
{
// ignored
}
}
_init = true;
return true;
}
public void Run()
{
_server = new TorchServer(_config);
_server.Init();
if (!_config.NoGui)
{
var ui = new TorchUI(_server);
if (_config.Autostart)
_server.Start();
ui.ShowDialog();
}
else
_server.Start();
}
private TorchConfig InitConfig()
{
var configName = "Torch.cfg";
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
if (File.Exists(configName))
{
Log.Info($"Loading config {configPath}");
return TorchConfig.LoadFrom(configPath);
}
else
{
Log.Info($"Generating default config at {configPath}");
var config = new TorchConfig { InstancePath = Path.GetFullPath("Instance") };
config.Save(configPath);
return config;
}
}
private static void RunSteamCmd()
{
var log = LogManager.GetLogger("SteamCMD");
if (!Directory.Exists(STEAMCMD_DIR))
{
Directory.CreateDirectory(STEAMCMD_DIR);
}
if (!File.Exists(RUNSCRIPT_PATH))
File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT);
if (!File.Exists(STEAMCMD_PATH))
{
try
{
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;
}
}
log.Info("Checking for DS updates.");
var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt")
{
WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), STEAMCMD_DIR),
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);
}
}
private void LogException(Exception ex)
{
if (ex.InnerException != null)
{
LogException(ex.InnerException);
}
Log.Fatal(ex);
if (ex is ReflectionTypeLoadException exti)
foreach (Exception exl in exti.LoaderExceptions)
LogException(exl);
}
private void HandleException(object sender, UnhandledExceptionEventArgs e)
{
var ex = (Exception)e.ExceptionObject;
LogException(ex);
Console.WriteLine("Exiting in 5 seconds.");
Thread.Sleep(5000);
LogManager.Flush();
if (_config.RestartOnCrash)
{
var exe = typeof(Program).Assembly.Location;
_config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
Process.Start(exe, _config.ToString());
}
Process.GetCurrentProcess().Kill();
}
}
}

View 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);
}
}
}

View File

@@ -0,0 +1,265 @@
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);
#pragma warning disable 649
[ReflectedGetter(Name = "Keys")]
private static readonly Func<ConditionalWeakTable<EntityViewModel, EntityControlViewModel>, ICollection<EntityViewModel>> _weakTableKeys;
#pragma warning restore 649
/// <summary>
/// Warning: Creates a giant list, avoid if possible.
/// </summary>
internal ICollection<EntityViewModel> Keys => _weakTableKeys(_models);
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);
}
}
}
}
}

View File

@@ -25,28 +25,20 @@ namespace Torch.Server.Managers
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg"; private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
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) public InstanceManager(ITorchBase torchInstance) : base(torchInstance)
{ {
} }
/// <inheritdoc />
public override void Init()
{
MyFileSystem.ExePath = Path.Combine(Torch.GetManager<FilesystemManager>().TorchDirectory, "DedicatedServer64");
MyFileSystem.Init("Content", Torch.Config.InstancePath);
//Initializes saves path. Why this isn't in Init() we may never know.
MyFileSystem.InitUserSpecific(null);
}
public void LoadInstance(string path, bool validate = true) public void LoadInstance(string path, bool validate = true)
{ {
if (validate) if (validate)
ValidateInstance(path); ValidateInstance(path);
MyFileSystem.Reset(); MyFileSystem.Reset();
MyFileSystem.ExePath = Path.Combine(Torch.GetManager<FilesystemManager>().TorchDirectory, "DedicatedServer64");
MyFileSystem.Init("Content", path); MyFileSystem.Init("Content", path);
//Initializes saves path. Why this isn't in Init() we may never know. //Initializes saves path. Why this isn't in Init() we may never know.
MyFileSystem.InitUserSpecific(null); MyFileSystem.InitUserSpecific(null);
@@ -129,7 +121,7 @@ namespace Torch.Server.Managers
public void SaveConfig() public void SaveConfig()
{ {
DedicatedConfig.Model.Save(); DedicatedConfig.Save(Path.Combine(Torch.Config.InstancePath, CONFIG_NAME));
Log.Info("Saved dedicated config."); Log.Info("Saved dedicated config.");
try try

View File

@@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.Linq;
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 Torch.API;
using Torch.API.Managers;
using Torch.Managers;
using Torch.Utils;
using Torch.ViewModels;
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>();
/// <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);
if (_gameOwnerIds.ContainsKey(steamId))
MyMultiplayer.Static.BanClient(_gameOwnerIds[steamId], banned);
});
}
/// <inheritdoc />
public bool IsBanned(ulong steamId) => _isClientBanned.Invoke(MyMultiplayer.Static, steamId) ||
MySandboxGame.ConfigDedicated.Banned.Contains(steamId);
/// <inheritdoc/>
public override void Attach()
{
base.Attach();
_gameServerValidateAuthTicketReplacer = _gameServerValidateAuthTicketFactory.Invoke();
_gameServerUserGroupStatusReplacer = _gameServerUserGroupStatusFactory.Invoke();
_gameServerValidateAuthTicketReplacer.Replace(
new Action<ulong, JoinResult, ulong>(ValidateAuthTicketResponse), MyGameService.GameServer);
_gameServerUserGroupStatusReplacer.Replace(new Action<ulong, ulong, bool, bool>(UserGroupStatusResponse),
MyGameService.GameServer);
_log.Info("Inserted steam 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 steam authentication intercept");
base.Detach();
}
#pragma warning disable 649
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.ValidateAuthTicketResponse),
typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
private static Func<ReflectedEventReplacer> _gameServerValidateAuthTicketFactory;
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.UserGroupStatusResponse),
typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
private static Func<ReflectedEventReplacer> _gameServerUserGroupStatusFactory;
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 = "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)
{
_log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}");
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)
{
MultiplayerManagerDedicatedEventShim.RaiseValidateAuthTicket(ref info);
if (info.FutureVerdict == null)
{
if (IsBanned(info.SteamOwner) || IsBanned(info.SteamID))
CommitVerdict(info.SteamID, JoinResult.BannedByAdmins);
else if (_isClientKicked(MyMultiplayer.Static, info.SteamID) ||
_isClientKicked(MyMultiplayer.Static, info.SteamOwner))
CommitVerdict(info.SteamID, JoinResult.KickedRecently);
else if (info.SteamResponse != JoinResult.OK)
CommitVerdict(info.SteamID, info.SteamResponse);
else if (MyMultiplayer.Static.MemberLimit > 0 &&
MyMultiplayer.Static.MemberCount + 1 > MyMultiplayer.Static.MemberLimit)
CommitVerdict(info.SteamID, JoinResult.ServerFull);
else if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
MySandboxGame.ConfigDedicated.Administrators.Contains(info.SteamID.ToString()) ||
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(info.SteamID)))
CommitVerdict(info.SteamID, JoinResult.OK);
else if (MySandboxGame.ConfigDedicated.GroupID == info.Group && (info.Member || info.Officer))
CommitVerdict(info.SteamID, JoinResult.OK);
else
CommitVerdict(info.SteamID, JoinResult.NotInGroup);
return;
}
info.FutureVerdict.ContinueWith((task) =>
{
JoinResult verdict;
if (task.IsFaulted)
{
_log.Error(task.Exception, $"Future validation verdict faulted");
verdict = JoinResult.TicketCanceled;
}
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
}
}

View File

@@ -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;
}
}

View File

@@ -25,6 +25,7 @@ using System.IO.Compression;
using System.Net; using System.Net;
using System.Security.Policy; using System.Security.Policy;
using Torch.Server.Managers; using Torch.Server.Managers;
using Torch.Utils;
using VRage.FileSystem; using VRage.FileSystem;
using VRageRender; using VRageRender;
@@ -32,268 +33,35 @@ namespace Torch.Server
{ {
internal static class Program internal static class Program
{ {
private static ITorchServer _server; /// <remarks>
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. /// This method must *NOT* load any types/assemblies from the vanilla game, otherwise automatic updates will fail.
/// </summary> /// </remarks>
[STAThread] [STAThread]
public static void Main(string[] args) public static void Main(string[] args)
{ {
//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 = new FileInfo(typeof(Program).Assembly.Location).Directory.ToString();
var binDir = Path.Combine(workingDir, "DedicatedServer64");
Directory.SetCurrentDirectory(workingDir);
foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.old")) if (!TorchLauncher.IsTorchWrapped())
File.Delete(file); {
TorchLauncher.Launch(Assembly.GetEntryAssembly().FullName,args, binDir);
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; return;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; }
if (!Environment.UserInteractive) if (!Environment.UserInteractive)
{ {
using (var service = new TorchService()) using (var service = new TorchService())
{
ServiceBase.Run(service); ServiceBase.Run(service);
}
return; return;
} }
//CommandLine reflection triggers assembly loading, so DS update must be completely separated. var initializer = new Initializer(workingDir);
if (!args.Contains("-noupdate")) if (!initializer.Initialize(args))
{
if (!Directory.Exists("DedicatedServer64"))
{
_log.Error("Game libraries not found. Press the Enter key to install the dedicated server.");
Console.ReadLine();
}
RunSteamCmd();
}
InitConfig();
if (!_config.Parse(args))
return; return;
if (!string.IsNullOrEmpty(_config.WaitForPID)) initializer.Run();
{
try
{
var pid = int.Parse(_config.WaitForPID);
var waitProc = Process.GetProcessById(pid);
_log.Info("Continuing in 5 seconds.");
Thread.Sleep(5000);
if (!waitProc.HasExited)
{
_log.Warn($"Killing old process {pid}.");
waitProc.Kill();
}
}
catch
{
// ignored
}
}
_restartOnCrash = _config.RestartOnCrash;
RunServer(_config);
}
public static void InitConfig()
{
var configName = "Torch.cfg";
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
if (File.Exists(configName))
{
_log.Info($"Loading config {configPath}");
_config = TorchConfig.LoadFrom(configPath);
}
else
{
_log.Info($"Generating default config at {configPath}");
_config = new TorchConfig { InstancePath = Path.GetFullPath("Instance") };
_config.Save(configPath);
}
}
private const string STEAMCMD_DIR = "steamcmd";
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)
return;
var log = LogManager.GetLogger("SteamCMD");
if (!Directory.Exists(STEAMCMD_DIR))
{
Directory.CreateDirectory(STEAMCMD_DIR);
}
if (!File.Exists(RUNSCRIPT_PATH))
File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT);
if (!File.Exists(STEAMCMD_PATH))
{
try
{
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;
}
}
log.Info("Checking for DS updates.");
var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt")
{
WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), STEAMCMD_DIR),
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);
}
_steamCmdDone = true;
}
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);
} }
} }
} }

View File

@@ -1,4 +1,17 @@
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyVersion("1.1.213.390")] [assembly: AssemblyTitle("Torch Server")]
[assembly: AssemblyFileVersion("1.1.213.390")] [assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Torch")]
[assembly: AssemblyCopyright("Copyright © Torch API 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
#if DEBUG
[assembly: AssemblyConfiguration("Debug")]
#else
[assembly: AssemblyConfiguration("Release")]
#endif

View File

@@ -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 #>")]

View File

@@ -2,8 +2,6 @@
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <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')" /> <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>{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}</ProjectGuid> <ProjectGuid>{CA50886B-7B22-4CD8-93A0-C06F38D4F77D}</ProjectGuid>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
@@ -15,10 +13,12 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath> <OutputPath>$(SolutionDir)\bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
@@ -27,7 +27,7 @@
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath> <OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@@ -35,7 +35,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
<DocumentationFile>bin\x64\Release\Torch.Server.xml</DocumentationFile> <DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.Server.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<StartupObject>Torch.Server.Program</StartupObject> <StartupObject>Torch.Server.Program</StartupObject>
@@ -63,7 +63,8 @@
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.11\lib\net45\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference> </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> <SpecificVersion>False</SpecificVersion>
@@ -124,6 +125,7 @@
<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> <SpecificVersion>False</SpecificVersion>
<HintPath>..\GameBinaries\VRage.Audio.dll</HintPath> <HintPath>..\GameBinaries\VRage.Audio.dll</HintPath>
<Private>False</Private>
</Reference> </Reference>
<Reference Include="VRage.Dedicated, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64"> <Reference Include="VRage.Dedicated, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
@@ -180,19 +182,26 @@
<HintPath>..\GameBinaries\VRage.Scripting.dll</HintPath> <HintPath>..\GameBinaries\VRage.Scripting.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="Managers\InstanceManager.cs" /> <Compile Include="..\Versioning\AssemblyVersion.cs">
<Compile Include="NativeMethods.cs" /> <Link>Properties\AssemblyVersion.cs</Link>
<Compile Include="Properties\AssemblyInfo.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AssemblyInfo.tt</DependentUpon>
</Compile> </Compile>
<Compile Include="Properties\AssemblyInfo1.cs" /> <Compile Include="ListBoxExtensions.cs" />
<Compile Include="Managers\EntityControlManager.cs" />
<Compile Include="Managers\MultiplayerManagerDedicated.cs" />
<Compile Include="Managers\InstanceManager.cs" />
<Compile Include="Managers\MultiplayerManagerDedicatedEventShim.cs" />
<Compile Include="NativeMethods.cs" />
<Compile Include="Initializer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServerStatistics.cs" /> <Compile Include="ServerStatistics.cs" />
<Compile Include="TorchConfig.cs" /> <Compile Include="TorchConfig.cs" />
<Compile Include="TorchService.cs"> <Compile Include="TorchService.cs">
@@ -207,6 +216,13 @@
<Compile Include="ViewModels\Entities\Blocks\PropertyViewModel.cs" /> <Compile Include="ViewModels\Entities\Blocks\PropertyViewModel.cs" />
<Compile Include="ViewModels\Entities\CharacterViewModel.cs" /> <Compile Include="ViewModels\Entities\CharacterViewModel.cs" />
<Compile Include="ViewModels\ConfigDedicatedViewModel.cs" /> <Compile Include="ViewModels\ConfigDedicatedViewModel.cs" />
<Compile Include="ViewModels\Entities\EntityControlViewModel.cs" />
<Compile Include="Views\Entities\EntityControlHost.xaml.cs">
<DependentUpon>EntityControlHost.xaml</DependentUpon>
</Compile>
<Compile Include="Views\Entities\EntityControlsView.xaml.cs">
<DependentUpon>EntityControlsView.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModels\EntityTreeViewModel.cs" /> <Compile Include="ViewModels\EntityTreeViewModel.cs" />
<Compile Include="ViewModels\Entities\EntityViewModel.cs" /> <Compile Include="ViewModels\Entities\EntityViewModel.cs" />
<Compile Include="ViewModels\Entities\FloatingObjectViewModel.cs" /> <Compile Include="ViewModels\Entities\FloatingObjectViewModel.cs" />
@@ -289,15 +305,23 @@
<ProjectReference Include="..\Torch.API\Torch.API.csproj"> <ProjectReference Include="..\Torch.API\Torch.API.csproj">
<Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project> <Project>{fba5d932-6254-4a1e-baf4-e229fa94e3c2}</Project>
<Name>Torch.API</Name> <Name>Torch.API</Name>
<Private>True</Private> <Private>False</Private>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Torch\Torch.csproj"> <ProjectReference Include="..\Torch\Torch.csproj">
<Project>{7e01635c-3b67-472e-bcd6-c5539564f214}</Project> <Project>{7e01635c-3b67-472e-bcd6-c5539564f214}</Project>
<Name>Torch</Name> <Name>Torch</Name>
<Private>True</Private> <Private>False</Private>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Page Include="Views\Entities\EntityControlHost.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Entities\EntityControlsView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\AddWorkshopItemsDialog.xaml"> <Page Include="Views\AddWorkshopItemsDialog.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
@@ -326,6 +350,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Views\PluginsControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Entities\VoxelMapView.xaml"> <Page Include="Views\Entities\VoxelMapView.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
@@ -338,10 +366,6 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Views\PluginsControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\TorchUI.xaml"> <Page Include="Views\TorchUI.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
@@ -357,24 +381,10 @@
<ItemGroup> <ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" /> <Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="Properties\AssemblyInfo.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
</Content>
</ItemGroup>
<ItemGroup /> <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>cd "$(TargetDir)" <PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"</PostBuildEvent>
copy "$(SolutionDir)NLog.config" "$(TargetDir)"
"Torch Server Release.bat"</PostBuildEvent>
</PropertyGroup> </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>

View File

@@ -8,6 +8,7 @@ using NLog;
namespace Torch.Server namespace Torch.Server
{ {
// TODO: redesign this gerbage
public class TorchConfig : CommandLine, ITorchConfig public class TorchConfig : CommandLine, ITorchConfig
{ {
private static Logger _log = LogManager.GetLogger("Config"); private static Logger _log = LogManager.GetLogger("Config");

View File

@@ -3,22 +3,28 @@ using Sandbox.Engine.Utils;
using Sandbox.Game; using Sandbox.Game;
using Sandbox.Game.World; using Sandbox.Game.World;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Principal; using System.Security.Principal;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Xml.Serialization.GeneratedAssembly; using Microsoft.Xml.Serialization.GeneratedAssembly;
using NLog;
using Sandbox.Engine.Analytics; using Sandbox.Engine.Analytics;
using Sandbox.Game.Multiplayer; using Sandbox.Game.Multiplayer;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using SteamSDK; using SteamSDK;
using Torch.API; using Torch.API;
using Torch.API.Managers;
using Torch.API.Session;
using Torch.Managers; using Torch.Managers;
using Torch.Server.Managers; using Torch.Server.Managers;
using Torch.Utils;
using VRage.Dedicated; using VRage.Dedicated;
using VRage.FileSystem; using VRage.FileSystem;
using VRage.Game; using VRage.Game;
@@ -29,6 +35,7 @@ using VRage.Library;
using VRage.ObjectBuilders; using VRage.ObjectBuilders;
using VRage.Plugins; using VRage.Plugins;
using VRage.Utils; using VRage.Utils;
#pragma warning disable 618 #pragma warning disable 618
namespace Torch.Server namespace Torch.Server
@@ -36,14 +43,59 @@ namespace Torch.Server
public class TorchServer : TorchBase, ITorchServer public class TorchServer : TorchBase, ITorchServer
{ {
//public MyConfigDedicated<MyObjectBuilder_SessionSettings> DedicatedConfig { get; set; } //public MyConfigDedicated<MyObjectBuilder_SessionSettings> DedicatedConfig { get; set; }
public float SimulationRatio { get => _simRatio; set { _simRatio = value; OnPropertyChanged(); } } /// <inheritdoc />
public TimeSpan ElapsedPlayTime { get => _elapsedPlayTime; set { _elapsedPlayTime = value; OnPropertyChanged(); } } public float SimulationRatio
{
get => _simRatio;
set
{
_simRatio = value;
OnPropertyChanged();
}
}
/// <inheritdoc />
public TimeSpan ElapsedPlayTime
{
get => _elapsedPlayTime;
set
{
_elapsedPlayTime = value;
OnPropertyChanged();
}
}
/// <inheritdoc />
public Thread GameThread { get; private set; } public Thread GameThread { get; private set; }
public ServerState State { get => _state; private set { _state = value; OnPropertyChanged(); } }
public bool IsRunning { get => _isRunning; set { _isRunning = value; OnPropertyChanged(); } } /// <inheritdoc />
public ServerState State
{
get => _state;
private set
{
_state = value;
OnPropertyChanged();
}
}
/// <inheritdoc />
public bool IsRunning
{
get => _isRunning;
set
{
_isRunning = value;
OnPropertyChanged();
}
}
/// <inheritdoc />
public InstanceManager DedicatedInstance { get; } public InstanceManager DedicatedInstance { get; }
/// <inheritdoc /> /// <inheritdoc />
public string InstanceName => Config?.InstanceName; public string InstanceName => Config?.InstanceName;
/// <inheritdoc /> /// <inheritdoc />
public string InstancePath => Config?.InstancePath; public string InstancePath => Config?.InstancePath;
@@ -51,74 +103,46 @@ namespace Torch.Server
private ServerState _state; private ServerState _state;
private TimeSpan _elapsedPlayTime; private TimeSpan _elapsedPlayTime;
private float _simRatio; private float _simRatio;
private readonly AutoResetEvent _stopHandle = new AutoResetEvent(false);
private Timer _watchdog; private Timer _watchdog;
private Stopwatch _uptime; private Stopwatch _uptime;
/// <inheritdoc />
public TorchServer(TorchConfig config = null) public TorchServer(TorchConfig config = null)
{ {
DedicatedInstance = new InstanceManager(this); DedicatedInstance = new InstanceManager(this);
AddManager(DedicatedInstance); AddManager(DedicatedInstance);
AddManager(new EntityControlManager(this));
Config = config ?? new TorchConfig(); Config = config ?? new TorchConfig();
MyFakes.ENABLE_INFINARIO = false;
var sessionManager = Managers.GetManager<ITorchSessionManager>();
sessionManager.AddFactory((x) => new MultiplayerManagerDedicated(this));
} }
/// <inheritdoc/>
protected override uint SteamAppId => 244850;
/// <inheritdoc/>
protected override string SteamAppName => "SpaceEngineersDedicated";
/// <inheritdoc /> /// <inheritdoc />
public override void Init() public override void Init()
{ {
Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'"); Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'");
Sandbox.Engine.Platform.Game.IsDedicated = true;
base.Init(); base.Init();
MyPerGameSettings.SendLogToKeen = false; Managers.GetManager<ITorchSessionManager>().SessionStateChanged += OnSessionStateChanged;
MyPerServerSettings.GameName = MyPerGameSettings.GameName;
MyPerServerSettings.GameNameSafe = MyPerGameSettings.GameNameSafe;
MyPerServerSettings.GameDSName = MyPerServerSettings.GameNameSafe + "Dedicated";
MyPerServerSettings.GameDSDescription = "Your place for space engineering, destruction and exploring.";
MySessionComponentExtDebug.ForceDisable = true;
MyPerServerSettings.AppId = 244850;
MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion;
InvokeBeforeRun();
MyObjectBuilderSerializer.RegisterFromAssembly(typeof(MyObjectBuilder_CheckpointSerializer).Assembly);
MyPlugins.RegisterGameAssemblyFile(MyPerGameSettings.GameModAssembly);
MyPlugins.RegisterGameObjectBuildersAssemblyFile(MyPerGameSettings.GameModObjBuildersAssembly);
MyPlugins.RegisterSandboxAssemblyFile(MyPerGameSettings.SandboxAssembly);
MyPlugins.RegisterSandboxGameAssemblyFile(MyPerGameSettings.SandboxGameAssembly);
MyPlugins.Load();
MyGlobalTypeMetadata.Static.Init();
GetManager<InstanceManager>().LoadInstance(Config.InstancePath); GetManager<InstanceManager>().LoadInstance(Config.InstancePath);
Plugins.LoadPlugins();
} }
private void InvokeBeforeRun() private void OnSessionStateChanged(ITorchSession session, TorchSessionState newState)
{ {
MySandboxGame.Log.Init("SpaceEngineers-Dedicated.log", MyFinalBuildConstants.APP_VERSION_STRING); if (newState == TorchSessionState.Unloading || newState == TorchSessionState.Unloaded)
MySandboxGame.Log.WriteLine("Steam build: Always true"); {
MySandboxGame.Log.WriteLine("Environment.ProcessorCount: " + MyEnvironment.ProcessorCount); _watchdog?.Dispose();
//MySandboxGame.Log.WriteLine("Environment.OSVersion: " + GetOsName()); _watchdog = null;
MySandboxGame.Log.WriteLine("Environment.CommandLine: " + Environment.CommandLine); }
MySandboxGame.Log.WriteLine("Environment.Is64BitProcess: " + MyEnvironment.Is64BitProcess);
MySandboxGame.Log.WriteLine("Environment.Is64BitOperatingSystem: " + Environment.Is64BitOperatingSystem);
//MySandboxGame.Log.WriteLine("Environment.Version: " + GetNETFromRegistry());
MySandboxGame.Log.WriteLine("Environment.CurrentDirectory: " + Environment.CurrentDirectory);
MySandboxGame.Log.WriteLine("MainAssembly.ProcessorArchitecture: " + Assembly.GetExecutingAssembly().GetArchitecture());
MySandboxGame.Log.WriteLine("ExecutingAssembly.ProcessorArchitecture: " + MyFileSystem.MainAssembly.GetArchitecture());
MySandboxGame.Log.WriteLine("IntPtr.Size: " + IntPtr.Size);
MySandboxGame.Log.WriteLine("Default Culture: " + CultureInfo.CurrentCulture.Name);
MySandboxGame.Log.WriteLine("Default UI Culture: " + CultureInfo.CurrentUICulture.Name);
MySandboxGame.Log.WriteLine("IsAdmin: " + new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator));
MyLog.Default = MySandboxGame.Log;
Thread.CurrentThread.Name = "Main thread";
//Because we want exceptions from users to be in english
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
MySandboxGame.Config = new MyConfig("SpaceEngineers-Dedicated.cfg");
MySandboxGame.Config.Load();
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -126,37 +150,60 @@ namespace Torch.Server
{ {
if (State != ServerState.Stopped) if (State != ServerState.Stopped)
return; return;
State = ServerState.Starting;
IsRunning = true;
Log.Info("Starting server.");
MySandboxGame.ConfigDedicated = DedicatedInstance.DedicatedConfig.Model;
DedicatedInstance.SaveConfig(); DedicatedInstance.SaveConfig();
_uptime = Stopwatch.StartNew(); _uptime = Stopwatch.StartNew();
IsRunning = true;
GameThread = Thread.CurrentThread;
Config.Save();
State = ServerState.Starting;
Log.Info("Starting server.");
var runInternal = typeof(DedicatedServer).GetMethod("RunInternal", BindingFlags.Static | BindingFlags.NonPublic);
MySandboxGame.IsDedicated = true;
Environment.SetEnvironmentVariable("SteamAppId", MyPerServerSettings.AppId.ToString());
VRage.Service.ExitListenerSTA.OnExit += delegate { MySandboxGame.Static?.Exit(); };
base.Start(); base.Start();
//Stops RunInternal from calling MyFileSystem.InitUserSpecific(null), we call it in InstanceManager. }
MySandboxGame.IsReloading = true;
runInternal.Invoke(null, null); /// <inheritdoc />
public override void Stop()
{
if (State == ServerState.Stopped)
Log.Error("Server is already stopped");
Log.Info("Stopping server.");
base.Stop();
Log.Info("Server stopped.");
MySandboxGame.Log.Close();
State = ServerState.Stopped; State = ServerState.Stopped;
IsRunning = false;
}
/// <summary>
/// Restart the program.
/// </summary>
public override void Restart()
{
Save(0).Wait();
Stop();
LogManager.Flush();
var exe = Assembly.GetExecutingAssembly().Location;
((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString();
Config.Autostart = true;
Process.Start(exe, Config.ToString());
Process.GetCurrentProcess().Kill();
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Init(object gameInstance) public override void Init(object gameInstance)
{ {
base.Init(gameInstance); base.Init(gameInstance);
State = ServerState.Running; var game = gameInstance as MySandboxGame;
SteamServerAPI.Instance.GameServer.SetKeyValue("SM", "Torch"); if (game != null && MySession.Static != null)
{
State = ServerState.Running;
// SteamServerAPI.Instance.GameServer.SetKeyValue("SM", "Torch");
}
else
{
State = ServerState.Stopped;
}
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -170,61 +217,85 @@ namespace Torch.Server
if (_watchdog == null && Config.TickTimeout > 0) if (_watchdog == null && Config.TickTimeout > 0)
{ {
Log.Info("Starting server watchdog."); Log.Info("Starting server watchdog.");
_watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero, TimeSpan.FromSeconds(Config.TickTimeout)); _watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero,
TimeSpan.FromSeconds(Config.TickTimeout));
} }
} }
#region Freeze Detection
private static void CheckServerResponding(object state) private static void CheckServerResponding(object state)
{ {
var mre = new ManualResetEvent(false); var mre = new ManualResetEvent(false);
((TorchServer)state).Invoke(() => mre.Set()); ((TorchServer) state).Invoke(() => mre.Set());
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout))) if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
{ {
var mainThread = MySandboxGame.Static.UpdateThread; #if DEBUG
mainThread.Suspend(); Log.Error(
var stackTrace = new StackTrace(mainThread, true); $"Server watchdog detected that the server was frozen for at least {((TorchServer) state).Config.TickTimeout} seconds.");
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.\n{stackTrace}"); Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread));
#else
Log.Error(DumpFrozenThread(MySandboxGame.Static.UpdateThread));
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.");
#endif
} }
else
Log.Debug("Server watchdog responded");
}
/// <inheritdoc />
public override void Stop()
{
if (State == ServerState.Stopped)
Log.Error("Server is already stopped");
if (Thread.CurrentThread != MySandboxGame.Static.UpdateThread)
{ {
Log.Debug("Invoking server stop on game thread."); Log.Debug("Server watchdog responded");
Invoke(Stop);
return;
} }
Log.Info("Stopping server.");
//Unload all the static junk.
//TODO: Finish unloading all server data so it's in a completely clean state.
MySandboxGame.Static.Exit();
Log.Info("Server stopped.");
_stopHandle.Set();
State = ServerState.Stopped;
IsRunning = false;
} }
/// <summary> private static string DumpFrozenThread(Thread thread, int traces = 3, int pause = 5000)
/// Restart the program. DOES NOT SAVE!
/// </summary>
public override void Restart()
{ {
var exe = Assembly.GetExecutingAssembly().Location; var stacks = new List<string>(traces);
((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString(); var totalSize = 0;
Process.Start(exe, Config.ToString()); for (var i = 0; i < traces; i++)
Environment.Exit(0); {
string dump = DumpStack(thread).ToString();
totalSize += dump.Length;
stacks.Add(dump);
Thread.Sleep(pause);
}
string commonPrefix = StringUtils.CommonSuffix(stacks);
// Advance prefix to include the line terminator.
commonPrefix = commonPrefix.Substring(commonPrefix.IndexOf('\n') + 1);
var result = new StringBuilder(totalSize - (stacks.Count - 1) * commonPrefix.Length);
result.AppendLine($"Frozen thread dump {thread.Name}");
result.AppendLine("Common prefix:").AppendLine(commonPrefix);
for (var i = 0; i < stacks.Count; i++)
if (stacks[i].Length > commonPrefix.Length)
{
result.AppendLine($"Suffix {i}");
result.AppendLine(stacks[i].Substring(0, stacks[i].Length - commonPrefix.Length));
}
return result.ToString();
} }
private static StackTrace DumpStack(Thread thread)
{
try
{
thread.Suspend();
}
catch
{
// ignored
}
var stack = new StackTrace(thread, true);
try
{
thread.Resume();
}
catch
{
// ignored
}
return stack;
}
#endregion
/// <inheritdoc/> /// <inheritdoc/>
public override Task Save(long callerId) public override Task Save(long callerId)
{ {
@@ -238,27 +309,33 @@ namespace Torch.Server
/// <param name="callerId">Caller of the save operation</param> /// <param name="callerId">Caller of the save operation</param>
private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0) private void SaveCompleted(SaveGameStatus statusCode, long callerId = 0)
{ {
string response = null;
switch (statusCode) switch (statusCode)
{ {
case SaveGameStatus.Success: case SaveGameStatus.Success:
Log.Info("Save completed."); Log.Info("Save completed.");
Multiplayer.SendMessage("Saved game.", playerId: callerId); response = "Saved game.";
break; break;
case SaveGameStatus.SaveInProgress: case SaveGameStatus.SaveInProgress:
Log.Error("Save failed, a save is already in progress."); Log.Error("Save failed, a save is already in progress.");
Multiplayer.SendMessage("Save failed, a save is already in progress.", playerId: callerId, font: MyFontEnum.Red); response = "Save failed, a save is already in progress.";
break; break;
case SaveGameStatus.GameNotReady: case SaveGameStatus.GameNotReady:
Log.Error("Save failed, game was not ready."); Log.Error("Save failed, game was not ready.");
Multiplayer.SendMessage("Save failed, game was not ready.", playerId: callerId, font: MyFontEnum.Red); response = "Save failed, game was not ready.";
break; break;
case SaveGameStatus.TimedOut: case SaveGameStatus.TimedOut:
Log.Error("Save failed, save timed out."); Log.Error("Save failed, save timed out.");
Multiplayer.SendMessage("Save failed, save timed out.", playerId: callerId, font: MyFontEnum.Red); response = "Save failed, save timed out.";
break; break;
default: default:
break; break;
} }
if (MySession.Static.Players.TryGetPlayerId(callerId, out MyPlayer.PlayerId result))
{
Managers.GetManager<IChatManagerServer>()?.SendMessageAsOther("Server", response,
statusCode == SaveGameStatus.Success ? MyFontEnum.Green : MyFontEnum.Red, result.SteamId);
}
} }
} }
} }

View File

@@ -14,13 +14,15 @@ namespace Torch.Server
{ {
public const string Name = "Torch (SEDS)"; public const string Name = "Torch (SEDS)";
private TorchServer _server; private TorchServer _server;
private static Logger _log = LogManager.GetLogger("Torch"); private Initializer _initializer;
public TorchService() public TorchService()
{ {
ServiceName = Name; var workingDir = new FileInfo(typeof(TorchService).Assembly.Location).Directory.ToString();
Directory.SetCurrentDirectory(workingDir);
_initializer = new Initializer(workingDir);
CanHandlePowerEvent = true; ServiceName = Name;
CanHandleSessionChangeEvent = false; CanHandleSessionChangeEvent = false;
CanPauseAndContinue = false; CanPauseAndContinue = false;
CanStop = true; CanStop = true;
@@ -31,17 +33,8 @@ namespace Torch.Server
{ {
base.OnStart(args); base.OnStart(args);
string configName = args.Length > 0 ? args[0] : "TorchConfig.xml"; _initializer.Initialize(args);
var options = new TorchConfig("Torch"); _initializer.Run();
if (File.Exists(configName))
options = TorchConfig.LoadFrom(configName);
else
options.Save(configName);
_server = new TorchServer(options);
_server.Init();
_server.RunArgs = args;
Task.Run(() => _server.Start());
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -50,17 +43,5 @@ namespace Torch.Server
_server.Stop(); _server.Stop();
base.OnStop(); base.OnStop();
} }
/// <inheritdoc />
protected override void OnShutdown()
{
base.OnShutdown();
}
/// <inheritdoc />
protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
{
return base.OnPowerEvent(powerStatus);
}
} }
} }

View File

@@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; using NLog;
using Sandbox.Engine.Utils; using Sandbox.Engine.Utils;
using Torch.Collections;
using VRage.Game; using VRage.Game;
using VRage.Game.ModAPI; using VRage.Game.ModAPI;
@@ -25,6 +26,7 @@ namespace Torch.Server.ViewModels
public ConfigDedicatedViewModel(MyConfigDedicated<MyObjectBuilder_SessionSettings> configDedicated) public ConfigDedicatedViewModel(MyConfigDedicated<MyObjectBuilder_SessionSettings> configDedicated)
{ {
_config = configDedicated; _config = configDedicated;
_config.IgnoreLastSession = true;
SessionSettings = new SessionSettingsViewModel(_config.SessionSettings); SessionSettings = new SessionSettingsViewModel(_config.SessionSettings);
Administrators = string.Join(Environment.NewLine, _config.Administrators); Administrators = string.Join(Environment.NewLine, _config.Administrators);
Banned = string.Join(Environment.NewLine, _config.Banned); Banned = string.Join(Environment.NewLine, _config.Banned);
@@ -52,13 +54,15 @@ namespace Torch.Server.ViewModels
Log.Warn($"'{mod}' is not a valid mod ID."); Log.Warn($"'{mod}' is not a valid mod ID.");
} }
// Never ever
_config.IgnoreLastSession = true;
_config.Save(path); _config.Save(path);
} }
private SessionSettingsViewModel _sessionSettings; private SessionSettingsViewModel _sessionSettings;
public SessionSettingsViewModel SessionSettings { get => _sessionSettings; set { _sessionSettings = value; OnPropertyChanged(); } } public SessionSettingsViewModel SessionSettings { get => _sessionSettings; set { _sessionSettings = value; OnPropertyChanged(); } }
public ObservableList<string> WorldPaths { get; } = new ObservableList<string>(); public MtObservableList<string> WorldPaths { get; } = new MtObservableList<string>();
private string _administrators; private string _administrators;
public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } } public string Administrators { get => _administrators; set { _administrators = value; OnPropertyChanged(); } }
private string _banned; private string _banned;
@@ -78,12 +82,6 @@ namespace Torch.Server.ViewModels
set { _config.GroupID = value; OnPropertyChanged(); } set { _config.GroupID = value; OnPropertyChanged(); }
} }
public bool IgnoreLastSession
{
get { return _config.IgnoreLastSession; }
set { _config.IgnoreLastSession = value; OnPropertyChanged(); }
}
public string IP public string IP
{ {
get { return _config.IP; } get { return _config.IP; }

View File

@@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Sandbox.Game.Entities.Cube; using Sandbox.Game.Entities.Cube;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using Sandbox.ModAPI.Interfaces; using Sandbox.ModAPI.Interfaces;
using Torch.Collections;
using Torch.Server.ViewModels.Entities; using Torch.Server.ViewModels.Entities;
namespace Torch.Server.ViewModels.Blocks namespace Torch.Server.ViewModels.Blocks
@@ -15,7 +16,7 @@ namespace Torch.Server.ViewModels.Blocks
public class BlockViewModel : EntityViewModel public class BlockViewModel : EntityViewModel
{ {
public IMyTerminalBlock Block { get; } public IMyTerminalBlock Block { get; }
public ObservableList<PropertyViewModel> Properties { get; } = new ObservableList<PropertyViewModel>(); public MtObservableList<PropertyViewModel> Properties { get; } = new MtObservableList<PropertyViewModel>();
public string FullName => $"{Block.CubeGrid.CustomName} - {Block.CustomName}"; public string FullName => $"{Block.CubeGrid.CustomName} - {Block.CustomName}";

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Torch.Server.ViewModels.Entities
{
public class EntityControlViewModel : ViewModel
{
internal const string SignalPropertyInvalidateControl =
"InvalidateControl-4124a476-704f-4762-8b5e-336a18e2f7e5";
internal void InvalidateControl()
{
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(SignalPropertyInvalidateControl);
}
private bool _hide;
/// <summary>
/// Should this element be forced into the <see cref="Visibility.Collapsed"/>
/// </summary>
public bool Hide
{
get => _hide;
protected set
{
if (_hide == value)
return;
_hide = value;
OnPropertyChanged();
}
}
}
}

View File

@@ -1,4 +1,8 @@
using VRage.Game.ModAPI; using System.Windows.Controls;
using Torch.API.Managers;
using Torch.Collections;
using Torch.Server.Managers;
using VRage.Game.ModAPI;
using VRage.ModAPI; using VRage.ModAPI;
using VRageMath; using VRageMath;
@@ -7,9 +11,25 @@ namespace Torch.Server.ViewModels.Entities
public class EntityViewModel : ViewModel public class EntityViewModel : ViewModel
{ {
protected EntityTreeViewModel Tree { get; } protected EntityTreeViewModel Tree { get; }
public IMyEntity Entity { get; }
private IMyEntity _backing;
public IMyEntity Entity
{
get => _backing;
protected set
{
_backing = value;
OnPropertyChanged();
EntityControls = TorchBase.Instance?.Managers.GetManager<EntityControlManager>()?.BoundModels(this);
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(nameof(EntityControls));
}
}
public long Id => Entity.EntityId; public long Id => Entity.EntityId;
public MtObservableList<EntityControlViewModel> EntityControls { get; private set; }
public virtual string Name public virtual string Name
{ {
get => Entity.DisplayName; get => Entity.DisplayName;

View File

@@ -2,6 +2,8 @@
using System.Linq; using System.Linq;
using Sandbox.Game.Entities; using Sandbox.Game.Entities;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using Torch.API.Managers;
using Torch.Collections;
using Torch.Server.ViewModels.Blocks; using Torch.Server.ViewModels.Blocks;
namespace Torch.Server.ViewModels.Entities namespace Torch.Server.ViewModels.Entities
@@ -9,7 +11,7 @@ namespace Torch.Server.ViewModels.Entities
public class GridViewModel : EntityViewModel, ILazyLoad public class GridViewModel : EntityViewModel, ILazyLoad
{ {
private MyCubeGrid Grid => (MyCubeGrid)Entity; private MyCubeGrid Grid => (MyCubeGrid)Entity;
public ObservableList<BlockViewModel> Blocks { get; } = new ObservableList<BlockViewModel>(); public MtObservableList<BlockViewModel> Blocks { get; } = new MtObservableList<BlockViewModel>();
/// <inheritdoc /> /// <inheritdoc />
public string DescriptiveName { get; } public string DescriptiveName { get; }
@@ -34,7 +36,7 @@ namespace Torch.Server.ViewModels.Entities
{ {
var block = obj.FatBlock as IMyTerminalBlock; var block = obj.FatBlock as IMyTerminalBlock;
if (block != null) if (block != null)
Blocks.Insert(new BlockViewModel(block, Tree), b => b.Name); Blocks.Add(new BlockViewModel(block, Tree));
OnPropertyChanged(nameof(Name)); OnPropertyChanged(nameof(Name));
} }

View File

@@ -4,6 +4,7 @@ using Sandbox.Game.Entities;
using VRage.Game.Entity; using VRage.Game.Entity;
using VRage.Game.ModAPI; using VRage.Game.ModAPI;
using System.Threading.Tasks; using System.Threading.Tasks;
using Torch.Collections;
namespace Torch.Server.ViewModels.Entities namespace Torch.Server.ViewModels.Entities
{ {
@@ -15,7 +16,7 @@ namespace Torch.Server.ViewModels.Entities
public override bool CanStop => false; public override bool CanStop => false;
public ObservableList<GridViewModel> AttachedGrids { get; } = new ObservableList<GridViewModel>(); public MtObservableList<GridViewModel> AttachedGrids { get; } = new MtObservableList<GridViewModel>();
public async Task UpdateAttachedGrids() public async Task UpdateAttachedGrids()
{ {

View File

@@ -11,16 +11,19 @@ using VRage.Game.ModAPI;
using VRage.ModAPI; using VRage.ModAPI;
using System.Windows.Threading; using System.Windows.Threading;
using NLog; using NLog;
using Torch.Collections;
namespace Torch.Server.ViewModels namespace Torch.Server.ViewModels
{ {
public class EntityTreeViewModel : ViewModel public class EntityTreeViewModel : ViewModel
{ {
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
//TODO: these should be sorted sets for speed //TODO: these should be sorted sets for speed
public ObservableList<GridViewModel> Grids { get; set; } = new ObservableList<GridViewModel>(); public MtObservableList<GridViewModel> Grids { get; set; } = new MtObservableList<GridViewModel>();
public ObservableList<CharacterViewModel> Characters { get; set; } = new ObservableList<CharacterViewModel>(); public MtObservableList<CharacterViewModel> Characters { get; set; } = new MtObservableList<CharacterViewModel>();
public ObservableList<EntityViewModel> FloatingObjects { get; set; } = new ObservableList<EntityViewModel>(); public MtObservableList<EntityViewModel> FloatingObjects { get; set; } = new MtObservableList<EntityViewModel>();
public ObservableList<VoxelMapViewModel> VoxelMaps { get; set; } = new ObservableList<VoxelMapViewModel>(); public MtObservableList<VoxelMapViewModel> VoxelMaps { get; set; } = new MtObservableList<VoxelMapViewModel>();
public Dispatcher ControlDispatcher => _control.Dispatcher; public Dispatcher ControlDispatcher => _control.Dispatcher;
private EntityViewModel _currentEntity; private EntityViewModel _currentEntity;
@@ -29,7 +32,7 @@ namespace Torch.Server.ViewModels
public EntityViewModel CurrentEntity public EntityViewModel CurrentEntity
{ {
get => _currentEntity; get => _currentEntity;
set { _currentEntity = value; OnPropertyChanged(); } set { _currentEntity = value; OnPropertyChanged(nameof(CurrentEntity)); }
} }
public EntityTreeViewModel(UserControl control) public EntityTreeViewModel(UserControl control)
@@ -45,39 +48,55 @@ namespace Torch.Server.ViewModels
private void MyEntities_OnEntityRemove(VRage.Game.Entity.MyEntity obj) private void MyEntities_OnEntityRemove(VRage.Game.Entity.MyEntity obj)
{ {
switch (obj) try
{ {
case MyCubeGrid grid: switch (obj)
Grids.RemoveWhere(m => m.Id == grid.EntityId); {
break; case MyCubeGrid grid:
case MyCharacter character: Grids.RemoveWhere(m => m.Id == grid.EntityId);
Characters.RemoveWhere(m => m.Id == character.EntityId); break;
break; case MyCharacter character:
case MyFloatingObject floating: Characters.RemoveWhere(m => m.Id == character.EntityId);
FloatingObjects.RemoveWhere(m => m.Id == floating.EntityId); break;
break; case MyFloatingObject floating:
case MyVoxelBase voxel: FloatingObjects.RemoveWhere(m => m.Id == floating.EntityId);
VoxelMaps.RemoveWhere(m => m.Id == voxel.EntityId); break;
break; case MyVoxelBase voxel:
VoxelMaps.RemoveWhere(m => m.Id == voxel.EntityId);
break;
}
}
catch (Exception e)
{
_log.Error(e);
// ignore error "it's only UI"
} }
} }
private void MyEntities_OnEntityAdd(VRage.Game.Entity.MyEntity obj) private void MyEntities_OnEntityAdd(VRage.Game.Entity.MyEntity obj)
{ {
switch (obj) try
{ {
case MyCubeGrid grid: switch (obj)
Grids.Insert(new GridViewModel(grid, this), g => g.Name); {
break; case MyCubeGrid grid:
case MyCharacter character: Grids.Add(new GridViewModel(grid, this));
Characters.Insert(new CharacterViewModel(character, this), c => c.Name); break;
break; case MyCharacter character:
case MyFloatingObject floating: Characters.Add(new CharacterViewModel(character, this));
FloatingObjects.Insert(new FloatingObjectViewModel(floating, this), f => f.Name); break;
break; case MyFloatingObject floating:
case MyVoxelBase voxel: FloatingObjects.Add(new FloatingObjectViewModel(floating, this));
VoxelMaps.Insert(new VoxelMapViewModel(voxel, this), v => v.Name); break;
break; case MyVoxelBase voxel:
VoxelMaps.Add(new VoxelMapViewModel(voxel, this));
break;
}
}
catch (Exception e)
{
_log.Error(e);
// ignore error "it's only UI"
} }
} }
} }

View File

@@ -6,18 +6,19 @@ using System.Threading.Tasks;
using Torch.API; using Torch.API;
using Torch.API.Managers; using Torch.API.Managers;
using Torch.API.Plugins; using Torch.API.Plugins;
using Torch.Collections;
namespace Torch.Server.ViewModels namespace Torch.Server.ViewModels
{ {
public class PluginManagerViewModel : ViewModel public class PluginManagerViewModel : ViewModel
{ {
public ObservableList<PluginViewModel> Plugins { get; } = new ObservableList<PluginViewModel>(); public MtObservableList<PluginViewModel> Plugins { get; } = new MtObservableList<PluginViewModel>();
private PluginViewModel _selectedPlugin; private PluginViewModel _selectedPlugin;
public PluginViewModel SelectedPlugin public PluginViewModel SelectedPlugin
{ {
get => _selectedPlugin; get => _selectedPlugin;
set { _selectedPlugin = value; OnPropertyChanged(); } set { _selectedPlugin = value; OnPropertyChanged(nameof(SelectedPlugin)); }
} }
public PluginManagerViewModel() { } public PluginManagerViewModel() { }
@@ -29,7 +30,7 @@ namespace Torch.Server.ViewModels
pluginManager.PluginsLoaded += PluginManager_PluginsLoaded; pluginManager.PluginsLoaded += PluginManager_PluginsLoaded;
} }
private void PluginManager_PluginsLoaded(IList<ITorchPlugin> obj) private void PluginManager_PluginsLoaded(IReadOnlyCollection<ITorchPlugin> obj)
{ {
Plugins.Clear(); Plugins.Clear();
foreach (var plugin in obj) foreach (var plugin in obj)

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using SharpDX.Toolkit.Collections; using SharpDX.Toolkit.Collections;
using Torch.Collections;
using VRage.Game; using VRage.Game;
using VRage.Library.Utils; using VRage.Library.Utils;
@@ -22,7 +23,7 @@ namespace Torch.Server.ViewModels
/// </summary> /// </summary>
public SessionSettingsViewModel() : this(new MyObjectBuilder_SessionSettings()) public SessionSettingsViewModel() : this(new MyObjectBuilder_SessionSettings())
{ {
} }
/// <summary> /// <summary>
@@ -35,7 +36,7 @@ namespace Torch.Server.ViewModels
BlockLimits.Add(new BlockLimitViewModel(this, limit.Key, limit.Value)); BlockLimits.Add(new BlockLimitViewModel(this, limit.Key, limit.Value));
} }
public ObservableList<BlockLimitViewModel> BlockLimits { get; } = new ObservableList<BlockLimitViewModel>(); public MtObservableList<BlockLimitViewModel> BlockLimits { get; } = new MtObservableList<BlockLimitViewModel>();
#region Multipliers #region Multipliers
@@ -74,6 +75,12 @@ namespace Torch.Server.ViewModels
{ {
get => _settings.HackSpeedMultiplier; set { _settings.HackSpeedMultiplier = value; OnPropertyChanged(); } get => _settings.HackSpeedMultiplier; set { _settings.HackSpeedMultiplier = value; OnPropertyChanged(); }
} }
/// <inheritdoc cref="MyObjectBuilder_SessionSettings.WelderSpeedMultiplier"/>
public float WelderSpeedMultiplier
{
get => _settings.WelderSpeedMultiplier; set { _settings.WelderSpeedMultiplier = value; OnPropertyChanged(); }
}
#endregion #endregion
#region NPCs #region NPCs
@@ -356,6 +363,19 @@ namespace Torch.Server.ViewModels
get => _settings.WorldSizeKm; set { _settings.WorldSizeKm = value; OnPropertyChanged(); } get => _settings.WorldSizeKm; set { _settings.WorldSizeKm = value; OnPropertyChanged(); }
} }
/// <inheritdoc cref="MyObjectBuilder_SessionSettings.ProceduralDensity"/>
public float ProceduralDensity
{
get => _settings.ProceduralDensity; set { _settings.ProceduralDensity = value; OnPropertyChanged(); }
}
/// <inheritdoc cref="MyObjectBuilder_SessionSettings.ProceduralSeed"/>
public int ProceduralSeed
{
get => _settings.ProceduralSeed;
set { _settings.ProceduralSeed = value; OnPropertyChanged(); }
}
/// <summary /> /// <summary />
public static implicit operator MyObjectBuilder_SessionSettings(SessionSettingsViewModel viewModel) public static implicit operator MyObjectBuilder_SessionSettings(SessionSettingsViewModel viewModel)
{ {

View File

@@ -10,20 +10,9 @@
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<ListView Grid.Row="0" x:Name="ChatItems" ItemsSource="{Binding ChatHistory}" Margin="5,5,5,5"> <ScrollViewer x:Name="ChatScroller" Grid.Row="0" Margin="5,5,5,5" HorizontalScrollBarVisibility="Disabled">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"/> <TextBlock x:Name="ChatItems" />
<ListView.ItemTemplate> </ScrollViewer>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Timestamp}"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Message}"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Row="1"> <Grid Grid.Row="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition/> <ColumnDefinition/>

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
@@ -20,7 +21,11 @@ using Sandbox.Engine.Multiplayer;
using Sandbox.Game.World; using Sandbox.Game.World;
using SteamSDK; using SteamSDK;
using Torch.API; using Torch.API;
using Torch.API.Managers;
using Torch.API.Session;
using Torch.Managers; using Torch.Managers;
using Torch.Server.Managers;
using VRage.Game;
namespace Torch.Server namespace Torch.Server
{ {
@@ -30,7 +35,6 @@ namespace Torch.Server
public partial class ChatControl : UserControl public partial class ChatControl : UserControl
{ {
private TorchBase _server; private TorchBase _server;
private MultiplayerManager _multiplayer;
public ChatControl() public ChatControl()
{ {
@@ -40,23 +44,77 @@ namespace Torch.Server
public void BindServer(ITorchServer server) public void BindServer(ITorchServer server)
{ {
_server = (TorchBase)server; _server = (TorchBase)server;
_multiplayer = (MultiplayerManager)server.Multiplayer; Dispatcher.InvokeAsync(() =>
ChatItems.Items.Clear(); {
DataContext = _multiplayer; ChatItems.Inlines.Clear();
if (_multiplayer.ChatHistory is INotifyCollectionChanged ncc) });
ncc.CollectionChanged += ChatHistory_CollectionChanged;
var sessionManager = server.Managers.GetManager<ITorchSessionManager>();
if (sessionManager != null)
sessionManager.SessionStateChanged += SessionStateChanged;
} }
private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) private void SessionStateChanged(ITorchSession session, TorchSessionState state)
{ {
if (VisualTreeHelper.GetChildrenCount(ChatItems) > 0) switch (state)
{ {
Border border = (Border)VisualTreeHelper.GetChild(ChatItems, 0); case TorchSessionState.Loading:
ScrollViewer scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0); Dispatcher.InvokeAsync(() => ChatItems.Inlines.Clear());
scrollViewer.ScrollToBottom(); break;
case TorchSessionState.Loaded:
{
var chatMgr = session.Managers.GetManager<IChatManagerClient>();
if (chatMgr != null)
chatMgr.MessageRecieved += OnMessageRecieved;
}
break;
case TorchSessionState.Unloading:
{
var chatMgr = session.Managers.GetManager<IChatManagerClient>();
if (chatMgr != null)
chatMgr.MessageRecieved -= OnMessageRecieved;
}
break;
case TorchSessionState.Unloaded:
break;
default:
throw new ArgumentOutOfRangeException(nameof(state), state, null);
} }
} }
private void OnMessageRecieved(TorchChatMessage msg, ref bool consumed)
{
InsertMessage(msg);
}
private static readonly Dictionary<string, Brush> _brushes = new Dictionary<string, Brush>();
private static Brush LookupBrush(string font)
{
if (_brushes.TryGetValue(font, out Brush result))
return result;
Brush brush = typeof(Brushes).GetField(font, BindingFlags.Static)?.GetValue(null) as Brush ?? Brushes.Blue;
_brushes.Add(font, brush);
return brush;
}
private void InsertMessage(TorchChatMessage msg)
{
if (Dispatcher.CheckAccess())
{
bool atBottom = ChatScroller.VerticalOffset + 8 > ChatScroller.ScrollableHeight;
var span = new Span();
span.Inlines.Add($"{msg.Timestamp} ");
span.Inlines.Add(new Run(msg.Author) { Foreground = LookupBrush(msg.Font) });
span.Inlines.Add($": {msg.Message}");
span.Inlines.Add(new LineBreak());
ChatItems.Inlines.Add(span);
if (atBottom)
ChatScroller.ScrollToBottom();
}
else
Dispatcher.InvokeAsync(() => InsertMessage(msg));
}
private void SendButton_Click(object sender, RoutedEventArgs e) private void SendButton_Click(object sender, RoutedEventArgs e)
{ {
OnMessageEntered(); OnMessageEntered();
@@ -75,27 +133,22 @@ namespace Torch.Server
if (string.IsNullOrEmpty(text)) if (string.IsNullOrEmpty(text))
return; return;
var commands = _server.Commands; var commands = _server.CurrentSession?.Managers.GetManager<Torch.Commands.CommandManager>();
if (commands.IsCommand(text)) if (commands != null && commands.IsCommand(text))
{ {
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text)); InsertMessage(new TorchChatMessage("Server", text, MyFontEnum.DarkBlue));
_server.Invoke(() => _server.Invoke(() =>
{ {
var response = commands.HandleCommandFromServer(text); string response = commands.HandleCommandFromServer(text);
Dispatcher.BeginInvoke(() => OnMessageEntered_Callback(response)); if (!string.IsNullOrWhiteSpace(response))
InsertMessage(new TorchChatMessage("Server", response, MyFontEnum.Blue));
}); });
} }
else else
{ {
_server.Multiplayer.SendMessage(text); _server.CurrentSession?.Managers.GetManager<IChatManagerClient>().SendMessageAsSelf(text);
} }
Message.Text = ""; Message.Text = "";
} }
private void OnMessageEntered_Callback(string response)
{
if (!string.IsNullOrEmpty(response))
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
}
} }
} }

View File

@@ -45,7 +45,6 @@
<Label Content=":" Width="12" /> <Label Content=":" Width="12" />
<TextBox Text="{Binding Port}" Width="48" Height="20" /> <TextBox Text="{Binding Port}" Width="48" Height="20" />
</StackPanel> </StackPanel>
<CheckBox IsChecked="{Binding IgnoreLastSession}" Content="Ignore Last Session" Margin="3" />
<CheckBox IsChecked="{Binding PauseGameWhenEmpty}" Content="Pause When Empty" Margin="3" /> <CheckBox IsChecked="{Binding PauseGameWhenEmpty}" Content="Pause When Empty" Margin="3" />
</StackPanel> </StackPanel>
<StackPanel Margin="3"> <StackPanel Margin="3">
@@ -104,6 +103,10 @@
<TextBox Text="{Binding AssemblerSpeedMultiplier}" Margin="3" Width="70" /> <TextBox Text="{Binding AssemblerSpeedMultiplier}" Margin="3" Width="70" />
<Label Content="Assembler Speed" /> <Label Content="Assembler Speed" />
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding WelderSpeedMultiplier}" Margin="3" Width="70" />
<Label Content="Welder Speed" />
</StackPanel>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<TextBox Text="{Binding GrinderSpeedMultiplier}" Margin="3" Width="70" /> <TextBox Text="{Binding GrinderSpeedMultiplier}" Margin="3" Width="70" />
<Label Content="Grinder Speed" /> <Label Content="Grinder Speed" />
@@ -170,6 +173,14 @@
DockPanel.Dock="Left" /> DockPanel.Dock="Left" />
<Label Content="Environment Hostility" /> <Label Content="Environment Hostility" />
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding ProceduralDensity}" Margin="3" Width="70" />
<Label Content="Procedural Density" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding ProceduralSeed}" Margin="3" Width="70" />
<Label Content="Procedural Seed" />
</StackPanel>
</StackPanel> </StackPanel>
</Expander> </Expander>
<Expander Header="Players"> <Expander Header="Players">

View File

@@ -1,34 +1,8 @@
using System; using System.Windows;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data; using Torch.API.Managers;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml.Serialization;
using NLog;
using Sandbox;
using Sandbox.Engine.Networking;
using Sandbox.Engine.Utils;
using Torch.Server.Managers; using Torch.Server.Managers;
using Torch.Server.ViewModels; using Torch.Server.ViewModels;
using Torch.Views;
using VRage;
using VRage.Dedicated;
using VRage.Game;
using VRage.ObjectBuilders;
using Path = System.IO.Path;
namespace Torch.Server.Views namespace Torch.Server.Views
{ {
@@ -42,7 +16,7 @@ namespace Torch.Server.Views
public ConfigControl() public ConfigControl()
{ {
InitializeComponent(); InitializeComponent();
_instanceManager = TorchBase.Instance.GetManager<InstanceManager>(); _instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>();
DataContext = _instanceManager.DedicatedConfig; DataContext = _instanceManager.DedicatedConfig;
} }

View File

@@ -5,6 +5,8 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Torch.Server.Views.Blocks" xmlns:local="clr-namespace:Torch.Server.Views.Blocks"
xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks" xmlns:blocks="clr-namespace:Torch.Server.ViewModels.Blocks"
xmlns:entities="clr-namespace:Torch.Server.Views.Entities"
xmlns:entities1="clr-namespace:Torch.Server.ViewModels.Entities"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.DataContext> <UserControl.DataContext>
<blocks:BlockViewModel /> <blocks:BlockViewModel />
@@ -12,6 +14,7 @@
<Grid Margin="3"> <Grid Margin="3">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
<RowDefinition/> <RowDefinition/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel Grid.Row="0"> <StackPanel Grid.Row="0">
@@ -22,22 +25,27 @@
</StackPanel> </StackPanel>
<Label Content="Properties"/> <Label Content="Properties"/>
</StackPanel> </StackPanel>
<ListView Grid.Row="1" ItemsSource="{Binding Properties}" Margin="3" IsEnabled="True"> <Expander Grid.Row="1" Header="Block Properties">
<ListView.ItemTemplate> <ListView ItemsSource="{Binding Properties}" Margin="3" IsEnabled="True">
<DataTemplate> <ListView.ItemTemplate>
<local:PropertyView /> <DataTemplate>
</DataTemplate> <local:PropertyView />
</ListView.ItemTemplate> </DataTemplate>
<ListView.ItemContainerStyle> </ListView.ItemTemplate>
<Style TargetType="ListViewItem"> <ListView.ItemContainerStyle>
<Setter Property="HorizontalContentAlignment" <Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" /> Value="Stretch" />
<Setter Property="VerticalContentAlignment" <Setter Property="VerticalContentAlignment"
Value="Center" /> Value="Center" />
<Setter Property="Focusable" <Setter Property="Focusable"
Value="false" /> Value="false" />
</Style> </Style>
</ListView.ItemContainerStyle> </ListView.ItemContainerStyle>
</ListView> </ListView>
</Expander>
<ScrollViewer Grid.Row="2" Margin="3" VerticalScrollBarVisibility="Auto">
<entities:EntityControlsView DataContext="{Binding}"/>
</ScrollViewer>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -0,0 +1,8 @@
<UserControl x:Class="Torch.Server.Views.Entities.EntityControlHost"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
</UserControl>

View File

@@ -0,0 +1,72 @@
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using Torch.Server.Managers;
using Torch.API.Managers;
using Torch.Server.ViewModels.Entities;
namespace Torch.Server.Views.Entities
{
/// <summary>
/// Interaction logic for EntityControlHost.xaml
/// </summary>
public partial class EntityControlHost : UserControl
{
public EntityControlHost()
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is ViewModel vmo)
{
vmo.PropertyChanged -= DataContext_OnPropertyChanged;
}
if (e.NewValue is ViewModel vmn)
{
vmn.PropertyChanged += DataContext_OnPropertyChanged;
}
RefreshControl();
}
private void DataContext_OnPropertyChanged(object sender, PropertyChangedEventArgs pa)
{
if (pa.PropertyName.Equals(EntityControlViewModel.SignalPropertyInvalidateControl))
RefreshControl();
else if (pa.PropertyName.Equals(nameof(EntityControlViewModel.Hide)))
RefreshVisibility();
}
private Control _currentControl;
private void RefreshControl()
{
if (Dispatcher.Thread != Thread.CurrentThread)
{
Dispatcher.InvokeAsync(RefreshControl);
return;
}
_currentControl = DataContext is EntityControlViewModel ecvm
? TorchBase.Instance?.Managers.GetManager<EntityControlManager>()?.CreateControl(ecvm)
: null;
Content = _currentControl;
RefreshVisibility();
}
private void RefreshVisibility()
{
if (Dispatcher.Thread != Thread.CurrentThread)
{
Dispatcher.InvokeAsync(RefreshVisibility);
return;
}
Visibility = (DataContext is EntityControlViewModel ecvm) && !ecvm.Hide && _currentControl != null
? Visibility.Visible
: Visibility.Collapsed;
}
}
}

View File

@@ -0,0 +1,31 @@
<ItemsControl x:Class="Torch.Server.Views.Entities.EntityControlsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:entities="clr-namespace:Torch.Server.Views.Entities"
xmlns:modelsEntities="clr-namespace:Torch.Server.ViewModels.Entities"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding EntityControls}">
<ItemsControl.DataContext>
<modelsEntities:EntityViewModel/>
</ItemsControl.DataContext>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<entities:EntityControlHost DataContext="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Control.VerticalContentAlignment" Value="Stretch"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>

View File

@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Torch.Server.Views.Entities
{
/// <summary>
/// Interaction logic for EntityControlsView.xaml
/// </summary>
public partial class EntityControlsView : ItemsControl
{
public EntityControlsView()
{
InitializeComponent();
}
}
}

View File

@@ -3,20 +3,28 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Torch.Server.Views.Entities"
xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities" xmlns:entities="clr-namespace:Torch.Server.ViewModels.Entities"
xmlns:local="clr-namespace:Torch.Server.Views.Entities"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.DataContext> <UserControl.DataContext>
<entities:GridViewModel /> <entities:GridViewModel />
</UserControl.DataContext> </UserControl.DataContext>
<StackPanel> <Grid>
<StackPanel Orientation="Horizontal"> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label Content="Name" Width="100"/> <Label Content="Name" Width="100"/>
<TextBox Text="{Binding Name}" Margin="3"/> <TextBox Text="{Binding Name}" Margin="3"/>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal"> <StackPanel Grid.Row="1" Orientation="Horizontal">
<Label Content="Position" Width="100"/> <Label Content="Position" Width="100"/>
<TextBox Text="{Binding Position}" Margin="3" /> <TextBox Text="{Binding Position}" Margin="3" />
</StackPanel> </StackPanel>
</StackPanel> <ScrollViewer Grid.Row="2" Margin="3" VerticalScrollBarVisibility="Auto">
<local:EntityControlsView DataContext="{Binding}"/>
</ScrollViewer>
</Grid>
</UserControl> </UserControl>

View File

@@ -9,14 +9,23 @@
<UserControl.DataContext> <UserControl.DataContext>
<entities:VoxelMapViewModel/> <entities:VoxelMapViewModel/>
</UserControl.DataContext> </UserControl.DataContext>
<StackPanel> <Grid>
<Label Content="Attached Grids"></Label> <Grid.RowDefinitions>
<ListView ItemsSource="{Binding AttachedGrids}" Margin="3"> <RowDefinition Height="Auto"/>
<ListView.ItemTemplate> <RowDefinition Height="*"/>
<DataTemplate> </Grid.RowDefinitions>
<TextBlock Text="{Binding Name}"/> <Expander Grid.Row="0" Header="Attached Grids">
</DataTemplate> <ListView ItemsSource="{Binding AttachedGrids}" Margin="3">
</ListView.ItemTemplate> <ListView.ItemTemplate>
</ListView> <DataTemplate>
</StackPanel> <TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
<ScrollViewer Grid.Row="1" Margin="3" VerticalScrollBarVisibility="Auto">
<local:EntityControlsView DataContext="{Binding}"/>
</ScrollViewer>
</Grid>
</UserControl> </UserControl>

View File

@@ -12,6 +12,7 @@ using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Navigation; using System.Windows.Navigation;
using System.Windows.Shapes; using System.Windows.Shapes;
using NLog;
using Torch; using Torch;
using Sandbox; using Sandbox;
using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Multiplayer;
@@ -20,7 +21,10 @@ using Sandbox.Game.World;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using SteamSDK; using SteamSDK;
using Torch.API; using Torch.API;
using Torch.API.Managers;
using Torch.API.Session;
using Torch.Managers; using Torch.Managers;
using Torch.Server.Managers;
using Torch.ViewModels; using Torch.ViewModels;
using VRage.Game.ModAPI; using VRage.Game.ModAPI;
@@ -31,6 +35,8 @@ namespace Torch.Server
/// </summary> /// </summary>
public partial class PlayerListControl : UserControl public partial class PlayerListControl : UserControl
{ {
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
private ITorchServer _server; private ITorchServer _server;
public PlayerListControl() public PlayerListControl()
@@ -41,19 +47,48 @@ namespace Torch.Server
public void BindServer(ITorchServer server) public void BindServer(ITorchServer server)
{ {
_server = server; _server = server;
DataContext = (MultiplayerManager)_server.Multiplayer;
var sessionManager = server.Managers.GetManager<ITorchSessionManager>();
sessionManager.SessionStateChanged += SessionStateChanged;
}
private void SessionStateChanged(ITorchSession session, TorchSessionState newState)
{
switch (newState)
{
case TorchSessionState.Loaded:
Dispatcher.InvokeAsync(() => DataContext = _server?.CurrentSession?.Managers.GetManager<MultiplayerManagerDedicated>());
break;
case TorchSessionState.Unloading:
Dispatcher.InvokeAsync(() => DataContext = null);
break;
}
} }
private void KickButton_Click(object sender, RoutedEventArgs e) private void KickButton_Click(object sender, RoutedEventArgs e)
{ {
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem; var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
_server.Multiplayer.KickPlayer(player.Key); try
{
_server.CurrentSession.Managers.GetManager<IMultiplayerManagerServer>().KickPlayer(player.Key);
}
catch (Exception ex)
{
_log.Warn(ex);
}
} }
private void BanButton_Click(object sender, RoutedEventArgs e) private void BanButton_Click(object sender, RoutedEventArgs e)
{ {
var player = (KeyValuePair<ulong, PlayerViewModel>) PlayerList.SelectedItem; var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
_server.Multiplayer.BanPlayer(player.Key); try
{
_server.CurrentSession.Managers.GetManager<IMultiplayerManagerServer>().BanPlayer(player.Key);
}
catch (Exception ex)
{
_log.Warn(ex);
}
} }
} }
} }

View File

@@ -12,7 +12,7 @@
</UserControl.DataContext> </UserControl.DataContext>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/> <ColumnDefinition Width="200"/>
<ColumnDefinition/> <ColumnDefinition/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid Grid.Column="0"> <Grid Grid.Column="0">
@@ -27,7 +27,7 @@
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
</ListView> </ListView>
<Button Grid.Row="1" Content="Open Folder" Margin="3" DockPanel.Dock="Bottom" IsEnabled="false"/> <Button Grid.Row="1" Content="Open Folder" Margin="3" DockPanel.Dock="Bottom" Click="OpenFolder_OnClick"/>
</Grid> </Grid>
<Frame Grid.Column="1" NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/> <Frame Grid.Column="1" NavigationUIVisibility="Hidden" Content="{Binding SelectedPlugin.Control}"/>
</Grid> </Grid>

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -15,6 +16,8 @@ using System.Windows.Navigation;
using System.Windows.Shapes; using System.Windows.Shapes;
using NLog; using NLog;
using Torch.API; using Torch.API;
using Torch.API.Managers;
using Torch.Managers;
using Torch.Server.ViewModels; using Torch.Server.ViewModels;
namespace Torch.Server.Views namespace Torch.Server.Views
@@ -24,6 +27,9 @@ namespace Torch.Server.Views
/// </summary> /// </summary>
public partial class PluginsControl : UserControl public partial class PluginsControl : UserControl
{ {
private ITorchServer _server;
private PluginManager _plugins;
public PluginsControl() public PluginsControl()
{ {
InitializeComponent(); InitializeComponent();
@@ -31,8 +37,15 @@ namespace Torch.Server.Views
public void BindServer(ITorchServer server) public void BindServer(ITorchServer server)
{ {
var pluginManager = new PluginManagerViewModel(server.Plugins); _server = server;
_plugins = _server.Managers.GetManager<PluginManager>();
var pluginManager = new PluginManagerViewModel(_plugins);
DataContext = pluginManager; DataContext = pluginManager;
} }
private void OpenFolder_OnClick(object sender, RoutedEventArgs e)
{
Process.Start("explorer.exe", _plugins.PluginDir);
}
} }
} }

View File

@@ -65,8 +65,8 @@ namespace Torch.Server
private void BtnStart_Click(object sender, RoutedEventArgs e) private void BtnStart_Click(object sender, RoutedEventArgs e)
{ {
_config.Save(); _server.GetManager<InstanceManager>().SaveConfig();
new Thread(_server.Start).Start(); _server.Start();
} }
private void BtnStop_Click(object sender, RoutedEventArgs e) private void BtnStop_Click(object sender, RoutedEventArgs e)
@@ -80,7 +80,6 @@ namespace Torch.Server
_config.WindowSize = newSize; _config.WindowSize = newSize;
var newPos = new Point((int)Left, (int)Top); var newPos = new Point((int)Left, (int)Top);
_config.WindowPosition = newPos; _config.WindowPosition = newPos;
_config.Save();
if (_server?.State == ServerState.Running) if (_server?.State == ServerState.Running)
_server.Stop(); _server.Stop();

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Mono.TextTransform" version="1.0.0" targetFramework="net461" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" /> <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net461" />
<package id="NLog" version="4.4.11" targetFramework="net461" /> <package id="NLog" version="4.4.12" targetFramework="net461" />
</packages> </packages>

386
Torch.Tests/PatchTest.cs Normal file
View File

@@ -0,0 +1,386 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL;
using Torch.Utils;
using Xunit;
// ReSharper disable UnusedMember.Local
namespace Torch.Tests
{
#pragma warning disable 414
public class PatchTest
{
#region TestRunner
private static readonly PatchManager _patchContext = new PatchManager(null);
[Theory]
[MemberData(nameof(Prefixes))]
public void TestPrefix(TestBootstrap runner)
{
runner.TestPrefix();
}
[Theory]
[MemberData(nameof(Transpilers))]
public void TestTranspile(TestBootstrap runner)
{
runner.TestTranspile();
}
[Theory]
[MemberData(nameof(Suffixes))]
public void TestSuffix(TestBootstrap runner)
{
runner.TestSuffix();
}
[Theory]
[MemberData(nameof(Combo))]
public void TestCombo(TestBootstrap runner)
{
runner.TestCombo();
}
public class TestBootstrap
{
public bool HasPrefix => _prefixMethod != null;
public bool HasTranspile => _transpileMethod != null;
public bool HasSuffix => _suffixMethod != null;
private readonly MethodInfo _prefixMethod, _prefixAssert;
private readonly MethodInfo _suffixMethod, _suffixAssert;
private readonly MethodInfo _transpileMethod, _transpileAssert;
private readonly MethodInfo _targetMethod, _targetAssert;
private readonly MethodInfo _resetMethod;
private readonly object _instance;
private readonly object[] _targetParams;
private readonly Type _type;
public TestBootstrap(Type t)
{
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
_type = t;
_prefixMethod = t.GetMethod("Prefix", flags);
_prefixAssert = t.GetMethod("AssertPrefix", flags);
_suffixMethod = t.GetMethod("Suffix", flags);
_suffixAssert = t.GetMethod("AssertSuffix", flags);
_transpileMethod = t.GetMethod("Transpile", flags);
_transpileAssert = t.GetMethod("AssertTranspile", flags);
_targetMethod = t.GetMethod("Target", flags);
_targetAssert = t.GetMethod("AssertNormal", flags);
_resetMethod = t.GetMethod("Reset", flags);
if (_targetMethod == null)
throw new Exception($"{t.FullName} must have a method named Target");
if (_targetAssert == null)
throw new Exception($"{t.FullName} must have a method named AssertNormal");
_instance = !_targetMethod.IsStatic ? Activator.CreateInstance(t) : null;
_targetParams = (object[])t.GetField("_targetParams", flags)?.GetValue(null) ?? new object[0];
}
private void Invoke(MethodBase i, params object[] args)
{
if (i == null) return;
i.Invoke(i.IsStatic ? null : _instance, args);
}
private void Invoke()
{
_targetMethod.Invoke(_instance, _targetParams);
Invoke(_targetAssert);
}
public void TestPrefix()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
context.GetPattern(_targetMethod).Prefixes.Add(_prefixMethod);
_patchContext.Commit();
Invoke();
Invoke(_prefixAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public void TestSuffix()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
context.GetPattern(_targetMethod).Suffixes.Add(_suffixMethod);
_patchContext.Commit();
Invoke();
Invoke(_suffixAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public void TestTranspile()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
context.GetPattern(_targetMethod).Transpilers.Add(_transpileMethod);
_patchContext.Commit();
Invoke();
Invoke(_transpileAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public void TestCombo()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
if (_prefixMethod != null)
context.GetPattern(_targetMethod).Prefixes.Add(_prefixMethod);
if (_transpileMethod != null)
context.GetPattern(_targetMethod).Transpilers.Add(_transpileMethod);
if (_suffixMethod != null)
context.GetPattern(_targetMethod).Suffixes.Add(_suffixMethod);
_patchContext.Commit();
Invoke();
Invoke(_prefixAssert);
Invoke(_transpileAssert);
Invoke(_suffixAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public override string ToString()
{
return _type.Name;
}
}
private class PatchTestAttribute : Attribute
{
}
private static readonly List<TestBootstrap> _patchTest;
static PatchTest()
{
TestUtils.Init();
foreach (Type type in typeof(PatchManager).Assembly.GetTypes())
if (type.Namespace?.StartsWith(typeof(PatchManager).Namespace ?? "") ?? false)
ReflectedManager.Process(type);
_patchTest = new List<TestBootstrap>();
foreach (Type type in typeof(PatchTest).GetNestedTypes(BindingFlags.NonPublic))
if (type.GetCustomAttribute(typeof(PatchTestAttribute)) != null)
_patchTest.Add(new TestBootstrap(type));
}
public static IEnumerable<object[]> Prefixes => _patchTest.Where(x => x.HasPrefix).Select(x => new object[] { x });
public static IEnumerable<object[]> Transpilers => _patchTest.Where(x => x.HasTranspile).Select(x => new object[] { x });
public static IEnumerable<object[]> Suffixes => _patchTest.Where(x => x.HasSuffix).Select(x => new object[] { x });
public static IEnumerable<object[]> Combo => _patchTest.Where(x => x.HasPrefix || x.HasTranspile || x.HasSuffix).Select(x => new object[] { x });
#endregion
#region PatchTests
[PatchTest]
private class StaticNoRetNoParm
{
private static bool _prefixHit, _normalHit, _suffixHit, _transpileHit;
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Prefix()
{
_prefixHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target()
{
_normalHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Suffix()
{
_suffixHit = true;
}
public static IEnumerable<MsilInstruction> Transpile(IEnumerable<MsilInstruction> instructions)
{
yield return new MsilInstruction(OpCodes.Ldnull);
yield return new MsilInstruction(OpCodes.Ldc_I4_1);
yield return new MsilInstruction(OpCodes.Stfld).InlineValue(typeof(StaticNoRetNoParm).GetField("_transpileHit", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public));
foreach (MsilInstruction i in instructions)
yield return i;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = _transpileHit = false;
}
public static void AssertTranspile()
{
Assert.True(_transpileHit, "Failed to transpile");
}
public static void AssertSuffix()
{
Assert.True(_suffixHit, "Failed to suffix");
}
public static void AssertNormal()
{
Assert.True(_normalHit, "Failed to execute normally");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
}
}
[PatchTest]
private class StaticNoRetParam
{
private static bool _prefixHit, _normalHit, _suffixHit;
private static readonly object[] _targetParams = { "test", 1, new StringBuilder("test1") };
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Prefix(string str, int i, StringBuilder o)
{
Assert.Equal(_targetParams[0], str);
Assert.Equal(_targetParams[1], i);
Assert.Equal(_targetParams[2], o);
_prefixHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target(string str, int i, StringBuilder o)
{
_normalHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Suffix(string str, int i, StringBuilder o)
{
Assert.Equal(_targetParams[0], str);
Assert.Equal(_targetParams[1], i);
Assert.Equal(_targetParams[2], o);
_suffixHit = true;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = false;
}
public static void AssertSuffix()
{
Assert.True(_suffixHit, "Failed to suffix");
}
public static void AssertNormal()
{
Assert.True(_normalHit, "Failed to execute normally");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
}
}
[PatchTest]
private class StaticNoRetParamReplace
{
private static bool _prefixHit, _normalHit, _suffixHit;
private static readonly object[] _targetParams = { "test", 1, new StringBuilder("stest1") };
private static readonly object[] _replacedParams = { "test2", 2, new StringBuilder("stest2") };
private static object[] _calledParams;
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Prefix(ref string str, ref int i, ref StringBuilder o)
{
Assert.Equal(_targetParams[0], str);
Assert.Equal(_targetParams[1], i);
Assert.Equal(_targetParams[2], o);
str = (string)_replacedParams[0];
i = (int)_replacedParams[1];
o = (StringBuilder)_replacedParams[2];
_prefixHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target(string str, int i, StringBuilder o)
{
_calledParams = new object[] { str, i, o };
_normalHit = true;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = false;
}
public static void AssertNormal()
{
Assert.True(_normalHit, "Failed to execute normally");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
for (var i = 0; i < 3; i++)
Assert.Equal(_replacedParams[i], _calledParams[i]);
}
}
[PatchTest]
private class StaticCancelExec
{
private static bool _prefixHit, _normalHit, _suffixHit;
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool Prefix()
{
_prefixHit = true;
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target()
{
_normalHit = true;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = false;
}
public static void AssertNormal()
{
Assert.False(_normalHit, "Executed normally when canceled");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
}
}
#endregion
}
#pragma warning restore 414
}

View File

@@ -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 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

View File

@@ -0,0 +1,336 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Torch.Utils;
using Xunit;
namespace Torch.Tests
{
public class ReflectionSystemTest
{
static ReflectionSystemTest()
{
TestUtils.Init();
}
private static ReflectionTestManager _manager = new ReflectionTestManager().Init(typeof(ReflectionTestBinding));
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
#region Results
#region Dummy
private class ReflectionTestTarget
{
public int TestField;
public int TestProperty { get; set; }
/// <summary>
/// Return true when greater or equal than 0
/// </summary>
public bool TestCall(int k)
{
return k >= 0;
}
public static int TestFieldStatic;
public static int TestPropertyStatic { get; set; }
/// <summary>
/// Return true when greater or equal than 0
/// </summary>
public static bool TestCallStatic(int k)
{
return k >= 0;
}
public event Action Event1;
public ReflectionTestTarget()
{
Event1 += Callback1;
}
public bool Callback1Flag = false;
public void Callback1()
{
Callback1Flag = true;
}
public bool Callback2Flag = false;
public void Callback2()
{
Callback2Flag = true;
}
public void RaiseEvent()
{
Event1?.Invoke();
}
}
private class ReflectionTestBinding
{
#region Instance
#region MemberInfo
[ReflectedFieldInfo(typeof(ReflectionTestTarget), "TestField")]
public static FieldInfo TestFieldInfo;
[ReflectedPropertyInfo(typeof(ReflectionTestTarget), "TestProperty")]
public static PropertyInfo TestPropertyInfo;
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall")]
public static MethodInfo TestMethodInfoGeneral;
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall", Parameters = new[] { typeof(int) })]
public static MethodInfo TestMethodInfoExplicitArgs;
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall", ReturnType = typeof(bool))]
public static MethodInfo TestMethodInfoExplicitReturn;
#endregion
[ReflectedGetter(Name = "TestField")]
public static Func<ReflectionTestTarget, int> TestFieldGetter;
[ReflectedSetter(Name = "TestField")]
public static Action<ReflectionTestTarget, int> TestFieldSetter;
[ReflectedGetter(Name = "TestProperty")]
public static Func<ReflectionTestTarget, int> TestPropertyGetter;
[ReflectedSetter(Name = "TestProperty")]
public static Action<ReflectionTestTarget, int> TestPropertySetter;
[ReflectedMethod]
public static Func<ReflectionTestTarget, int, bool> TestCall;
[ReflectedEventReplace(typeof(ReflectionTestTarget), "Event1", typeof(ReflectionTestTarget), "Callback1")]
public static Func<ReflectedEventReplacer> TestEventReplacer;
#endregion
#region Static
#region MemberInfo
[ReflectedFieldInfo(typeof(ReflectionTestTarget), "TestFieldStatic")]
public static FieldInfo TestStaticFieldInfo;
[ReflectedPropertyInfo(typeof(ReflectionTestTarget), "TestPropertyStatic")]
public static PropertyInfo TestStaticPropertyInfo;
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic")]
public static MethodInfo TestStaticMethodInfoGeneral;
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic", Parameters = new[] { typeof(int) })]
public static MethodInfo TestStaticMethodInfoExplicitArgs;
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic", ReturnType = typeof(bool))]
public static MethodInfo TestStaticMethodInfoExplicitReturn;
#endregion
[ReflectedGetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
public static Func<int> TestStaticFieldGetter;
[ReflectedSetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
public static Action<int> TestStaticFieldSetter;
[ReflectedGetter(Name = "TestPropertyStatic", Type = typeof(ReflectionTestTarget))]
public static Func<int> TestStaticPropertyGetter;
[ReflectedSetter(Name = "TestPropertyStatic", Type = typeof(ReflectionTestTarget))]
public static Action<int> TestStaticPropertySetter;
[ReflectedStaticMethod(Type = typeof(ReflectionTestTarget))]
public static Func<int, bool> TestCallStatic;
#endregion
}
#endregion
private readonly Random _rand = new Random();
private int AcquireRandomNum()
{
return _rand.Next();
}
#region Instance
[Fact]
public void TestInstanceFieldGet()
{
ReflectedManager.Process(typeof(ReflectionTestBinding));
int testNumber = AcquireRandomNum();
var target = new ReflectionTestTarget
{
TestField = testNumber
};
Assert.Equal(testNumber, ReflectionTestBinding.TestFieldGetter.Invoke(target));
}
[Fact]
public void TestInstanceFieldSet()
{
ReflectedManager.Process(typeof(ReflectionTestBinding));
int testNumber = AcquireRandomNum();
var target = new ReflectionTestTarget();
ReflectionTestBinding.TestFieldSetter.Invoke(target, testNumber);
Assert.Equal(testNumber, target.TestField);
}
[Fact]
public void TestInstancePropertyGet()
{
ReflectedManager.Process(typeof(ReflectionTestBinding));
int testNumber = AcquireRandomNum();
var target = new ReflectionTestTarget
{
TestProperty = testNumber
};
Assert.Equal(testNumber, ReflectionTestBinding.TestPropertyGetter.Invoke(target));
}
[Fact]
public void TestInstancePropertySet()
{
ReflectedManager.Process(typeof(ReflectionTestBinding));
int testNumber = AcquireRandomNum();
var target = new ReflectionTestTarget();
ReflectionTestBinding.TestPropertySetter.Invoke(target, testNumber);
Assert.Equal(testNumber, target.TestProperty);
}
[Fact]
public void TestInstanceInvoke()
{
ReflectedManager.Process(typeof(ReflectionTestBinding));
var target = new ReflectionTestTarget();
Assert.True(ReflectionTestBinding.TestCall.Invoke(target, 1));
Assert.False(ReflectionTestBinding.TestCall.Invoke(target, -1));
}
#endregion
#region Static
[Fact]
public void TestStaticFieldGet()
{
ReflectedManager.Process(typeof(ReflectionTestBinding));
int testNumber = AcquireRandomNum();
ReflectionTestTarget.TestFieldStatic = testNumber;
Assert.Equal(testNumber, ReflectionTestBinding.TestStaticFieldGetter.Invoke());
}
[Fact]
public void TestStaticFieldSet()
{
ReflectedManager.Process(typeof(ReflectionTestBinding));
int testNumber = AcquireRandomNum();
ReflectionTestBinding.TestStaticFieldSetter.Invoke(testNumber);
Assert.Equal(testNumber, ReflectionTestTarget.TestFieldStatic);
}
[Fact]
public void TestStaticPropertyGet()
{
ReflectedManager.Process(typeof(ReflectionTestBinding));
int testNumber = AcquireRandomNum();
ReflectionTestTarget.TestPropertyStatic = testNumber;
Assert.Equal(testNumber, ReflectionTestBinding.TestStaticPropertyGetter.Invoke());
}
[Fact]
public void TestStaticPropertySet()
{
ReflectedManager.Process(typeof(ReflectionTestBinding));
int testNumber = AcquireRandomNum();
ReflectionTestBinding.TestStaticPropertySetter.Invoke(testNumber);
Assert.Equal(testNumber, ReflectionTestTarget.TestPropertyStatic);
}
[Fact]
public void TestStaticInvoke()
{
ReflectedManager.Process(typeof(ReflectionTestBinding));
Assert.True(ReflectionTestBinding.TestCallStatic.Invoke(1));
Assert.False(ReflectionTestBinding.TestCallStatic.Invoke(-1));
}
[Fact]
public void TestInstanceEventReplace()
{
var target = new ReflectionTestTarget();
target.Callback1Flag = false;
target.RaiseEvent();
Assert.True(target.Callback1Flag, "Control test failed");
target.Callback1Flag = false;
target.Callback2Flag = false;
ReflectedEventReplacer binder = ReflectionTestBinding.TestEventReplacer.Invoke();
Assert.True(binder.Test(target), "Binder was unable to find the requested method");
binder.Replace(new Action(() => target.Callback2()), target);
target.RaiseEvent();
Assert.True(target.Callback2Flag, "Substitute callback wasn't called");
Assert.False(target.Callback1Flag, "Original callback wasn't removed");
target.Callback1Flag = false;
target.Callback2Flag = false;
binder.Restore(target);
target.RaiseEvent();
Assert.False(target.Callback2Flag, "Substitute callback wasn't removed");
Assert.True(target.Callback1Flag, "Original callback wasn't restored");
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Torch.Utils;
namespace Torch.Tests
{
public class ReflectionTestManager
{
#region FieldProvider
public struct FieldRef
{
public FieldInfo Field;
public FieldRef(FieldInfo f)
{
Field = f;
}
public override string ToString()
{
if (Field == null)
return "Ignored";
return Field.DeclaringType?.FullName + "." + Field.Name;
}
}
private readonly HashSet<object[]> _getters = new HashSet<object[]>();
private readonly HashSet<object[]> _setters = new HashSet<object[]>();
private readonly HashSet<object[]> _invokers = new HashSet<object[]>();
private readonly HashSet<object[]> _memberInfo = new HashSet<object[]>();
private readonly HashSet<object[]> _events = new HashSet<object[]>();
public ReflectionTestManager()
{
_getters.Add(new object[] { new FieldRef(null) });
_setters.Add(new object[] { new FieldRef(null) });
_invokers.Add(new object[] { new FieldRef(null) });
_memberInfo.Add(new object[] {new FieldRef(null)});
_events.Add(new object[] {new FieldRef(null)});
}
public ReflectionTestManager Init(Assembly asm)
{
try
{
foreach (Type type in asm.GetTypes())
Init(type);
}
catch (ReflectionTypeLoadException e)
{
throw e.LoaderExceptions[0];
}
return this;
}
public ReflectionTestManager Init(Type type)
{
foreach (FieldInfo field in type.GetFields(BindingFlags.Static |
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic))
{
var args = new object[] { new FieldRef(field) };
foreach (ReflectedMemberAttribute attr in field.GetCustomAttributes<ReflectedMemberAttribute>())
{
if (!field.IsStatic)
throw new ArgumentException("Field must be static to be reflected");
switch (attr)
{
case ReflectedMethodAttribute rma:
_invokers.Add(args);
break;
case ReflectedGetterAttribute rga:
_getters.Add(args);
break;
case ReflectedSetterAttribute rsa:
_setters.Add(args);
break;
case ReflectedFieldInfoAttribute rfia:
case ReflectedPropertyInfoAttribute rpia:
case ReflectedMethodInfoAttribute rmia:
_memberInfo.Add(args);
break;
}
}
var reflectedEventReplacer = field.GetCustomAttribute<ReflectedEventReplaceAttribute>();
if (reflectedEventReplacer != null)
{
if (!field.IsStatic)
throw new ArgumentException("Field must be static to be reflected");
_events.Add(args);
}
}
return this;
}
public IEnumerable<object[]> Getters => _getters;
public IEnumerable<object[]> Setters => _setters;
public IEnumerable<object[]> Invokers => _invokers;
public IEnumerable<object[]> MemberInfo => _memberInfo;
public IEnumerable<object[]> Events => _events;
#endregion
}
}

Some files were not shown because too many files have changed in this diff Show More