TamaSkrypt – Launcher

Modułowy launcher TamaSkrypt: pobiera manifest, weryfikuje hash, ładuje moduły i uruchamia grę.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         TamaSkrypt – Launcher
// @namespace    https://github.com/qxsxhsyoj7369/TamaSkrypt
// @version      3.2.2
// @description  Modułowy launcher TamaSkrypt: pobiera manifest, weryfikuje hash, ładuje moduły i uruchamia grę.
// @author       TamaSkrypt / Gelek
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      api.github.com
// @connect      raw.githubusercontent.com
// @connect      cdn.jsdelivr.net
// @connect      gelek-995f2-default-rtdb.europe-west1.firebasedatabase.app
// @run-at       document-end
// @noframes
// ==/UserScript==

(function () {
  'use strict';

  const GITHUB_OWNER = 'qxsxhsyoj7369';
  const GITHUB_REPO = 'TamaSkrypt';
  const GITHUB_BRANCH = 'main';
  const MANIFEST_PATH = 'manifest.json';
  const MANIFEST_URL = `https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/${GITHUB_BRANCH}/${MANIFEST_PATH}`;
  const MANIFEST_CDN_URL = `https://cdn.jsdelivr.net/gh/${GITHUB_OWNER}/${GITHUB_REPO}@${GITHUB_BRANCH}/${MANIFEST_PATH}`;
  const GITHUB_REF_API_URL = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/git/ref/heads/${GITHUB_BRANCH}`;
  const RAW_REPO_PREFIX = `https://raw.githubusercontent.com/${GITHUB_OWNER}/${GITHUB_REPO}/`;
  const FIREBASE_DB_URL = 'https://gelek-995f2-default-rtdb.europe-west1.firebasedatabase.app';
  const CHECK_INTERVAL_MS = 0;

  const KEY_CODE = '__ts_launcher_code__';
  const KEY_VERSION = '__ts_launcher_version__';
  const KEY_CHECKED = '__ts_launcher_checked__';
  const KEY_SCHEMA_VER = '__ts_schema_version__';
  const KEY_MODE = '__ts_launcher_mode__';
  const KEY_MODULE_MANIFEST = '__ts_launcher_modules_manifest__';
  const KEY_MODULE_META = '__ts_launcher_modules_meta__';
  const KEY_MODULE_CODE_PREFIX = '__ts_launcher_mod_code__::';
  const KEY_COMMIT_SHA = '__ts_launcher_commit_sha__';
  const HOST_SKIP_RE = /(^|\.)github\.com$|(^|\.)githubusercontent\.com$/i;

  function setLauncherDiagnostics(info) {
    const current = window.__TS_LAUNCHER_DIAGNOSTICS__ || {};
    window.__TS_LAUNCHER_DIAGNOSTICS__ = {
      mode: info && info.mode ? info.mode : current.mode || 'unknown',
      manifestVersion: info && info.manifestVersion ? String(info.manifestVersion) : current.manifestVersion || '',
      source: info && info.source ? info.source : current.source || 'unknown',
      commitSha: info && info.commitSha ? String(info.commitSha) : current.commitSha || '',
      updatedAt: Date.now(),
    };
  }

  function rewriteRawGithubUrl(url, ref) {
    if (!url || !ref || typeof url !== 'string') return url;
    if (!url.startsWith(RAW_REPO_PREFIX)) return url;
    const rest = url.slice(RAW_REPO_PREFIX.length);
    const slashIdx = rest.indexOf('/');
    if (slashIdx < 0) return url;
    const pathPart = rest.slice(slashIdx + 1);
    return RAW_REPO_PREFIX + ref + '/' + pathPart;
  }

  function applyCommitShaToManifest(manifest, commitSha) {
    if (!manifest || !commitSha) return manifest;
    const cloned = JSON.parse(JSON.stringify(manifest));
    cloned.__commitSha = commitSha;
    cloned.scriptUrl = rewriteRawGithubUrl(cloned.scriptUrl, commitSha);
    if (Array.isArray(cloned.modules)) {
      cloned.modules = cloned.modules.map((entry) => ({
        ...entry,
        url: rewriteRawGithubUrl(entry.url, commitSha),
      }));
    }
    return cloned;
  }

  function getJson(key, fallback) {
    try {
      const raw = GM_getValue(key, null);
      if (raw === null || raw === undefined || raw === '') return fallback;
      return JSON.parse(raw);
    } catch {
      return fallback;
    }
  }

  function setJson(key, value) {
    GM_setValue(key, JSON.stringify(value));
  }

  function stripHeader(code) {
    return code.replace(/\/\/\s*==UserScript==[\s\S]*?\/\/\s*==\/UserScript==/m, '');
  }

  async function runCode(code, label) {
    try {
      // eslint-disable-next-line no-eval
      eval(stripHeader(code));
      return true;
    } catch (error) {
      console.error('[TamaSkrypt Launcher] Błąd wykonania:', label, error);
      return false;
    }
  }

  function shouldSkipOnHost() {
    const host = (window.location && window.location.hostname) ? String(window.location.hostname) : '';
    return HOST_SKIP_RE.test(host);
  }

  function requestText(url, timeout = 15000) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: 'GET',
        url: url + (url.includes('?') ? '&' : '?') + '_=' + Date.now(),
        timeout,
        onload: function (resp) {
          if (resp.status < 200 || resp.status >= 300) {
            reject(new Error(`HTTP ${resp.status} for ${url}`));
            return;
          }
          resolve(resp.responseText);
        },
        onerror: function () { reject(new Error(`Network error for ${url}`)); },
        ontimeout: function () { reject(new Error(`Timeout for ${url}`)); },
      });
    });
  }

  async function sha256Hex(text) {
    const buffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text));
    return Array.from(new Uint8Array(buffer)).map(b => b.toString(16).padStart(2, '0')).join('');
  }

  function normalizeHashInput(text) {
    return String(text || '')
      .replace(/^\uFEFF/, '')
      .replace(/\r\n/g, '\n')
      .replace(/\r/g, '\n');
  }

  async function verifyHash(text, expectedHash) {
    if (!expectedHash) return true;
    const normalized = String(expectedHash).toLowerCase().replace(/^sha256:/, '');
    const got = await sha256Hex(normalizeHashInput(text));
    return got === normalized;
  }

  function showSchemaUpdateAlert(newVersion) {
    if (sessionStorage.getItem('__ts_schema_alert_shown__')) return;
    sessionStorage.setItem('__ts_schema_alert_shown__', 'true');
    console.warn(`[TamaSkrypt] Schemat bazy Firebase został zaktualizowany do v${newVersion}`);
  }

  async function checkFirebaseSchemaVersion() {
    try {
      const body = await requestText(FIREBASE_DB_URL + '/meta/schemaVersion.json', 5000);
      const schemaVer = JSON.parse(body);
      const localVer = parseFloat(GM_getValue(KEY_SCHEMA_VER, '1.0'));
      if (schemaVer > localVer) {
        showSchemaUpdateAlert(schemaVer);
        GM_setValue(KEY_SCHEMA_VER, String(schemaVer));
      }
      return schemaVer;
    } catch {
      return null;
    }
  }

  function createProgressUI(total) {
    const id = '__ts_loader_progress__';
    const old = document.getElementById(id);
    if (old) old.remove();

    const el = document.createElement('div');
    el.id = id;
    el.style.cssText = 'position:fixed;left:16px;right:16px;bottom:16px;z-index:2147483647;font-family:Segoe UI,Arial,sans-serif;background:#111a;border:1px solid #ffffff33;border-radius:10px;padding:8px 10px;color:#fff;backdrop-filter:blur(3px);';
    el.innerHTML = [
      '<div style="display:flex;justify-content:space-between;font-size:12px;margin-bottom:6px;">',
      '<span id="__ts_loader_label__">Ładowanie modułów...</span>',
      `<span id="__ts_loader_count__">0/${total}</span>`,
      '</div>',
      '<div style="height:8px;background:#ffffff22;border-radius:999px;overflow:hidden;">',
      '<div id="__ts_loader_fill__" style="height:100%;width:0%;background:linear-gradient(90deg,#7c5cff,#36d1dc);transition:width .2s ease;"></div>',
      '</div>'
    ].join('');
    (document.body || document.documentElement).appendChild(el);
    return {
      update: function (done, label) {
        const pct = total > 0 ? Math.max(0, Math.min(100, Math.round((done / total) * 100))) : 100;
        const fill = document.getElementById('__ts_loader_fill__');
        const count = document.getElementById('__ts_loader_count__');
        const text = document.getElementById('__ts_loader_label__');
        if (fill) fill.style.width = pct + '%';
        if (count) count.textContent = `${done}/${total}`;
        if (text && label) text.textContent = label;
        window.dispatchEvent(new CustomEvent('__ts_loader_progress_event__', {
          detail: { done, total, label: label || '' }
        }));
      },
      done: function (label = 'Załadowano') {
        this.update(total, label);
        window.dispatchEvent(new CustomEvent('__ts_loader_done_event__'));
        setTimeout(() => {
          const node = document.getElementById(id);
          if (node) node.remove();
        }, 800);
      },
      fail: function (label = 'Błąd ładowania') {
        this.update(0, label);
        window.dispatchEvent(new CustomEvent('__ts_loader_fail_event__', {
          detail: { total, label }
        }));
        const fill = document.getElementById('__ts_loader_fill__');
        if (fill) fill.style.background = 'linear-gradient(90deg,#ff4d4f,#ff7a45)';
        setTimeout(() => {
          const node = document.getElementById(id);
          if (node) node.remove();
        }, 2500);
      }
    };
  }

  function normalizeModuleManifest(manifest) {
    const list = Array.isArray(manifest?.modules) ? manifest.modules : [];
    return list.map((entry, idx) => ({
      name: entry.name || `module_${idx}`,
      url: entry.url,
      version: entry.version || manifest.version || '0',
      hash: entry.hash || '',
      execute: entry.execute || 'raw',
      required: entry.required !== false,
    })).filter(m => typeof m.url === 'string' && m.url.length > 0);
  }

  async function fetchLatestCommitSha() {
    try {
      const body = await requestText(GITHUB_REF_API_URL, 10000);
      const data = JSON.parse(body);
      const sha = data && data.object && typeof data.object.sha === 'string' ? data.object.sha : '';
      return sha || '';
    } catch (error) {
      if (/HTTP 403/i.test(String(error && error.message))) {
        console.info('[TamaSkrypt Launcher] GitHub ref API rate-limited; using branch/CDN fallback.');
      } else {
        console.warn('[TamaSkrypt Launcher] GitHub ref API unavailable:', error.message);
      }
      return '';
    }
  }

  async function executeModules(manifest, options = {}) {
    const moduleList = normalizeModuleManifest(manifest);
    if (!moduleList.length) return false;
    const strictHashes = manifest && manifest.strictHashes === true;

    const allowNetwork = options.allowNetwork !== false;
    const progress = createProgressUI(moduleList.length);
    const moduleMeta = getJson(KEY_MODULE_META, {});
    const resolvedCode = {};
    let completed = 0;

    try {
      setLauncherDiagnostics({
        mode: 'modules',
        manifestVersion: manifest && manifest.version,
        source: allowNetwork ? 'network' : 'cache',
        commitSha: manifest && manifest.__commitSha,
      });

      for (const mod of moduleList) {
        const cacheKey = KEY_MODULE_CODE_PREFIX + mod.name;
        const cachedInfo = moduleMeta[mod.name] || {};
        const cachedCode = GM_getValue(cacheKey, '');
        const cacheMatches = cachedCode
          && cachedInfo.url === mod.url
          && String(cachedInfo.version) === String(mod.version)
          && String(cachedInfo.hash || '') === String(mod.hash || '');

        if (cacheMatches) {
          resolvedCode[mod.name] = cachedCode;
          completed += 1;
          progress.update(completed, `Cache: ${mod.name}`);
          continue;
        }

        if (!allowNetwork) throw new Error(`Brak cache dla modułu ${mod.name}`);

        let code = '';
        try {
          code = await requestText(mod.url, 15000);
        } catch (downloadError) {
          if (mod.required) throw downloadError;
          console.warn('[TamaSkrypt Launcher] Pomijam opcjonalny moduł (download):', mod.name, downloadError.message);
          completed += 1;
          progress.update(completed, `Skip: ${mod.name}`);
          continue;
        }

        const validHash = await verifyHash(code, mod.hash);
        if (!validHash) {
          if (strictHashes) {
            if (mod.required) throw new Error(`Hash mismatch: ${mod.name}`);
            console.warn('[TamaSkrypt Launcher] Pomijam opcjonalny moduł (hash mismatch):', mod.name);
            completed += 1;
            progress.update(completed, `Skip: ${mod.name}`);
            continue;
          }
          console.warn('[TamaSkrypt Launcher] Hash mismatch (advisory):', mod.name);
        }

        resolvedCode[mod.name] = code;
        moduleMeta[mod.name] = {
          url: mod.url,
          version: mod.version,
          hash: mod.hash || '',
          updatedAt: Date.now(),
        };
        GM_setValue(cacheKey, code);
        completed += 1;
        progress.update(completed, `Pobrano: ${mod.name}`);
      }

      window.GelekModules = window.GelekModules || {};
      for (const mod of moduleList) {
        const code = resolvedCode[mod.name];
        if (!(await runCode(code, mod.name)) && mod.required) {
          throw new Error(`Nie udało się uruchomić modułu ${mod.name}`);
        }
      }

      if (manifest.entryModule) {
        const entryFn = window.GelekModules && window.GelekModules[manifest.entryModule];
        if (typeof entryFn !== 'function') {
          throw new Error(`Brak entryModule: ${manifest.entryModule}`);
        }
        await Promise.resolve(entryFn({ manifest }));
      }

      setJson(KEY_MODULE_MANIFEST, {
        version: manifest.version,
        entryModule: manifest.entryModule || '',
        modules: moduleList,
        __commitSha: manifest.__commitSha || '',
      });
      setJson(KEY_MODULE_META, moduleMeta);
      GM_setValue(KEY_MODE, 'modules');
      GM_setValue(KEY_VERSION, String(manifest.version || ''));
      GM_setValue(KEY_COMMIT_SHA, String(manifest.__commitSha || ''));
      GM_setValue(KEY_CHECKED, Date.now());
      progress.done('Moduły gotowe');
      return true;
    } catch (error) {
      console.error('[TamaSkrypt Launcher] Błąd modułów:', error);
      progress.fail('Błąd ładowania modułów');
      return false;
    }
  }

  async function executeCachedModules() {
    const cachedManifest = getJson(KEY_MODULE_MANIFEST, null);
    if (!cachedManifest || !Array.isArray(cachedManifest.modules)) return false;
    return executeModules(cachedManifest, { allowNetwork: false });
  }

  async function executeLegacyScript(manifest, allowNetwork) {
    const cachedCode = GM_getValue(KEY_CODE, '');
    const cachedVersion = GM_getValue(KEY_VERSION, '');

    if (!allowNetwork || (manifest.version === cachedVersion && cachedCode)) {
      setLauncherDiagnostics({
        mode: 'legacy',
        manifestVersion: manifest && manifest.version ? manifest.version : cachedVersion,
        source: 'cache',
        commitSha: manifest && manifest.__commitSha ? manifest.__commitSha : GM_getValue(KEY_COMMIT_SHA, ''),
      });
      return await runCode(cachedCode, 'legacy-cache');
    }

    if (!manifest.scriptUrl) return false;
    try {
      setLauncherDiagnostics({
        mode: 'legacy',
        manifestVersion: manifest && manifest.version,
        source: 'network',
        commitSha: manifest && manifest.__commitSha,
      });
      const code = await requestText(manifest.scriptUrl, 15000);
      GM_setValue(KEY_CODE, code);
      GM_setValue(KEY_VERSION, String(manifest.version || ''));
      GM_setValue(KEY_COMMIT_SHA, String(manifest.__commitSha || ''));
      GM_setValue(KEY_CHECKED, Date.now());
      GM_setValue(KEY_MODE, 'legacy');
      return await runCode(code, 'legacy-download');
    } catch (error) {
      console.warn('[TamaSkrypt Launcher] Legacy download failed:', error.message);
      setLauncherDiagnostics({
        mode: 'legacy',
        manifestVersion: cachedVersion || (manifest && manifest.version) || '',
        source: 'cache-fallback',
        commitSha: GM_getValue(KEY_COMMIT_SHA, ''),
      });
      return cachedCode ? await runCode(cachedCode, 'legacy-cache-fallback') : false;
    }
  }

  async function runFromCacheByMode() {
    const mode = GM_getValue(KEY_MODE, 'legacy');
    if (mode === 'modules') {
      const ok = await executeCachedModules();
      if (ok) return true;
    }
    const code = GM_getValue(KEY_CODE, '');
    setLauncherDiagnostics({
      mode: 'legacy',
      manifestVersion: GM_getValue(KEY_VERSION, ''),
      source: 'cache',
      commitSha: GM_getValue(KEY_COMMIT_SHA, ''),
    });
    return code ? await runCode(code, 'legacy-cache') : false;
  }

  async function fetchManifest() {
    const latestCommitSha = await fetchLatestCommitSha();
    const preferredUrl = latestCommitSha
      ? rewriteRawGithubUrl(MANIFEST_URL, latestCommitSha)
      : MANIFEST_URL;
    const cdnPreferredUrl = latestCommitSha
      ? `https://cdn.jsdelivr.net/gh/${GITHUB_OWNER}/${GITHUB_REPO}@${latestCommitSha}/${MANIFEST_PATH}`
      : MANIFEST_CDN_URL;

    try {
      const body = await requestText(preferredUrl, 10000);
      const manifest = JSON.parse(body);
      return applyCommitShaToManifest(manifest, latestCommitSha);
    } catch (error) {
      console.warn('[TamaSkrypt Launcher] Primary manifest unavailable, trying fallback:', error.message);
      try {
        const body = await requestText(MANIFEST_URL, 10000);
        return JSON.parse(body);
      } catch (branchError) {
        console.warn('[TamaSkrypt Launcher] Branch manifest unavailable, trying CDN:', branchError.message);
        const body = await requestText(cdnPreferredUrl, 10000);
        const manifest = JSON.parse(body);
        return applyCommitShaToManifest(manifest, latestCommitSha);
      }
    }
  }

  async function run() {
    if (shouldSkipOnHost()) {
      return;
    }

    await checkFirebaseSchemaVersion();

    const mode = GM_getValue(KEY_MODE, 'legacy');
    const lastCheck = GM_getValue(KEY_CHECKED, 0);
    const needsCheck = mode !== 'modules' || (Date.now() - lastCheck) > CHECK_INTERVAL_MS;

    console.log('[TamaSkrypt Launcher] mode=', mode, 'needsCheck=', needsCheck);

    if (!needsCheck) {
      const ok = await runFromCacheByMode();
      if (ok) return;
    }

    try {
      const manifest = await fetchManifest();
      const hasModules = Array.isArray(manifest.modules) && manifest.modules.length > 0;

      let ok = false;
      if (hasModules) {
        ok = await executeModules(manifest, { allowNetwork: true });
        if (!ok) {
          ok = await executeCachedModules();
        }
        if (!ok) {
          ok = await executeLegacyScript(manifest, true);
        }
      } else {
        ok = await executeLegacyScript(manifest, true);
      }

      if (!ok) {
        await runFromCacheByMode();
      }
    } catch (error) {
      console.warn('[TamaSkrypt Launcher] Manifest unavailable, fallback to cache:', error.message);
      await runFromCacheByMode();
    }
  }

  run();

})();