~nickwinston123/armagetronad/arma_chatbot_config

« back to all changes in this revision

Viewing changes to game_manager/game_manager.py

  • Committer: hackermans
  • Date: 2025-05-28 18:34:25 UTC
  • Revision ID: nickwinston123@gmail.com-20250528183425-z5cssgt5eeqyqox3

consolidated arma chatbot config programs

- arma_terminal: live console viewer with input passthrough
- game_manager: manages bans, IP rotation, and keeping the game open
- game_updater: syncs updated game files from shared folder
- ollama_chat: ai chatbot 

- launcher: launches all not already opened programs with positioning and window checks

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import os
 
2
import sys
 
3
import time
 
4
import random
 
5
import subprocess
 
6
import psutil
 
7
import requests
 
8
import ctypes
 
9
import logging
 
10
import configparser
 
11
import re
 
12
 
 
13
config = configparser.ConfigParser()
 
14
ini_path = os.path.join(os.path.dirname(__file__), 'game_manager_real.ini')
 
15
try:
 
16
    config.read(ini_path)
 
17
except Exception as e:
 
18
    logging.error(f"Error reading configuration: {e}")
 
19
    sys.exit(1)
 
20
 
 
21
try:
 
22
    OPENVPN_PATH       = config.get('Paths', 'openvpn_path')
 
23
    VPN_LOG            = config.get('Paths', 'vpn_log')
 
24
    OVPN_DIR           = config.get('Paths', 'ovpn_dir')
 
25
    BANNED_FILE        = config.get('Paths', 'banned_file')
 
26
    COMMANDS_FILE      = config.get('Paths', 'commands_file')
 
27
    BANNED_LOG         = config.get('Paths', 'banned_log')
 
28
    OUTPUT_LOG         = config.get('Paths', 'output_log')
 
29
    EXE_PATH           = config.get('Paths', 'exe_path')
 
30
    UPDATE_FILE_CHECK  = config.get('Paths', 'update_file_check')
 
31
    REAL_IP            = config.get('Settings', 'real_ip')
 
32
 
 
33
except Exception as e:
 
34
    logging.error(f"Error retrieving configuration values: {e}")
 
35
    sys.exit(1)
 
36
 
 
37
 
 
38
log_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s", datefmt="%H:%M:%S")
 
39
 
 
40
file_handler = logging.FileHandler(OUTPUT_LOG)
 
41
file_handler.setFormatter(log_formatter)
 
42
 
 
43
import io
 
44
 
 
45
class StreamToUTF8(io.TextIOWrapper):
 
46
    def write(self, b):
 
47
        try:
 
48
            super().write(b)
 
49
        except UnicodeEncodeError:
 
50
            super().write(b.encode('utf-8', errors='replace').decode('utf-8'))
 
51
 
 
52
console_handler = logging.StreamHandler(StreamToUTF8(sys.stdout.buffer, encoding='utf-8', errors='replace'))
 
53
console_handler.setFormatter(log_formatter)
 
54
 
 
55
 
 
56
logger = logging.getLogger()
 
57
logger.setLevel(logging.INFO)
 
58
logger.addHandler(file_handler)
 
59
logger.addHandler(console_handler)
 
60
 
 
61
 
 
62
# global variables
 
63
alt_mode                 = False
 
64
force_rebuild_active     = False
 
65
rebuild_start_time       = 0
 
66
last_rebuild_ban_trigger = -1
 
67
script_start_time        = time.time()
 
68
last_ban_time            = None
 
69
reset_after_ban_applied  = False
 
70
current_mode             = "Default"
 
71
last_applied_mode        = None
 
72
command_write_count      = 0
 
73
initial_launch           = True  
 
74
ban_count                = 0  
 
75
active_ban_count         = 0  
 
76
 
 
77
def print_log(message):
 
78
    print(message)
 
79
    logging.info(message)
 
80
 
 
81
 
 
82
def log_stats():
 
83
    try:
 
84
        elapsed = int(time.time() - script_start_time)
 
85
        hrs, rem = divmod(elapsed, 3600)
 
86
        mins, secs = divmod(rem, 60)
 
87
        uptime_str = f"{hrs}h {mins}m {secs}s"
 
88
        logging.info("-------- STATS --------")
 
89
        logging.info(f"Total Bans: {ban_count}")
 
90
        logging.info(f"Active Bans: {active_ban_count}")
 
91
        logging.info(f"Script Uptime: {uptime_str}")
 
92
        if last_ban_time:
 
93
            since_last = int(time.time() - last_ban_time)
 
94
            m, s = divmod(since_last, 60)
 
95
            last_ban_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(last_ban_time))
 
96
            logging.info(f"Last Ban Time: {last_ban_str}")
 
97
            logging.info(f"Time Since Last Ban: {m}m {s}s")
 
98
        logging.info(f"Current Mode: {current_mode}")
 
99
        logging.info(f"Force Rebuild Active: {force_rebuild_active}")
 
100
        if force_rebuild_active:
 
101
            remaining = int(300 - (time.time() - rebuild_start_time))
 
102
            m, s = divmod(remaining, 60)
 
103
            logging.info(f"Rebuild Cooldown: {m}m {s}s remaining")
 
104
        logging.info(f"Alt Mode: {'ON' if alt_mode else 'OFF'}")
 
105
        logging.info("-----------------------")
 
106
    except Exception as e:
 
107
        logging.error(f"Error logging stats: {e}")
 
108
 
 
109
def run_as_admin():
 
110
    try:
 
111
        admin = os.getuid() == 0
 
112
    except AttributeError:
 
113
        try:
 
114
            admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
 
115
        except Exception as e:
 
116
            logging.error(f"Error checking admin privileges: {e}")
 
117
            admin = False
 
118
    if not admin:
 
119
        logging.warning("Elevating privileges...")
 
120
        try:
 
121
            params = " ".join(sys.argv)
 
122
            ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, params, None, 1)
 
123
        except Exception as e:
 
124
            logging.error(f"Error during privilege elevation: {e}")
 
125
        sys.exit()
 
126
 
 
127
def kill_existing_vpn():
 
128
    try:
 
129
        logging.info("Killing any existing OpenVPN processes...")
 
130
        for proc in psutil.process_iter(['name']):
 
131
            try:
 
132
                if proc.info['name'] and "openvpn.exe" in proc.info['name'].lower():
 
133
                    logging.info(f"Killing process {proc.pid} ({proc.info['name']})")
 
134
                    proc.kill()
 
135
            except Exception as e:
 
136
                logging.error(f"Error killing process {proc.pid}: {e}")
 
137
    except Exception as e:
 
138
        logging.error(f"Error iterating processes for VPN kill: {e}")
 
139
 
 
140
def parse_ban_duration(reason):
 
141
    try:
 
142
        m = re.search(r'at least (\d+)', reason)
 
143
        if m:
 
144
            return int(m.group(1)) * 60
 
145
    except Exception as e:
 
146
        logging.error(f"Error parsing ban duration from reason '{reason}': {e}")
 
147
    return 3600
 
148
 
 
149
def add_to_banned_log(ovpn_file, banned_ip, ban_reason):
 
150
    try:
 
151
        if "kicked" in ban_reason.lower():
 
152
            logging.info("Kick detected; entry not logged: " + ban_reason)
 
153
            return
 
154
 
 
155
        ban_duration = parse_ban_duration(ban_reason)
 
156
        ts = time.time()
 
157
        entry = f"{banned_ip} | {ovpn_file} | {ts} | {ban_duration} | {ban_reason}"
 
158
        
 
159
        file_exists = os.path.exists(BANNED_LOG)
 
160
        file_empty = not file_exists or os.path.getsize(BANNED_LOG) == 0
 
161
 
 
162
        with open(BANNED_LOG, 'a') as f:
 
163
            if file_empty:
 
164
                header = "Banned IP | OVPN File | Timestamp | Duration (sec) | Ban Reason"
 
165
                f.write(header + "\n")
 
166
            f.write(entry + "\n")
 
167
        
 
168
        logging.info(f"Added banned log entry: {entry}")
 
169
    except Exception as e:
 
170
        logging.error(f"Error adding to banned log: {e}")
 
171
 
 
172
def perform_ban_actions():
 
173
    global current_mode, last_applied_mode, force_rebuild_active, rebuild_start_time, last_rebuild_ban_trigger, alt_mode
 
174
    try:
 
175
        if active_ban_count == 3 and last_applied_mode != "RANDOM_SMART":
 
176
            update_commands(COMMANDS_FILE, "PLAYER_NAME_RANDOM_SMART 1")
 
177
            current_mode = last_applied_mode = "RANDOM_SMART"
 
178
        elif active_ban_count == 5 and last_applied_mode != "RANDOM_STEAL":
 
179
            update_commands(COMMANDS_FILE, "PLAYER_NAME_RANDOM_SMART 0")
 
180
            update_commands(COMMANDS_FILE, "PLAYER_NAME_RANDOM_STEAL 1")
 
181
            current_mode = last_applied_mode = "RANDOM_STEAL"
 
182
        elif active_ban_count == 7 and last_applied_mode != "PLAYERIDS":
 
183
            update_commands(COMMANDS_FILE, "PLAYER_NAME_RANDOM_SMART 0")
 
184
            update_commands(COMMANDS_FILE, "PLAYER_NAME_RANDOM_STEAL 0")
 
185
            update_commands(COMMANDS_FILE, "PLAYER_NAME_PLAYERIDS 1")
 
186
            current_mode = last_applied_mode = "PLAYERIDS"
 
187
        elif active_ban_count >= 8:
 
188
            if active_ban_count % 8 == 0 and active_ban_count != last_rebuild_ban_trigger:
 
189
                update_commands(COMMANDS_FILE, "FORCE_PLAYER_ZREBUILD 1")
 
190
                rebuild_start_time = time.time()
 
191
                force_rebuild_active = True
 
192
                last_rebuild_ban_trigger = active_ban_count
 
193
                logging.info("FORCE_PLAYER_ZREBUILD activated.")
 
194
            if alt_mode:
 
195
                update_commands(COMMANDS_FILE, "PLAYER_NAME_PLAYERIDS 1")
 
196
                update_commands(COMMANDS_FILE, "PLAYER_NAME_RANDOM_STEAL 0")
 
197
                current_mode = "RANDOM_STEAL"
 
198
            else:
 
199
                update_commands(COMMANDS_FILE, "PLAYER_NAME_PLAYERIDS 0")
 
200
                update_commands(COMMANDS_FILE, "PLAYER_NAME_RANDOM_STEAL 1")
 
201
                current_mode = "RANDOM_STEAL"
 
202
            alt_mode = not alt_mode
 
203
    except Exception as e:
 
204
        logging.error(f"Error performing ban actions: {e}")
 
205
 
 
206
def load_banned_log():
 
207
    banned = {}
 
208
    try:
 
209
        if os.path.exists(BANNED_LOG):
 
210
            with open(BANNED_LOG, 'r') as f:
 
211
                header_skipped = False
 
212
                for line in f:
 
213
                    if not header_skipped:
 
214
                        if line.strip().startswith("Banned IP"):
 
215
                            header_skipped = True
 
216
                            continue
 
217
                        header_skipped = True  
 
218
                    parts = line.strip().split(" | ")
 
219
                    if len(parts) == 5:
 
220
                        ip, ovpn_file, ts, duration, reason = parts
 
221
                        banned[ip] = (ovpn_file, float(ts), int(duration), reason)
 
222
    except Exception as e:
 
223
        logging.error(f"Error loading banned log: {e}")
 
224
    return banned
 
225
 
 
226
def is_ip_banned(ip):
 
227
    try:
 
228
        banned = load_banned_log()
 
229
        if ip in banned:
 
230
            ovpn_file, ts, duration, reason = banned[ip]
 
231
            if time.time() < ts + duration:
 
232
                logging.warning(f"IP {ip} is banned until {time.strftime('%H:%M:%S', time.localtime(ts+duration))}.")
 
233
                return True
 
234
    except Exception as e:
 
235
        logging.error(f"Error checking if IP {ip} is banned: {e}")
 
236
    return False
 
237
 
 
238
def update_commands(commands_file, command_str):
 
239
    global command_write_count
 
240
    try:
 
241
        with open(commands_file, 'a') as f:
 
242
            f.write(command_str + "\n")
 
243
        command_write_count += 1
 
244
        logging.info(f"-> {command_str} (Total Written: {command_write_count})")
 
245
    except Exception as e:
 
246
        logging.error(f"Error updating commands file: {e}")
 
247
 
 
248
def start_game():
 
249
    while os.path.exists(UPDATE_FILE_CHECK):
 
250
        logging.info("Update in progress—waiting for it to finish…")
 
251
        time.sleep(1)
 
252
 
 
253
    logging.info("Waiting for Armagetronad executable to be available…")
 
254
    max_wait = 300
 
255
    waited  = 0
 
256
    interval = 2
 
257
 
 
258
    while not os.path.isfile(EXE_PATH):
 
259
        if waited >= max_wait:
 
260
            logging.error("Timed out waiting for Armagetronad.exe.")
 
261
            return
 
262
        time.sleep(interval)
 
263
        waited += interval
 
264
 
 
265
    logging.info("Armagetronad executable found. Launching…")
 
266
    subprocess.Popen([EXE_PATH])
 
267
 
 
268
def connect_vpn_filtered(vpn_path, ovpn_dir, log_path):
 
269
    try:
 
270
        kill_existing_vpn()
 
271
        try:
 
272
            open(log_path, 'w').close()
 
273
        except Exception as e:
 
274
            logging.error(f"Error clearing VPN log: {e}")
 
275
        
 
276
        all_ovpn_files = []
 
277
        try:
 
278
            all_ovpn_files = [os.path.join(ovpn_dir, f) for f in os.listdir(ovpn_dir)
 
279
                              if f.lower().endswith('.ovpn')]
 
280
        except Exception as e:
 
281
            logging.error(f"Error listing ovpn files: {e}")
 
282
        
 
283
        total_count = len(all_ovpn_files)
 
284
        if not all_ovpn_files:
 
285
            logging.error("No .ovpn files found!")
 
286
            return None, None
 
287
 
 
288
        banned = load_banned_log()
 
289
        banned_ovpn_files = set(entry[0] for entry in banned.values() if time.time() < entry[1] + entry[2])
 
290
        banned_count = sum(1 for f in all_ovpn_files if f in banned_ovpn_files)
 
291
        
 
292
        available_ovpn_files = [f for f in all_ovpn_files if f not in banned_ovpn_files]
 
293
        available_count = len(available_ovpn_files)
 
294
 
 
295
        logging.info(f"OVPN Files - Total: {total_count}, Available: {available_count}, Banned: {banned_count}")
 
296
 
 
297
        if not available_ovpn_files:
 
298
            logging.warning("All ovpn files are banned; using one anyway.")
 
299
            available_ovpn_files = all_ovpn_files
 
300
 
 
301
        rand_file = random.choice(available_ovpn_files)
 
302
        logging.info(f"Using VPN configuration file: {rand_file}")
 
303
        
 
304
        proc = subprocess.Popen(
 
305
            [vpn_path, '--config', rand_file, '--dev', 'tun', '--ifconfig', '10.8.0.2', '10.8.0.1'],
 
306
            stdout=open(log_path, 'w'), stderr=subprocess.STDOUT
 
307
        )
 
308
        return proc, rand_file
 
309
    except Exception as e:
 
310
        logging.error(f"Error in connect_vpn_filtered: {e}")
 
311
        return None, None
 
312
 
 
313
def wait_for_vpn_initialization(log_path, timeout=30, interval=1):
 
314
    start_time = time.time()
 
315
    while time.time() - start_time < timeout:
 
316
        try:
 
317
            with open(log_path, 'r') as f:
 
318
                content = f.read()
 
319
                if "Initialization Sequence Completed" in content:
 
320
                    logging.info("VPN initialization complete.")
 
321
                    return True
 
322
                if "AUTH: Received control message: AUTH_FAILED" in content:
 
323
                    logging.error("VPN authentication failed.")
 
324
                    return False
 
325
        except Exception as e:
 
326
            logging.error(f"Error reading VPN log: {e}")
 
327
        time.sleep(interval)
 
328
    logging.warning("Timeout reached waiting for VPN initialization.")
 
329
    return False
 
330
 
 
331
def get_current_ip(real_ip, retry_interval=1, max_retries=30):
 
332
    retries = 0
 
333
    while retries < max_retries:
 
334
        try:
 
335
            resp = requests.get('https://httpbin.org/ip', timeout=5)
 
336
            current_ip = resp.json()['origin'].strip()
 
337
            logging.info(f"Fetched IP: {current_ip}")
 
338
            if current_ip != real_ip:
 
339
                return current_ip
 
340
            logging.warning("Current IP still matches REAL_IP. Waiting for new IP...")
 
341
        except Exception as e:
 
342
            logging.error(f"Error fetching IP (retrying): {e}")
 
343
        time.sleep(retry_interval)
 
344
        retries += 1
 
345
    return None
 
346
 
 
347
def connect_until_new_ip(real_ip, banned_ip=None):
 
348
    while True:
 
349
        try:
 
350
            proc, used_ovpn = connect_vpn_filtered(OPENVPN_PATH, OVPN_DIR, VPN_LOG)
 
351
            if not proc:
 
352
                logging.error("VPN process not started; retrying...")
 
353
                time.sleep(3)
 
354
                continue
 
355
            if not wait_for_vpn_initialization(VPN_LOG):
 
356
                if proc and proc.poll() is None:
 
357
                    proc.kill()
 
358
                    time.sleep(3)
 
359
                continue
 
360
            candidate_ip = get_current_ip(real_ip)
 
361
            if candidate_ip is None:
 
362
                continue
 
363
            if banned_ip and candidate_ip == banned_ip:
 
364
                logging.warning("Candidate IP matches banned IP. Killing VPN and retrying...")
 
365
                kill_existing_vpn()
 
366
                time.sleep(3)
 
367
                continue
 
368
            if is_ip_banned(candidate_ip):
 
369
                logging.warning(f"Candidate IP {candidate_ip} is in banned log. Retrying...")
 
370
                kill_existing_vpn()
 
371
                time.sleep(3)
 
372
                continue
 
373
            logging.info(f"VPN IP acquired: {candidate_ip}")
 
374
            return candidate_ip, used_ovpn
 
375
        except Exception as e:
 
376
            logging.error(f"Error in connect_until_new_ip: {e}")
 
377
            time.sleep(3)
 
378
 
 
379
def check_banned(banned_file):
 
380
    try:
 
381
        if os.path.exists(banned_file):
 
382
            with open(banned_file, 'r') as f:
 
383
                content = f.read().strip()
 
384
            if content:
 
385
                logging.warning("Ban detected!")
 
386
                logging.info(f"Ban Reason: {content}")
 
387
                return True
 
388
    except Exception as e:
 
389
        logging.error(f"Error reading ban file: {e}")
 
390
    return False
 
391
 
 
392
def main():
 
393
    global alt_mode, force_rebuild_active, rebuild_start_time, last_rebuild_ban_trigger
 
394
    global last_ban_time, reset_after_ban_applied, current_mode, last_applied_mode, initial_launch
 
395
    global ban_count, active_ban_count
 
396
 
 
397
    try:
 
398
        run_as_admin()
 
399
    except Exception as e:
 
400
        logging.error(f"Error in run_as_admin: {e}")
 
401
        sys.exit(1)
 
402
    
 
403
    for cmd in [
 
404
        "PLAYER_NAME_RANDOM_SMART 0",
 
405
        "PLAYER_NAME_RANDOM_STEAL 0",
 
406
        "PLAYER_NAME_PLAYERIDS 0",
 
407
        "FORCE_PLAYER_ZREBUILD 0"
 
408
    ]:
 
409
        update_commands(COMMANDS_FILE, cmd)
 
410
 
 
411
    counter = 0
 
412
 
 
413
    while True:
 
414
        try:
 
415
            if any("armagetronad.exe" in p.name() for p in psutil.process_iter()):
 
416
                logging.info("Armagetronad is running.")
 
417
            else:
 
418
                logging.info("Armagetronad is not running. Checking VPN...")
 
419
                try:
 
420
                    resp = requests.get('https://httpbin.org/ip', timeout=5)
 
421
                    current_ip = resp.json()['origin'].strip()
 
422
                except Exception as e:
 
423
                    logging.error(f"IP fetch error: {e}")
 
424
                    current_ip = REAL_IP
 
425
 
 
426
                new_ip, used_ovpn = connect_until_new_ip(REAL_IP)
 
427
 
 
428
                if not initial_launch and check_banned(BANNED_FILE):
 
429
                    ban_count += 1
 
430
                    active_ban_count += 1
 
431
                    last_ban_time = time.time()
 
432
                    reset_after_ban_applied = False
 
433
                    try:
 
434
                        with open(BANNED_FILE, 'r') as f:
 
435
                            ban_reason = f.read().strip()
 
436
                    except Exception as e:
 
437
                        logging.error(f"Error reading banned file: {e}")
 
438
                        ban_reason = "Unknown"
 
439
                    logging.warning(f"[Ban {ban_count}] Triggered at {time.strftime('%H:%M:%S')}")
 
440
                    log_stats()
 
441
                    add_to_banned_log(used_ovpn, new_ip, ban_reason)
 
442
                    
 
443
                    perform_ban_actions()                
 
444
 
 
445
                    kill_existing_vpn()
 
446
                    time.sleep(3)
 
447
                    new_ip, used_ovpn = connect_until_new_ip(REAL_IP, banned_ip=new_ip)
 
448
                
 
449
                if force_rebuild_active:
 
450
                    update_commands(COMMANDS_FILE, "FORCE_PLAYER_ZREBUILD 1")
 
451
                    
 
452
                start_game()
 
453
                
 
454
                if initial_launch:
 
455
                    initial_launch = False
 
456
 
 
457
            if last_ban_time and (time.time() - last_ban_time > 1800) and not reset_after_ban_applied:
 
458
                logging.info("30 minutes since last ban. Resetting command modes to default and active ban count.")
 
459
                for cmd in [
 
460
                    "PLAYER_NAME_RANDOM_SMART 0",
 
461
                    "PLAYER_NAME_RANDOM_STEAL 0",
 
462
                    "PLAYER_NAME_PLAYERIDS 0",
 
463
                    "FORCE_PLAYER_ZREBUILD 0"
 
464
                ]:
 
465
                    update_commands(COMMANDS_FILE, cmd)
 
466
                reset_after_ban_applied = True
 
467
                current_mode = last_applied_mode = "Default"
 
468
                active_ban_count = 0  
 
469
 
 
470
            counter += 1
 
471
            if counter >= 10:
 
472
                log_stats()
 
473
                counter = 0
 
474
 
 
475
            if force_rebuild_active and time.time() - rebuild_start_time > 300:
 
476
                update_commands(COMMANDS_FILE, "FORCE_PLAYER_ZREBUILD 0")
 
477
                logging.info("5-minute punishment ended.")
 
478
                force_rebuild_active = False
 
479
 
 
480
            time.sleep(5)
 
481
        except Exception as e:
 
482
            logging.error(f"Error in main loop: {e}")
 
483
            time.sleep(5)
 
484
 
 
485
if __name__ == '__main__':
 
486
    try:
 
487
        main()
 
488
    except Exception as e:
 
489
        logging.critical(f"Fatal error in main: {e}")
 
490
        sys.exit(1)