#!/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 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 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 description_clean = re.sub(r'<[^>]+>', '', description) if description else "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

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())