#include "lisans_canli.hpp" #include #include #include #include #include #include #include "ayar_kalici.hpp" // ayarOku/ayarYaz (URL) #include "fiko/cekirdek/lisans_uzak.hpp" // canliKararCoz (imzalı karar doğrulama) #include "fiko/cekirdek/makine_kimlik.hpp" #include "fiko/cekirdek/surum.hpp" // SURUM_METIN #include "fiko/core/gunluk.hpp" #include "imgui.h" #include "lisans_yukle.hpp" // lisansAcikAnahtarHex, lisansOturum #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX #define NOMINMAX #endif #include #include namespace fiko { namespace { // Karakter sabitleri (kaçışsız): JSON üretimi/çözümlemesi ASCII kodlarıyla. constexpr char NL = static_cast(10); constexpr char TAB = static_cast(9); constexpr char TERS = static_cast(92); // JSON kaçış işareti constexpr char TIRNAK = static_cast(34); constexpr const char* CANLI_URL_AYAR = "lisans_canli_url"; constexpr DWORD YENIDEN_BAGLANMA_MIN_MS = 5000; // kopunca ilk deneme constexpr DWORD YENIDEN_BAGLANMA_MAKS_MS = 60000; // tavana büyür constexpr DWORD PING_ARALIGI_SN = 30; // NAT canlı tutma // ── Durum ──────────────────────────────────────────────────────────────────── enum class KanalDurum : u8 { Kapali = 0, Baglaniyor, Bagli, Kopuk }; std::mutex g_mtx; std::map g_kararlar; // seri → son imzalı karar KanalDurum g_durum = KanalDurum::Kapali; std::string g_hata; // son bağlantı hatası i64 g_sonKararZaman = 0; // son kabul edilen kararın zamanı int g_kararSayisi = 0; // bu oturumda kabul edilen karar sayısı std::string g_sunucuAdi; // bağlı host (özet için) std::atomic g_isParcacigiSuruyor{false}; std::atomic g_taze{false}; // yeni karar → pompa lisansı yeniden yükler std::atomic g_yenidenBaglanIste{false}; // "Şimdi bağlan" düğmesi std::string urlOku() { std::string url = ayarOku(CANLI_URL_AYAR, ""); std::string kirp; for (char c : url) if (!std::isspace((unsigned char)c)) kirp.push_back(c); return kirp; } // ── URL: [ws://|wss://]host[:port]/yol ─────────────────────────────────────── struct UrlParca { bool https = true; u16 port = 443; std::string host, yol; }; bool urlCoz(const std::string& url, UrlParca& u) { std::string kalan = url; for (char& c : kalan) if (c == static_cast(92)) c = '/'; // ters bölü → eğik (yanlışlıkla) if (kalan.rfind("wss://", 0) == 0) { u.https = true; kalan.erase(0, 6); } else if (kalan.rfind("ws://", 0) == 0) { u.https = false; kalan.erase(0, 5); } if (kalan.empty()) return false; std::string yetki = kalan, yol = "/"; if (const std::size_t egik = kalan.find('/'); egik != std::string::npos) { yetki = kalan.substr(0, egik); yol = kalan.substr(egik); } if (yetki.empty()) return false; std::string host = yetki; if (!yetki.empty() && yetki.front() == '[') { // [IPv6]:port const std::size_t kapa = yetki.find(']'); if (kapa == std::string::npos) return false; host = yetki.substr(1, kapa - 1); if (kapa + 1 < yetki.size() && yetki[kapa + 1] == ':') u.port = (u16)std::atoi(yetki.c_str() + kapa + 2); } else if (const std::size_t iki = yetki.rfind(':'); iki != std::string::npos) { host = yetki.substr(0, iki); u.port = (u16)std::atoi(yetki.c_str() + iki + 1); } if (host.empty() || u.port == 0) return false; u.host = host; u.yol = yol; return true; } // ── JSON üretimi (değerler kaçışlanır — kaynak içinde kaçış dizisi YOK) ────── void jsonDizgeEkle(std::string& j, const char* anahtar, const std::string& deger) { j += TIRNAK; j += anahtar; j += TIRNAK; j += ':'; j += TIRNAK; for (char c : deger) { if (c == TIRNAK || c == TERS) { j += TERS; // kaçış işareti j += c; // ardından kendisi (" veya \) } else if (c == NL) { j += TERS; j += 'n'; } else if (c == TAB) { j += TERS; j += 't'; } else if ((unsigned char)c < 0x20) { // diğer kontrol karakterleri → boşluk (mesaj/bilgisayar adı temizliği) j += ' '; } else { j += c; } } j += TIRNAK; } void jsonSayiEkle(std::string& j, const char* anahtar, long long deger) { j += TIRNAK; j += anahtar; j += TIRNAK; j += ':'; j += std::to_string(deger); } // Alt nesne/dizi açıcılar: önceki alanla araya virgül koyar (JSON geçerliliği). void jsonNesneAc(std::string& j, const char* anahtar) { j += ','; j += TIRNAK; j += anahtar; j += TIRNAK; j += ':'; } void jsonDiziAc(std::string& j, const char* anahtar) { jsonNesneAc(j, anahtar); j += '['; } // Merhaba mesajı: sunucu panosunda satır olarak görünecek her şey. std::string merhabaMesajiUret() { const LisansOturum& o = lisansOturum(); std::string j = "{"; jsonDizgeEkle(j, "tip", "merhaba"); jsonDizgeEkle(j, "seri", o.bilgi.seri); jsonDizgeEkle(j, "musteri", o.bilgi.musteri); if (const char* cn = std::getenv("COMPUTERNAME"); cn && *cn) jsonDizgeEkle(j, "bilgisayar", cn); jsonDizgeEkle(j, "surum", SURUM_METIN ? SURUM_METIN : ""); jsonSayiEkle(j, "pid", (long long)GetCurrentProcessId()); jsonDiziAc(j, "makine"); bool ilk = true; for (const std::string& h : makineParmakIzi().hashListesi()) { if (!ilk) j += ','; ilk = false; j += TIRNAK; for (char c : h) { if (c == TIRNAK || c == TERS || c == NL) { j += TERS; j += 'n'; } else { j += c; } } j += TIRNAK; } j += "]}"; return j; } // ── Karar uygulama ─────────────────────────────────────────────────────────── void kararUygula(const CanliKarar& k) { { std::lock_guard lk(g_mtx); g_kararlar[k.seri] = k; ++g_kararSayisi; g_sonKararZaman = k.zaman; } gunluk::bilgi("Canlı lisans kararı: {} → {} ({})", k.seri, k.engelli ? "ENGELLI" : "izinli", k.mesaj); g_taze = true; // pompa UI iş parçacığında lisansı yeniden yükler } // ── WS oturumu: bağlan → merhaba → alışveriş döngüsü ──────────────────────── // Dönüş: ne kadar süre sonra yeniden bağlanma denenecek (ms). DWORD wsOturumu(const UrlParca& u) { const std::wstring whost(u.host.begin(), u.host.end()); const std::wstring wyol(u.yol.begin(), u.yol.end()); HINTERNET oturum = WinHttpOpen(L"FikoYT-LisansCanli/1.0", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!oturum) { std::lock_guard lk(g_mtx); g_hata = "WinHttpOpen başarısız"; return YENIDEN_BAGLANMA_MIN_MS; } WinHttpSetTimeouts(oturum, 8000, 8000, 12000, INFINITE); HINTERNET baglanti = WinHttpConnect(oturum, whost.c_str(), u.port, 0); HINTERNET istek = baglanti ? WinHttpOpenRequest(baglanti, L"GET", wyol.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, u.https ? WINHTTP_FLAG_SECURE : 0) : nullptr; HINTERNET ws = nullptr; do { if (!istek) { std::lock_guard lk(g_mtx); g_hata = "bağlantı kurulamadı"; break; } // WS yükseltme bayrağı: istek Upgrade: websocket başlığıyla gönderilir. DWORD yukselt = WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET; if (!WinHttpSetOption(istek, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, &yukselt, sizeof(yukselt))) { std::lock_guard lk(g_mtx); g_hata = "WS yükseltme ayarlanamadı"; break; } if (!WinHttpSendRequest(istek, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) || !WinHttpReceiveResponse(istek, nullptr)) { std::lock_guard lk(g_mtx); g_hata = "sunucuya erişilemiyor"; break; } DWORD kod = 0, boyut = sizeof(kod); WinHttpQueryHeaders(istek, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &kod, &boyut, WINHTTP_NO_HEADER_INDEX); if (kod != 101) { std::lock_guard lk(g_mtx); g_hata = "sunucu WS yükseltmesini reddetti (HTTP " + std::to_string(kod) + ")"; break; } ws = WinHttpWebSocketCompleteUpgrade(istek, 0); // bağlam yok (DWORD_PTR) if (!ws) { std::lock_guard lk(g_mtx); g_hata = "WS yükseltme tamamlanamadı"; break; } // Bağlandı → durum + merhaba. { std::lock_guard lk(g_mtx); g_durum = KanalDurum::Bagli; g_hata.clear(); g_sunucuAdi = u.host; } gunluk::bilgi("Canlı lisans kanalı bağlandı: {}", u.host); const std::string merhaba = merhabaMesajiUret(); if (WinHttpWebSocketSend(ws, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, (PVOID)merhaba.data(), (DWORD)merhaba.size()) != NO_ERROR) { std::lock_guard lk(g_mtx); g_hata = "merhaba gönderilemedi"; break; } // Alışveriş döngüsü: gelen mesajları çöz; periyodik ping gönder. std::string birikim; auto sonPing = std::chrono::steady_clock::now(); for (;;) { // Ping zamanı geldiyse gönder (NAT canlı tutma). if (std::chrono::steady_clock::now() - sonPing > std::chrono::seconds(PING_ARALIGI_SN)) { sonPing = std::chrono::steady_clock::now(); std::string ping = "{"; jsonDizgeEkle(ping, "tip", "ping"); ping += '}'; if (WinHttpWebSocketSend(ws, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, (PVOID)ping.data(), (DWORD)ping.size()) != NO_ERROR) break; } char tampon[4096]; DWORD okunan = 0; WINHTTP_WEB_SOCKET_BUFFER_TYPE tur = WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE; const DWORD rc = WinHttpWebSocketReceive(ws, tampon, sizeof(tampon), &okunan, &tur); if (rc != NO_ERROR) { std::lock_guard lk(g_mtx); g_hata = "bağlantı koptu (okuma)"; break; } if (tur == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) { std::lock_guard lk(g_mtx); g_hata = "sunucu bağlantıyı kapattı"; break; } birikim.append(tampon, okunan); if (tur != WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE) continue; // parça // Tam UTF-8 mesaj geldi. const std::string mesaj = std::move(birikim); birikim.clear(); CanliKarar k; std::string hata; const UzakPolitikaDurum d = canliKararCoz(mesaj, lisansAcikAnahtarHex(), k, hata); if (d == UzakPolitikaDurum::Gecerli) { kararUygula(k); } else if (d == UzakPolitikaDurum::Bicim && hata.find("tip") != std::string::npos) { // ping/pong gibi başka tipler → sessizce yok say. } else { gunluk::uyari("Canlı kanal mesajı reddedildi ({}): {}", hata, mesaj.substr(0, 120)); } } } while (false); if (ws) { WinHttpWebSocketClose(ws, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, nullptr, 0); WinHttpCloseHandle(ws); } if (istek) WinHttpCloseHandle(istek); if (baglanti) WinHttpCloseHandle(baglanti); WinHttpCloseHandle(oturum); { std::lock_guard lk(g_mtx); if (g_durum == KanalDurum::Bagli) g_durum = KanalDurum::Kopuk; } gunluk::bilgi("Canlı lisans kanalı kapandı"); return YENIDEN_BAGLANMA_MIN_MS; } // Arka plan iş parçacığı: URL dolu olduğu sürece bağlan/kop/yeniden bağlan. void canliIsParcacigi() { DWORD beklemeMs = 0; for (;;) { if (urlOku().empty()) { std::lock_guard lk(g_mtx); g_durum = KanalDurum::Kapali; // URL silindi → bekle; ayar tekrar dolarsa dışarıdan yeniden başlatılır. for (int i = 0; i < 100 && urlOku().empty(); ++i) std::this_thread::sleep_for(std::chrono::milliseconds(200)); continue; } if (g_yenidenBaglanIste.exchange(false)) beklemeMs = 0; UrlParca u; if (!urlCoz(urlOku(), u)) { { std::lock_guard lk(g_mtx); g_hata = "geçersiz URL"; g_durum = KanalDurum::Kopuk; } std::this_thread::sleep_for(std::chrono::seconds(10)); continue; } { std::lock_guard lk(g_mtx); g_durum = KanalDurum::Baglaniyor; } beklemeMs = wsOturumu(u); // Üstel geri çekilme: her başarısız turda büyüt, tavanla sınırla. beklemeMs = std::min(beklemeMs * 2, YENIDEN_BAGLANMA_MAKS_MS); for (DWORD bekledi = 0; bekledi < beklemeMs; bekledi += 250) { if (g_yenidenBaglanIste.load()) break; // elle yeniden bağlanma isteği std::this_thread::sleep_for(std::chrono::milliseconds(250)); } } } } // namespace void uzakCanliBaslat() { static const bool birKez = [] { if (urlOku().empty()) { std::lock_guard lk(g_mtx); g_durum = KanalDurum::Kapali; return true; // kanal kapalı — iş parçacığı başlatma } if (g_isParcacigiSuruyor.exchange(true)) return true; std::thread([] { canliIsParcacigi(); g_isParcacigiSuruyor = false; }).detach(); return true; }(); (void)birKez; } bool uzakCanliKararVarMi(const std::string& seri) { std::lock_guard lk(g_mtx); return g_kararlar.count(seri) != 0; } bool uzakCanliEngelliMi(const std::string& seri) { std::lock_guard lk(g_mtx); const auto it = g_kararlar.find(seri); return it != g_kararlar.end() && it->second.engelli; } std::string uzakCanliMesaj(const std::string& seri) { std::lock_guard lk(g_mtx); const auto it = g_kararlar.find(seri); return it != g_kararlar.end() ? it->second.mesaj : std::string(); } bool uzakCanliTazeKontrol() { return g_taze.exchange(false); } std::string uzakCanliDurumOzet() { std::lock_guard lk(g_mtx); switch (g_durum) { case KanalDurum::Kapali: return "Canlı kanal: kapalı (URL ayarlanmamış)"; case KanalDurum::Baglaniyor: return "Canlı kanal: bağlanıyor..."; case KanalDurum::Bagli: return "Canlı kanal: BAĞLI — " + g_sunucuAdi + " · " + std::to_string(g_kararSayisi) + " karar"; case KanalDurum::Kopuk: return "Canlı kanal: kopuk, yeniden bağlanıyor" + (g_hata.empty() ? std::string() : " (" + g_hata + ")"); } return "Canlı kanal: ?"; } void uzakCanliAyarBolumu() { ImGui::TextDisabled("%s", uzakCanliDurumOzet().c_str()); static char buf[512] = ""; static bool bufDolu = false; if (!bufDolu) { snprintf(buf, sizeof(buf), "%s", ayarOku(CANLI_URL_AYAR, "").c_str()); bufDolu = true; } ImGui::InputText("##canliurl", buf, sizeof(buf)); if (ImGui::IsItemHovered()) { std::string ipucu = "Canlı yönetim sunucusu (WebSocket). Örn: wss://fikoyt.com/lisans-ws"; ipucu += NL; ipucu += "Boş = canlı kanal kapalı (.pol dosyalı uzak politika bağımsız çalışır)"; ImGui::SetTooltip("%s", ipucu.c_str()); } ImGui::SameLine(); if (ImGui::Button("Bağlan")) { ayarYaz(CANLI_URL_AYAR, buf); uzakCanliBaslat(); g_yenidenBaglanIste = true; } ImGui::SameLine(); if (ImGui::Button("Kanalı kapat")) ayarYaz(CANLI_URL_AYAR, ""); } } // namespace fiko