Weltkarten-Daten fest eingebettet (SEGMENTS/LEVEL_NAMES), parse_worldmap raus aus dem Spiel -- Extraktor als tools/worldmap_extract.py, Laufzeit braucht nur noch PCKELL.DAT
This commit is contained in:
@@ -1777,3 +1777,15 @@ Verifikation: eigener Karten-Render vs. DOSBox-Frame, mittlerer
|
|||||||
Pixelfehler 5.0 (identisch mit dem Ganzbild-Grundrauschen der
|
Pixelfehler 5.0 (identisch mit dem Ganzbild-Grundrauschen der
|
||||||
VGA-DAC-Quantisierung) in beiden Blinkphasen => pixelidentisch.
|
VGA-DAC-Quantisierung) in beiden Blinkphasen => pixelidentisch.
|
||||||
--once exit 0.
|
--once exit 0.
|
||||||
|
|
||||||
|
## 26.07.2026 (17) -- Weltkarten-Daten fest eingebettet, START.EXE-Abhaengigkeit weg
|
||||||
|
|
||||||
|
Stefans Wunsch: Daten dauerhaft ins Spiel, parse_worldmap() raus.
|
||||||
|
Umgesetzt: SEGMENTS + LEVEL_NAMES als feste Tabellen in game/worldmap.py
|
||||||
|
(Rohkoordinaten wie im Binary, Transform/Anker unveraendert zur Laufzeit).
|
||||||
|
Der Parser lebt als Dev-Werkzeug tools/worldmap_extract.py weiter (gibt
|
||||||
|
die Tabelle als Python-Literal aus, nur fuer etwaige Regeneration).
|
||||||
|
Verifiziert: Extraktor-Output == eingebettete Tabelle, Karten-Render
|
||||||
|
weiterhin Pixelfehler 5.14 (= VGA-DAC-Grundrauschen) gegen die
|
||||||
|
DOSBox-Referenz in beiden Blinkphasen. --once exit 0. Das Spiel braucht
|
||||||
|
zur Laufzeit jetzt wieder NUR PCKELL.DAT.
|
||||||
|
|||||||
@@ -50,9 +50,7 @@ der Python-Code hier ist eine eigenständige Neuimplementierung.
|
|||||||
- `Pillow` (PCX-Dekodierung), `numpy` (optional, für den Scanline-Effekt im Intro)
|
- `Pillow` (PCX-Dekodierung), `numpy` (optional, für den Scanline-Effekt im Intro)
|
||||||
- `gcc` (einmalig, um den TFMX-Renderer zu bauen — passiert automatisch beim
|
- `gcc` (einmalig, um den TFMX-Renderer zu bauen — passiert automatisch beim
|
||||||
ersten Start)
|
ersten Start)
|
||||||
- Die Original-Datei `PCKELL.DAT` unter `game/original/` (nicht im Repo);
|
- Die Original-Datei `PCKELL.DAT` unter `game/original/` (nicht im Repo)
|
||||||
`START.EXE` daneben ist optional — daraus liest die Weltkarte zur Laufzeit
|
|
||||||
die Original-Pfad- und Levelpunkt-Daten (ohne sie: Fallback nur Level 1)
|
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo apt install python3-pygame python3-pil python3-numpy gcc
|
sudo apt install python3-pygame python3-pil python3-numpy gcc
|
||||||
@@ -105,6 +103,8 @@ tools/ Reine Dev-Werkzeuge (Assets auspacken / als PNG ansehen)
|
|||||||
dat_extract.py PCKELL.DAT → alle 178 Einzeldateien (nach extracted_dat/)
|
dat_extract.py PCKELL.DAT → alle 178 Einzeldateien (nach extracted_dat/)
|
||||||
pcc_to_png.py Eine PCC-Bilddatei → PNG
|
pcc_to_png.py Eine PCC-Bilddatei → PNG
|
||||||
render_assets.py Batch-Render aller BOB/ICO/MAP → preview/render_out/
|
render_assets.py Batch-Render aller BOB/ICO/MAP → preview/render_out/
|
||||||
|
worldmap_extract.py START.EXE → Weltkarten-Pfaddaten (Quelle der Tabelle
|
||||||
|
in game/worldmap.py; nur zur Regeneration nötig)
|
||||||
preview/ Vorab gerenderte Assets zum Ansehen/Anhören (Archiv, KEINE
|
preview/ Vorab gerenderte Assets zum Ansehen/Anhören (Archiv, KEINE
|
||||||
Laufzeit-Abhängigkeit — das Spiel rendert alles selbst on the fly)
|
Laufzeit-Abhängigkeit — das Spiel rendert alles selbst on the fly)
|
||||||
png_out/ Vorab gerenderte PCC-Bilder
|
png_out/ Vorab gerenderte PCC-Bilder
|
||||||
@@ -116,9 +116,10 @@ NOTES.md Vollständiges Reverse-Engineering-Log
|
|||||||
|
|
||||||
`game/formats.py` ist das gemeinsame Fundament: sowohl das Spiel als auch die
|
`game/formats.py` ist das gemeinsame Fundament: sowohl das Spiel als auch die
|
||||||
Dev-Werkzeuge in `tools/` importieren daraus. Zur Laufzeit braucht das Spiel
|
Dev-Werkzeuge in `tools/` importieren daraus. Zur Laufzeit braucht das Spiel
|
||||||
**nur** `game/original/PCKELL.DAT` (plus optional `START.EXE` für die
|
**nur** `game/original/PCKELL.DAT` — alles unter `preview/` sind Verifikations-/Archiv-
|
||||||
Weltkarten-Daten) — alles unter `preview/` sind Verifikations-/Archiv-
|
Artefakte, keine Laufzeit-Abhängigkeit. Die Weltkarten-Pfaddaten (einmalig
|
||||||
Artefakte, keine Laufzeit-Abhängigkeit. Auch die Musik unter `preview/audio/`
|
aus `START.EXE` extrahiert, siehe `tools/worldmap_extract.py`) sind fest in
|
||||||
|
`game/worldmap.py` eingebettet. Auch die Musik unter `preview/audio/`
|
||||||
wird vom Spiel nicht gelesen: `game/tfmx_player/` rendert sie zur Laufzeit
|
wird vom Spiel nicht gelesen: `game/tfmx_player/` rendert sie zur Laufzeit
|
||||||
frisch aus `PCKELL.DAT` (nach `audio_cache/`).
|
frisch aus `PCKELL.DAT` (nach `audio_cache/`).
|
||||||
|
|
||||||
|
|||||||
+62
-81
@@ -1,7 +1,9 @@
|
|||||||
"""Weltkarte mit Levelauswahl -- Pfad, Levelpunkte und Namen aus dem
|
"""Weltkarte mit Levelauswahl -- Marker/Koepfe aus KARTE0-9.PCC, Pfad-,
|
||||||
START.EXE-Binary (NE-Datensegment), Marker/Koepfe aus KARTE0-9.PCC.
|
Levelpunkt- und Namensdaten fest eingebettet (siehe SEGMENTS unten).
|
||||||
|
|
||||||
Binary-Struktur (Fund 25.07.2026, Offsets ~145114..146760 in START.EXE):
|
Die Daten wurden einmalig aus dem START.EXE-Binary extrahiert
|
||||||
|
(NE-Datensegment, Offsets ~145114..146760; Extraktor:
|
||||||
|
tools/worldmap_extract.py). Binary-Struktur dort, Fund 25.07.2026:
|
||||||
Header, dann pro Level ein Segment aus 0xffff-terminierten u16-Records:
|
Header, dann pro Level ein Segment aus 0xffff-terminierten u16-Records:
|
||||||
[x y 0 25] Standpunkt (Wiederholung des vorigen Endpunkts)
|
[x y 0 25] Standpunkt (Wiederholung des vorigen Endpunkts)
|
||||||
[x y 1 25] ... Wegpunkte (die Figur laeuft sie ab; 25 = Ticks)
|
[x y 1 25] ... Wegpunkte (die Figur laeuft sie ab; 25 = Ticks)
|
||||||
@@ -12,12 +14,8 @@ Binary-Struktur (Fund 25.07.2026, Offsets ~145114..146760 in START.EXE):
|
|||||||
|
|
||||||
Karten-Sprites: KARTE0/2 = grosser oranger/blauer Punkt, KARTE1/3 = kleine
|
Karten-Sprites: KARTE0/2 = grosser oranger/blauer Punkt, KARTE1/3 = kleine
|
||||||
Varianten, KARTE4-7 = Mini-Koepfe (Tony, Smacks, Toucan, Coco) als
|
Varianten, KARTE4-7 = Mini-Koepfe (Tony, Smacks, Toucan, Coco) als
|
||||||
Spielfigur-Marker. Blink-Animation des aktiven Punkts laut Binary-Tabelle:
|
Spielfigur-Marker.
|
||||||
[delay 100] 900,902,900 = KARTE0/KARTE2-Wechsel.
|
|
||||||
"""
|
"""
|
||||||
import re
|
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
@@ -46,64 +44,58 @@ def _tf(p):
|
|||||||
return (p[0], MAP_Y_FLIP - p[1])
|
return (p[0], MAP_Y_FLIP - p[1])
|
||||||
|
|
||||||
|
|
||||||
def parse_worldmap(exe_path):
|
# 21 Pfadsegmente (Rohkoordinaten wie im Binary, y noch ungeflippt) --
|
||||||
'''Liest Pfadsegmente + Levelnamen aus START.EXE. Rueckgabe:
|
# extrahiert am 26.07.2026 aus START.EXE mit tools/worldmap_extract.py.
|
||||||
(segments, names) mit segments = Liste von dicts
|
# Segment i gehoert zu LEVEL_NAMES[i+1]: W1L0 hat KEINEN Kartenpunkt
|
||||||
{waypoints: [(x,y)...], stand: (x,y), label: (x,y)}.
|
# (Bonus-/Geheimlevel?), die 21 Punkte sind W1L1..W3L7 (3 Welten x 7).
|
||||||
Wirft bei Strukturbruch Exception (Aufrufer faellt zurueck).'''
|
LEVEL_NAMES = ['W1L0', 'W1L1', 'W1L2', 'W1L3', 'W1L4', 'W1L5', 'W1L6', 'W1L7',
|
||||||
d = open(exe_path, 'rb').read()
|
'W2L1', 'W2L2', 'W2L3', 'W2L4', 'W2L5', 'W2L6', 'W2L7',
|
||||||
anchor = struct.pack('<4H', 58, 40, 0, 25)
|
'W3L1', 'W3L2', 'W3L3', 'W3L4', 'W3L5', 'W3L6', 'W3L7']
|
||||||
start = d.find(anchor)
|
|
||||||
if start < 0:
|
|
||||||
raise ValueError('Kartenpfad-Anker nicht gefunden')
|
|
||||||
names_at = d.find(b'\x04W1L0\x04W1L1')
|
|
||||||
if names_at < 0:
|
|
||||||
raise ValueError('Levelnamen nicht gefunden')
|
|
||||||
# Namen lesen (22 Pascal-Strings)
|
|
||||||
names = []
|
|
||||||
p = names_at
|
|
||||||
while len(names) < 22 and p < len(d) - 1:
|
|
||||||
ln = d[p]
|
|
||||||
if not (1 <= ln <= 8):
|
|
||||||
break
|
|
||||||
names.append(d[p + 1:p + 1 + ln].decode('ascii'))
|
|
||||||
p += 1 + ln
|
|
||||||
|
|
||||||
# Records lesen
|
SEGMENTS = [
|
||||||
def read_list(p):
|
dict(waypoints=[(58, 40), (58, 40)],
|
||||||
vals = []
|
stand=(58, 40), label=(53, 56)),
|
||||||
while p <= names_at - 2:
|
dict(waypoints=[(58, 40), (68, 37), (74, 36), (81, 36), (87, 35), (94, 35), (101, 35), (107, 35), (113, 35)],
|
||||||
v = struct.unpack('<H', d[p:p + 2])[0]
|
stand=(113, 35), label=(108, 51)),
|
||||||
p += 2
|
dict(waypoints=[(113, 35), (119, 41), (122, 47), (125, 53), (128, 58), (131, 65)],
|
||||||
if v == 0xffff:
|
stand=(131, 65), label=(126, 81)),
|
||||||
return vals, p
|
dict(waypoints=[(131, 65), (139, 58), (142, 52), (144, 47), (146, 42), (147, 37)],
|
||||||
vals.append(v)
|
stand=(147, 37), label=(142, 53)),
|
||||||
if len(vals) > 8:
|
dict(waypoints=[(147, 37), (155, 40), (160, 47)],
|
||||||
return None, p
|
stand=(160, 47), label=(155, 63)),
|
||||||
return vals, p
|
dict(waypoints=[(160, 47), (168, 44), (173, 42), (177, 42)],
|
||||||
|
stand=(177, 42), label=(172, 58)),
|
||||||
segments = []
|
dict(waypoints=[(177, 42), (187, 39), (192, 39), (197, 39), (202, 41)],
|
||||||
p = start
|
stand=(202, 41), label=(197, 57)),
|
||||||
cur = {'waypoints': [], 'stand': None, 'label': None}
|
dict(waypoints=[(202, 41), (211, 39), (215, 42), (217, 47), (218, 53), (218, 59), (220, 65), (223, 75)],
|
||||||
while p < names_at - 2:
|
stand=(223, 75), label=(218, 91)),
|
||||||
vals, p = read_list(p)
|
dict(waypoints=[(223, 75), (226, 79), (226, 85), (224, 91), (219, 93), (210, 96)],
|
||||||
if vals is None:
|
stand=(210, 96), label=(205, 112)),
|
||||||
continue
|
dict(waypoints=[(210, 96), (204, 93), (199, 93), (189, 96)],
|
||||||
if len(vals) == 4 and vals[3] == 25:
|
stand=(189, 96), label=(184, 112)),
|
||||||
x, y, flag = vals[0], vals[1], vals[2]
|
dict(waypoints=[(189, 96), (184, 94), (178, 92), (172, 90), (163, 91)],
|
||||||
if x > 320 or y > 200:
|
stand=(163, 91), label=(158, 107)),
|
||||||
continue # Sonder-/Sprungrecords (Weltwechsel) ueberspringen
|
dict(waypoints=[(163, 91), (158, 89), (153, 89), (147, 89), (137, 91)],
|
||||||
cur['waypoints'].append((x, y))
|
stand=(137, 91), label=(132, 107)),
|
||||||
if flag == 0 and len(cur['waypoints']) > 1:
|
dict(waypoints=[(137, 91), (132, 89), (127, 89), (121, 89), (112, 91)],
|
||||||
cur['stand'] = (x, y)
|
stand=(112, 91), label=(107, 107)),
|
||||||
elif len(vals) == 2:
|
dict(waypoints=[(112, 91), (107, 89), (102, 86), (97, 84), (92, 82), (86, 81), (80, 80), (74, 79), (65, 81)],
|
||||||
cur['label'] = (vals[0], vals[1])
|
stand=(65, 81), label=(60, 97)),
|
||||||
if cur['stand'] is None and cur['waypoints']:
|
dict(waypoints=[(65, 81), (65, 86), (63, 92), (61, 98), (56, 103), (50, 113)],
|
||||||
cur['stand'] = cur['waypoints'][-1]
|
stand=(50, 113), label=(45, 129)),
|
||||||
segments.append(cur)
|
dict(waypoints=[(50, 113), (58, 113), (63, 116), (68, 120), (73, 123), (78, 127), (83, 130)],
|
||||||
cur = {'waypoints': [], 'stand': None, 'label': None}
|
stand=(83, 130), label=(78, 146)),
|
||||||
# len==1 ([0]-Flag) und leere Listen: ignorieren
|
dict(waypoints=[(83, 130), (92, 129), (98, 130), (103, 130), (108, 131), (113, 132), (119, 134), (125, 135), (131, 137), (136, 139)],
|
||||||
return segments, names
|
stand=(136, 139), label=(131, 155)),
|
||||||
|
dict(waypoints=[(136, 139), (144, 138), (149, 139), (154, 140), (159, 141), (164, 142), (169, 143), (174, 144), (179, 147)],
|
||||||
|
stand=(179, 147), label=(174, 163)),
|
||||||
|
dict(waypoints=[(179, 147), (188, 145), (194, 145), (200, 142), (206, 138), (212, 135), (218, 135), (224, 136), (229, 139), (223, 141), (237, 143)],
|
||||||
|
stand=(237, 143), label=(232, 159)),
|
||||||
|
dict(waypoints=[(237, 143), (245, 138), (250, 134)],
|
||||||
|
stand=(250, 134), label=(245, 150)),
|
||||||
|
dict(waypoints=[(250, 134), (256, 137), (255, 144), (256, 151), (259, 158)],
|
||||||
|
stand=(259, 158), label=(254, 174)),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class WorldMap:
|
class WorldMap:
|
||||||
@@ -113,22 +105,11 @@ class WorldMap:
|
|||||||
self.assets = assets
|
self.assets = assets
|
||||||
self.karte = menu_assets.karte
|
self.karte = menu_assets.karte
|
||||||
self.font = menu_assets.font_small
|
self.font = menu_assets.font_small
|
||||||
exe = None
|
self.segments = [dict(waypoints=list(s['waypoints']),
|
||||||
import os
|
stand=s['stand'], label=s['label'])
|
||||||
base = os.path.dirname(os.path.abspath(assets.container.path)) \
|
for s in SEGMENTS]
|
||||||
if hasattr(assets.container, 'path') else None
|
# segments[i] gehoert zu LEVEL_NAMES[i+1] (W1L0 ohne Kartenpunkt)
|
||||||
try:
|
self.level_names = LEVEL_NAMES[1:]
|
||||||
from .intro import PROJECT_ROOT
|
|
||||||
exe = os.path.join(PROJECT_ROOT, 'game', 'original', 'START.EXE')
|
|
||||||
self.segments, self.names = parse_worldmap(exe)
|
|
||||||
except Exception as exc:
|
|
||||||
print(f'[worldmap] Binary-Pfaddaten nicht lesbar ({exc}) -- '
|
|
||||||
f'Fallback nur Level 1', file=sys.stderr)
|
|
||||||
self.segments = [dict(waypoints=[(53, 56)], stand=(53, 56), label=(53, 56))]
|
|
||||||
self.names = ['W1L0', 'W1L1']
|
|
||||||
# 21 Segmente fuer 21 anwaehlbare Level (W1L1..W3L7, 3 Welten x 7);
|
|
||||||
# W1L0 (Name Nr. 0) hat KEINEN Kartenpunkt -> segments[i] = names[i+1].
|
|
||||||
self.level_names = self.names[1:]
|
|
||||||
# Positionen ins Screen-System transformieren (y-Flip)
|
# Positionen ins Screen-System transformieren (y-Flip)
|
||||||
for seg in self.segments:
|
for seg in self.segments:
|
||||||
seg['waypoints'] = [_tf(p) for p in seg['waypoints']]
|
seg['waypoints'] = [_tf(p) for p in seg['waypoints']]
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Extrahiert die Weltkarten-Daten (Pfadsegmente, Levelpunkte, Levelnamen)
|
||||||
|
aus dem Original-START.EXE und gibt sie als Python-Literal aus -- damit
|
||||||
|
wurde die eingebettete SEGMENTS/LEVEL_NAMES-Tabelle in game/worldmap.py
|
||||||
|
erzeugt (26.07.2026). Nur noetig, falls die Tabelle je neu generiert
|
||||||
|
werden muss; das Spiel selbst liest START.EXE nicht mehr.
|
||||||
|
|
||||||
|
Aufruf: python3 tools/worldmap_extract.py [pfad/zu/START.EXE]
|
||||||
|
|
||||||
|
Binary-Struktur (NE-Datensegment, Offsets ~145114..146760):
|
||||||
|
Header, dann pro Level ein Segment aus 0xffff-terminierten u16-Records:
|
||||||
|
[x y 0 25] Standpunkt (Wiederholung des vorigen Endpunkts)
|
||||||
|
[x y 1 25] ... Wegpunkte (die Kartenfigur laeuft sie ab)
|
||||||
|
[x y 0 25] Endpunkt = Standpunkt an diesem Level
|
||||||
|
[label_x label_y] Position des Levelpunkt-Markers
|
||||||
|
[0] (Flag/Reserve)
|
||||||
|
danach die 22 Levelnamen als Pascal-Strings (\x04W1L0...).
|
||||||
|
y-Achse ist geflippt (Screen-Basis = (x, 218 - y), siehe game/worldmap.py).
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def parse_worldmap(exe_path):
|
||||||
|
'''Rueckgabe: (segments, names); segments = Liste von dicts
|
||||||
|
{waypoints: [(x,y)...], stand: (x,y), label: (x,y)}.'''
|
||||||
|
d = open(exe_path, 'rb').read()
|
||||||
|
anchor = struct.pack('<4H', 58, 40, 0, 25)
|
||||||
|
start = d.find(anchor)
|
||||||
|
if start < 0:
|
||||||
|
raise ValueError('Kartenpfad-Anker nicht gefunden')
|
||||||
|
names_at = d.find(b'\x04W1L0\x04W1L1')
|
||||||
|
if names_at < 0:
|
||||||
|
raise ValueError('Levelnamen nicht gefunden')
|
||||||
|
# Namen lesen (22 Pascal-Strings)
|
||||||
|
names = []
|
||||||
|
p = names_at
|
||||||
|
while len(names) < 22 and p < len(d) - 1:
|
||||||
|
ln = d[p]
|
||||||
|
if not (1 <= ln <= 8):
|
||||||
|
break
|
||||||
|
names.append(d[p + 1:p + 1 + ln].decode('ascii'))
|
||||||
|
p += 1 + ln
|
||||||
|
|
||||||
|
def read_list(p):
|
||||||
|
vals = []
|
||||||
|
while p <= names_at - 2:
|
||||||
|
v = struct.unpack('<H', d[p:p + 2])[0]
|
||||||
|
p += 2
|
||||||
|
if v == 0xffff:
|
||||||
|
return vals, p
|
||||||
|
vals.append(v)
|
||||||
|
if len(vals) > 8:
|
||||||
|
return None, p
|
||||||
|
return vals, p
|
||||||
|
|
||||||
|
segments = []
|
||||||
|
p = start
|
||||||
|
cur = {'waypoints': [], 'stand': None, 'label': None}
|
||||||
|
while p < names_at - 2:
|
||||||
|
vals, p = read_list(p)
|
||||||
|
if vals is None:
|
||||||
|
continue
|
||||||
|
if len(vals) == 4 and vals[3] == 25:
|
||||||
|
x, y, flag = vals[0], vals[1], vals[2]
|
||||||
|
if x > 320 or y > 200:
|
||||||
|
continue # Sonder-/Sprungrecords (Weltwechsel) ueberspringen
|
||||||
|
cur['waypoints'].append((x, y))
|
||||||
|
if flag == 0 and len(cur['waypoints']) > 1:
|
||||||
|
cur['stand'] = (x, y)
|
||||||
|
elif len(vals) == 2:
|
||||||
|
cur['label'] = (vals[0], vals[1])
|
||||||
|
if cur['stand'] is None and cur['waypoints']:
|
||||||
|
cur['stand'] = cur['waypoints'][-1]
|
||||||
|
segments.append(cur)
|
||||||
|
cur = {'waypoints': [], 'stand': None, 'label': None}
|
||||||
|
# len==1 ([0]-Flag) und leere Listen: ignorieren
|
||||||
|
return segments, names
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
default = os.path.join(os.path.dirname(__file__), '..',
|
||||||
|
'game', 'original', 'START.EXE')
|
||||||
|
exe = sys.argv[1] if len(sys.argv) > 1 else default
|
||||||
|
segments, names = parse_worldmap(exe)
|
||||||
|
print('LEVEL_NAMES = %r' % (names,))
|
||||||
|
print()
|
||||||
|
print('SEGMENTS = [')
|
||||||
|
for s in segments:
|
||||||
|
print(' dict(waypoints=%r,' % (s['waypoints'],))
|
||||||
|
print(' stand=%r, label=%r),' % (s['stand'], s['label']))
|
||||||
|
print(']')
|
||||||
|
print('# %d Segmente, %d Namen' % (len(segments), len(names)),
|
||||||
|
file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user