overmind-mcp 2.8.47 → 2.8.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/overmind_keygen2000.py +871 -0
- package/dist/bin/cli.js +66 -10
- package/dist/bin/cli.js.map +1 -1
- package/dist/bridge/BridgeHttpClient.d.ts +1 -0
- package/dist/bridge/BridgeHttpClient.d.ts.map +1 -1
- package/dist/bridge/BridgeHttpClient.js +29 -9
- package/dist/bridge/BridgeHttpClient.js.map +1 -1
- package/dist/bridge/OverBridgeServer.d.ts +5 -0
- package/dist/bridge/OverBridgeServer.d.ts.map +1 -1
- package/dist/bridge/OverBridgeServer.js +42 -7
- package/dist/bridge/OverBridgeServer.js.map +1 -1
- package/dist/bridge/SessionStore.d.ts.map +1 -1
- package/dist/bridge/SessionStore.js +15 -1
- package/dist/bridge/SessionStore.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
gen_evm_key_retro.py — Générateur de clés privées EVM style KEYGEN des années 2000.
|
|
4
|
+
Dimensions verrouillées : 500x360 pixels.
|
|
5
|
+
Thème visuel : Ville Overmind en parallaxe sur 3 couches, route perspective 3D animée, scanlines CRT.
|
|
6
|
+
Thème sonore : Syntheur Chiptune basse fréquence sans aucun aigu strident (style Mephisto Attack).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import math
|
|
14
|
+
import os
|
|
15
|
+
import random
|
|
16
|
+
import struct
|
|
17
|
+
import sys
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
# =========================================================================
|
|
23
|
+
# SYSTEME DE GENERATION DE CLES CRYPTOGRAPHIQUES EVM (EIP-55 Checksum)
|
|
24
|
+
# =========================================================================
|
|
25
|
+
try:
|
|
26
|
+
# eth_account utilise le CSPRNG du système d'exploitation sous le capot
|
|
27
|
+
from eth_account import Account
|
|
28
|
+
except ImportError:
|
|
29
|
+
sys.stderr.write("Erreur : le module 'eth-account' est manquant.\n")
|
|
30
|
+
sys.stderr.write("Veuillez l'installer avec : pip install eth-account\n")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def gen_one(no_0x: bool = False) -> dict[str, str]:
|
|
35
|
+
"""
|
|
36
|
+
Génère une clé privée de 32 octets via un générateur pseudo-aléatoire cryptographiquement sûr.
|
|
37
|
+
Dérive ensuite l'adresse publique Ethereum avec le checksum conforme à la norme EIP-55.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
no_0x (bool): Si True, la clé privée sera retournée sans le préfixe '0x'.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
dict[str, str]: Contient la clé privée sous 'private_key' et l'adresse sous 'address'.
|
|
44
|
+
"""
|
|
45
|
+
# Création d'un compte éphémère sécurisé
|
|
46
|
+
acct = Account.create()
|
|
47
|
+
priv = acct.key.hex()
|
|
48
|
+
if not no_0x:
|
|
49
|
+
priv = "0x" + priv
|
|
50
|
+
return {"private_key": priv, "address": acct.address}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Détection de la disponibilité de l'interface graphique Tkinter
|
|
54
|
+
GUI_AVAILABLE = False
|
|
55
|
+
try:
|
|
56
|
+
import tkinter as tk
|
|
57
|
+
from tkinter import font as tkfont
|
|
58
|
+
GUI_AVAILABLE = True
|
|
59
|
+
except ImportError:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
# Détection de la compatibilité audio Windows
|
|
63
|
+
WINSOUND_AVAILABLE = False
|
|
64
|
+
if sys.platform == "win32":
|
|
65
|
+
try:
|
|
66
|
+
import winsound
|
|
67
|
+
WINSOUND_AVAILABLE = True
|
|
68
|
+
except ImportError:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# =========================================================================
|
|
73
|
+
# CHIPTUNE SYNTH — Moteur Audio Basse Fréquence (Anti-Aigu & Anti-Clics)
|
|
74
|
+
# =========================================================================
|
|
75
|
+
class ChiptuneSynth:
|
|
76
|
+
"""
|
|
77
|
+
Générateur de musique chiptune rétro 8-bit mono à 22 050 Hz.
|
|
78
|
+
Toutes les notes ont été transposées d'une octave supplémentaire vers le bas (40 Hz - 130 Hz)
|
|
79
|
+
afin d'éliminer toute fréquence aiguë ou stridence agaçante pour l'utilisateur.
|
|
80
|
+
Le signal utilise une enveloppe linéaire d'attaque et de relâchement pour éviter les bruits de clic.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
SAMPLE_RATE = 22050 # Fréquence d'échantillonnage standard chiptune
|
|
84
|
+
|
|
85
|
+
def __init__(self) -> None:
|
|
86
|
+
self.playing = False
|
|
87
|
+
self.temp_file = None
|
|
88
|
+
self.wav_data: bytes | None = None
|
|
89
|
+
self._generate_wav() # Prégénération du signal en mémoire pour un chargement instantané
|
|
90
|
+
|
|
91
|
+
def _generate_wav(self) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Génère les échantillons PCM 16-bit et construit le conteneur binaire au format RIFF/WAVE.
|
|
94
|
+
La structure de la chanson comprend : Couplet x2 -> Refrain x2 -> Couplet x1 -> Pont x1 -> Refrain x2.
|
|
95
|
+
|
|
96
|
+
Nettoie aussi tout fichier temporaire orphelin laissé par une session crashée précédente.
|
|
97
|
+
"""
|
|
98
|
+
import io
|
|
99
|
+
|
|
100
|
+
# --- Purge fichier temporaire orphelin d'une session précédente crashée ---
|
|
101
|
+
try:
|
|
102
|
+
import tempfile
|
|
103
|
+
_orphan = os.path.join(tempfile.gettempdir(), "overmind_keygen_music.wav")
|
|
104
|
+
if os.path.exists(_orphan):
|
|
105
|
+
os.remove(_orphan)
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
# Fréquences transposées très bas (basse lourde et chaude)
|
|
110
|
+
# 1. Couplet (Riff sombre et rapide en E-Phrygien)
|
|
111
|
+
verse = [
|
|
112
|
+
41.20, 41.20, 43.65, 41.20, 49.00, 43.65, 41.20, 43.65,
|
|
113
|
+
41.20, 41.20, 61.74, 55.00, 49.00, 43.65, 41.20, 43.65
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
# 2. Refrain (Ligne mélodique plus présente et enveloppante)
|
|
117
|
+
chorus = [
|
|
118
|
+
55.00, 65.41, 82.41, 110.00, 98.00, 82.41, 87.31, 73.42,
|
|
119
|
+
55.00, 65.41, 82.41, 110.00, 123.47, 130.81, 110.00, 98.00
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
# 3. Pont (Pulsations lourdes de sub-basse)
|
|
123
|
+
bridge = [
|
|
124
|
+
32.70, 32.70, 41.20, 32.70, 36.71, 36.71, 43.65, 36.71,
|
|
125
|
+
49.00, 49.00, 55.00, 49.00, 55.00, 55.00, 65.41, 55.00
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
# Assemblage structurel de la boucle audio
|
|
129
|
+
melody = verse * 2 + chorus * 2 + verse + bridge + chorus * 2
|
|
130
|
+
|
|
131
|
+
note_duration = 0.09 # Durée de 90ms par note (rythme rapide d'arpégiateur)
|
|
132
|
+
samples_per_note = int(self.SAMPLE_RATE * note_duration)
|
|
133
|
+
|
|
134
|
+
data = []
|
|
135
|
+
for freq in melody:
|
|
136
|
+
for i in range(samples_per_note):
|
|
137
|
+
t = i / self.SAMPLE_RATE
|
|
138
|
+
# Génération de l'onde sinusoïdale pure (plus douce que les ondes carrées)
|
|
139
|
+
val = math.sin(2 * math.pi * freq * t)
|
|
140
|
+
|
|
141
|
+
# Enveloppe ADSR simplifiée (Attaque de 15% et Relâchement de 25%) pour supprimer les clics
|
|
142
|
+
envelope = 1.0
|
|
143
|
+
fade_in_samples = int(samples_per_note * 0.15)
|
|
144
|
+
fade_out_samples = int(samples_per_note * 0.25)
|
|
145
|
+
|
|
146
|
+
if i < fade_in_samples:
|
|
147
|
+
envelope = i / fade_in_samples
|
|
148
|
+
elif i > (samples_per_note - fade_out_samples):
|
|
149
|
+
envelope = (samples_per_note - i) / fade_out_samples
|
|
150
|
+
|
|
151
|
+
# Amplitude calibrée à 6% maximum pour rester une musique d'ambiance très discrète
|
|
152
|
+
val_scaled = int(val * 32767 * 0.06 * envelope)
|
|
153
|
+
data.append(val_scaled)
|
|
154
|
+
|
|
155
|
+
# En-tête standard du format audio WAVE (PCM 16-bit Mono)
|
|
156
|
+
num_samples = len(data)
|
|
157
|
+
data_bytes = struct.pack(f"<{num_samples}h", *data)
|
|
158
|
+
byte_rate = self.SAMPLE_RATE * 2
|
|
159
|
+
data_size = num_samples * 2
|
|
160
|
+
|
|
161
|
+
buffer = io.BytesIO()
|
|
162
|
+
buffer.write(b"RIFF")
|
|
163
|
+
buffer.write(struct.pack("<I", 36 + data_size))
|
|
164
|
+
buffer.write(b"WAVEfmt ")
|
|
165
|
+
buffer.write(struct.pack("<IHHIIHH", 16, 1, 1, self.SAMPLE_RATE, byte_rate, 2, 16))
|
|
166
|
+
buffer.write(b"data")
|
|
167
|
+
buffer.write(struct.pack("<I", data_size))
|
|
168
|
+
buffer.write(data_bytes)
|
|
169
|
+
self.wav_data = buffer.getvalue()
|
|
170
|
+
|
|
171
|
+
def start(self) -> None:
|
|
172
|
+
"""Démarre la lecture asynchrone de la musique en boucle depuis un fichier temporaire."""
|
|
173
|
+
if not WINSOUND_AVAILABLE or not self.wav_data:
|
|
174
|
+
return
|
|
175
|
+
self.playing = True
|
|
176
|
+
try:
|
|
177
|
+
import tempfile
|
|
178
|
+
# Écriture du buffer binaire sur le disque pour contourner les limitations SND_MEMORY de Windows
|
|
179
|
+
self.temp_file = os.path.join(tempfile.gettempdir(), "overmind_keygen_music.wav")
|
|
180
|
+
with open(self.temp_file, "wb") as f:
|
|
181
|
+
f.write(self.wav_data)
|
|
182
|
+
# Lecture asynchrone en boucle infinie
|
|
183
|
+
winsound.PlaySound(self.temp_file, winsound.SND_FILENAME | winsound.SND_ASYNC | winsound.SND_LOOP)
|
|
184
|
+
except Exception:
|
|
185
|
+
pass
|
|
186
|
+
|
|
187
|
+
def stop(self) -> None:
|
|
188
|
+
"""Arrête la musique et supprime proprement le fichier WAV temporaire."""
|
|
189
|
+
self.playing = False
|
|
190
|
+
if WINSOUND_AVAILABLE:
|
|
191
|
+
try:
|
|
192
|
+
winsound.PlaySound(None, winsound.SND_PURGE)
|
|
193
|
+
if self.temp_file and os.path.exists(self.temp_file):
|
|
194
|
+
os.remove(self.temp_file)
|
|
195
|
+
except Exception:
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
def toggle(self) -> bool:
|
|
199
|
+
"""Bascule entre la lecture et la mise en pause."""
|
|
200
|
+
if self.playing:
|
|
201
|
+
self.stop()
|
|
202
|
+
return False
|
|
203
|
+
self.start()
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
def reveal_beep(self) -> None:
|
|
207
|
+
"""Génère un signal sonore de transition grave/médium doux lors du reveal de la clé."""
|
|
208
|
+
if not WINSOUND_AVAILABLE:
|
|
209
|
+
return
|
|
210
|
+
try:
|
|
211
|
+
# Balayage fréquentiel grave (120 Hz à 240 Hz) pour éviter tout sifflement strident
|
|
212
|
+
sps = int(self.SAMPLE_RATE * 0.18)
|
|
213
|
+
raw = bytearray()
|
|
214
|
+
for i in range(sps):
|
|
215
|
+
t = i / self.SAMPLE_RATE
|
|
216
|
+
f = 120.0 + (240.0 - 120.0) * (i / sps)
|
|
217
|
+
val = math.sin(2 * math.pi * f * t)
|
|
218
|
+
env = 1.0 - (i / sps) * 0.8
|
|
219
|
+
v = int(val * 127 * 0.3 * env)
|
|
220
|
+
raw.append((v + 128) & 0xFF)
|
|
221
|
+
|
|
222
|
+
data_size = len(raw)
|
|
223
|
+
buf = bytearray()
|
|
224
|
+
buf += b"RIFF"
|
|
225
|
+
buf += struct.pack("<I", 36 + data_size)
|
|
226
|
+
buf += b"WAVEfmt "
|
|
227
|
+
buf += struct.pack("<IHHIIHH", 16, 1, 1, self.SAMPLE_RATE, self.SAMPLE_RATE, 1, 8)
|
|
228
|
+
buf += b"data"
|
|
229
|
+
buf += struct.pack("<I", data_size)
|
|
230
|
+
buf += raw
|
|
231
|
+
winsound.PlaySound(bytes(buf), winsound.SND_MEMORY | winsound.SND_ASYNC)
|
|
232
|
+
except Exception:
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# =========================================================================
|
|
237
|
+
# APPLICATION GRAPHIQUE Retro Keygen 2000 (Tkinter)
|
|
238
|
+
# =========================================================================
|
|
239
|
+
class KeygenApp:
|
|
240
|
+
# Palette de couleurs Cyberpunk néon
|
|
241
|
+
BG_DEEP = "#000000"
|
|
242
|
+
BG_PANEL = "#07070d"
|
|
243
|
+
BG_TITLE = "#0f0f15"
|
|
244
|
+
FG_GREEN = "#00ff41"
|
|
245
|
+
FG_CYAN = "#00ffff"
|
|
246
|
+
FG_MAG = "#ff00ff"
|
|
247
|
+
FG_YEL = "#ffff00"
|
|
248
|
+
FG_RED = "#ff0055"
|
|
249
|
+
|
|
250
|
+
# Dimensions fixes obligatoires du Keygen
|
|
251
|
+
W = 500
|
|
252
|
+
H = 360
|
|
253
|
+
CANVAS_H = 216 # Hauteur dédiée à la scène graphique animée
|
|
254
|
+
|
|
255
|
+
def __init__(self, root: tk.Tk) -> None:
|
|
256
|
+
self.root = root
|
|
257
|
+
self.root.title("OVERMIND BEARER KEYGEN 2000")
|
|
258
|
+
self.root.configure(bg=self.BG_DEEP)
|
|
259
|
+
self.root.resizable(False, False)
|
|
260
|
+
|
|
261
|
+
# Mode Borderless (pas de bordures système Windows)
|
|
262
|
+
self.root.overrideredirect(True)
|
|
263
|
+
sw, sh = root.winfo_screenwidth(), root.winfo_screenheight()
|
|
264
|
+
x, y = (sw - self.W) // 2, (sh - self.H) // 2
|
|
265
|
+
self.root.geometry(f"{self.W}x{self.H}+{x}+{y}")
|
|
266
|
+
|
|
267
|
+
# Variables pour permettre le déplacement (drag & drop) de la fenêtre
|
|
268
|
+
self._drag_x: int | None = None
|
|
269
|
+
self._drag_y: int | None = None
|
|
270
|
+
root.bind("<ButtonPress-1>", self._on_press)
|
|
271
|
+
root.bind("<ButtonRelease-1>", self._on_release)
|
|
272
|
+
root.bind("<B1-Motion>", self._on_motion)
|
|
273
|
+
|
|
274
|
+
# Raccourcis clavier (Échap pour quitter, F1 pour générer)
|
|
275
|
+
root.bind("<Escape>", lambda e: self._close())
|
|
276
|
+
root.bind("<F1>", lambda e: self._start_generation())
|
|
277
|
+
|
|
278
|
+
# Variables d'état des animations
|
|
279
|
+
self.grid_offset = 0.0
|
|
280
|
+
self.traffic_offset = 0.0
|
|
281
|
+
self.scan_y = 0
|
|
282
|
+
self.stars: list[dict[str, Any]] = []
|
|
283
|
+
|
|
284
|
+
# ---------------------------------------------------------------------
|
|
285
|
+
# DESIGN DE LA SCENE URBAINE DE LA VILLE EN PARALLAXE (3 COUCHES)
|
|
286
|
+
# ---------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
# 1. COUCHE ARRIÈRE-PLAN (Lente, silhouettes très sombres)
|
|
289
|
+
# Défilement : -0.07px par frame
|
|
290
|
+
self.buildings_far = [
|
|
291
|
+
{"x": 10.0, "w": 60, "h": 160, "col": "#0b0417"},
|
|
292
|
+
{"x": 100.0, "w": 75, "h": 140, "col": "#05081f"},
|
|
293
|
+
{"x": 200.0, "w": 55, "h": 170, "col": "#0b0417"},
|
|
294
|
+
{"x": 290.0, "w": 80, "h": 150, "col": "#05081f"},
|
|
295
|
+
{"x": 390.0, "w": 65, "h": 165, "col": "#0b0417"},
|
|
296
|
+
{"x": 480.0, "w": 70, "h": 135, "col": "#05081f"}
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
# 2. COUCHE INTERMÉDIAIRE (Vitesse moyenne, immeubles filaires mauves)
|
|
300
|
+
# Défilement : -0.18px par frame
|
|
301
|
+
self.buildings_mid = [
|
|
302
|
+
{"x": 30.0, "w": 45, "h": 115, "col": "#1f0933", "roof": 1},
|
|
303
|
+
{"x": 110.0, "w": 55, "h": 135, "col": "#101647", "roof": 0},
|
|
304
|
+
{"x": 210.0, "w": 50, "h": 120, "col": "#1f0933", "roof": 3},
|
|
305
|
+
{"x": 310.0, "w": 60, "h": 145, "col": "#101647", "roof": 1},
|
|
306
|
+
{"x": 410.0, "w": 45, "h": 125, "col": "#1f0933", "roof": 0}
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
# 3. COUCHE PREMIER PLAN (Rapide, détails néons, antennes, panneaux publicitaires)
|
|
310
|
+
# Défilement : -0.48px par frame
|
|
311
|
+
# types de toit (roof): 0=plat, 1=escalier, 2=antenne + feu de signalisation, 3=dôme, 4=double flèche
|
|
312
|
+
self.buildings_fg = [
|
|
313
|
+
{"x": 5.0, "w": 40, "h": 90, "col": "#b800b8", "roof": 2, "sign": "OVR"},
|
|
314
|
+
{"x": 65.0, "w": 30, "h": 120, "col": "#0055ff", "roof": 0, "sign": ""},
|
|
315
|
+
{"x": 115.0, "w": 35, "h": 75, "col": "#5a00bd", "roof": 1, "sign": ""},
|
|
316
|
+
{"x": 170.0, "w": 45, "h": 135, "col": "#ff00ff", "roof": 4, "sign": "EVM"},
|
|
317
|
+
{"x": 235.0, "w": 30, "h": 85, "col": "#0088ff", "roof": 0, "sign": ""},
|
|
318
|
+
{"x": 285.0, "w": 35, "h": 110, "col": "#00ffcc", "roof": 3, "sign": "SYS"},
|
|
319
|
+
{"x": 340.0, "w": 40, "h": 130, "col": "#990099", "roof": 2, "sign": ""},
|
|
320
|
+
{"x": 400.0, "w": 45, "h": 115, "col": "#00ff99", "roof": 1, "sign": ""},
|
|
321
|
+
{"x": 465.0, "w": 35, "h": 80, "col": "#0055ff", "roof": 0, "sign": ""}
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
self.current_key: dict[str, str] | None = None
|
|
325
|
+
|
|
326
|
+
# --- Splash LOADING style keygen 2000 (1.5s) ---
|
|
327
|
+
self._splash_start = time.time()
|
|
328
|
+
self._splash_label: tk.Label | None = None
|
|
329
|
+
self._splash_bar: int = 0
|
|
330
|
+
|
|
331
|
+
self._build_ui()
|
|
332
|
+
self._show_splash()
|
|
333
|
+
|
|
334
|
+
# Génération aléatoire d'un champ d'étoiles colorées et scintillantes
|
|
335
|
+
for _ in range(40):
|
|
336
|
+
self.stars.append({
|
|
337
|
+
"x": random.randint(2, self.W - 2),
|
|
338
|
+
"y": random.randint(2, 128),
|
|
339
|
+
"color": random.choice(["#ffffff", "#00ffff", "#ff00ff", "#8888aa"]),
|
|
340
|
+
"size": random.choice([1, 2])
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
# Initialisation et lecture de la musique
|
|
344
|
+
self.synth = ChiptuneSynth()
|
|
345
|
+
self.synth.start()
|
|
346
|
+
|
|
347
|
+
# Démarrage des boucles temporelles d'animation Tkinter
|
|
348
|
+
self._tick_city()
|
|
349
|
+
self._tick_glitch()
|
|
350
|
+
|
|
351
|
+
# Event Handlers pour le déplacement sans bordure de la fenêtre
|
|
352
|
+
def _on_press(self, e):
|
|
353
|
+
self._drag_x = e.x
|
|
354
|
+
self._drag_y = e.y
|
|
355
|
+
|
|
356
|
+
def _on_release(self, e):
|
|
357
|
+
self._drag_x = None
|
|
358
|
+
self._drag_y = None
|
|
359
|
+
|
|
360
|
+
def _on_motion(self, e):
|
|
361
|
+
if self._drag_x is None:
|
|
362
|
+
return
|
|
363
|
+
dx = e.x - self._drag_x
|
|
364
|
+
dy = e.y - self._drag_y
|
|
365
|
+
self.root.geometry(f"+{self.root.winfo_x() + dx}+{self.root.winfo_y() + dy}")
|
|
366
|
+
|
|
367
|
+
def _tick_city(self) -> None:
|
|
368
|
+
"""Dessine et anime l'ensemble de la scène graphique néon de façon synchrone."""
|
|
369
|
+
self.canvas_bg.delete("all")
|
|
370
|
+
|
|
371
|
+
horizon_y = 130
|
|
372
|
+
max_y = self.CANVAS_H
|
|
373
|
+
|
|
374
|
+
# 1. ÉTOILES SCINTILLANTES
|
|
375
|
+
for star in self.stars:
|
|
376
|
+
# Twinkle discret par variation d'intensité aléatoire
|
|
377
|
+
color = star["color"]
|
|
378
|
+
if random.random() < 0.08:
|
|
379
|
+
color = random.choice(["#333344", "#555566", star["color"]])
|
|
380
|
+
|
|
381
|
+
s = star["size"]
|
|
382
|
+
self.canvas_bg.create_oval(
|
|
383
|
+
star["x"], star["y"], star["x"] + s, star["y"] + s,
|
|
384
|
+
fill=color, outline=""
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# 2. SILHOUETTES URBAINES D'ARRIÈRE-PLAN (COUCHE 1 - LENTE)
|
|
388
|
+
for b in self.buildings_far:
|
|
389
|
+
b["x"] -= 0.07
|
|
390
|
+
if b["x"] + b["w"] < 0:
|
|
391
|
+
b["x"] = self.W
|
|
392
|
+
|
|
393
|
+
x, w, h, col = b["x"], b["w"], b["h"], b["col"]
|
|
394
|
+
top_y = horizon_y - h
|
|
395
|
+
# Rectangle plein avec bordure wireframe très sombre
|
|
396
|
+
self.canvas_bg.create_rectangle(
|
|
397
|
+
x, top_y, x + w, horizon_y,
|
|
398
|
+
fill="#020006", outline=col, width=1
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# 3. IMMEUBLES DE COUCHE INTERMÉDIAIRE (COUCHE 2 - MOYENNE)
|
|
402
|
+
for b in self.buildings_mid:
|
|
403
|
+
b["x"] -= 0.18
|
|
404
|
+
if b["x"] + b["w"] < 0:
|
|
405
|
+
b["x"] = self.W
|
|
406
|
+
|
|
407
|
+
x, w, h, col, roof = b["x"], b["w"], b["h"], b["col"], b["roof"]
|
|
408
|
+
top_y = horizon_y - h
|
|
409
|
+
|
|
410
|
+
# Dessin de l'immeuble de base
|
|
411
|
+
self.canvas_bg.create_rectangle(x, top_y, x + w, horizon_y, fill="#04000b", outline=col, width=1)
|
|
412
|
+
|
|
413
|
+
# Structures architecturales supplémentaires sur le toit
|
|
414
|
+
if roof == 1: # Stepped/Escalier
|
|
415
|
+
self.canvas_bg.create_rectangle(x + 5, top_y - 6, x + w - 5, top_y, fill="#04000b", outline=col, width=1)
|
|
416
|
+
elif roof == 3: # Dome/Coupole
|
|
417
|
+
self.canvas_bg.create_arc(x + 6, top_y - 12, x + w - 6, top_y + 4, start=0, extent=180, fill="#04000b", outline=col, width=1)
|
|
418
|
+
|
|
419
|
+
# 4. IMMEUBLES DU PREMIER PLAN DETAILLES (COUCHE 3 - RAPIDE)
|
|
420
|
+
for b in self.buildings_fg:
|
|
421
|
+
b["x"] -= 0.48
|
|
422
|
+
if b["x"] + b["w"] < 0:
|
|
423
|
+
b["x"] = self.W
|
|
424
|
+
|
|
425
|
+
x, w, h, col, roof = b["x"], b["w"], b["h"], b["col"], b["roof"]
|
|
426
|
+
sign = b.get("sign", "")
|
|
427
|
+
top_y = horizon_y - h
|
|
428
|
+
|
|
429
|
+
# Structure du toit de premier plan
|
|
430
|
+
if roof == 1: # Toit en escalier
|
|
431
|
+
self.canvas_bg.create_rectangle(x, top_y, x + w, horizon_y, fill="#060012", outline=col, width=1.5)
|
|
432
|
+
self.canvas_bg.create_rectangle(x + 4, top_y - 5, x + w - 4, top_y, fill="#060012", outline=col, width=1.5)
|
|
433
|
+
self.canvas_bg.create_rectangle(x + 8, top_y - 10, x + w - 8, top_y - 5, fill="#060012", outline=col, width=1.5)
|
|
434
|
+
elif roof == 2: # Toit avec mât et feu rouge clignotant
|
|
435
|
+
self.canvas_bg.create_rectangle(x, top_y, x + w, horizon_y, fill="#060012", outline=col, width=1.5)
|
|
436
|
+
cx = x + w // 2
|
|
437
|
+
self.canvas_bg.create_line(cx, top_y, cx, top_y - 15, fill=col, width=1.5)
|
|
438
|
+
# Clignotement du voyant de sécurité
|
|
439
|
+
flash_col = "#ff0000" if (int(time.time() * 3.5) % 2 == 0) else "#440000"
|
|
440
|
+
self.canvas_bg.create_oval(cx - 2.5, top_y - 17.5, cx + 2.5, top_y - 12.5, fill=flash_col, outline="")
|
|
441
|
+
elif roof == 3: # Coupole
|
|
442
|
+
self.canvas_bg.create_rectangle(x, top_y, x + w, horizon_y, fill="#060012", outline=col, width=1.5)
|
|
443
|
+
self.canvas_bg.create_arc(x + 4, top_y - 14, x + w - 4, top_y + 2, start=0, extent=180, fill="#060012", outline=col, width=1.5)
|
|
444
|
+
elif roof == 4: # Deux antennes / flèches
|
|
445
|
+
self.canvas_bg.create_rectangle(x, top_y, x + w, horizon_y, fill="#060012", outline=col, width=1.5)
|
|
446
|
+
self.canvas_bg.create_line(x + 6, top_y, x + 6, top_y - 12, fill=col, width=1.2)
|
|
447
|
+
self.canvas_bg.create_line(x + w - 6, top_y, x + w - 6, top_y - 12, fill=col, width=1.2)
|
|
448
|
+
# Feux de signalisation alternés
|
|
449
|
+
f1_col = "#ff0000" if (int(time.time() * 3) % 2 == 0) else "#440000"
|
|
450
|
+
f2_col = "#440000" if (int(time.time() * 3) % 2 == 0) else "#ff0000"
|
|
451
|
+
self.canvas_bg.create_oval(x + 4, top_y - 14, x + 8, top_y - 10, fill=f1_col, outline="")
|
|
452
|
+
self.canvas_bg.create_oval(x + w - 8, top_y - 14, x + w - 4, top_y - 10, fill=f2_col, outline="")
|
|
453
|
+
else: # Toit plat simple
|
|
454
|
+
self.canvas_bg.create_rectangle(x, top_y, x + w, horizon_y, fill="#060012", outline=col, width=1.5)
|
|
455
|
+
|
|
456
|
+
# Dessin des enseignes néons lumineuses publicitaires
|
|
457
|
+
if sign:
|
|
458
|
+
# Plaque de fond néon
|
|
459
|
+
self.canvas_bg.create_rectangle(
|
|
460
|
+
x + w//2 - 14, top_y + 12, x + w//2 + 14, top_y + 24,
|
|
461
|
+
fill="#000000", outline=self.FG_CYAN if sign == "SYS" else self.FG_MAG, width=1
|
|
462
|
+
)
|
|
463
|
+
self.canvas_bg.create_text(
|
|
464
|
+
x + w//2, top_y + 18, text=sign,
|
|
465
|
+
fill=self.FG_CYAN if sign == "SYS" else self.FG_MAG,
|
|
466
|
+
font=("Courier", 8, "bold")
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
# Fenêtres éclairées de façon dynamique à l'aide d'un PRNG déterministe basé sur l'abscisse
|
|
470
|
+
random.seed(int(x) + 400)
|
|
471
|
+
cols = w // 8
|
|
472
|
+
rows = h // 11
|
|
473
|
+
for r in range(1, rows):
|
|
474
|
+
for c in range(1, cols):
|
|
475
|
+
# Taux de fenêtres allumées (40%)
|
|
476
|
+
if random.random() < 0.40:
|
|
477
|
+
win_x = x + c * 8
|
|
478
|
+
win_y = top_y + r * 11
|
|
479
|
+
# Variantes de couleurs chaudes de fenêtres rétro
|
|
480
|
+
win_c = random.choice(["#ffff55", "#ffaa00", "#ffffff"])
|
|
481
|
+
self.canvas_bg.create_rectangle(
|
|
482
|
+
win_x, win_y, win_x + 2, win_y + 2,
|
|
483
|
+
fill=win_c, outline=""
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
# 5. ROUTE HIGHWAY PERSPECTIVE 3D ET TRAFFIC D'IMPULSION
|
|
487
|
+
# Ligne d'horizon néon magenta
|
|
488
|
+
self.canvas_bg.create_line(0, horizon_y, self.W, horizon_y, fill="#ff00ff", width=2)
|
|
489
|
+
|
|
490
|
+
# Dessin des lignes de fuite verticales (Perspective radiale)
|
|
491
|
+
for i in range(-12, 13):
|
|
492
|
+
self.canvas_bg.create_line(250 + i * 6, horizon_y, 250 + i * 45, max_y, fill="#003c55", width=1)
|
|
493
|
+
|
|
494
|
+
# Lignes d'horizon horizontales qui se rapprochent et s'accélèrent vers le bas (effet tunnel)
|
|
495
|
+
# L'incrément oscille entre 1.2 et 3.5 pour simuler des cycles d'accélération/décélération
|
|
496
|
+
if not hasattr(self, "_grid_speed"):
|
|
497
|
+
self._grid_speed = 1.2
|
|
498
|
+
self._grid_dir = 1
|
|
499
|
+
# Bascule de direction aux bornes pour rester dans une plage visuelle stable
|
|
500
|
+
if self._grid_speed >= 3.5:
|
|
501
|
+
self._grid_dir = -1
|
|
502
|
+
elif self._grid_speed <= 1.2:
|
|
503
|
+
self._grid_dir = 1
|
|
504
|
+
self._grid_speed += 0.012 * self._grid_dir
|
|
505
|
+
self.grid_offset = (self.grid_offset + self._grid_speed) % 12
|
|
506
|
+
y_lines = [130, 133, 137, 143, 152, 165, 183, 208, 240]
|
|
507
|
+
for idx, y in enumerate(y_lines[:-1]):
|
|
508
|
+
next_y = y_lines[idx+1]
|
|
509
|
+
interpolated_y = y + (next_y - y) * (self.grid_offset / 12.0)
|
|
510
|
+
if interpolated_y <= max_y:
|
|
511
|
+
self.canvas_bg.create_line(0, interpolated_y, self.W, interpolated_y, fill="#007799", width=1)
|
|
512
|
+
|
|
513
|
+
# Animation des flux de trafic néon (les "voitures" ou paquets binaires)
|
|
514
|
+
self.traffic_offset = (self.traffic_offset + 0.035) % 1.0
|
|
515
|
+
# Les véhicules se déplacent sur les voies clés du réseau routier virtuel
|
|
516
|
+
for lane_idx in [-5, -2, 2, 5]:
|
|
517
|
+
for car_p in range(3):
|
|
518
|
+
p = (self.traffic_offset + car_p / 3.0) % 1.0
|
|
519
|
+
p_curved = p * p # Distorsion quadratique perspective correcte
|
|
520
|
+
|
|
521
|
+
y_p = horizon_y + p_curved * (max_y - horizon_y)
|
|
522
|
+
x_p = 250 + lane_idx * (6 + p_curved * 39)
|
|
523
|
+
|
|
524
|
+
# Taille proportionnelle à la proximité de la caméra
|
|
525
|
+
size = 1 + p_curved * 4.5
|
|
526
|
+
color = self.FG_CYAN if lane_idx > 0 else self.FG_MAG
|
|
527
|
+
|
|
528
|
+
self.canvas_bg.create_rectangle(
|
|
529
|
+
x_p - size, y_p - size/2.5, x_p + size, y_p + size/2.5,
|
|
530
|
+
fill=color, outline=""
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
# 6. FILTRE CRT ET EFFET SCANLINE BALAYAGE VISUEL
|
|
535
|
+
self.scan_y = (self.scan_y + 3) % (self.H + 80)
|
|
536
|
+
self.canvas_bg.create_rectangle(
|
|
537
|
+
0, self.scan_y - 20, self.W, self.scan_y,
|
|
538
|
+
fill=self.FG_GREEN, stipple="gray12", outline=""
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
# Quadrillage de lignes horizontales d'interfaçage moniteur cathodique
|
|
542
|
+
for y in range(0, self.CANVAS_H, 3):
|
|
543
|
+
self.canvas_bg.create_line(0, y, self.W, y, fill="#010a05")
|
|
544
|
+
|
|
545
|
+
# Prochain rafraîchissement d'affichage (~30 FPS)
|
|
546
|
+
self.root.after(33, self._tick_city)
|
|
547
|
+
|
|
548
|
+
def _tick_glitch(self) -> None:
|
|
549
|
+
"""Gère les micro-glitchs textuels et clignotements chromatiques du titre du Keygen."""
|
|
550
|
+
try:
|
|
551
|
+
colors = [self.FG_GREEN, self.FG_CYAN, self.FG_MAG, self.FG_YEL, self.FG_RED]
|
|
552
|
+
self.title_lbl.config(fg=random.choice(colors))
|
|
553
|
+
t = "🚀 OVERMIND BEARER KEYGEN 2 0 0 0 🚀"
|
|
554
|
+
# Une chance sur quatre d'avoir un caractère altéré/cryptique temporaire
|
|
555
|
+
if random.random() < 0.25:
|
|
556
|
+
idx = random.randint(0, len(t) - 1)
|
|
557
|
+
t = t[:idx] + random.choice("█▓▒░*#@&$") + t[idx + 1:]
|
|
558
|
+
self.title_lbl.config(text=t)
|
|
559
|
+
except Exception:
|
|
560
|
+
pass
|
|
561
|
+
self.root.after(160, self._tick_glitch)
|
|
562
|
+
|
|
563
|
+
def _build_ui(self) -> None:
|
|
564
|
+
"""Initialise, positionne et configure tous les widgets Tkinter de l'application."""
|
|
565
|
+
# Canvas principal recevant le rendu de la ville
|
|
566
|
+
self.canvas_bg = tk.Canvas(
|
|
567
|
+
self.root, width=self.W, height=self.CANVAS_H,
|
|
568
|
+
bg="#020005", highlightthickness=0, bd=0
|
|
569
|
+
)
|
|
570
|
+
self.canvas_bg.place(x=0, y=26)
|
|
571
|
+
|
|
572
|
+
# Barre de titre supérieure permettant de faire glisser la fenêtre borderless
|
|
573
|
+
title_bar = tk.Frame(self.root, bg=self.BG_TITLE, height=26)
|
|
574
|
+
title_bar.place(x=0, y=0, width=self.W, height=26)
|
|
575
|
+
title_bar.bind("<ButtonPress-1>", self._on_press)
|
|
576
|
+
title_bar.bind("<ButtonRelease-1>", self._on_release)
|
|
577
|
+
title_bar.bind("<B1-Motion>", self._on_motion)
|
|
578
|
+
|
|
579
|
+
self.title_lbl = tk.Label(
|
|
580
|
+
title_bar, text="🚀 OVERMIND BEARER KEYGEN 2 0 0 0 🚀",
|
|
581
|
+
fg=self.FG_GREEN, bg=self.BG_TITLE,
|
|
582
|
+
font=("Courier", 11, "bold"),
|
|
583
|
+
)
|
|
584
|
+
self.title_lbl.pack(side="left", padx=8)
|
|
585
|
+
|
|
586
|
+
# Bouton Fermer [X]
|
|
587
|
+
close_btn = tk.Button(
|
|
588
|
+
self.root, text="[X]", command=self._close,
|
|
589
|
+
fg=self.FG_RED, bg=self.BG_TITLE,
|
|
590
|
+
activeforeground="#ffffff", activebackground="#660000",
|
|
591
|
+
bd=1, relief="raised",
|
|
592
|
+
font=("Courier", 10, "bold"),
|
|
593
|
+
width=4, cursor="hand2",
|
|
594
|
+
)
|
|
595
|
+
close_btn.place(x=self.W - 38, y=2)
|
|
596
|
+
|
|
597
|
+
# Ruban de texte défilant de pied de scène (Marquee)
|
|
598
|
+
self.marquee_text = " *** OVERMIND BEARER KEYGEN v1.0 *** CRAFTED BY DEMON-CORP *** FORGE YOUR KEY SECURELY *** FOR OVERMIND BEARER USE *** GREETZ TO ALL CO-AGENTS IN THE GRID *** "
|
|
599
|
+
self.foot_lbl = tk.Label(
|
|
600
|
+
self.root,
|
|
601
|
+
text=self.marquee_text,
|
|
602
|
+
fg=self.FG_MAG, bg=self.BG_DEEP,
|
|
603
|
+
font=("Courier", 8, "bold")
|
|
604
|
+
)
|
|
605
|
+
self.foot_lbl.place(x=10, y=243)
|
|
606
|
+
|
|
607
|
+
# Zone d'affichage des clés générées (Formulaire enclavé)
|
|
608
|
+
self.key_frame = tk.Frame(self.root, bg=self.BG_PANEL, bd=1, relief="sunken")
|
|
609
|
+
self.key_frame.place(x=10, y=264, width=self.W - 20, height=52)
|
|
610
|
+
self.key_frame.columnconfigure(1, weight=1)
|
|
611
|
+
|
|
612
|
+
# Label et Entry de l'Adresse Ethereum
|
|
613
|
+
tk.Label(self.key_frame, text="ADDRESS:", fg=self.FG_CYAN, bg=self.BG_PANEL, font=("Courier", 8, "bold")).grid(row=0, column=0, sticky="w", padx=5, pady=2)
|
|
614
|
+
self.addr_var = tk.StringVar(value="0x........................................")
|
|
615
|
+
self.addr_entry = tk.Entry(self.key_frame, textvariable=self.addr_var, fg=self.FG_GREEN, bg="#050a06", insertbackground=self.FG_GREEN, font=("Courier", 8, "bold"), bd=1, width=52)
|
|
616
|
+
self.addr_entry.grid(row=0, column=1, padx=5, pady=2, sticky="ew")
|
|
617
|
+
self.addr_entry.config(state="readonly")
|
|
618
|
+
|
|
619
|
+
# Label et Entry de la Clé Privée (Verrouillée en Cyan `#00ffff` sur fond sombre)
|
|
620
|
+
tk.Label(self.key_frame, text="PRIVATE:", fg=self.FG_CYAN, bg=self.BG_PANEL, font=("Courier", 8, "bold")).grid(row=1, column=0, sticky="w", padx=5, pady=2)
|
|
621
|
+
self.priv_var = tk.StringVar(value="")
|
|
622
|
+
self.priv_entry = tk.Entry(self.key_frame, textvariable=self.priv_var, fg=self.FG_CYAN, bg="#050a0a", insertbackground=self.FG_CYAN, font=("Courier", 8, "bold"), bd=1, show="*", width=52)
|
|
623
|
+
self.priv_entry.grid(row=1, column=1, padx=5, pady=2, sticky="ew")
|
|
624
|
+
self.priv_entry.config(state="readonly")
|
|
625
|
+
|
|
626
|
+
# Conteneur des boutons d'actions inférieurs
|
|
627
|
+
btn_frame = tk.Frame(self.root, bg=self.BG_DEEP)
|
|
628
|
+
btn_frame.place(x=10, y=320)
|
|
629
|
+
|
|
630
|
+
# Usine à boutons stylisés
|
|
631
|
+
def btn(parent, text, cmd, fg=None, width=15):
|
|
632
|
+
return tk.Button(
|
|
633
|
+
parent, text=text, command=cmd,
|
|
634
|
+
fg=fg or self.FG_GREEN, bg=self.BG_TITLE,
|
|
635
|
+
activeforeground="#ffffff", activebackground="#333333",
|
|
636
|
+
bd=2, relief="raised",
|
|
637
|
+
font=("Courier", 10, "bold"),
|
|
638
|
+
width=width, padx=6, pady=4,
|
|
639
|
+
cursor="hand2",
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
self.gen_btn = btn(btn_frame, "[ GENERATE ]", self._start_generation)
|
|
643
|
+
self.gen_btn.pack(side="left", padx=5)
|
|
644
|
+
|
|
645
|
+
self.copy_btn = btn(btn_frame, "[ COPY PRIVATE ]", self._copy_private, fg="#ffcc00")
|
|
646
|
+
self.copy_btn.pack(side="left", padx=5)
|
|
647
|
+
self.copy_btn.config(state="disabled")
|
|
648
|
+
|
|
649
|
+
self.music_btn = btn(btn_frame, "[ MUSIC: ON ]", self._toggle_music, fg=self.FG_GREEN)
|
|
650
|
+
self.music_btn.pack(side="left", padx=5)
|
|
651
|
+
|
|
652
|
+
def _show_splash(self) -> None:
|
|
653
|
+
"""Affiche un écran de chargement rétro 'LOADING OVERMIND OS...' pendant 1.5s."""
|
|
654
|
+
# Overlay plein écran sur le canvas de scène
|
|
655
|
+
self._splash_overlay = tk.Frame(self.root, bg="#000000", bd=2, relief="ridge",
|
|
656
|
+
highlightthickness=2, highlightbackground=self.FG_CYAN)
|
|
657
|
+
self._splash_overlay.place(x=4, y=30, width=self.W - 8, height=self.CANVAS_H - 8)
|
|
658
|
+
|
|
659
|
+
tk.Label(
|
|
660
|
+
self._splash_overlay, text=">>> LOADING OVERMIND OS v1.0 <<<",
|
|
661
|
+
fg=self.FG_GREEN, bg="#000000",
|
|
662
|
+
font=("Courier", 10, "bold"),
|
|
663
|
+
).pack(pady=(40, 8))
|
|
664
|
+
|
|
665
|
+
# Progress bar textuelle
|
|
666
|
+
self._splash_bar_lbl = tk.Label(
|
|
667
|
+
self._splash_overlay, text="[ ] 0%",
|
|
668
|
+
fg=self.FG_YEL, bg="#000000",
|
|
669
|
+
font=("Courier", 10, "bold"),
|
|
670
|
+
)
|
|
671
|
+
self._splash_bar_lbl.pack(pady=4)
|
|
672
|
+
|
|
673
|
+
tk.Label(
|
|
674
|
+
self._splash_overlay, text="Mounting entropy pool...",
|
|
675
|
+
fg=self.FG_MAG, bg="#000000",
|
|
676
|
+
font=("Courier", 8, "bold"),
|
|
677
|
+
).pack(pady=(20, 0))
|
|
678
|
+
|
|
679
|
+
# Démarre l'animation de la progress bar
|
|
680
|
+
self._update_splash(0)
|
|
681
|
+
|
|
682
|
+
def _update_splash(self, pct: int) -> None:
|
|
683
|
+
"""Avance la jauge LOADING et détruit l'overlay à 100%."""
|
|
684
|
+
try:
|
|
685
|
+
bar = "[" + "█" * (pct // 5) + " " * (20 - pct // 5) + "]"
|
|
686
|
+
self._splash_bar_lbl.config(text=f"{bar} {pct:3d}%")
|
|
687
|
+
except Exception:
|
|
688
|
+
return
|
|
689
|
+
if pct >= 100:
|
|
690
|
+
# Splash terminé -> on retire l'overlay
|
|
691
|
+
try:
|
|
692
|
+
self._splash_overlay.destroy()
|
|
693
|
+
except Exception:
|
|
694
|
+
pass
|
|
695
|
+
return
|
|
696
|
+
self.root.after(30, self._update_splash, pct + 2)
|
|
697
|
+
|
|
698
|
+
def _glitch_rgb_shift(self) -> None:
|
|
699
|
+
"""Effet VHS : décale temporairement les couleurs de l'overlay frame pendant 150ms."""
|
|
700
|
+
try:
|
|
701
|
+
# On crée 3 copies translatées du canvas de scène en R/G/B (effet chromatisme)
|
|
702
|
+
w, h = self.W, self.CANVAS_H
|
|
703
|
+
# 3 lignes fines de couleurs pures qui flashent (simule un shift RGB discret)
|
|
704
|
+
self.canvas_bg.create_rectangle(2, 0, w, 3, fill="#ff0000", stipple="gray25", outline="", tags="rgb")
|
|
705
|
+
self.canvas_bg.create_rectangle(-2, 0, w - 4, 3, fill="#00ff00", stipple="gray25", outline="", tags="rgb")
|
|
706
|
+
self.canvas_bg.create_rectangle(0, 0, w, 3, fill="#0000ff", stipple="gray25", outline="", tags="rgb")
|
|
707
|
+
# Auto-cleanup
|
|
708
|
+
self.root.after(150, lambda: self.canvas_bg.delete("rgb"))
|
|
709
|
+
except Exception:
|
|
710
|
+
pass
|
|
711
|
+
|
|
712
|
+
def _start_generation(self) -> None:
|
|
713
|
+
"""Déclenche la routine asynchrone de génération et de reveal de la paire de clés."""
|
|
714
|
+
if self.gen_btn.cget("state") == "disabled":
|
|
715
|
+
return
|
|
716
|
+
self.gen_btn.config(state="disabled")
|
|
717
|
+
self.copy_btn.config(state="disabled")
|
|
718
|
+
self.priv_var.set("")
|
|
719
|
+
self.addr_var.set("0x" + "•" * 40)
|
|
720
|
+
t = threading.Thread(target=self._scan_then_reveal, daemon=True)
|
|
721
|
+
t.start()
|
|
722
|
+
|
|
723
|
+
def _scan_then_reveal(self) -> None:
|
|
724
|
+
"""Génère la clé, joue le signal sonore basse-fréquence, et révèle les caractères l'un après l'autre."""
|
|
725
|
+
try:
|
|
726
|
+
key = gen_one()
|
|
727
|
+
except Exception:
|
|
728
|
+
self.root.after(0, lambda: self.gen_btn.config(state="normal"))
|
|
729
|
+
return
|
|
730
|
+
|
|
731
|
+
self.current_key = key
|
|
732
|
+
addr = key["address"]
|
|
733
|
+
priv = key["private_key"]
|
|
734
|
+
|
|
735
|
+
# Signal de transition sonore non strident
|
|
736
|
+
self.synth.reveal_beep()
|
|
737
|
+
|
|
738
|
+
# Révélation progressive de l'Adresse
|
|
739
|
+
for i in range(len(addr)):
|
|
740
|
+
shown = addr[: i + 1] + "•" * (len(addr) - i - 1)
|
|
741
|
+
self.root.after(0, lambda s=shown: self.addr_var.set(s))
|
|
742
|
+
time.sleep(0.015)
|
|
743
|
+
self.root.after(0, lambda: self.addr_var.set(addr))
|
|
744
|
+
|
|
745
|
+
# Flash lumineux d'impact rétro sur le canvas
|
|
746
|
+
self.root.after(0, self._flash)
|
|
747
|
+
# Glitch VHS (décalage RGB) qui accompagne le reveal
|
|
748
|
+
self.root.after(0, self._glitch_rgb_shift)
|
|
749
|
+
|
|
750
|
+
# Révélation progressive de la Clé Privée
|
|
751
|
+
for i in range(len(priv)):
|
|
752
|
+
shown = priv[: i + 1] + "•" * (len(priv) - i - 1)
|
|
753
|
+
self.root.after(0, lambda s=shown: self.priv_var.set(s))
|
|
754
|
+
time.sleep(0.02)
|
|
755
|
+
|
|
756
|
+
# Déverrouillage des boutons d'actions
|
|
757
|
+
self.root.after(0, lambda: self.gen_btn.config(state="normal"))
|
|
758
|
+
self.root.after(0, lambda: self.copy_btn.config(state="normal"))
|
|
759
|
+
|
|
760
|
+
def _flash(self) -> None:
|
|
761
|
+
"""Crée un flash lumineux stroboscopique vert transparent d'une fraction de seconde."""
|
|
762
|
+
fl = self.canvas_bg.create_rectangle(
|
|
763
|
+
0, 0, self.W, self.CANVAS_H, fill=self.FG_GREEN, stipple="gray12", outline=""
|
|
764
|
+
)
|
|
765
|
+
self.root.after(120, lambda: self.canvas_bg.delete(fl))
|
|
766
|
+
|
|
767
|
+
def _copy_private(self) -> None:
|
|
768
|
+
"""Copie la paire {address, private_key} en JSON dans le presse-papiers système.
|
|
769
|
+
|
|
770
|
+
Format: {"address":"0x...","private_key":"0x..."} (une seule ligne, prêt à coller).
|
|
771
|
+
"""
|
|
772
|
+
if not self.current_key:
|
|
773
|
+
return
|
|
774
|
+
# JSON une ligne, séparateurs serrés, prêt à coller dans un autre outil
|
|
775
|
+
payload = json.dumps(
|
|
776
|
+
{"address": self.current_key["address"],
|
|
777
|
+
"private_key": self.current_key["private_key"]},
|
|
778
|
+
separators=(",", ":"),
|
|
779
|
+
)
|
|
780
|
+
try:
|
|
781
|
+
self.root.clipboard_clear()
|
|
782
|
+
self.root.clipboard_append(payload)
|
|
783
|
+
self.root.update()
|
|
784
|
+
except Exception:
|
|
785
|
+
pass
|
|
786
|
+
# Feedback visuel temporaire sur le bouton
|
|
787
|
+
orig = "[ COPY PRIVATE ]"
|
|
788
|
+
self.copy_btn.config(text="[ COPIED! ]", fg=self.FG_GREEN)
|
|
789
|
+
self.root.after(1500, lambda: self.copy_btn.config(text=orig, fg="#ffcc00"))
|
|
790
|
+
|
|
791
|
+
def _toggle_music(self) -> None:
|
|
792
|
+
"""Active/Désactive la lecture du module chiptune de fond."""
|
|
793
|
+
playing = self.synth.toggle()
|
|
794
|
+
if playing:
|
|
795
|
+
self.music_btn.config(text="[ MUSIC: ON ]", fg=self.FG_GREEN)
|
|
796
|
+
else:
|
|
797
|
+
self.music_btn.config(text="[ MUSIC: OFF ]", fg=self.FG_RED)
|
|
798
|
+
|
|
799
|
+
def start_marquee(self) -> None:
|
|
800
|
+
"""Démarre le fil de texte défilant dans un thread autonome."""
|
|
801
|
+
def scroll():
|
|
802
|
+
text = self.marquee_text
|
|
803
|
+
while True:
|
|
804
|
+
try:
|
|
805
|
+
text = text[1:] + text[0]
|
|
806
|
+
self.foot_lbl.config(text=text)
|
|
807
|
+
time.sleep(0.1)
|
|
808
|
+
except Exception:
|
|
809
|
+
break
|
|
810
|
+
t = threading.Thread(target=scroll, daemon=True)
|
|
811
|
+
t.start()
|
|
812
|
+
|
|
813
|
+
def _close(self) -> None:
|
|
814
|
+
"""Stoppe les audios et détruit la fenêtre Tkinter pour quitter proprement."""
|
|
815
|
+
self.synth.stop()
|
|
816
|
+
try:
|
|
817
|
+
self.root.destroy()
|
|
818
|
+
except Exception:
|
|
819
|
+
pass
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
# =========================================================================
|
|
823
|
+
# LOBBY DE LANCEMENT ET EXECUTION CLI / GUI
|
|
824
|
+
# =========================================================================
|
|
825
|
+
def run_gui() -> None:
|
|
826
|
+
root = tk.Tk()
|
|
827
|
+
app = KeygenApp(root)
|
|
828
|
+
app.start_marquee()
|
|
829
|
+
root.mainloop()
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def main() -> int:
|
|
833
|
+
p = argparse.ArgumentParser(
|
|
834
|
+
description="OVERMIND BEARER KEYGEN 2000 — Générateur rétro sécurisé d'adresses et clés privées EVM"
|
|
835
|
+
)
|
|
836
|
+
p.add_argument("--cli", action="store_true", help="Forcer le mode console CLI")
|
|
837
|
+
p.add_argument("--count", type=int, default=1, help="Nombre de clés à générer (mode CLI)")
|
|
838
|
+
p.add_argument("--out", type=str, default=None, help="Exporter les résultats en format JSON dans un fichier")
|
|
839
|
+
p.add_argument("--no-0x", action="store_true", help="Générer la clé privée brute sans préfixe 0x")
|
|
840
|
+
args = p.parse_args()
|
|
841
|
+
|
|
842
|
+
# Si Tkinter est indisponible ou si l'utilisateur spécifie explicitement le mode CLI
|
|
843
|
+
if not GUI_AVAILABLE or args.cli or args.out or args.count > 1:
|
|
844
|
+
if args.count < 1 or args.count > 1000:
|
|
845
|
+
sys.stderr.write("Erreur : --count doit être compris entre 1 et 1000.\n")
|
|
846
|
+
return 1
|
|
847
|
+
keys: list[dict[str, str]] = [gen_one(no_0x=args.no_0x) for _ in range(args.count)]
|
|
848
|
+
payload: Any = keys[0] if args.count == 1 else keys
|
|
849
|
+
text = json.dumps(payload, indent=2)
|
|
850
|
+
|
|
851
|
+
if args.out:
|
|
852
|
+
tmp = args.out + ".tmp"
|
|
853
|
+
with open(tmp, "w", encoding="utf-8") as f:
|
|
854
|
+
f.write(text)
|
|
855
|
+
f.write("\n")
|
|
856
|
+
try:
|
|
857
|
+
os.chmod(tmp, 0o600) # Restreindre la lecture/écriture au propriétaire de la machine
|
|
858
|
+
except Exception:
|
|
859
|
+
pass
|
|
860
|
+
os.replace(tmp, args.out)
|
|
861
|
+
sys.stderr.write(f"Succès : {len(keys)} clé(s) écrite(s) dans '{args.out}' (droits 0600).\n")
|
|
862
|
+
else:
|
|
863
|
+
sys.stdout.write(text + "\n")
|
|
864
|
+
return 0
|
|
865
|
+
|
|
866
|
+
run_gui()
|
|
867
|
+
return 0
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
if __name__ == "__main__":
|
|
871
|
+
sys.exit(main())
|