#!/usr/bin/env python3 """ Comprehensive Game Server Documentation Generator for GSP This script generates PHP documentation files for game servers in the GSP billing module. USAGE: # Generate docs for all incomplete games: python3 generate_game_docs.py # Generate docs for specific game(s): python3 generate_game_docs.py --games minecraft csgo rust # Regenerate docs (overwrite existing): python3 generate_game_docs.py --games minecraft --force # Process all "todo" category games: python3 generate_game_docs.py --todo-only OPTIONS: --games GAME1 GAME2 ... Generate docs for specific game folder names --force Overwrite existing documentation files --todo-only Only process games with category="todo" --help Show this help message The generator extracts information from: - XML configurations (modules/config_games/server_configs/*.xml) - YAML knowledgepack (modules/billing/docs/gameserver_knowledgepack_v2.yaml) - Existing metadata.json files Generated documentation includes: - Quick info (ports, RAM, Steam App ID) - Network ports and firewall configuration - Installation steps with SteamCMD commands - Server configuration files and settings - Detailed startup parameters from XML - Comprehensive troubleshooting guide - Performance optimization tips - Security best practices """ import os import sys import json import yaml import re import argparse import html from pathlib import Path from datetime import datetime import xml.etree.ElementTree as ET class GameDocGenerator: def __init__(self, docs_dir, config_dir, knowledgepack_path): self.docs_dir = Path(docs_dir) self.config_dir = Path(config_dir) self.knowledgepack_path = Path(knowledgepack_path) self.knowledgepack_data = None self.xml_configs = {} def load_knowledgepack(self): """Load the YAML knowledgepack with game information""" try: with open(self.knowledgepack_path, 'r', encoding='utf-8') as f: data = yaml.safe_load(f) self.knowledgepack_data = data.get('games', []) print(f"Loaded knowledgepack with {len(self.knowledgepack_data)} games") return True except Exception as e: print(f"Error loading knowledgepack: {e}") return False def load_xml_configs(self): """Load all XML configuration files""" xml_files = list(self.config_dir.glob("*.xml")) for xml_file in xml_files: try: tree = ET.parse(xml_file) root = tree.getroot() game_key = root.find('game_key') if game_key is not None and game_key.text: self.xml_configs[game_key.text] = { 'file': xml_file.name, 'tree': root } except Exception as e: print(f"Error parsing {xml_file}: {e}") print(f"Loaded {len(self.xml_configs)} XML configurations") def get_game_info_from_knowledgepack(self, game_name): """Find game info in knowledgepack by name""" if not self.knowledgepack_data: return None # Try exact match first for game in self.knowledgepack_data: if game.get('name', '').lower() == game_name.lower(): return game # Try partial match for game in self.knowledgepack_data: if game_name.lower() in game.get('name', '').lower(): return game return None def get_xml_config(self, folder_name): """Find matching XML config for a folder""" # Try exact match for key, config in self.xml_configs.items(): if key.lower() == folder_name.lower() or key.lower().replace('_', '') == folder_name.lower(): return config['tree'] # Try partial match for key, config in self.xml_configs.items(): if folder_name.lower() in key.lower() or key.lower() in folder_name.lower(): return config['tree'] return None def extract_ports_from_xml(self, xml_root): """Extract port information from XML config""" ports = [] # Look for replace_texts with port keys replace_texts = xml_root.find('replace_texts') if replace_texts is not None: for text in replace_texts.findall('text'): key = text.get('key', '') if 'port' in key.lower(): filepath = text.find('filepath') if filepath is not None: ports.append({ 'key': key, 'file': filepath.text }) # Look for custom_fields with port information custom_fields = xml_root.find('custom_fields') if custom_fields is not None: for field in custom_fields.findall('field'): key = field.get('key', '') if 'port' in key.lower(): default_value = field.find('default_value') desc = field.find('desc') ports.append({ 'key': key, 'default': default_value.text if default_value is not None else None, 'description': desc.text if desc is not None else None }) return ports def extract_startup_parameters_from_xml(self, xml_root): """Extract detailed startup parameters from XML server_params section""" startup_params = [] # Get CLI template and params cli_template = xml_root.find('cli_template') cli_template_text = cli_template.text if cli_template is not None else "" cli_params_elem = xml_root.find('cli_params') cli_params_info = {} if cli_params_elem is not None: for param in cli_params_elem.findall('cli_param'): param_id = param.get('id', '') cli_string = param.get('cli_string', '') options = param.get('options', '') if param_id: cli_params_info[param_id] = { 'cli_string': cli_string, 'options': options } # Extract server_params - these are the configurable startup parameters server_params = xml_root.find('server_params') if server_params is not None: for param in server_params.findall('param'): param_key = param.get('key', '') param_id = param.get('id', '') param_type = param.get('type', 'text') # Get caption and description caption_elem = param.find('caption') desc_elem = param.find('desc') default_elem = param.find('default') caption = caption_elem.text if caption_elem is not None else param_key description = desc_elem.text if desc_elem is not None else "No description available" default_value = default_elem.text if default_elem is not None else None # For select type, get options options = [] if param_type == 'select': for option in param.findall('option'): opt_value = option.get('value', '') opt_text = option.text if option.text is not None else opt_value options.append({'value': opt_value, 'text': opt_text}) startup_params.append({ 'key': param_key, 'id': param_id, 'type': param_type, 'caption': caption, 'description': description, 'default': default_value, 'options': options }) return { 'cli_template': cli_template_text, 'cli_params': cli_params_info, 'server_params': startup_params } def extract_config_files_from_xml(self, xml_root): """Extract configuration file paths from XML""" config_files = [] config_files_elem = xml_root.find('configuration_files') if config_files_elem is not None: for file_elem in config_files_elem.findall('file'): desc = file_elem.get('description', 'Configuration file') path = file_elem.text if file_elem.text else '' config_files.append({ 'description': desc, 'path': path }) return config_files def generate_php_doc(self, folder_name, metadata): """Generate comprehensive PHP documentation for a game""" game_name = metadata.get('name', folder_name.replace('_', ' ').title()) # Get additional data kb_info = self.get_game_info_from_knowledgepack(game_name) xml_config = self.get_xml_config(folder_name) # Extract ports, configs, and startup parameters ports_info = [] config_files = [] startup_params_data = {} if xml_config is not None: ports_info = self.extract_ports_from_xml(xml_config) config_files = self.extract_config_files_from_xml(xml_config) startup_params_data = self.extract_startup_parameters_from_xml(xml_config) # Build the PHP document php_content = self.build_php_content(game_name, folder_name, kb_info, xml_config, ports_info, config_files, startup_params_data) return php_content def get_steam_app_id(self, game_name, folder_name): """Get Steam App ID for a game""" # Common Steam App IDs app_ids = { '7daystodie': '294420', 'ark': '376030', 'arkse': '376030', 'arma3': '233780', 'arma2oa': '33930', 'csgo': '740', 'css': '232330', 'dayz': '221100', 'garrysmod': '4020', 'gmod': '4020', 'killingfloor': '215350', 'killingfloor2': '232130', 'left4dead': '222840', 'left4dead2': '222860', 'rust': '258550', 'squad': '403240', 'tf2': '232250', 'terraria': '105600', 'theforest': '556450', 'unturned': '1110390', 'valheim': '896660', 'insurgency': '237410', 'insurgencysandstorm': '581320', 'conanexiles': '443030', 'dontstarvetogether': '343050', 'lifeisfeudal': '320850', 'mordhau': '629760', } # Try folder name if folder_name.lower() in app_ids: return app_ids[folder_name.lower()] # Try game name game_lower = game_name.lower().replace(' ', '').replace(':', '').replace('-', '') for key, appid in app_ids.items(): if key in game_lower or game_lower in key: return appid return 'N/A' def build_php_content(self, game_name, folder_name, kb_info, xml_config, ports_info, config_files, startup_params_data): """Build the complete PHP documentation content""" # Extract data from various sources default_port = "Varies (see configuration)" protocol = "TCP/UDP" min_ram = "1GB" engine = "Various" startup_cmd = "" app_id = self.get_steam_app_id(game_name, folder_name) # Try to get port from XML first if xml_config is not None: # Check for default port in mods section mods = xml_config.find('mods') if mods is not None: mod = mods.find('mod') if mod is not None: installer_name = mod.find('installer_name') if installer_name is not None and installer_name.text and app_id == 'N/A': app_id = installer_name.text if kb_info: network = kb_info.get('network', {}) default_ports = network.get('default_ports', []) if default_ports: port_info = default_ports[0] port_str = port_info.get('port', '') if '/' in port_str: default_port = port_str.split('/')[0] protocol = port_str.split('/')[1].upper() else: default_port = port_str requirements = kb_info.get('requirements', {}) min_ram = requirements.get('ram', '1GB') engine = kb_info.get('engine', 'Various') startup = kb_info.get('typical_startup', {}) startup_cmd = startup.get('linux', '') or startup.get('windows', '') php_doc = '''

📚 Quick Navigation

Quick Info 🔌 Ports Installation Configuration ⚙️ Startup Parameters 🔧 Troubleshooting Performance Security

''' + game_name + ''' Server Hosting Guide

Overview

''' + game_name + ''' is a multiplayer game server that can be hosted on a VPS or dedicated server. This comprehensive guide covers everything you need to know about hosting a ''' + game_name + ''' server for your community.

Quick Info

🔌 Network Ports

Required Ports

''' # Add port information if kb_info and kb_info.get('network', {}).get('default_ports'): php_doc += ''' ''' for port_info in kb_info['network']['default_ports']: port_str = port_info.get('port', '') purpose = port_info.get('purpose', 'Game server') port_num = port_str.split('/')[0] if '/' in port_str else port_str proto = port_str.split('/')[1].upper() if '/' in port_str else 'TCP/UDP' php_doc += f''' ''' # Add additional ports if available additional_ports = kb_info['network'].get('additional_ports', []) for port_info in additional_ports: port_str = port_info.get('port', '') purpose = port_info.get('purpose', 'Additional functionality') port_num = port_str.split('/')[0] if '/' in port_str else port_str proto = port_str.split('/')[1].upper() if '/' in port_str else 'TCP/UDP' php_doc += f''' ''' php_doc += '''
Port Protocol Purpose
{port_num} {proto} {purpose}
{port_num} {proto} {purpose} (Optional)
''' else: php_doc += '''

The ''' + game_name + ''' server typically uses a configurable port. Check your server configuration files for the specific port settings.

''' php_doc += '''

Firewall Configuration

Allow server ports through your firewall:

# UFW (Ubuntu/Debian)
sudo ufw allow [PORT]/tcp
sudo ufw allow [PORT]/udp
sudo ufw reload

# FirewallD (CentOS/RHEL)
sudo firewall-cmd --permanent --add-port=[PORT]/tcp
sudo firewall-cmd --permanent --add-port=[PORT]/udp
sudo firewall-cmd --reload

# Windows Firewall
netsh advfirewall firewall add rule name="''' + game_name + ''' Server" dir=in action=allow protocol=TCP localport=[PORT]
netsh advfirewall firewall add rule name="''' + game_name + ''' Server" dir=in action=allow protocol=UDP localport=[PORT]

⚠️ Port Security Notes

Installation & Setup

System Requirements

''' # Add dependencies if available if kb_info and kb_info.get('requirements', {}).get('dependencies'): dependencies = kb_info['requirements']['dependencies'] php_doc += '''

Required Dependencies

''' php_doc += '''

Installation Steps

Linux (Ubuntu/Debian)

# Update system packages
sudo apt update && sudo apt upgrade -y

# Create server directory
mkdir -p ~/gameserver
cd ~/gameserver

# Download server files (method varies by game)
# Check official documentation for download links
''' if startup_cmd: php_doc += f'''

Starting the Server

{startup_cmd}
''' php_doc += '''

Windows Server

Download the server files from the official game website or through Steam (if applicable). Extract to a dedicated folder and run the server executable.

''' # Add SteamCMD section with actual App ID if app_id != 'N/A': php_doc += f'''

Using SteamCMD - RECOMMENDED METHOD

This game can be installed via SteamCMD using App ID: {app_id}

Install SteamCMD (Ubuntu/Debian)

# Update package list
sudo apt update

# Enable 32-bit architecture
sudo dpkg --add-architecture i386
sudo apt update

# Install SteamCMD
sudo apt install -y lib32gcc-s1 steamcmd

Download Server Files

# Create directory for game server
mkdir -p ~/gameservers/{folder_name}

# Run SteamCMD and download
steamcmd +login anonymous \\
         +force_install_dir ~/gameservers/{folder_name} \\
         +app_update {app_id} validate \\
         +quit

# Server files are now in ~/gameservers/{folder_name}/
cd ~/gameservers/{folder_name}
ls -la

Windows Installation with SteamCMD

  1. Download SteamCMD from: https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip
  2. Extract to C:\\steamcmd\\
  3. Open Command Prompt and run:
cd C:\\steamcmd
steamcmd.exe +login anonymous ^
             +force_install_dir C:\\gameservers\\{folder_name} ^
             +app_update {app_id} validate ^
             +quit
''' else: php_doc += '''

Manual Installation

This game requires manual download. Check the official game website or Steam store page for dedicated server downloads.

''' php_doc += '''

Server Configuration

After installation, you'll need to configure your server. Here's where to find the configuration files and what settings you can change.

Essential Settings

''' # Add config files section if available if config_files: php_doc += '''

Configuration Files

Important configuration files for this server:

''' php_doc += '''

Server Commands

Common administrative commands (access via console or RCON):

# Kick player
kick [player_name]

# Ban player
ban [player_name]

# Change map/level (syntax varies by game)
changelevel [map_name]

# Set admin password (if supported)
setadminpassword [password]

⚙️ Startup Parameters

''' # Add detailed startup parameters from XML if available if startup_params_data and startup_params_data.get('server_params'): server_params = startup_params_data['server_params'] cli_template = startup_params_data.get('cli_template', '') if cli_template: php_doc += f'''

Command Line Template

The server uses the following command line template:

{cli_template}
''' php_doc += '''

Available Startup Parameters

The following parameters can be configured when starting the server:

''' for param in server_params: param_key = param['key'] caption = param['caption'] description = param['description'] param_type = param['type'] default = param.get('default') options = param.get('options', []) # Clean HTML from description - unescape HTML entities and remove tags if description: description_clean = html.unescape(description) # Remove HTML tags (simple but effective for our use case) description_clean = re.sub(r'<[^<>]+>', '', description_clean) else: description_clean = "No description available" php_doc += f'''

{param_key} - {caption}

{description_clean}

''' if param_type == 'select' and options: php_doc += '''

Options:

''' if default: php_doc += f'''

Default: {default}

''' php_doc += '''
''' php_doc += '''
''' else: # Fallback to generic parameters if no XML data php_doc += '''

Basic Startup

''' if startup_cmd: php_doc += f'''
{startup_cmd}
''' else: php_doc += '''
# Generic startup command structure
./server_executable [parameters]
''' php_doc += '''

Common Parameters

''' # Common sections for all games (whether they have XML params or not) php_doc += '''

Creating a Start Script

Linux (start.sh):

#!/bin/bash
cd /path/to/server
./server_executable [parameters] 2>&1 | tee server.log
chmod +x start.sh
./start.sh

Windows (start.bat):

@echo off
cd /d "%~dp0"
server_executable.exe [parameters]
pause

Running as a Service

Linux (systemd):

# Create service file: /etc/systemd/system/gameserver.service
[Unit]
Description=''' + game_name + ''' Server
After=network.target

[Service]
Type=simple
User=gameserver
WorkingDirectory=/home/gameserver/server
ExecStart=/home/gameserver/server/start.sh
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable gameserver
sudo systemctl start gameserver
sudo systemctl status gameserver

🔧 Troubleshooting

Server Won't Start

''' # Add troubleshooting from knowledgepack if available if kb_info and kb_info.get('troubleshooting', {}).get('common_issues'): issues = kb_info['troubleshooting']['common_issues'] for issue_item in issues: issue = issue_item.get('issue', '') fix = issue_item.get('fix', '') php_doc += f'''

{issue}

{fix}

''' else: php_doc += '''

Check Server Logs

# View recent log entries
tail -f server.log

# Or check system logs
journalctl -u gameserver -f

Port Already in Use

# Find what's using the port
sudo lsof -i :[PORT]
sudo netstat -tulpn | grep [PORT]

# Kill the process or change server port

Missing Dependencies

Ensure all required dependencies are installed. Check the error messages for missing libraries or packages.

''' php_doc += '''

Connection Issues

Can't Connect to Server

  1. Verify server is running: ps aux | grep server
  2. Check port is listening: netstat -an | grep [PORT]
  3. Verify firewall rules (see Ports section above)
  4. Check server IP: Use external IP, not localhost
  5. Router/NAT: Ensure port forwarding is configured

High Latency/Lag

Performance Issues

Server Lag

  1. Monitor resources: Use htop or top
  2. Check disk I/O: Use iotop
  3. Review server logs for errors or warnings
  4. Reduce player count or increase server resources
  5. Optimize configuration based on server capacity

Memory Leaks

# Monitor memory usage
free -h
top -p $(pgrep -f server)

# Restart server regularly via cron if needed
0 4 * * * /home/gameserver/restart.sh

Performance Optimization

Server Tuning

Operating System Optimization

# Increase file descriptor limits
echo "* soft nofile 65536" >> /etc/security/limits.conf
echo "* hard nofile 65536" >> /etc/security/limits.conf

# Network tuning
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 87380 16777216"

Monitoring

Set up monitoring to track server health:

Backup Strategy

#!/bin/bash
# backup.sh - Run via cron
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/gameserver"
SERVER_DIR="/home/gameserver/server"

# Create backup
tar -czf $BACKUP_DIR/backup_$DATE.tar.gz -C $SERVER_DIR .

# Keep only last 7 days
find $BACKUP_DIR -name "backup_*.tar.gz" -mtime +7 -delete

Security Best Practices

Firewall Configuration

# Minimal firewall - only allow necessary ports
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow [SERVER_PORT]/tcp
sudo ufw allow [SERVER_PORT]/udp
sudo ufw allow 22/tcp  # SSH
sudo ufw enable

Strong Passwords

Regular Updates

Access Control

DDoS Protection

Additional Resources

''' # Add references from knowledgepack if available if kb_info and kb_info.get('references'): php_doc += '''

External References

''' php_doc += '''

Important Notes

Last updated: ''' + datetime.now().strftime("%B %Y") + ''' | For ''' + game_name + ''' server hosting

''' return php_doc def process_incomplete_games(self): """Process all games with complete=false or generic text""" processed = 0 errors = [] skipped = 0 # Find all game folders for folder in self.docs_dir.iterdir(): if not folder.is_dir(): continue # Skip special folders if folder.name.startswith('.') or folder.name.startswith('_') or folder.name in ['common-issues', 'getting-started']: continue metadata_file = folder / 'metadata.json' index_file = folder / 'index.php' if not metadata_file.exists(): continue try: # Read metadata with open(metadata_file, 'r', encoding='utf-8') as f: content = f.read() # Remove BOM if present content = content.lstrip('\ufeff') metadata = json.loads(content) # Skip if already complete (unless it has generic text) is_complete = metadata.get('complete', False) has_generic_text = False if index_file.exists(): with open(index_file, 'r', encoding='utf-8') as f: index_content = f.read() if 'Check server configuration' in index_content or 'check your server configuration' in index_content.lower(): has_generic_text = True if is_complete and not has_generic_text: skipped += 1 continue print(f"Processing: {folder.name} (complete={is_complete}, has_generic={has_generic_text})") # Generate new documentation php_content = self.generate_php_doc(folder.name, metadata) # Write the new index.php with open(index_file, 'w', encoding='utf-8') as f: f.write(php_content) # Update metadata category from 'todo' to 'game' if needed if metadata.get('category', '').lower() == 'todo': metadata['category'] = 'game' # Mark as complete metadata['complete'] = True with open(metadata_file, 'w', encoding='utf-8') as f: json.dump(metadata, f, indent=4, ensure_ascii=False) processed += 1 print(f" ✓ Generated comprehensive documentation") except Exception as e: error_msg = f"Error processing {folder.name}: {e}" print(f" ✗ {error_msg}") errors.append(error_msg) return processed, skipped, errors def process_todo_folders(self): """Process all folders with category 'todo' """ processed = 0 errors = [] # Find all todo folders for folder in self.docs_dir.iterdir(): if not folder.is_dir(): continue metadata_file = folder / 'metadata.json' index_file = folder / 'index.php' if not metadata_file.exists(): continue try: # Read metadata with open(metadata_file, 'r', encoding='utf-8') as f: content = f.read() # Remove BOM if present content = content.lstrip('\ufeff') metadata = json.loads(content) # Check if it's a todo category if metadata.get('category', '').lower() != 'todo': continue print(f"Processing: {folder.name}") # Generate new documentation php_content = self.generate_php_doc(folder.name, metadata) # Write the new index.php with open(index_file, 'w', encoding='utf-8') as f: f.write(php_content) # Update metadata category from 'todo' to 'game' metadata['category'] = 'game' with open(metadata_file, 'w', encoding='utf-8') as f: json.dump(metadata, f, indent=4, ensure_ascii=False) processed += 1 print(f" ✓ Generated documentation for {folder.name}") except Exception as e: error_msg = f"Error processing {folder.name}: {e}" print(f" ✗ {error_msg}") errors.append(error_msg) return processed, errors def process_specific_games(self, game_names, force_overwrite=False): """Process specific game folders by name""" processed = 0 skipped = 0 errors = [] for game_name in game_names: folder = self.docs_dir / game_name if not folder.is_dir(): error_msg = f"Game folder not found: {game_name}" print(f" ✗ {error_msg}") errors.append(error_msg) continue metadata_file = folder / 'metadata.json' index_file = folder / 'index.php' if not metadata_file.exists(): error_msg = f"metadata.json not found for {game_name}" print(f" ✗ {error_msg}") errors.append(error_msg) continue try: # Read metadata with open(metadata_file, 'r', encoding='utf-8') as f: content = f.read() content = content.lstrip('\ufeff') metadata = json.loads(content) # Check if we should skip if index_file.exists() and not force_overwrite: print(f"Skipping {game_name} (file exists, use --force to overwrite)") skipped += 1 continue print(f"Processing: {game_name}") # Generate new documentation php_content = self.generate_php_doc(folder.name, metadata) # Write the new index.php with open(index_file, 'w', encoding='utf-8') as f: f.write(php_content) # Update metadata if it was todo if metadata.get('category', '').lower() == 'todo': metadata['category'] = 'game' # Mark as complete metadata['complete'] = True with open(metadata_file, 'w', encoding='utf-8') as f: json.dump(metadata, f, indent=4, ensure_ascii=False) processed += 1 print(f" ✓ Generated documentation for {game_name}") except Exception as e: error_msg = f"Error processing {game_name}: {e}" print(f" ✗ {error_msg}") errors.append(error_msg) return processed, skipped, errors def main(): # Parse command line arguments parser = argparse.ArgumentParser( description='Generate comprehensive game server documentation for GSP', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: # Generate docs for all incomplete games: python3 generate_game_docs.py # Generate docs for specific game(s): python3 generate_game_docs.py --games minecraft csgo rust # Regenerate docs (overwrite existing): python3 generate_game_docs.py --games minecraft --force # Process all "todo" category games: python3 generate_game_docs.py --todo-only """ ) parser.add_argument('--games', nargs='+', metavar='GAME', help='Generate docs for specific game folder names') parser.add_argument('--force', action='store_true', help='Overwrite existing documentation files') parser.add_argument('--todo-only', action='store_true', help='Only process games with category="todo"') args = parser.parse_args() docs_dir = "/home/runner/work/GSP/GSP/modules/billing/docs" config_dir = "/home/runner/work/GSP/GSP/modules/config_games/server_configs" knowledgepack = "/home/runner/work/GSP/GSP/modules/billing/docs/gameserver_knowledgepack_v2.yaml" generator = GameDocGenerator(docs_dir, config_dir, knowledgepack) print("="*70) print("COMPREHENSIVE GAME SERVER DOCUMENTATION GENERATOR") print("="*70) print() print("Loading data sources...") generator.load_knowledgepack() generator.load_xml_configs() processed = 0 skipped = 0 errors = [] # Determine which mode to run if args.games: # Process specific games print("\n" + "="*70) print(f"Processing {len(args.games)} specified game(s)...") if args.force: print("FORCE mode: Will overwrite existing files") print("="*70) processed, skipped, errors = generator.process_specific_games(args.games, args.force) elif args.todo_only: # Process only todo category games print("\n" + "="*70) print("Processing TODO category games...") print("="*70) processed, errors = generator.process_todo_folders() else: # Process incomplete games (default) print("\n" + "="*70) print("Processing INCOMPLETE game documentation...") print("="*70) processed, skipped, errors = generator.process_incomplete_games() print(f"\n{'='*70}") print(f"Documentation generation complete!") print(f" ✓ Processed: {processed}") if skipped > 0: print(f" → Skipped: {skipped}") print(f" ✗ Errors: {len(errors)}") if errors: print("\nErrors encountered:") for error in errors[:10]: # Show first 10 errors print(f" - {error}") if len(errors) > 10: print(f" ... and {len(errors) - 10} more") print(f"\nGenerated documentation includes:") print(f" • Actual port information from XML configs") print(f" • Steam App IDs and exact SteamCMD commands") print(f" • Configuration file details from XML configs") print(f" • DETAILED startup parameters extracted from XML") print(f" • Comprehensive troubleshooting sections") print("="*70) return 0 if not errors else 1 if __name__ == "__main__": sys.exit(main())