#include "lisans_yukle.hpp" #include #include #include #include #include #include #include #include #if defined(_WIN32) #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include // ShellExecuteW (mailto: varsayılan e-posta istemcisi) #endif #include "ayar_kalici.hpp" // ayarOku/ayarYaz (saat geri alma damgası) #include "fiko/cekirdek/gecici.hpp" #include "lisans_uzak.hpp" // uzak politika (kill-switch) istemcisi #include "fiko/cekirdek/lisans.hpp" #include "fiko/cekirdek/lisans_sayac.hpp" // merkezi (cihaz geneli) kota sayacı #include "fiko/cekirdek/makine_kimlik.hpp" #include "fiko/core/gunluk.hpp" #include "fiko/ui/bilesen_ek.hpp" // ui::toastGoster (kaydet/e-posta geri bildirimi) #include "fiko/ui/tema.hpp" // ui::tema (ayar sayfası renkleri) #include "imgui.h" namespace fs = std::filesystem; namespace fiko { namespace { // ── Lisans imza açık anahtarı (ECDSA P-256, X||Y = 64 bayt hex) ────────────── // Sürüm-paketi anahtarından (guncelleme_indir.cpp) AYRI bir çifttir. Eşi olan özel // anahtar YALNIZ üretici makinede: %USERPROFILE%\.fikoyt\lisans_ozel.key — depoya // asla girmez. Boş bırakılırsa hiçbir lisans doğrulanamaz (sessiz bypass yasak). // Bu anahtarı değiştirmek DAHA ÖNCE KESİLMİŞ TÜM LİSANSLARI geçersiz kılar. constexpr const char* LISANS_ACIK_ANAHTAR_HEX = "605dbff7195ed0906efb3aaf618f1f788c83908cec75c4ca4c1811be68fb5188" "35b9c053533be019f7483a78428241b995c74e3b988610032ef2acd5fe42f502"; // Lisans taleplerinin gideceği adres ("✉ E-posta ile gönder" düğmesi). Satıcı/bayi // dağıtımında burayı değiştirmek yeter — başka yerde gömülü adres yok. constexpr const char* SATICI_EPOSTA = "lisans@fikoyt.com"; LisansOturum g_oturum; bool g_yuklendi = false; std::string dosyaOku(const fs::path& p) { std::ifstream f(p, std::ios::binary); if (!f) return ""; std::ostringstream ss; ss << f.rdbuf(); // Şifreli (FKLE1) zarfı aç; düz metin lisanslar aynen geri döner (geriye uyum). u8 zarfAnahtar[32]; lisansZarfAnahtari(zarfAnahtar); return lisansZarfCoz(ss.str(), zarfAnahtar); } } // namespace LisansSistemi& lisansSistemi() { static LisansSistemi s; return s; } const char* lisansAcikAnahtarHex() { return LISANS_ACIK_ANAHTAR_HEX; } bool lisansUreticiMakinesiMi() { const char* profil = std::getenv("USERPROFILE"); if (!profil || !*profil) return false; std::error_code ec; return fs::is_regular_file(fs::path(profil) / ".fikoyt" / "lisans_ozel.key", ec); } namespace { // Lisans YOK/GEÇERSİZ/SÜRESİ DOLMUŞ/BAŞKA MAKİNE/YAMALI YAPI hâlinde izinli TEK // şey destek/idare komutlarıdır — dosya açma, görüntüleme, ölçüm dahil HİÇBİR şey // çalışmaz (kasıtlı karar: 2026-08, önceki "CAD görüntüleyici" davranışı kaldırıldı). // • arac.komut_denetim → ⚠️ ZORUNLU: lisans/makine kimliği penceresi; kapatırsak // kullanıcı lisans bile talep edemeden kendini dışarı kilitler. // • arac.ayarlar/arac.guncelleme/arac.geri_bildirim → destek hattı canlı kalsın. // • arac.lisans_uretici → yalnız üretici makinede zaten (ayrı gate); müşteride bu // komut hiç KAYDEDİLMEZ, burada izinli olması müşteriye bir şey açmaz. static const char* const TEMEL_IZINLI_KOMUTLAR[] = { "arac.komut_denetim", "arac.ayarlar", "arac.guncelleme", "arac.geri_bildirim", "arac.lisans_uretici", }; // Tam kilit kipi — lisans YOK/GEÇERSİZ/SÜRESİ DOLMUŞ/BAŞKA MAKİNE/YAMALI YAPI // hâllerinin ORTAK sonucu. Tek yerde tanımlı: bir kanalı unutmak = o durumda // gizmoyla modelleme açık kalması demek (yaşandı). Grup gate'i KASITLI boş // bırakılıyor — komut whitelist'i (haklar_) tek başına yeterli ve daha kesin; // boş haklar_ "kısıtlama yok" anlamına geldiği için TEMEL_IZINLI_KOMUTLAR'ın en // az bir gerçek girdi içermesi ZORUNLU (aksi hâlde politika sessizce açılır). void goruntuleyiciKipiUygula(LisansSistemi& sis) { sis.politikaSifirla(); // önceki (kısmen uygulanmış) lisans politikasını temizle sis.kipAyarla(LisansSistemi::Kip::Kisitli); for (const char* k : TEMEL_IZINLI_KOMUTLAR) sis.komutIzinVer(k); sis.saltOkunurAyarla(true); } // ---- Saat geri alma koruması ---- // Offline lisansın klasik açığı: kullanıcı sistem saatini geri alıp süresi dolmuş // lisansı yeniden çalıştırır. Her açılışta gördüğümüz EN BÜYÜK zaman kalıcı ayara // yazılır; şimdiki zaman ondan belirgin ölçüde geriyse saat oynanmış demektir. // Tolerans 1 gün: saat dilimi/yaz saati/NTP düzeltmesi meşru küçük geri gidişler yapar. constexpr i64 SAAT_TOLERANS = 86400; constexpr const char* SON_ZAMAN_ANAHTAR = "lisans_son_zaman"; bool saatGeriAlinmisMi(i64 suan) { const std::string kayit = ayarOku(SON_ZAMAN_ANAHTAR, ""); const i64 sonGorulen = kayit.empty() ? 0 : std::strtoll(kayit.c_str(), nullptr, 10); const bool geri = sonGorulen != 0 && suan + SAAT_TOLERANS < sonGorulen; if (suan > sonGorulen) ayarYaz(SON_ZAMAN_ANAHTAR, std::to_string(suan)); // ileri = normal return geri; } // "12.05.2027" — bitiş damgasını okunur tarihe çevirir (0 → "süresiz"). std::string tarihMetni(i64 unix) { if (unix == 0) return "süresiz"; const std::time_t t = (std::time_t)unix; std::tm tm{}; char buf[32] = "?"; if (localtime_s(&tm, &t) == 0) std::strftime(buf, sizeof(buf), "%d.%m.%Y", &tm); return buf; } // ── Müşteri parmak izi paylaşımı (lisans talebi) ──────────────────────────── // Müşteri lisans alabilmek için makine kimliğini satıcıya iletmek zorunda. Bu // yardımcılar o tek adımı üç ayrı kanaldan (pano / dosya / e-posta) kolaylaştırır; // hepsi AYNI metni üretir ki satıcıya ulaşan bilgi kanala göre değişmesin. // "20.07.2026 14:33" — belge başlığı için. std::string simdiMetni() { const std::time_t t = std::time(nullptr); std::tm tm{}; char buf[32] = "?"; if (localtime_s(&tm, &t) == 0) std::strftime(buf, sizeof(buf), "%d.%m.%Y %H:%M", &tm); return buf; } // Ekranda okunur kısaltma: ilk 8 … son 4. Tam değer tooltip'te gösterilir. std::string kisaHash(const std::string& h) { if (h.size() <= 16) return h; return h.substr(0, 8) + "…" + h.substr(h.size() - 4); } // Lisans dosyasına birebir girecek satırlar. `yapiDahil` → sürüme bağlı lisans için // `yapi=` satırı da eklenir (panoya kopyalama eski davranışı korur: yalnız makine=). std::string parmakIziSatirlari(bool yapiDahil) { const MakineParmakIzi& p = makineParmakIzi(); std::string s; for (const std::string* h : {&p.kurulum, &p.disk, &p.cpu, &p.bios}) if (!h->empty()) s += "makine=" + *h + "\n"; if (yapiDahil && !uygulamaYapiHash().empty()) s += "yapi=" + uygulamaYapiHash() + "\n"; return s; } // Masaüstüne kaydedilen/e-postaya giren tam belge. Müşteri ne gönderdiğini anlasın // diye başlık + açıklama var; satıcı ise yalnız `makine=`/`yapi=` satırlarını alır. std::string makineKimlikBelgesi() { std::string s = "FikoCAD — makine kimliği (lisans talebi)\n"; s += "Oluşturma : " + simdiMetni() + "\n"; if (const char* cn = std::getenv("COMPUTERNAME"); cn && *cn) s += "Bilgisayar adı : " + std::string(cn) + "\n"; if (const char* un = std::getenv("USERNAME"); un && *un) s += "Kullanıcı : " + std::string(un) + "\n"; s += "\n--- Aşağıdaki satırları satıcınıza gönderin ---\n"; s += parmakIziSatirlari(true); s += "\nBu bilgi yalnız bu bilgisayarı tanır; kişisel veri içermez ve donanım\n" "bilgileri geri çevrilemez biçimde özetlenmiştir. Lisansınız bu bilgiye göre\n" "hazırlanır ve yalnız bu bilgisayarda çalışır.\n"; return s; } // Masaüstü (%USERPROFILE%\Desktop); yoksa exe yanı — kayıt hiçbir hâlde kaybolmasın. fs::path masaustuYolu() { std::error_code ec; if (const char* up = std::getenv("USERPROFILE"); up && *up) { const fs::path d = fs::path(up) / "Desktop"; if (fs::is_directory(d, ec)) return d; } return gecici::uygulamaDizini(); } #if defined(_WIN32) std::wstring genisMetin(const std::string& s) { const int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0); std::wstring w(n > 0 ? (std::size_t)(n - 1) : 0, L'\0'); if (n > 1) MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, w.data(), n); return w; } #endif // mailto: gövdesi için yüzde kodlama. Satır sonu CRLF olmalı (%0D%0A) — tek %0A'yı // bazı istemciler yok sayar ve tüm parmak izi tek satıra yapışır. std::string urlKodla(const std::string& s) { static const char HEX[] = "0123456789ABCDEF"; std::string c; c.reserve(s.size() * 3); for (const unsigned char ch : s) { if (std::isalnum(ch) || ch == '-' || ch == '_' || ch == '.' || ch == '~') { c += (char)ch; } else if (ch == '\r') { continue; } else if (ch == '\n') { c += "%0D%0A"; } else { c += '%'; c += HEX[ch >> 4]; c += HEX[ch & 0x0F]; } } return c; } // Varsayılan e-posta istemcisinde hazır taslak açar. Uzun mailto URL'lerini kimi // istemci/kabuk kırptığı için ~1900 karakteri aşarsa gövde yalnız hash satırlarına // indirilir (bilgi kaybı yok — satıcının ihtiyacı olan kısım zaten o). bool epostaIleGonder(std::string& mesaj) { #if defined(_WIN32) const std::string bas = "mailto:" + std::string(SATICI_EPOSTA) + "?subject=" + urlKodla("FikoCAD lisans talebi") + "&body="; const std::string govde = "Merhaba,\n\nFikoCAD lisansı almak istiyorum. Bilgisayarımın kimliği aşağıdadır:\n\n" + parmakIziSatirlari(true) + "\nTeşekkürler.\n"; std::string url = bas + urlKodla(govde); if (url.size() > 1900) url = bas + urlKodla(parmakIziSatirlari(true)); const HINSTANCE r = ShellExecuteW(nullptr, L"open", genisMetin(url).c_str(), nullptr, nullptr, SW_SHOWNORMAL); if ((INT_PTR)r <= 32) { gunluk::uyari("Lisans talebi e-postası açılamadı (ShellExecute {})", (INT_PTR)r); mesaj = "E-posta uygulaması açılamadı — 'Panoya kopyala' ile alıp elle gönderin."; return false; } mesaj = std::string("E-posta taslağı açıldı: ") + SATICI_EPOSTA; return true; #else mesaj = "E-posta gönderimi yalnız Windows'ta desteklenir — 'Dosyaya kaydet' kullanın."; return false; #endif } } // namespace bool lisansSaglikKontrol() { if (lisansSistemi().saglikKontrol()) return true; gunluk::uyari("Lisans politikası bütünlüğü bozuldu — kısıtlı kipe geçildi"); return false; } bool surukleBirakIzinli(const std::string& yol) { const std::size_t nokta = yol.find_last_of('.'); std::string uz = nokta == std::string::npos ? "" : yol.substr(nokta + 1); for (char& c : uz) c = (char)std::tolower((unsigned char)c); // Uzantı → gereken komut hakkı (menüden açarken hangi komut çalışacaksa o). const char* kimlik = nullptr; if (uz == "fikocad") kimlik = "dosya.ac"; else if (uz == "step" || uz == "stp" || uz == "iges" || uz == "igs" || uz == "brep" || uz == "stl" || uz == "obj") kimlik = "dosya.step_ice"; else if (uz == "fikoapt" || uz == "apt") kimlik = "dosya.apt_ac"; else if (uz == "fikoop" || uz == "wrl") kimlik = "dosya.webcad_ac"; if (!kimlik) return true; // tanımadığımız uzantı: eski davranış (yolAc karar verir) // Grup adı gate'in ikinci ekseni; bu kimliklerin hepsi "Dosya" grubunda. return lisansSistemi().ogeGorunur(kimlik, "Dosya"); } std::vector lisansAranacakYollar() { std::vector yollar; if (const char* env = std::getenv("FIKO_LISANS")) { if (*env) yollar.push_back(env); } yollar.push_back((gecici::uygulamaDizini() / "lisans.fkl").string()); const char* pd = std::getenv("ProgramData"); if (pd && *pd) yollar.push_back((fs::path(pd) / "FikoCAD" / "lisans.fkl").string()); // ÇOKLU LİSANS: `lisanslar/` dizinindeki TÜM .fkl dosyaları da yüklenir. Müşteri // sonradan paket satın alınca ikinci dosyayı buraya bırakır — eskisi bozulmaz, // haklar BİRLEŞİR. Her dosya ayrı ayrı imza + makine doğrulamasından geçer. std::error_code ec; std::vector kokler{gecici::uygulamaDizini() / "lisanslar"}; if (pd && *pd) kokler.push_back(fs::path(pd) / "FikoCAD" / "lisanslar"); for (const fs::path& kok : kokler) { if (!fs::is_directory(kok, ec)) continue; std::vector bulunan; // sıralı: her açılışta aynı sıra → aynı politika for (const auto& g : fs::directory_iterator(kok, ec)) { if (g.is_regular_file(ec) && g.path().extension() == ".fkl") bulunan.push_back(g.path().string()); } std::sort(bulunan.begin(), bulunan.end()); yollar.insert(yollar.end(), bulunan.begin(), bulunan.end()); } return yollar; } const LisansOturum& lisansYukleVeUygula(LisansSistemi& sis) { g_yuklendi = true; g_oturum = LisansOturum{}; g_oturum.zorunlu = true; g_oturum.ureticiTamErisim = lisansUreticiMakinesiMi(); // Uzak politika (kill-switch): önbelleği senkron yükler + arka plan denetimini // başlatır (statik init → sonraki çağrılar no-op). İlk yüklemeye ÖNCE olmalı ki // engelleme kararı bu turda etkili olsun. uzakPolitikaBaslat(); // Merkezi kota sayacı (cihaz geneli, süreçler arası). BİR KEZ bağlanır: ikinci // fikocad süreci ve yeni belge pencereleri AYNI sayacı görür. static bool sayacBagli = false; if (!sayacBagli) { sis.sayacDeposuAyarla(lisansSayacDeposu( /*kok*/ "", [] { return ayarOku("lisans_kota", ""); }, [](const std::string& s) { ayarYaz("lisans_kota", s); })); sayacBagli = true; } // ---- ÇOKLU LİSANS: bulunan TÜM .fkl dosyaları yüklenir, haklar BİRLEŞİR ---- // Her dosya ayrı ayrı imza + makine + yapı doğrulamasından geçer; biri geçersizse // yalnız o atlanır (diğerleri çalışmaya devam eder). En az biri geçerliyse oturum // geçerlidir. const i64 suan = (i64)std::time(nullptr); g_oturum.saatGeriAlinmis = saatGeriAlinmisMi(suan); const std::vector yerel = makineParmakIzi().hashListesi(); // ÜRETİM KAZASI KORUMASI: anahtar gömülmeden sürüm çıkarsa HİÇBİR lisans doğrulanamaz // (her müşteri görüntüleyici kipine düşer). Sessiz kalmasın — günlük + panel uyarısı. if (LISANS_ACIK_ANAHTAR_HEX[0] == '\0') { gunluk::uyari( "Lisans açık anahtarı GÖMÜLMEMİŞ (lisans_yukle.cpp) — hiçbir lisans doğrulanamaz. " "Üretim sürümü için 'fiko_lisans anahtar' çıktısındaki açık anahtarı göm."); } sis.politikaSifirla(); // her yüklemede temiz başla int gecerliSayi = 0; for (const std::string& y : lisansAranacakYollar()) { const std::string metin = dosyaOku(y); if (metin.empty()) continue; LisansBilgi bilgi; std::string hata; LisansDurum d = lisansCoz(metin, LISANS_ACIK_ANAHTAR_HEX, bilgi, hata); // UZAK KONTROL: imza/makine/süre geçse bile satıcının imzalı uzak politikası // bu seriyi engelliyorsa lisans GEÇERSİZDİR (kill-switch). Üretici makinesi // muaf değildir burada — muafiyet en sonda ureticiTamErisim dalındadır; ama // engellenen lisansın hakları hiç uygulanmasın diye kontrol uygulamadan ÖNCE. if (d == LisansDurum::Gecerli && !g_oturum.ureticiTamErisim && uzakSeriEngelliMi(bilgi.seri)) { d = LisansDurum::UzaktanEngellendi; gunluk::uyari("Lisans uzak politikayla ENGELLENDİ: {} ({})", bilgi.seri, y); } if (d == LisansDurum::Gecerli) { d = lisansUygula(bilgi, sis, yerel, suan, /*birlestir*/ gecerliSayi > 0); // Saat geri alınmışsa SÜRELİ lisans geçersiz sayılır (süresiz etkilenmez). if (g_oturum.saatGeriAlinmis && bilgi.bitis != 0 && d == LisansDurum::Gecerli) { d = LisansDurum::SuresiDolmus; gunluk::uyari("Sistem saati geri alınmış — süreli lisans geçersiz sayıldı: {}", y); } } if (d == LisansDurum::Gecerli) { if (gecerliSayi == 0) { // ilk geçerli lisans "ana" bilgi (özet/panel onu gösterir) g_oturum.bilgi = bilgi; g_oturum.yol = y; for (const std::string& yy : yerel) for (const std::string& m : bilgi.makineHashleri) if (yy == m) ++g_oturum.eslesenAlan; } ++gecerliSayi; g_oturum.lisanslar.push_back( {y, bilgi.seri, bilgi.musteri, bilgi.bitis, (int)bilgi.paketler.size()}); gunluk::bilgi("Lisans yüklendi: {} ({})", bilgi.seri, y); } else { g_oturum.hata = hata.empty() ? lisansDurumMetni(d) : hata; if (g_oturum.yol.empty()) g_oturum.yol = y; g_oturum.durum = d; gunluk::uyari("Lisans atlandı ({}): {}", lisansDurumMetni(d), y); } } if (g_oturum.ureticiTamErisim) { // Özel anahtar yalnız lisans kesen makinede bulunur. Üretici, süresi dolmuş veya // makineye kilitli bir müşteri lisansını incelerken dahi tüm komutlara erişebilmelidir. sis.politikaSifirla(); gunluk::bilgi("Üretici makinesi algılandı — lisans politikası uygulanmadı"); } else if (gecerliSayi > 0) { g_oturum.durum = LisansDurum::Gecerli; } else { if (g_oturum.durum == LisansDurum::Gecerli) g_oturum.durum = LisansDurum::Bos; if (g_oturum.hata.empty()) g_oturum.hata = "Lisans dosyası bulunamadı"; goruntuleyiciKipiUygula(sis); } gunluk::bilgi("Lisans durumu: {} ({} dosya geçerli)", lisansDurumMetni(g_oturum.durum), gecerliSayi); return g_oturum; } const LisansOturum& lisansOturum() { return g_oturum; } bool lisansYuklendiMi() { return g_yuklendi; } int LisansOturum::kalanGun() const { if (bilgi.bitis == 0) return -1; // süresiz const i64 fark = bilgi.bitis - (i64)std::time(nullptr); return fark <= 0 ? 0 : (int)(fark / 86400); } std::string lisansOzet() { if (g_oturum.ureticiTamErisim) return "Üretici modu — tam erişim"; if (g_oturum.gecerliMi()) { std::string s = "Lisanslı"; if (!g_oturum.bilgi.musteri.empty()) s += " — " + g_oturum.bilgi.musteri; if (g_oturum.lisanslar.size() > 1) { s += " (" + std::to_string(g_oturum.lisanslar.size()) + " lisans dosyası)"; } else if (!g_oturum.bilgi.komutlar.empty() || !g_oturum.bilgi.paketler.empty()) { s += " (" + std::to_string(g_oturum.bilgi.komutlar.size()) + " komut, " + std::to_string(g_oturum.bilgi.paketler.size()) + " paket)"; } return s; } return std::string(g_oturum.zorunlu ? "Lisanssız — görüntüleyici kipi: " : "Lisanssız (serbest): ") + (g_oturum.hata.empty() ? "?" : g_oturum.hata); } void lisansAyarBolumu() { const LisansOturum& o = lisansOturum(); const LisansSistemi& sis = lisansSistemi(); const ui::Tema& tm = ui::tema(); ImGui::Spacing(); ImGui::SeparatorText("Lisans durumu"); ImGui::TextColored(o.gecerliMi() ? ImVec4(0.35f, 0.85f, 0.45f, 1) : ImVec4(0.95f, 0.65f, 0.25f, 1), "%s", lisansOzet().c_str()); if (o.gecerliMi()) { ImGui::Text("Seri: %s", o.bilgi.seri.c_str()); ImGui::SameLine(); if (const int gun = o.kalanGun(); gun >= 0) { ImGui::TextColored(gun <= 30 ? ImVec4(1.0f, 0.65f, 0.25f, 1) : tm.metinSoluk, " Bitiş: %s (%d gün)", tarihMetni(o.bilgi.bitis).c_str(), gun); } else { ImGui::TextDisabled(" Süresiz"); } if (!o.bilgi.makineHashleri.empty()) ImGui::TextDisabled("Makine: %d/%d alan eşleşti (gereken %d)", o.eslesenAlan, (int)o.bilgi.makineHashleri.size(), o.bilgi.makineTolerans); if (o.saatGeriAlinmis) ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.35f, 1), "⚠ Sistem saati geri alınmış"); } // ---- Yüklü lisans dosyaları (çoklu lisans: haklar birleşir) ---- if (o.lisanslar.size() > 1 && ImGui::TreeNode("Yüklü lisans dosyaları")) { for (const auto& d : o.lisanslar) { ImGui::BulletText("%s — %s (%s, %d paket)", d.seri.c_str(), d.musteri.c_str(), tarihMetni(d.bitis).c_str(), d.paketSayi); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", d.yol.c_str()); } ImGui::TreePop(); } // ---- Paketler: her biri kendi bitişi + içerdiği komut desenleriyle ---- if (!o.bilgi.paketler.empty()) { ImGui::SeparatorText("Paketler"); if (ImGui::BeginTable("lisansPaket", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Paket"); ImGui::TableSetupColumn("Bitiş", ImGuiTableColumnFlags_WidthFixed, 130); ImGui::TableSetupColumn("Komut", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableHeadersRow(); const i64 suan = (i64)std::time(nullptr); for (const LisansPaket& p : o.bilgi.paketler) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(p.ad.empty() ? p.kimlik.c_str() : p.ad.c_str()); if (ImGui::IsItemHovered() && !p.komutlar.empty()) { std::string liste; for (const std::string& k : p.komutlar) liste += k + "\n"; ImGui::SetTooltip("%s", liste.c_str()); } ImGui::TableSetColumnIndex(1); const bool dolmus = p.bitis != 0 && suan > p.bitis; ImGui::TextColored(dolmus ? ImVec4(1.0f, 0.4f, 0.35f, 1) : tm.metin, "%s%s", tarihMetni(p.bitis).c_str(), dolmus ? " (doldu)" : ""); ImGui::TableSetColumnIndex(2); ImGui::Text("%d", (int)p.komutlar.size()); } ImGui::EndTable(); } } // ---- Kotalar: dönem + kullanılan/limit + kalan ---- const auto& kurallar = sis.kotaKurallari(); if (!kurallar.empty()) { ImGui::SeparatorText("Kullanım hakları (kota)"); if (ImGui::BeginTable("lisansKota", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("Komut"); ImGui::TableSetupColumn("Dönem", ImGuiTableColumnFlags_WidthFixed, 70); ImGui::TableSetupColumn("Kullanım", ImGuiTableColumnFlags_WidthFixed, 80); ImGui::TableSetupColumn("Kalan", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableHeadersRow(); for (const auto& k : kurallar) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(k.desen.c_str()); if (!k.paket.empty() && ImGui::IsItemHovered()) ImGui::SetTooltip("Paket: %s", k.paket.c_str()); ImGui::TableSetColumnIndex(1); ImGui::TextUnformatted(kotaPeriyotMetni(k.periyot).c_str()); ImGui::TableSetColumnIndex(2); ImGui::Text("%llu / %llu", (unsigned long long)sis.kotaKullanim(k), (unsigned long long)k.limit); ImGui::TableSetColumnIndex(3); const u64 kalan = sis.kotaKalan(k); ImGui::TextColored(kalan == 0 ? ImVec4(1.0f, 0.4f, 0.35f, 1) : tm.metin, "%llu", (unsigned long long)kalan); } ImGui::EndTable(); } ImGui::TextColored(tm.metinSoluk, "Sayaçlar CİHAZ GENELİ: tüm pencereler ve süreçler aynı hakkı paylaşır.\n" "Dönem penceresi değişince sıfırlanır."); } } // ── Kalıcı lisans banner'ı ─────────────────────────────────────────────────── // Reaktif toast (LisansReddedildi olayına bağlı) yalnız kısıtlı bir komut // DENENDİĞİNDE çıkar; yalnız görüntüleme/STEP açma/ölçüm yapan lisanssız bir // kullanıcı hiçbir komutu reddedilmediği için o toast'ı hiç görmez (yaşandı — // bu banner tam ONUN için var, koşulsuz çizilir). // // Üretici makinede (`ureticiTamErisim`) `gecerliMi()` yine false dönebilir (geçerli // müşteri lisansı yok) ama `LisansSistemi` fiilen Kip::Serbest'tir (bkz. // lisansYukleVeUygula) — yani hiçbir şey aslında kilitli DEĞİLDİR. Bu durumu ayrı // mesajla göster; aksi hâlde banner "kilitli" der ama sürükle-bırak/STEP fiilen // açılır — kafa karıştırıcı olur (yaşandı). Banner koşulsuz, kare başına çizilir; // kullanıcı davranışından bağımsızdır. void lisansBanneriCiz() { const LisansOturum& o = lisansOturum(); if (o.gecerliMi()) return; // lisans geçerliyse hiçbir şey çizme ImGuiViewport* vp = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(ImVec2(vp->WorkPos.x + vp->WorkSize.x * 0.5f, vp->WorkPos.y + 10.0f), ImGuiCond_Always, ImVec2(0.5f, 0.0f)); ImGui::SetNextWindowBgAlpha(0.93f); constexpr ImGuiWindowFlags bayrak = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove; ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.95f, 0.65f, 0.25f, 1.0f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.5f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f); if (ImGui::Begin("##lisans_banner", nullptr, bayrak)) { if (o.ureticiTamErisim) { ImGui::TextColored(ImVec4(0.95f, 0.65f, 0.25f, 1.0f), "%s", "🏭 Üretici modu — lisans kısıtlaması uygulanmıyor (bu makineye özel)"); } else { ImGui::TextColored( ImVec4(0.95f, 0.65f, 0.25f, 1.0f), "%s", "🔒 Lisanssız — uygulama kilitli (dosya açma/görüntüleme/ölçüm dahil)"); ImGui::SameLine(); ImGui::TextDisabled("(Araçlar ▸ Komut Denetim: makine kimliği)"); } } ImGui::End(); ImGui::PopStyleVar(2); ImGui::PopStyleColor(); } void lisansDurumBolumu(LisansSistemi& sis, Cekirdek& cek) { static std::string sonIslem; // son dışa aktarma/yeniden yükleme sonucu const LisansOturum& o = lisansOturum(); ImGui::SeparatorText("Lisans"); if (LISANS_ACIK_ANAHTAR_HEX[0] == '\0') { ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.35f, 1), "⚠ Açık anahtar gömülmemiş — lisans doğrulama DEVRE DIŞI"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Geliştirme yapısı. Üretim için: fiko_lisans anahtar → çıkan\n" "açık anahtarı lisans_yukle.cpp içine göm, özel anahtarı sakla."); } const bool ok = o.gecerliMi(); ImGui::TextColored(ok ? ImVec4(0.35f, 0.85f, 0.45f, 1) : ImVec4(0.95f, 0.65f, 0.25f, 1), "%s", lisansOzet().c_str()); if (!o.yol.empty()) { ImGui::TextDisabled("Dosya: %s", o.yol.c_str()); } else { ImGui::TextDisabled("Aranan: %s", lisansAranacakYollar().front().c_str()); } if (ok) { if (!o.bilgi.seri.empty()) ImGui::Text("Seri: %s", o.bilgi.seri.c_str()); if (const int gun = o.kalanGun(); gun >= 0) { const bool yakin = gun <= 30; ImGui::TextColored(yakin ? ImVec4(1.0f, 0.65f, 0.25f, 1) : ImVec4(0.7f, 0.7f, 0.7f, 1), "Kalan süre: %d gün%s", gun, yakin ? " ⚠ yenileme zamanı" : ""); } else { ImGui::TextDisabled("Süresiz lisans"); } if (o.saatGeriAlinmis) ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.35f, 1), "⚠ Sistem saati geri alınmış"); if (!o.bilgi.makineHashleri.empty()) ImGui::Text("Makine: %d/%d alan eşleşti (gereken %d)", o.eslesenAlan, (int)o.bilgi.makineHashleri.size(), o.bilgi.makineTolerans); } // ── Makine kimliği: müşterinin lisans alabilmesi için TEK adım ───────────── // Lisans yokken bu bölüm müşterinin yapması gereken TEK iş — o yüzden kendiliğinden // açılır ve başlığı eylem söyler. Lisans varken nötr başlıkla kapalı durur. // ⚠️ Bu pencere görüntüleyici kipinde de açıktır (arac.komut_denetim izinli) — // lisanssız müşteri buraya erişemezse kendini dışarı kilitler. if (!ok) ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNodeEx("makineKimligi", ImGuiTreeNodeFlags_SpanAvailWidth, "%s", ok ? "Bu makinenin kimliği (parmak izi)" : "🔑 Lisans almak için: bu makinenin kimliği")) { ImGui::TextWrapped( "Aşağıdaki bilgiyi kopyalayıp satıcınıza gönderin.\n" "Lisansınız yalnız bu bilgisayarda çalışacak şekilde hazırlanır.\n" "Bu bilgide kişisel veri yoktur."); ImGui::Spacing(); const MakineParmakIzi& p = makineParmakIzi(); std::string panoMetni; // eski davranış: panoya YALNIZ makine= satırları for (const auto& [ad, h] : {std::pair{"kurulum", p.kurulum}, std::pair{"disk", p.disk}, std::pair{"cpu", p.cpu}, std::pair{"bios", p.bios}}) { ImGui::TextDisabled("%-9s", ad); ImGui::SameLine(); ImGui::TextUnformatted(h.empty() ? "(okunamadı)" : kisaHash(h).c_str()); if (!h.empty()) { if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", h.c_str()); panoMetni += std::string("makine=") + h + "\n"; } } ImGui::TextDisabled("%-9s", "yapi"); ImGui::SameLine(); ImGui::TextUnformatted(kisaHash(uygulamaYapiHash()).c_str()); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", uygulamaYapiHash().c_str()); ImGui::SameLine(); ImGui::TextDisabled("(sürüme bağlı lisans için)"); ImGui::Spacing(); if (ImGui::Button("📋 Panoya kopyala")) { ImGui::SetClipboardText(panoMetni.c_str()); sonIslem = "Makine kimliği panoya kopyalandı — satıcınıza yapıştırıp gönderin."; ui::toastGoster("Makine kimliği panoya kopyalandı", ui::ToastTip::Basari); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Lisans satırlarını panoya alır; mesaj/e-postaya yapıştırabilirsiniz."); ImGui::SameLine(); if (ImGui::Button("💾 Dosyaya kaydet")) { const fs::path hedef = masaustuYolu() / "fikocad-makine-kimligi.txt"; std::ofstream f(hedef, std::ios::binary); if (f) f << makineKimlikBelgesi(); if (f) { f.close(); sonIslem = "Makine kimliği kaydedildi: " + hedef.string(); ui::toastGoster(("Kaydedildi: " + hedef.string()).c_str(), ui::ToastTip::Basari, 6.0f); } else { sonIslem = "Dosya yazılamadı: " + hedef.string(); gunluk::uyari("Makine kimliği dosyası yazılamadı: {}", hedef.string()); ui::toastGoster("Dosya yazılamadı — 'Panoya kopyala' kullanın", ui::ToastTip::Hata, 6.0f); } } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Masaüstüne 'fikocad-makine-kimligi.txt' yazar; dosyayı ekleyip yollayın."); ImGui::SameLine(); if (ImGui::Button("✉ E-posta ile gönder")) { std::string mesaj; const bool basarili = epostaIleGonder(mesaj); sonIslem = mesaj; ui::toastGoster(mesaj.c_str(), basarili ? ui::ToastTip::Basari : ui::ToastTip::Hata, 6.0f); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Varsayılan e-posta uygulamanızda hazır bir lisans talebi açar (%s).", SATICI_EPOSTA); ImGui::TreePop(); } // Satın alınan komut hakları (joker dahil). if (!o.bilgi.komutlar.empty() && ImGui::TreeNode("Satın alınan komutlar")) { for (const std::string& k : o.bilgi.komutlar) ImGui::BulletText("%s", k.c_str()); ImGui::TreePop(); } // Kilitli komutlar: menüde/toolbar'da GİZLİLER (satın alınmadılar) — ama burada // listelenirler ki kullanıcı ne alabileceğini görsün ve kimliği bize iletebilsin. if (ImGui::TreeNode("Kilitli komutlar (satın alınabilir)")) { int n = 0; if (ImGui::BeginTable("kilitli", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 180))) { ImGui::TableSetupColumn("Komut"); ImGui::TableSetupColumn("Kimlik", ImGuiTableColumnFlags_WidthFixed, 150); ImGui::TableHeadersRow(); for (const MenuOge& m : cek.menuler().tumKayit()) { const std::string kimlik = komutKimligi(m); if (sis.ogeGorunur(kimlik, m.grup)) continue; // görünür = satın alınmış ++n; ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text("%s › %s", m.grup.c_str(), m.ad.c_str()); ImGui::TableSetColumnIndex(1); ImGui::TextDisabled("%s", kimlik.c_str()); } ImGui::EndTable(); } if (n == 0) ImGui::TextDisabled("Kilitli komut yok — tüm haklar açık."); ImGui::TreePop(); } if (ImGui::Button("Lisansı yeniden yükle")) { lisansYukleVeUygula(sis); sonIslem = "Lisans yeniden yüklendi: " + lisansOzet(); } ImGui::SameLine(); if (ImGui::Button("Katalog CSV")) { const fs::path hedef = gecici::uygulamaDizini() / "lisans_katalog.csv"; std::ofstream f(hedef, std::ios::binary); if (f) { f << lisansKatalogCsv(cek.menuler()); sonIslem = "Katalog yazıldı: " + hedef.string(); } else { sonIslem = "Katalog yazılamadı: " + hedef.string(); } } if (!sonIslem.empty()) ImGui::TextWrapped("%s", sonIslem.c_str()); } } // namespace fiko