const https = require('https');
const httpProxy = require('http-proxy');
const url = require('url');
const path = require('path');
const fs = require('fs');
const os = require('os');
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const chalk = require('chalk');
const { EventEmitter } = require('events');
const axios = require('axios');
const geoip = require('geoip-lite');
const exec = require('child_process').exec;
const dgram = require('dgram');
const basicAuth = require('express-basic-auth');
const requestIp = require('request-ip');
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const slowDown = require('express-slow-down');

const synFloodTracker = {};
const MAX_SYN_REQUESTS = 10;
const SYN_BLOCK_TIME_MS = 60000;

const rateLimitConfig = {};
const MAX_REQUESTS_PER_MINUTE = 100;
const BLOCK_TIME_MS = 60000;

const dnsWhitelist = ['8.8.8.8', '1.1.1.1'];
const MAX_DNS_REQUESTS_PER_MINUTE = 50;
const dnsRateLimit = {};

const udpRateLimit = {};
const MAX_UDP_REQUESTS_PER_MINUTE = 100;
const UDP_BLOCK_TIME_MS = 60000;

const networkUsage = {};
const MAX_BYTES_PER_MINUTE = 1024 * 1024; // 1 MB
const NETWORK_BLOCK_TIME_MS = 60000;

const cloudServerIpRanges = ['35.', '52.', '169.', '198.', '199.', '200.', '216.', '47.'];
const allowedCountries = ['ID', 'SG', 'PH'];

const configApp = express();
const CONFIG_PORT = 6969;

configApp.set('view engine', 'ejs');
configApp.use(bodyParser.urlencoded({ extended: true }));
configApp.use(express.static('public'));

// Security Middleware
configApp.use(requestIp.mw());
configApp.use(helmet());

// Generic Rate Limiter
const limiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 50, // Limit each IP to 50 requests per window
});
configApp.use(limiter);

// Slow Down Attacks
const speedLimiter = slowDown({
  windowMs: 60 * 1000, // 1 minute
  delayAfter: 40,
  delayMs: 500 // Delay by 500ms for each request over 40
});
configApp.use(speedLimiter);

// CAPTCHA Middleware
configApp.use((req, res, next) => {
  const ip = req.clientIp;
  const urlPath = req.path.toLowerCase();

  // Exclude specific paths from CAPTCHA checks
  const isExcludedPath = (req.method === 'POST' && urlPath === '/growtopia/server_data.php') ||
                         (req.method === 'GET' && urlPath.startsWith('/cache'));

  if (!isExcludedPath && isSuspiciousIp(ip)) {
    return res.status(429).send('HTTP-KING CHALLENGED YOU!');
  }

  next();
});

// Function to check and update DNS rate limits
const isSuspiciousDNSRequest = (ip, requestUrl) => {
  if (!requestUrl.toLowerCase().includes('dns')) return false;
  if (!dnsWhitelist.includes(ip)) {
    const now = Date.now();
    if (!dnsRateLimit[ip]) {
      dnsRateLimit[ip] = { count: 1, timestamp: now };
    } else {
      const timePassed = now - dnsRateLimit[ip].timestamp;
      if (timePassed > BLOCK_TIME_MS) {
        dnsRateLimit[ip] = { count: 1, timestamp: now };
      } else {
        dnsRateLimit[ip].count++;
      }
      
      if (dnsRateLimit[ip].count > MAX_DNS_REQUESTS_PER_MINUTE) {
        return true;
      }
    }
  }
  return false;
};

const isUdpRateLimited = (ip) => {
  const now = Date.now();
  if (!udpRateLimit[ip]) {
    udpRateLimit[ip] = { count: 1, timestamp: now };
    return false;
  } else {
    const timePassed = now - udpRateLimit[ip].timestamp;
    if (timePassed > UDP_BLOCK_TIME_MS) {
      udpRateLimit[ip] = { count: 1, timestamp: now };
      return false;
    } else {
      udpRateLimit[ip].count++;
      if (udpRateLimit[ip].count > MAX_UDP_REQUESTS_PER_MINUTE) {
        return true;
      }
    }
  }
  return false;
};

const isNetworkUsageLimited = (ip, bytes) => {
  const now = Date.now();
  if (!networkUsage[ip]) {
    networkUsage[ip] = { sent: bytes, received: bytes, timestamp: now };
    return false;
  } else {
    const timePassed = now - networkUsage[ip].timestamp;
    if (timePassed > NETWORK_BLOCK_TIME_MS) {
      networkUsage[ip] = { sent: bytes, received: bytes, timestamp: now };
      return false;
    } else {
      networkUsage[ip].sent += bytes;
      networkUsage[ip].received += bytes;
      if (networkUsage[ip].sent > MAX_BYTES_PER_MINUTE || networkUsage[ip].received > MAX_BYTES_PER_MINUTE) {
        return true;
      }
    }
  }
  return false;
};

const logBlockedIP = (ip, protocol = 'TCP') => {
  fs.appendFile('blocked_ips.log', `${new Date().toISOString()} - Blocked IP: ${ip} [${protocol}]\n`, (err) => {
    if (err) console.error(chalk.red('Error logging blocked IP:'), err);
  });
};

const normalizeIp = (ip) => {
  if (ip.startsWith("::ffff:")) {
    return ip.substring(7);
  }
  if (ip === "::1") {
    return "127.0.0.1";
  }
  return ip;
};

const isCloudOrServerIp = (ip) => {
  return cloudServerIpRanges.some((range) => ip.startsWith(range));
};

const isBlockedGeoIp = (ip) => {
  const geo = geoip.lookup(ip);
  if (geo && !allowedCountries.includes(geo.country)) {
    return true;
  }
  return false;
};

const animateLoading = async () => {
  const frames = [
    '\x1b[32m[■□□□□□□□□□]\x1b[0m', 
    '\x1b[32m[■■□□□□□□□□]\x1b[0m', 
    '\x1b[32m[■■■□□□□□□□]\x1b[0m', 
    '\x1b[32m[■■■■□□□□□□]\x1b[0m', 
    '\x1b[32m[■■■■■□□□□□]\x1b[0m', 
    '\x1b[32m[■■■■■■□□□□]\x1b[0m', 
    '\x1b[32m[■■■■■■■□□□]\x1b[0m', 
    '\x1b[32m[■■■■■■■■□□]\x1b[0m', 
    '\x1b[32m[■■■■■■■■■□]\x1b[0m', 
    '\x1b[32m[■■■■■■■■■■]\x1b[0m',
  ];
  
  let i = 0;
  const interval = setInterval(() => {
    console.clear();
    process.stdout.write(`${frames[i]}\r`);
    i = (i + 1) % frames.length;
  }, 300);
  
  setTimeout(() => {
    clearInterval(interval);
    console.clear();
    process.stdout.write('Loading complete!\n');
    startServer();
  }, 5000);
};

animateLoading();

const configPath = path.resolve(__dirname, './config/main.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));

const readConfig = () => {
  try {
    const data = fs.readFileSync(configPath, 'utf8');
    return JSON.parse(data);
  } catch (err) {
    console.error(chalk.red('Error membaca main.json:'), err);
    return {};
  }
};

const writeConfig = (config) => {
  try {
    fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
    console.log(chalk.green('Konfigurasi berhasil diperbarui.'));
  } catch (err) {
    console.error(chalk.red('Error menulis ke main.json:'), err);
  }
};

configApp.use('/config', basicAuth({
  users: { [config.username]: config.password },
  challenge: true,
  realm: 'Config Area'
}));

configApp.get('/config', (req, res) => {
  const config = readConfig();
  res.render('config', { config });
});

configApp.post('/config', (req, res) => {
  const { ip, port, loginurl, cdn } = req.body;
  const newConfig = { ip, port: parseInt(port), loginurl, cdn };
  
  writeConfig(newConfig);
  
  res.redirect('/config');
});

const isSynFlood = (ip) => {
  const now = Date.now();
  if (!synFloodTracker[ip]) {
    synFloodTracker[ip] = { count: 1, timestamp: now };
    return false;
  }

  const timePassed = now - synFloodTracker[ip].timestamp;
  if (timePassed > SYN_BLOCK_TIME_MS) {
    synFloodTracker[ip] = { count: 1, timestamp: now };
    return false;
  } else {
    synFloodTracker[ip].count++;
    if (synFloodTracker[ip].count > MAX_SYN_REQUESTS) {
      console.log(chalk.red(`SYN flood detected from IP: ${ip}`));
      logBlockedIP(ip, 'SYN Flood');
      return true;
    }
  }

  return false;
};

// Start UI Server
configApp.listen(CONFIG_PORT, () => {
  console.log(chalk.blue(`Admin dashboard is running on http://localhost:${CONFIG_PORT}/config`));
});

// Fetch password from Pastebin
const fetchPassword = async () => {
  try {
    const response = await axios.get('https://pastebin.com/raw/xuWjQnYu');
    return response.data.trim();
  } catch (error) {
    console.error(chalk.red('SERVER DOWN'));
    process.exit(1);
  }
};

// Prompt for password
const promptForPassword = async () => {
  const password = await new Promise((resolve) => {
    process.stdout.write('Enter password: ');
    process.stdin.on('data', (data) => {
      resolve(data.toString().trim());
    });
  });

  const correctPassword = await fetchPassword();
  if (password === correctPassword) {
    console.log(chalk.green('Password correct. Starting server...'));
    return true;
  } else {
    console.log(chalk.red('Incorrect password.'));
    process.exit(1);
  }
};

// Start Server Function
const startServer = async () => {
  await promptForPassword();
  const proxy = httpProxy.createProxyServer({});
  const pk = fs.readFileSync(path.join(__dirname, 'server.key'));
  const pc = fs.readFileSync(path.join(__dirname, 'server.crt'));
  const optss = { key: pk, cert: pc };
  const trustedIPs = ['127.0.0.1'];
  const port = process.argv[2] || 443;
  import('open').then(module => {
    module.default(`https://localhost:${CONFIG_PORT}/config`);
  }).catch(err => {
    console.error(chalk.red('Failed to open admin dashboard automatically', err));
  });

  EventEmitter.defaultMaxListeners = 100;

  const packet = `server|${config.ip}\nport|${config.port}\ntype|1\n#maint|Server is currently initializing or re-syncing with sub servers. Please try again in a minute.\n\n\nloginurl|${config.loginurl}\nbeta_server|127.0.0.1\nbeta_port|17091\nbeta_type|1\nbeta2_server|127.0.0.1\nbeta2_port|17099\nbeta2_type|1\nmeta|${Math.floor(Date.now() / 1000)}\nRTENDMARKERBS1001`;

  const server = https.createServer(optss, function (req, res) {
    let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    const normalizedIp = normalizeIp(ip);
    const hostHeader = req.headers.host;
    console.log(chalk.green(`[DEVELOPER MODE]`, req.headers['user-agent']));
    console.log(chalk.green(`Request received from IP: ${normalizedIp}`));

    if (isSynFlood(ip)) {
        console.log(chalk.red(`SYN flood detected from IP: ${normalizedIp}`));
        logBlockedIP(normalizedIp, 'SYN Flood');
        
        req.socket.end();
        req.socket.destroy();
        return;
    }

    if (!rateLimitConfig[normalizedIp]) {
      rateLimitConfig[normalizedIp] = { count: 1, timestamp: Date.now() };
    } else {
      const currentTime = Date.now();
      const timePassed = currentTime - rateLimitConfig[normalizedIp].timestamp;
  
      if (timePassed > BLOCK_TIME_MS) {
        rateLimitConfig[normalizedIp] = { count: 1, timestamp: currentTime };
      } else {
        rateLimitConfig[normalizedIp].count++;
      }
  
      if (rateLimitConfig[normalizedIp].count > MAX_REQUESTS_PER_MINUTE) {
        console.log(chalk.red(`Rate limit exceeded for IP: ${normalizedIp}`));
        logBlockedIP(normalizedIp, 'TCP');
        res.statusCode = 429;
        res.end('Too many requests. Your connection is being throttled.');
        return;
      }
    }
  
    if (isSuspiciousDNSRequest(normalizedIp, req.url)) {
      console.log(chalk.red(`DNS DDoS detected from IP: ${normalizedIp}`));
      logBlockedIP(normalizedIp, 'TCP-DNS');
      res.statusCode = 403;
      res.end('Forbidden: DNS DDoS attack detected.');
      return;
    }

    const requestBytes = Buffer.byteLength(req.url);
    if (isNetworkUsageLimited(normalizedIp, requestBytes)) {
      console.log(chalk.red(`Network usage limit exceeded for IP: ${normalizedIp} (TCP)`));
      logBlockedIP(normalizedIp, 'TCP-NETWORK');
      res.statusCode = 429;
      res.end('Too many requests. Your connection is being throttled.');
      return;
    }
  
    if (!trustedIPs.includes(normalizedIp) && isCloudOrServerIp(normalizedIp)) {
      console.log(chalk.red(`Blocking cloud/server IP: ${normalizedIp}`));
      logBlockedIP(normalizedIp, 'TCP-Cloud');
      res.statusCode = 403;
      res.end('Forbidden: Access from cloud/server IP is blocked.');
      return;
    }
  
    if (isBlockedGeoIp(normalizedIp)) {
      console.log(chalk.red(`Blocking access from IP: ${normalizedIp} (GeoIP Block)`));
      logBlockedIP(normalizedIp, 'TCP-GeoIP');
      res.statusCode = 403;
      res.end('Forbidden: Access from your location is blocked.');
      return;
    }
  
    const parsedUrl = url.parse(req.url);
    const pathname = `.${parsedUrl.pathname}`;
    const ext = path.parse(pathname).ext;
  
    console.log(chalk.blue(`Connection Request from: ${normalizedIp} URL: ${req.url} Method: ${req.method}`));
  
    const map = {
      '.ico': 'image/x-icon',
      '.html': 'text/html',
      '.js': 'text/javascript',
      '.json': 'application/json',
      '.css': 'text/css',
      '.png': 'image/png',
      '.jpg': 'image/jpeg',
      '.wav': 'audio/wav',
      '.mp3': 'audio/mpeg',
      '.svg': 'image/svg+xml',
      '.pdf': 'application/pdf',
      '.doc': 'application/msword'
    };
  
    const allowedUserAgents = [
      'UbiServices_SDK_2022.Release.9_PC64_ansi_static',
      'UbiServices_SDK_2022.Release.9_ANDROID64_static',
      'UbiServices_SDK_2022.Release.9_ANDROID32_static',
      'UbiServices_SDK_2022.Release.9_IOS64',
      'UbiServices_SDK_2022.Release.9_IOS32',
      'UbiServices_SDK_2022.Release.9_MAC64',
      'UbiServices_SDK_2022.Release.9_MAC32'
    ];
  
    const userAgent = req.headers['user-agent'];

    if (req.url === "/growtopia/server_data.php") {
      if (req.method !== 'POST' || hostHeader !== 'www.growtopia2.com') {
        console.log(chalk.red(`Mata mata :  ${req.url}`));
        res.statusCode = 403;
        res.end(`
server|${normalizedIp}
port|${Math.floor(Date.now() / 69)}
type|1
#maint|Server is currently initializing or re-syncing with sub servers. Please try again in a minute.
  
loginurl|http-king.com
beta_server|${normalizedIp}
beta_port|${Math.floor(Date.now() / 69)}
beta_type|1
beta2_server|${normalizedIp}
beta2_port|${Math.floor(Date.now() / 69)}
beta2_type|1
meta|69-SCP-PROTECTION
RTENDMARKERBS1001
        `);
        return;
      }
  
      res.write(packet, function (err) {
        if (err) console.log(chalk.red(err));
      });
      res.end();
    } else if (req.url.indexOf("/cache") !== -1 || req.url.indexOf("/0098") !== -1) {
      if (req.method !== 'GET' || !allowedUserAgents.includes(userAgent)) {
        console.log(chalk.red(`Blocked: Invalid method or User-Agent for ${req.url}`));
        res.statusCode = 403;
        res.end('Hello world');
        return;
      }

      console.log(chalk.blue(`[CACHE-SPEED] Connection from: ${normalizedIp}\n Downloading assets: ${req.url}`));
  
      fs.exists(pathname, function (exist) {
        if (!exist) {
          res.statusCode = 301;
          res.writeHead(301, {
            Location: `https://ubistatic-a.akamaihd.net/${config.cdn}${req.url}`
          }).end();
          return;
        }
  
        fs.readFile(pathname, function (err, data) {
          if (err) {
            res.statusCode = 404;
            res.end(`error`);
          } else {
            res.setHeader('Content-type', map[ext] || 'text/plain');
            res.end(data);
          }
        });
      });
    }
    else {
      console.log(chalk.red(`🛡 [PROTECTION] Blocked request: ${req.method} ${req.url} from IP: ${normalizedIp}`));
      logBlockedIP(normalizedIp, 'TCP-Other');
      res.statusCode = 403;
      res.end('Hello world');
    }
  });

  server.listen(port, () => {
    console.log(chalk.green(`Server is running on port ${port}`));
  });

  const udpServer = dgram.createSocket('udp4');
  const UDP_PORT = 53;

  udpServer.on('error', (err) => {
    console.error(`UDP server error:\n${err.stack}`);
    udpServer.close();
  });

  udpServer.on('message', (msg, rinfo) => {
    const ip = rinfo.address;
    const normalizedIp = normalizeIp(ip);
    console.log(chalk.yellow(`UDP request from IP: ${normalizedIp}, Port: ${rinfo.port}`));

    if (isUdpRateLimited(normalizedIp)) {
      console.log(chalk.red(`UDP Rate limit exceeded for IP: ${normalizedIp}`));
      logBlockedIP(normalizedIp, 'UDP');
      return;
    }

    if (isBlockedGeoIp(normalizedIp)) {
      console.log(chalk.red(`Blocking UDP access from IP: ${normalizedIp} (GeoIP Block)`));
      logBlockedIP(normalizedIp, 'UDP-GeoIP');
      return;
    }

    const messageBytes = msg.length;
    if (isNetworkUsageLimited(normalizedIp, messageBytes)) {
      console.log(chalk.red(`Network usage limit exceeded for IP: ${normalizedIp} (UDP)`));
      logBlockedIP(normalizedIp, 'UDP-NETWORK');
      return;
    }

    const externalDns = '8.8.8.8';
    const externalDnsPort = 53;
    const externalClient = dgram.createSocket('udp4');

    externalClient.send(msg, 0, msg.length, externalDnsPort, externalDns, (error) => {
      if (error) {
        console.error(`Error forwarding UDP request: ${error}`);
        externalClient.close();
      }
    });

    externalClient.on('message', (response) => {
      udpServer.send(response, 0, response.length, rinfo.port, rinfo.address, (err) => {
        if (err) {
          console.error(`Error sending UDP response: ${err}`);
        }
        externalClient.close();
      });
    });

    externalClient.on('error', (err) => {
      console.error(`External DNS client error: ${err}`);
      externalClient.close();
    });

    externalClient.setTimeout(5000, () => {
      console.error(`External DNS request timed out for IP: ${normalizedIp}`);
      externalClient.close();
    });
  });

  udpServer.on('listening', () => {
    const address = udpServer.address();
    console.log(chalk.green(`UDP Server listening on ${address.address}:${address.port}`));
  });

  udpServer.bind(UDP_PORT);

  
  console.log('')
  console.log(chalk.magenta('========================================================'));
  console.log(chalk.magenta(`- CONFIGURATION LOADS : `));
  console.log(chalk.magenta(`[@] Login URL : ${config.loginurl}`));
  console.log(chalk.magenta(`[@] HTTP Port ${port}`));
  console.log(chalk.magenta(`[@] ANTI-HOSTCHECK ${port}`));
  console.log(chalk.magenta(`[@] ANTI-SCAN SERVER DATA PHP STATUS : ENABLED [PENTEST-SECURITY]`));
  console.log(chalk.magenta(`[@] OPERATION SYSTEM : WINDOWS`));
  console.log(chalk.magenta(`[@] HTTP KING VERSION : 3.3.8`));
  console.log(chalk.greenBright(`[@] SUBSCRIPTION : VIP-SUPER`));
  console.log(chalk.greenBright(`[@] LAST HTTP UPDATE : 6/10/2024`));
  console.log(chalk.magenta('========================================================'));
  console.log('')
  console.log(chalk.green(`
  
  
      :::    ::: ::::::::::: ::::    :::  ::::::::       :::     :::     ::::::::       :::::::: 
     :+:   :+:      :+:     :+:+:   :+: :+:    :+:      :+:     :+:    :+:    :+:     :+:    :+: 
    +:+  +:+       +:+     :+:+:+  +:+ +:+             +:+     +:+           +:+            +:+  
   +#++:++        +#+     +#+ +:+ +#+ :#:             +#+     +:+        +#++:          +#++:    
  +#+  +#+       +#+     +#+  +#+#+# +#+   +#+#       +#+   +#+            +#+            +#+    
 #+#   #+#      #+#     #+#   #+#+# #+#    #+#         #+#+#          #+#    #+#     #+#    #+#  
###    ### ########### ###    ####  ########            ###          ########      ########

    `));
}


can you add more only allowed application response to the POST /growtopia/server_data.php the application is "Growtopia.exe*64"