or verify your email address before making a post.
Click here for more information
or to verify your email.');
// ---
function twister_captcha_output_data($data) {
  if (isset($_GET['framed'])) {
    twister_captcha_output_html($data);
  }
  else {
    header('Content-Type: application/json');
    echo json_encode($data);
  }
}
function twister_captcha_output_html($data) {
  header('Content-Security-Policy: frame-ancestors https://*.' . TWISTER_DOMAIN . ';');
  $now = $_SERVER['REQUEST_TIME'];
?>
You can now close this page and try getting a captcha again.
 true, 'ticket' => false ]);
}
function twister_captcha_get_ticket_captcha_response() {
  if (isset($_GET['ticket_resp'])) {
    return $_GET['ticket_resp'];
  }
  
  return false;
}
function twister_captcha_get_hcaptcha_private_key() {
  $path = '/www/global/yotsuba/config/captcha_config.ini';
  
  $cfg = file_get_contents($path);
  
  if (!$cfg) {
    return false;
  }
  
  $res = preg_match('/^HCAPTCHA_API_KEY_PRIVATE ?= ?([^\s]+)$/m', $cfg, $m);
  
  if (!$res || empty($m) || !$m[1]) {
    return false;
  }
  
  return $m[1];
}
function twister_captcha_verify_ticket_captcha() {
  $response = twister_captcha_get_ticket_captcha_response();
  
  if (!$response || strlen($response) > 4096) {
    return false;
  }
  
  $captcha_private_key = twister_captcha_get_hcaptcha_private_key();
  
  if (!$captcha_private_key) {
    // Don't block in case of misconfiguration on our end
    return true;
  }
  
  $url = 'https://hcaptcha.com/siteverify';
  
  $post = array(
    'secret' => $captcha_private_key,
    'response' => $response,
    'remoteip' => $_SERVER['REMOTE_ADDR'],
    'sitekey' => TWISTER_HCAPTCHA_SITEKEY,
  );
  
  $curl = curl_init();
  
  curl_setopt($curl, CURLOPT_URL, $url);
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 2);
  curl_setopt($curl, CURLOPT_TIMEOUT, 4);
  curl_setopt($curl, CURLOPT_USERAGENT, '4chan');
  curl_setopt($curl, CURLOPT_POSTFIELDS, $post);
  
  $resp = curl_exec($curl);
  
  if ($resp === false) {
    curl_close($curl);
    return false;
  }
  
  $resp_status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  
  if ($resp_status >= 300) {
    curl_close($curl);
    return false;
  }
  
  curl_close($curl);
  
  $json = json_decode($resp, true);
  
  // BAD
  if (json_last_error() !== JSON_ERROR_NONE) {
    return false;
  }
  
  // GOOD
  if ($json && isset($json['success']) && $json['success']) {
    return true;
  }
  
  // BAD
  return false;
}
function twister_captcha_process_ticket_captcha($pcd, $now, $long_ip, $board, $msg = null, $bypassable = false) {
  // Captcha response was sent, verify it
  if (twister_captcha_get_ticket_captcha_response()) {
    if (twister_captcha_verify_ticket_captcha() === true) {
      // Captcha is valid, return a new ticket
      twister_captcha_output_new_ticket($pcd, $now, $long_ip, $board, $msg, $bypassable);
    }
    else {
      // Wrong captcha or captcha malfunction
      twister_captcha_error(TWISTER_ERR_GENERIC . ' (tcr0)');
    }
  }
  // No captcha reponse provided, tell the frontend to show a ticket captcha
  else {
    twister_captcha_output_ticket_captcha();
  }
}
function twister_captcha_output_new_ticket($pcd, $now, $long_ip, $board, $msg = null, $bypassable = false) {
  $ticket = twister_captcha_generate_ticket($now, $long_ip, $board);
  
  if (!$ticket) {
    twister_captcha_error(TWISTER_ERR_GENERIC . ' (gt1)');
  }
  
  twister_captcha_output_ticket_pcd($ticket, $pcd, $msg, $bypassable);
}
function twister_captcha_output_ticket_pcd($ticket, $pcd, $msg, $bypassable) {
  $data = [];
  
  if ($ticket) {
    $data['ticket'] = $ticket;
  }
  
  $data['pcd'] = $pcd;
  
  if ($msg) {
    $data['pcd_msg'] = $msg;
  }
  
  if ($bypassable) {
    $data['bpcd'] = true;
  }
  
  twister_captcha_output_data($data);
}
function twister_captcha_error($msg, $extra = null) {
  //http_response_code(500);
  $data = [ 'error' => $msg ];
  
  if ($extra) {
    $data = array_merge($data, $extra);
  }
  
  twister_captcha_output_data($data);
  
  die();
}
function twister_captcha_is_req_suspicious() {
  /*
  if (isset($_SERVER['HTTP_X_HTTP_VERSION'])) {
    if (strpos($_SERVER['HTTP_X_HTTP_VERSION'], 'HTTP/1') === 0) {
      return true;
    }
  }
  */
  $no_lang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) === false;
  $no_accept = isset($_SERVER['HTTP_ACCEPT']) === false;
  
  if ($no_lang && $no_accept) {
    return true;
  }
  
  if ($no_lang && strpos($_SERVER['HTTP_USER_AGENT'], '; wv)') !== false) {
    return true;
  }
  
  if ($no_accept && strpos($_SERVER['HTTP_USER_AGENT'], 'iPhone') !== false && strpos($_SERVER['HTTP_USER_AGENT'], 'Mobile') === false) {
    return true;
  }
  
  if ($no_lang && isset($_SERVER['HTTP_REFERER'])) {
    $ref = $_SERVER['HTTP_REFERER'];
    
    if (strpos($ref, 'sys.4chan.org') !== false || strpos($ref, '/thread/') !== false) {
      return true;
    }
  }
  
  return false;
}
function twister_captcha_need_hcaptcha() {
  if (!isset($_SERVER['HTTP_X_BOT_SCORE'])) {
    return true;
  }
  
  $ua = $_SERVER['HTTP_USER_AGENT'];
  
  $score = (int)$_SERVER['HTTP_X_BOT_SCORE'];
  
  // Skip Android Webviews
  if ($score == 1 && strpos($ua, '; wv)') !== false) {
    return false;
  }
  
  return $score < 99;
}
function twister_captcha_check_likely_automated($memcached, $now, $threshold = 29) {
  if (!isset($_SERVER['HTTP_X_BOT_SCORE'])) {
    return false;
  }
  
  $ua = $_SERVER['HTTP_USER_AGENT'];
  
  // Skip Android Webviews
  if (strpos($ua, '; wv)') !== false) {
    return false;
  }
  
  // Skip iPhone Webviews
  if (preg_match('/iPhone|iPad/', $ua) && !preg_match('/Mobile|Safari/', $ua)) {
    return false;
  }
  
  $score = (int)$_SERVER['HTTP_X_BOT_SCORE'];
  
  if ($score > 1 && $score <= $threshold) {
    $key = 'bmbot' . $_SERVER['REMOTE_ADDR'];
    $memcached->set($key, 1, $now + 43200);
    return true;
  }
  
  return false;
}
function twister_captcha_get_pcd_penalty($board, $thread_id) {
  $count = 0;
  
  return 0; // FIXME
  
  // Reports
  if ($thread_id === 1 && $board !== '!') {
    return 0;
  }
  
  if (isset($_SERVER['HTTP_X_GEO_ASN'])) {
    $asn = (int)$_SERVER['HTTP_X_GEO_ASN'];
  }
  else {
    $asn = 0;
  }
  
  if (isset($_SERVER['HTTP_X_GEO_COUNTRY'])) {
    $country = $_SERVER['HTTP_X_GEO_COUNTRY'];
  }
  else {
    $country = null;
  }
  
  // Mobile clients
  if ($asn > 0 && in_array($asn, TWISTER_MOBILE_ASNS)) {
    $count++;
  }
  else if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/Mobile|iPhone|; wv/', $_SERVER['HTTP_USER_AGENT'])) {
    $count++;
  }
  
  // Rare countries
  if ($country && !in_array($country, TWISTER_WHITELIST_COUNTRIES)) {
    $count++;
  }
  
  // Rare ISPs
  if ($asn > 0 && !in_array($asn, TWISTER_WHITELIST_ASNS)) {
    $count++;
  }
  
  return $count * TWISTER_PRE_CD_PENALITY;
}
function twister_captcha_store_challenge($memcached, $challenge_uid, $long_ip, $expiration) {
  if (!$challenge_uid || !$long_ip || !$expiration) {
    return false;
  }
  
  return $memcached->set("ch$long_ip", $challenge_uid, $expiration);
}
// Cooldowns
function twister_captcha_store_cooldown($memcached, $long_ip, $expiration) {
  if (!$long_ip || !$expiration) {
    return false;
  }
  
  return $memcached->set("cd$long_ip", $expiration, $expiration);
}
function twister_captcha_get_cooldown($memcached, $long_ip) {
  if (!$long_ip) {
    return false;
  }
  
  $val = $memcached->get("cd$long_ip");
  
  if ($val === false && $memcached->getResultCode() === Memcached::RES_NOTFOUND) {
    return 0;
  }
  
  return $val;
}
function twister_captcha_generate_ticket($now, $long_ip, $board) {
  if (!$long_ip || !$now) {
    return false;
  }
  
  $hmac_secret = base64_decode(TWISTER_HMAC_SECRET);
  
  if (!$hmac_secret) {
    return false;
  }
  
  $hash = hash_hmac('sha256', "$now.$long_ip.$board", $hmac_secret);
  
  if (!$hash) {
    return false;
  }
  
  return "$now.$hash";
}
function twister_captcha_decode_ticket($ticket, $long_ip, $board) {
  if (!$long_ip || !$ticket) {
    return false;
  }
  
  $hmac_secret = base64_decode(TWISTER_HMAC_SECRET);
  
  if (!$hmac_secret) {
    return false;
  }
  
  list($ts, $hash) = explode('.', $ticket);
  
  $ts = (int)$ts;
  
  if (!$ts || !$hash) {
    return false;
  }
  
  $this_hash = hash_hmac('sha256', "$ts.$long_ip.$board", $hmac_secret);
  
  if ($this_hash === $hash) {
    return $ts;
  }
  
  return false;
}
function twister_captcha_should_purge_ticket($ticket, $now) {
  if (!$ticket || !$now) {
    return false;
  }
  
  list($ts, $hash) = explode('.', $ticket);
  
  if (!$ts) {
    return false;
  }
  
  return $ts + TWISTER_TICKET_TTL <= $now;
}
function twister_captcha_store_session($memcached, $long_ip, $count, $expiration) {
  if (!$long_ip || !$expiration || $count < 1) {
    return false;
  }
  
  $key = "us$long_ip";
  
  $res = $memcached->replace($key, $count, $expiration);
  
  if ($res === false) {
    if ($memcached->getResultCode() === Memcached::RES_NOTSTORED) {
      return $memcached->set($key, $count, $expiration);
    }
    else {
      return false;
    }
  }
  
  return true;
}
function twister_captcha_get_session($memcached, $long_ip) {
  if (!$long_ip) {
    return false;
  }
  
  $val = $memcached->get("us$long_ip");
  
  if ($val === false) {
    if ($memcached->getResultCode() === Memcached::RES_NOTFOUND) {
      return 0;
    }
    else {
      return false;
    }
  }
  
  return $val;
}
function twister_captcha_get_credits($memcached, $pwd) {
  if (!$pwd) {
    return false;
  }
  
  $key = "cr-$pwd";
  $val = $memcached->get($key);
  
  if ($val === false) {
    return false;
  }
  
  $val = explode('.', $val);
  
  $count = (int)$val[0];
  $ts = (int)$val[1];
  
  if ($count <= 0 || $ts <= 0) {
    $memcached->delete($key);
    return false;
  }
  
  return [ $count, $ts ];
}
function twister_captcha_get_userpwd($user_ip) {
  if (isset($_COOKIE['4chan_pass'])) {
    $_c = $_COOKIE['4chan_pass'];
  }
  else {
    $_c = null;
  }
  
  return new UserPwd($user_ip, TWISTER_DOMAIN, $_COOKIE['4chan_pass']);
}
// ---
// Dummy page for cloudflare challenges
if (isset($_GET['opened'])) {
  twister_captcha_output_dummy();
  die();
}
// ---
// Block TOR immediately
if ($_SERVER['HTTP_X_GEO_CONTINENT'] === 'T1') {
  twister_captcha_error(TWISTER_ERR_GENERIC . ' (T1)');
}
// Check parameters
if (isset($_GET['board']) && preg_match('/^[!0-9a-z]{1,10}$/', $_GET['board'])) {
  $param_board = $_GET['board'];
}
else {
  $param_board = '!';
}
if (isset($_GET['thread_id']) && $_GET['thread_id']) {
  $param_thread_id = (int)$_GET['thread_id'];
}
else {
  $param_thread_id = 0;
}
$now = $_SERVER['REQUEST_TIME'];
$user_ip = $_SERVER['REMOTE_ADDR'];
$user_long_ip = ip2long($user_ip);
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
  $user_agent = '!';
}
else {
  $user_agent = md5($_SERVER['HTTP_USER_AGENT']);
}
$userpwd = twister_captcha_get_userpwd($user_ip);
if (!$userpwd) {
  twister_captcha_error(TWISTER_ERR_GENERIC . ' (GU1)');
}
if (!$userpwd->isNew() && $param_board !== '!signin') {
  $password = $userpwd->getPwd();
}
else {
  $password = '!';
}
// ---
$use_static = false;
$difficulty = TwisterCaptcha::LEVEL_NORMAL;
$char_count = TWISTER_CHARS_MAX;
// ---
$m = new Memcached();
// Only call the following once (when getServerList() is empty) if using persistent connections
//$m->setOption(Memcached::OPT_TCP_NODELAY, true);
$m->setOption(Memcached::OPT_SERVER_FAILURE_LIMIT, 1);
$m->setOption(Memcached::OPT_SEND_TIMEOUT, 500000); // 500ms
$m->setOption(Memcached::OPT_RECV_TIMEOUT, 500000); // 500ms
// Only use one server. Having multiple servers will break the captcha
// as "set" is used instead of "replace + add"
if ($m->addServer(TWISTER_MEMCACHED_HOST, TWISTER_MEMCACHED_PORT) === false) {
  twister_captcha_error(TWISTER_ERR_GENERIC . ' (c0)');
}
// ---
// If CF's bot score is low, store the information for 1h and use it during posting
twister_captcha_check_likely_automated($m, $now);
/**
 * Pre-cooldowns
 */
$pcd = 0;
// Posting a thread
if ($param_thread_id === 0) {
  if ($userpwd->threadCount() < 1 || ($userpwd->ipChanged() && $userpwd->postCount() < 2)) {
    $pcd = TWISTER_PRE_CD_THREAD;
  }
}
// Posting a reply (reporting uses a thread id of 1)
else if ($param_thread_id !== 1) {
  if ($userpwd->postCount() < 1 || ($userpwd->ipChanged() && $userpwd->postCount() < 2)) {
    $pcd = TWISTER_PRE_CD_REPLY;
  }
}
// Reporting a post
else if ($param_board !== '!') {
  if ($userpwd->reportCount() < 1 && $userpwd->postCount() < 1) {
    $pcd = TWISTER_PRE_CD_REPORT;
  }
}
if ($param_thread_id != 1 && !$userpwd->verifiedLevel()) {
  // Initial cooldown, bypassable by verifying your email
  if ($userpwd->postCount() < 1 || !$userpwd->isUserKnown(15)) {
    $pcd = 900;
  }
  // Cooldown for when the user is still new and the mask changes
  else if ($userpwd->postCount() > 0 && $userpwd->pwdLifetime() < 86400 && $userpwd->maskChanged()) { // 24h
    $pcd = 180;
  }
}
// Pre-cooldown needed
if ($pcd > 0 && $param_board !== '!signin') {
  // Extra pre-cooldown for unverified users
  if (!$userpwd->verifiedLevel()) {
    $pcd += twister_captcha_get_pcd_penalty($param_board, $param_thread_id);
  }
  
  $ticket_ts = twister_captcha_decode_ticket($_GET['ticket'], $user_long_ip, $param_board);
  
  $bypassable = false;
  
  if ($param_thread_id === 0) {
    $pcd_msg = TWISTER_ERR_PCD_THREAD;
  }
  else if ($param_thread_id !== 1 && $param_board !== '!') {
    $pcd_msg = TWISTER_ERR_PCD_REPLY;
  }
  
  if ($param_thread_id !== 1 && $pcd >= 900) {
    $pcd_msg = TWISTER_ERR_PCD_SIGNIN;
    $bypassable = true;
  }
  
  if (!$ticket_ts) {
    //if (TWISTER_USE_TICKET_CAPTCHA && !in_array((int)$_SERVER['HTTP_X_GEO_ASN'], TWISTER_WHITELIST_ASNS)) {
    if (TWISTER_USE_TICKET_CAPTCHA && twister_captcha_need_hcaptcha()) {
      twister_captcha_process_ticket_captcha($pcd, $now, $user_long_ip, $param_board, $pcd_msg, $bypassable);
    }
    else {
      twister_captcha_output_new_ticket($pcd, $now, $user_long_ip, $param_board, $pcd_msg, $bypassable);
    }
    
    die();
  }
  
  $ticket_lifetime = $now - $ticket_ts;
  
  if ($ticket_lifetime < $pcd) {
    $pcd = $pcd - $ticket_lifetime;
    twister_captcha_output_ticket_pcd(null, $pcd, $pcd_msg, $bypassable);
    die();
  }
  
  // Ticket expired
  if ($ticket_lifetime >= TWISTER_TICKET_TTL) {
    if (TWISTER_USE_TICKET_CAPTCHA && twister_captcha_need_hcaptcha()) {
      twister_captcha_process_ticket_captcha($pcd, $now, $user_long_ip, $param_board, $pcd_msg, $bypassable);
    }
    else {
      twister_captcha_output_new_ticket($pcd, $now, $user_long_ip, $param_board, $pcd_msg, $bypassable);
    }
    
    die();
  }
}
/**
 * Adjust difficulty
 */
$ip_ttl_static = TWISTER_PWD_TTL_IP_STATIC;
$mask_ttl_static = TWISTER_PWD_TTL_MASK_STATIC;
$ip_ttl_min = TWISTER_PWD_TTL_IP_MIN;
$idle_ttl = TWISTER_PWD_TTL_IDLE;
// Serve max len twister to bad actors
$bad_actor = false;
if (!isset($_SERVER['HTTP_USER_AGENT']) || !$_SERVER['HTTP_USER_AGENT']) {
  $bad_actor = true;
}
else if (preg_match(TWISTER_BAD_UA, $_SERVER['HTTP_USER_AGENT'])) {
  $bad_actor = true;
}
else if (twister_captcha_is_req_suspicious()) {
  $bad_actor = true;
}
// Serve static captcha to known users.
// Serve max length captchas for unknown users.
// Only applies to replies. Theads always use slider captchas.
if ($param_thread_id !== 0 && !$bad_actor) {
  // Known and post count check
  if ($userpwd->isUserKnown(TWISTER_PWD_STATIC_KNOWN) && $userpwd->postCount() >= TWISTER_PWD_STATIC_POSTS) {
    // Inactivity and IP change checks for unverified users
    if ($userpwd->verifiedLevel()) {
      $use_static = true;
      $char_count = TWISTER_CHARS_MIN;
    }
    else if ($userpwd->idleLifetime() <= TWISTER_PWD_SLIDER_IDLE && !$userpwd->ipChanged()) {
      $use_static = true;
      $char_count = TWISTER_CHARS;
    }
  }
}
// Check captcha bypassing credits
if (TWISTER_ALLOW_NOOP && !in_array($param_board, TWISTER_NO_NOOP_BOARDS) && !$bad_actor && $param_thread_id !== 0) {
  if ($userpwd->isUserKnown(TWISTER_PWD_NOOP_KNOWN) && $userpwd->postCount() >= TWISTER_PWD_NOOP_POSTS) {
    $credits = twister_captcha_get_credits($m, $userpwd->getPwd());
    
    if ($credits !== false && $credits[0] > 0) {
      $data = [
        'challenge' => 'noop',
        'ttl' => min(TWISTER_TTL, $credits[1] - $now),
        'cd' => TWISTER_COOLDOWN
      ];
      
      twister_captcha_output_data($data);
      
      die();
    }
  }
}
// Check cooldown
$should_cd_until = twister_captcha_get_cooldown($m, $user_long_ip);
if ($should_cd_until === false) {
  twister_captcha_error(TWISTER_ERR_GENERIC . ' (scdu)');
}
if ($should_cd_until > 1) {
  twister_captcha_error(TWISTER_ERR_COOLDOWN, ['cd' => $should_cd_until - $now]);
}
// Number of unsolved captchas requested recently
$unsolved_count = twister_captcha_get_session($m, $user_long_ip);
if ($unsolved_count === false) {
  twister_captcha_error(TWISTER_ERR_GENERIC . ' (gus1)');
}
if ($unsolved_count > 2) {
  $cooldown = TWISTER_COOLDOWN_LONG * min($unsolved_count, 20);
  
  if ($unsolved_count > 10) {
    $cooldown = 300;
  }
}
else {
  $cooldown = TWISTER_COOLDOWN;
}
if (twister_captcha_store_cooldown($m, $user_long_ip, $now + $cooldown) === false) {
  twister_captcha_error(TWISTER_ERR_GENERIC . ' (sc0)');
}
// Generate images
$c = new TwisterCaptcha(TWISTER_FONT_PATH);
$c->setDifficulty($difficulty);
// Adjust features
// User is suspicious
$should_harden = $bad_actor && ($userpwd->ipChanged() || !$userpwd->isUserKnownOrVerified(7200) || mt_rand(0, 9) === 9);
$should_fog = false;
if ($should_harden) {
  $use_static = false;
  
  if (mt_rand(0, 1)) {
    $c->useInkBlot(true);
    
    if (mt_rand(0, 1)) {
      $c->useNegateBlotFilter(true);
    }
  }
  else {
    $c->useEdgeBlock(true);
  }
  
  $c->useFakeCharPadding(true);
  $c->useJumpyMode(true);
  
  if (mt_rand(0, 9) === 9) {
    $c->setDifficulty(TwisterCaptcha::LEVEL_LUNATIC);
  }
  else {
    $c->setDifficulty(TwisterCaptcha::LEVEL_HARD);
  }
  
  if (mt_rand(0, 9) === 9) {
    $char_count = 8;
  }
  
  if (mt_rand(0, 1)) {
    $c->useGridLines(true);
  }
  else {
    $c->useScoreLines(true);
  }
}
// Other new users
else if ($userpwd->postCount() < 5 || $userpwd->maskLifetime() < 3600) {
  if (!$use_static) {
    $_boards = [ 'a', 'v', 'vg', 'co', 'vp', 'g', 'biz', 'b', 'vt', 'mu', 'pol', 'tv', 'sp', 'int', 'soc', 'test' ];
    
    if (true || in_array($param_board, $_boards) || $param_thread_id == 0) {
      $should_fog = true;
    }
    else {
      $c->useInkBlot(true);
      $c->useScoreLines(true);
    }
  }
  else {
    $c->useInkBlot(true);
    //$c->useFakeCharPadding(true);
    
    if (mt_rand(0, 1)) {
      $c->useJumpyMode(true);
    }
  }
  
  $char_count = mt_rand(TWISTER_CHARS, TWISTER_CHARS_MAX);
}
if ($param_board === '!signin') {
  $should_fog = true;
  $use_static = false;
}
if ($use_static) {
  if (mt_rand(0, 9) === 9) {
    $c->useScoreLines(true);
  }
  
  list($challenge_str, $img, $img_width, $img_height) = $c->generateStatic($char_count);
  $img_bg = null;
}
else {
  if ($should_fog) {
    if (false && $param_board == 'co' && $param_thread_id == 0) {
      $c->useEdgeDetect(true);
      $c->useSpecialRot(true);
      //$c->useOverlayId(5, true);
      $c->useInvert(true);
      //$c->useAltBlackWhite(true);
      $c->useGridLines(true);
      $c->useSimplexBg(true);
      //$c->useEmboss(true);
      //list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHFogNew($char_count);
      list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHFogNew(7, 28, 28);
      //list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterV(3);
    }
    else {
      $_olid = mt_rand(0, 6);
      if ($_olid) {
        $c->useOverlayId($_olid, (bool)mt_rand(0, 1));
      }
      
      if (mt_rand(0, 1)) {
        $c->useInvert(true);
      }
      
      if (mt_rand(0, 3) === 3) {
        $c->useFakeCharPadding(true);
      }
      
      if ($param_thread_id == 0) {
        $_olid = 2;
      }
      else {
        $_olid = mt_rand(1, 2);
      }
      
      $_olid = 2;
      
      // Simplex
      if ($_olid === 1) {
        if (mt_rand(0, 3) === 3) {
          $c->useScoreLines(true);
        }
        
        if (mt_rand(0, 1)) {
          $c->useInvert(true);
        }
        
        list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHSimplex($char_count);
      }
      // Fog New
      else if ($_olid === 2) {
        if (mt_rand(0, 1)) {
          $c->useSimplexBg(true);
        }
        
        if (mt_rand(0, 1)) {
          $c->useEmboss(true);
        }
        else {
          $c->useEdgeDetect(true);
        }
        
        if (mt_rand(0, 1)) {
          $c->useAltBlackWhite(true);
        }
        
        if (mt_rand(0, 1)) {
          $c->useInvert(true);
        }
        
        //if (mt_rand(0, 3) === 3) {
          //$c->useSpecialRot(true);
          //$char_count = 5;
        //}
        
        if (isset($_SERVER['HTTP_X_BOT_SCORE'])) {
          $_bot_score = (int)$_SERVER['HTTP_X_BOT_SCORE'];
        }
        else {
          $_bot_score = 100;
        }
        
        if ($param_board === '!signin') {
          if ($_bot_score > 95) {
            list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHFogNew(5, 30, 50);
          }
          else {
            list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateSimpleTask(2);
          }
        }
        else {
          //$c->useSpecialRot(true);
          
          if ($_bot_score <= 80 && mt_rand(0, 1) === 0) {
            list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateSimpleTask(2);
          }
          else {
            list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHFogNew(5, 30, 50);
          }
        }
      }
      // Default
      else {
        list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterHFogNew($char_count);
      }
    }
  }
  else {
    list($challenge_str, $img, $img_width, $img_height, $img_bg, $bg_width) = $c->generateTwisterH($char_count);
  }
}
if (!$challenge_str) {
  twister_captcha_error(TWISTER_ERR_GENERIC . ' (ncs1)');
}
list($challenge_uid, $challenge_hash) = TwisterCaptcha::getChallengeHash(
  $challenge_str,
  [$user_ip, $password, $user_agent, $param_board, $param_thread_id]
);
if (!$challenge_uid || !$challenge_hash) {
  twister_captcha_error(TWISTER_ERR_GENERIC . ' (gch1)');
}
// Register challenge
if (twister_captcha_store_challenge($m, $challenge_uid, $user_long_ip, $now + TWISTER_TTL) === false) {
  twister_captcha_error(TWISTER_ERR_GENERIC . ' (sch1)');
}
// Register unsolved session
if (twister_captcha_store_session($m, $user_long_ip, $unsolved_count + 1, $now + TWISTER_TTL_UNSOLVED) === false) {
  twister_captcha_error(TWISTER_ERR_GENERIC . ' (sus1)');
}
// Generate base 64 urls of images
list($img_b64, $img_bg_b64) = TwisterCaptcha::getBase64Images($img, $img_bg);
$data = [
  'challenge' => "$challenge_uid.$challenge_hash",
  'ttl' => TWISTER_TTL,
  'cd' => $cooldown,
  'img' => $img_b64,
  'img_width' => $img_width,
  'img_height' => $img_height
];
if ($img_bg) {
  $data['bg'] = $img_bg_b64;
  $data['bg_width'] = $bg_width;
}
if (twister_captcha_should_purge_ticket($_GET['ticket'], $now)) {
  $data['ticket'] = false;
}
twister_captcha_output_data($data);