1- using CounterStrikeSharp . API ;
2- using CounterStrikeSharp . API . Core ;
3- using CounterStrikeSharp . API . Core . Attributes . Registration ;
4- using CounterStrikeSharp . API . Modules . Admin ;
5- using CounterStrikeSharp . API . Modules . Commands ;
6- using CounterStrikeSharp . API . Modules . Config ;
7- using CounterStrikeSharp . API . Modules . Utils ;
8- using CounterStrikeSharp . API . Modules . Cvars ;
9- using Microsoft . Extensions . Logging ;
10-
11- namespace DemoRecorder ;
12-
13- public class DemoRecorder : BasePlugin , IPluginConfig < PluginConfig >
14- {
15- public override string ModuleName { get ; } = "DemoRecorder" ;
16- public override string ModuleVersion { get ; } = "1.0.0" ;
17- public override string ModuleAuthor { get ; } = "SAPSAN" ;
18-
19- public PluginConfig Config { get ; set ; }
20-
21- public List < CCSPlayerController > connectedPlayers = new List < CCSPlayerController > ( ) ;
22-
23- public string g_sDemosName = "" ,
24- g_sDemosDir = "" ,
25- g_sServerName = "" ;
26-
27- public bool g_bChangeMap , bOldState ;
28-
29- public void OnConfigParsed ( PluginConfig config )
30- {
31- config = ConfigManager . Load < PluginConfig > ( ModuleName ) ;
32- Config = config ;
33- }
34-
35- public override void Load ( bool hotReload )
36- {
37- RegisterEventHandler < EventCsIntermission > ( OnEventCsIntermissionPost ) ;
38- RegisterListener < Listeners . OnMapStart > ( OnMapStartHandler ) ;
39- RegisterListener < Listeners . OnMapEnd > ( OnMapEndHandler ) ;
40-
41- g_sDemosDir = Directory . GetCurrentDirectory ( ) . Replace ( "bin/linuxsteamrt64" , "" ) ;
42- Directory . SetCurrentDirectory ( g_sDemosDir ) ;
43-
44- g_sServerName = ConVar . Find ( "hostname" ) . StringValue ;
45-
46- g_sDemosDir += "/csgo/addons/counterstrikesharp/data/" + Config . DemosDir ;
47-
48- if ( ! Directory . Exists ( g_sDemosDir ) )
49- {
50- Logger . LogInformation ( ">> Create folder for demos: {Folder}." , g_sDemosDir ) ;
51-
52- Directory . CreateDirectory ( g_sDemosDir ) ;
53- }
54- g_sDemosName = new string ( DateTime . Now . ToString ( "dd_MM_yyyy_HH_mm" ) + "-" + Server . MapName + ".dem" ) ;
55-
56- UploadAllDemos ( ) ;
57- }
58-
59- [ GameEventHandler ( mode : HookMode . Post ) ]
60- private HookResult OnEventCsIntermissionPost ( EventCsIntermission @event , GameEventInfo info )
61- {
62- RecordDemo ( false , true ) ;
1+ using CounterStrikeSharp . API ;
2+ using CounterStrikeSharp . API . Core ;
3+ using CounterStrikeSharp . API . Core . Attributes . Registration ;
4+ using CounterStrikeSharp . API . Modules . Admin ;
5+ using CounterStrikeSharp . API . Modules . Commands ;
6+ using CounterStrikeSharp . API . Modules . Config ;
7+ using CounterStrikeSharp . API . Modules . Utils ;
8+ using CounterStrikeSharp . API . Modules . Memory . DynamicFunctions ;
9+ using Microsoft . Extensions . Logging ;
10+ using System . Runtime . InteropServices ;
11+
12+ namespace DemoRecorder ;
13+
14+ public class DemoRecorder : BasePlugin , IPluginConfig < PluginConfig >
15+ {
16+ public override string ModuleName { get ; } = "DemoRecorder" ;
17+ public override string ModuleVersion { get ; } = "1.0.1" ;
18+ public override string ModuleAuthor { get ; } = "SAPSAN" ;
19+ public required PluginConfig Config { get ; set ; }
20+
21+ public List < CCSPlayerController > connectedPlayers = new ( ) ;
22+
23+ private static string g_sDemosName = new ( DateTime . Now . ToString ( "dd_MM_yyyy_HH_mm" ) + "-" + Server . MapName + ".dem" ) ,
24+ g_sDemosDir = Server . GameDirectory ;
25+
26+ private static readonly string g_BinaryPath = g_sDemosDir + "/bin/" + ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) ? "linuxsteamrt64/libengine2.so" : "win64/engine2.dll" ) ,
27+ g_Signature = RuntimeInformation . IsOSPlatform ( OSPlatform . Linux )
28+ ? @"\x55\x48\x89\xE5\x41\x57\x41\x56\x41\x55\x49\x89\xF5\x41\x54\x4C\x8D\x67\x08"
29+ : @"\x40\x55\x56\x41\x57\x48\x8D\x6C\x24\x00\x48\x81\xEC\x00\x00\x00\x00\x80\xB9\x00\x00\x00\x00\x00" ;
30+
31+ public bool g_bChangeMap , bOldState , g_bState ;
32+
33+ public MemoryFunctionVoid < IntPtr , IntPtr > RecordEnd = new ( g_Signature , g_BinaryPath ) ;
34+
35+ private HookResult RecordEndHookResult ( DynamicHook hook )
36+ {
37+ Task . Delay ( 1000 ) . ContinueWith ( ( task ) =>
38+ {
39+ UploadDemo ( g_sDemosDir + g_sDemosName , g_bState ) ;
40+ } ) ;
41+ return HookResult . Continue ;
42+ }
43+
44+ public void OnConfigParsed ( PluginConfig config )
45+ {
46+ config = ConfigManager . Load < PluginConfig > ( ModuleName ) ;
47+ Config = config ;
48+
49+ g_sDemosDir = Server . GameDirectory + "/csgo/addons/counterstrikesharp/data/" + Config . DemosDir ;
50+ CreateDemoDir ( ) ;
51+ }
52+
53+ public override void Load ( bool hotReload )
54+ {
55+ Directory . SetCurrentDirectory ( Server . GameDirectory ) ;
56+
57+ CreateDemoDir ( ) ;
58+
59+ RecordEnd . Hook ( RecordEndHookResult , HookMode . Post ) ;
60+
61+ RegisterEventHandler < EventCsIntermission > ( OnEventCsIntermissionPost ) ;
62+ RegisterListener < Listeners . OnMapStart > ( OnMapStartHandler ) ;
63+ RegisterListener < Listeners . OnMapEnd > ( OnMapEndHandler ) ;
64+ UploadAllDemos ( ) ;
65+ }
66+
67+ public override void Unload ( bool hotReload )
68+ {
69+ RecordEnd . Unhook ( RecordEndHookResult , HookMode . Post ) ;
70+ }
71+
72+ [ GameEventHandler ( mode : HookMode . Post ) ]
73+ private HookResult OnEventCsIntermissionPost ( EventCsIntermission @event , GameEventInfo info )
74+ {
75+ g_bState = true ;
76+ RecordDemo ( false ) ;
6377 g_bChangeMap = true ;
64- return HookResult . Continue ;
65- }
66-
67- [ RequiresPermissions ( "@css/root" ) ]
68- [ ConsoleCommand ( "css_dr_reload" ) ]
69- public void OnReloadCommand ( CCSPlayerController ? controller , CommandInfo info )
70- {
71- OnConfigParsed ( Config ) ;
72- Logger . LogInformation ( ">> Config reloaded!" ) ;
73- controller ? . PrintToChat ( $ " { ChatColors . Red } [Demo Recorder] { ChatColors . Default } Config reloaded { ChatColors . Green } success{ ChatColors . Default } !") ;
74- }
75-
76- [ GameEventHandler ]
77- public HookResult OnPlayerConnectedFull ( EventPlayerConnectFull @event , GameEventInfo info )
78- {
79- var player = @event . Userid ;
80-
81- if ( ! player . IsBot )
82- {
83- connectedPlayers . Add ( player ) ;
84-
78+ return HookResult . Continue ;
79+ }
80+
81+ [ RequiresPermissions ( "@css/root" ) ]
82+ [ ConsoleCommand ( "css_dr_reload" ) ]
83+ public void OnReloadCommand ( CCSPlayerController ? controller , CommandInfo info )
84+ {
85+ OnConfigParsed ( Config ) ;
86+ Logger . LogInformation ( ">> Config reloaded!" ) ;
87+ controller ? . PrintToChat ( $ " { ChatColors . Red } [Demo Recorder] { ChatColors . Default } Config reloaded { ChatColors . Green } success{ ChatColors . Default } !") ;
88+ }
89+
90+ [ GameEventHandler ]
91+ public HookResult OnPlayerConnectedFull ( EventPlayerConnectFull @event , GameEventInfo info )
92+ {
93+ var player = @event . Userid ;
94+
95+ if ( ! player . IsBot )
96+ {
97+ connectedPlayers . Add ( player ) ;
98+
8599 if ( GetActivePlayerCount ( ) >= Config . MinOnline )
86100 {
87- RecordDemo ( true , false ) ;
101+ g_bState = false ;
102+ RecordDemo ( true ) ;
88103 }
89- return HookResult . Continue ;
90- }
91- return HookResult . Continue ;
92- }
93-
94- [ GameEventHandler ]
95- public HookResult OnPlayerDisconnect ( EventPlayerDisconnect @event , GameEventInfo info )
96- {
97- var player = @event . Userid ;
98-
99- connectedPlayers . Remove ( player ) ;
100-
104+ return HookResult . Continue ;
105+ }
106+ return HookResult . Continue ;
107+ }
108+
109+ [ GameEventHandler ]
110+ public HookResult OnPlayerDisconnect ( EventPlayerDisconnect @event , GameEventInfo info )
111+ {
112+ var player = @event . Userid ;
113+
114+ connectedPlayers . Remove ( player ) ;
115+
101116 if ( GetActivePlayerCount ( ) < Config . MinOnline )
102117 {
103- RecordDemo ( false , true ) ;
118+ g_bState = true ;
119+ RecordDemo ( false ) ;
104120
105- }
106- return HookResult . Continue ;
107- }
108- private void OnMapStartHandler ( string mapName )
109- {
110- g_bChangeMap = false ;
111- g_sDemosName = new string ( DateTime . Now . ToString ( "dd_MM_yyyy_HH_mm" ) + "-" + mapName + ".dem" ) ;
121+ }
122+ return HookResult . Continue ;
112123 }
113124
114- private void OnMapEndHandler ( )
115- {
125+ private void OnMapStartHandler ( string mapName )
126+ {
127+ g_bChangeMap = false ;
128+ g_sDemosName = new string ( DateTime . Now . ToString ( "dd_MM_yyyy_HH_mm" ) + "-" + mapName + ".dem" ) ;
129+ }
130+
131+ private void CreateDemoDir ( )
132+ {
133+ if ( ! Directory . Exists ( g_sDemosDir ) )
134+ {
135+ Logger . LogInformation ( ">> Create folder for demos: {Folder}." , g_sDemosDir ) ;
136+
137+ Directory . CreateDirectory ( g_sDemosDir ) ;
138+ }
139+ }
140+
141+ private void OnMapEndHandler ( )
142+ {
116143 if ( ! g_bChangeMap )
117144 {
118- RecordDemo ( false , false ) ;
145+ g_bState = false ;
146+ RecordDemo ( false ) ;
119147 g_bChangeMap = true ;
120- }
121- }
122-
123- private void RecordDemo ( bool bState , bool State )
124- {
125- if ( g_bChangeMap )
126- {
127- return ;
128148 }
149+ }
129150
130- if ( bState && ! bOldState )
131- {
151+ private void RecordDemo ( bool bState )
152+ {
153+ if ( g_bChangeMap )
154+ {
155+ return ;
156+ }
157+
158+ if ( bState && ! bOldState )
159+ {
132160 bOldState = true ;
133161
134- Server . ExecuteCommand ( $ "tv_record \" addons/counterstrikesharp/data/{ Config . DemosDir } { g_sDemosName } \" ") ;
135-
136- Logger . LogInformation ( ">> Recording start ({Name})." , g_sDemosName ) ;
137- }
138- else if ( ! bState && bOldState )
139- {
140- bOldState = false ;
141- Server . ExecuteCommand ( $ "tv_stoprecord") ;
142- Logger . LogInformation ( ">> Recording stop ({Name})." , g_sDemosName ) ;
162+ Server . ExecuteCommand ( $ "tv_record \" addons/counterstrikesharp/data/{ Config . DemosDir } { g_sDemosName } \" ") ;
163+
164+ Logger . LogInformation ( ">> Recording start ({Name})." , g_sDemosName ) ;
165+ }
166+ else if ( ! bState && bOldState )
167+ {
168+ bOldState = false ;
169+ Server . ExecuteCommand ( $ "tv_stoprecord") ;
170+ Logger . LogInformation ( ">> Recording stop ({Name})." , g_sDemosName ) ;
143171
144- Task . Delay ( 800 ) . ContinueWith ( ( task ) =>
145- {
146- UploadDemo ( g_sDemosDir + g_sDemosName , State ) ;
147- } ) ;
148- }
149- }
150-
172+ }
173+ }
174+
151175 async void UploadDemo ( string path , bool bUploadOld = false )
152176 {
153177 if ( ! File . Exists ( path ) ) return ;
@@ -166,16 +190,15 @@ async void UploadDemo(string path, bool bUploadOld = false)
166190 }
167191 catch ( Exception ex )
168192 {
169- Logger . LogInformation ( ">> HttpPut Exception: {ex}" , ex ) ;
193+ Logger . LogInformation ( ">> UploadDemo Exception: {ex}" , ex ) ;
170194 }
171195
172196 if ( bUploadOld )
173197 {
174198 UploadAllDemos ( ) ;
175199 }
176-
177- }
178-
200+ }
201+
179202 public async Task < string > UploadFile ( string path )
180203 {
181204 using var client = new HttpClient ( ) ;
@@ -188,8 +211,6 @@ public async Task<string> UploadFile(string path)
188211 using var req = new HttpRequestMessage ( HttpMethod . Put , Config . UploadUrl ) ;
189212 {
190213 req . Headers . Add ( "Auth" , Config . Token ) ;
191- req . Headers . Add ( "Server-Name" , g_sServerName ) ;
192- req . Headers . Add ( "Map-Name" , path . Split ( '-' ) . Last ( ) . Replace ( ".dem" , "" ) ) ;
193214 req . Headers . Add ( "Demo-Name" , path . Split ( '/' ) . Last ( ) ) ;
194215 req . Headers . Add ( "Demo-ServerId" , Config . ServerId . ToString ( ) ) ;
195216 req . Headers . Add ( "Demo-Time" , File . GetCreationTime ( path ) . ToString ( ) ) ;
@@ -204,18 +225,19 @@ public async Task<string> UploadFile(string path)
204225 }
205226 }
206227 }
207- }
228+ }
229+
208230 void UploadAllDemos ( )
209231 {
210232 foreach ( string file in Directory . GetFiles ( g_sDemosDir ) )
211233 {
212234 Logger . LogInformation ( ">> Try upload old demo: {File}" , file ) ;
213235 UploadDemo ( file ) ;
214236 }
215- }
216-
237+ }
238+
217239 private int GetActivePlayerCount ( )
218240 {
219241 return connectedPlayers . Count ;
220- }
221- }
242+ }
243+ }
0 commit comments