# -*- coding: utf-8 -*-
from phBot import *
import QtBind
import binascii
import json
import os
import struct
import time
import webbrowser
pName = 'FScriptHelper'
pVersion = '1.1.1'
DISCORD_URL = 'https://discord.gg/eB9sGSMYBg'
DEFAULT_LANGUAGE = 'en'
language = DEFAULT_LANGUAGE
TEXT = {
'en': {
'subtitle': 'v%s · NPC recorder',
'switch_language': '🇹🇷 Türkçe',
'nearby_npcs': '◆ NEARBY NPCS',
'refresh_npcs': '↻ Refresh NPC List',
'nearby_help': 'Nearby NPC list for reference',
'record': '◆ RECORD',
'record_name': 'Record name',
'start_recording': '● Start Recording',
'finish_recording': '■ Save & Finish',
'raw_packets': 'Record all C→S packets (advanced)',
'record_help': 'Start recording and click the target NPC.
Then perform the actions in game.',
'saved_commands': '▣ SAVED NPC COMMANDS',
'record_count': '%d records',
'packet_count': '%d packets',
'play_selected': '▶ Play Selected',
'show_packets': '≡ Show Packets',
'delete_record': '✕ Delete Record',
'cancel_action': '■ Cancel Action',
'stop_bot': 'Pause bot during playback',
'packet_preview': 'PACKET PREVIEW',
'ready': 'READY',
'ready_detail': 'NPC list can be refreshed',
'npc_list': 'NPC LIST',
'nearby_found': '%d nearby NPCs found',
'error': 'ERROR',
'enter_name': 'Enter a record name',
'waiting_npc': 'WAITING FOR NPC',
'click_npc': '%s · click the NPC in game',
'warning': 'WARNING',
'npc_not_selected': 'No NPC has been selected in game yet',
'no_action': 'No action was captured after NPC selection',
'saved': '%s saved',
'cancelled': 'CANCELLED',
'action_stopped': 'Active action stopped',
'select_delete': 'Select a record to delete',
'deleted': '%s deleted',
'record_not_found': 'Record not found',
'record_invalid': 'Record not found or invalid',
'npc_not_nearby': 'Recorded NPC is not nearby',
'playing': 'PLAYING',
'playing_detail': '%s · %d packets',
'completed': 'COMPLETED',
'play_completed': 'NPC command played successfully',
'select_play': 'Select a record to play',
'recording': 'RECORDING',
'recording_npc': '%s · %s',
'recording_packets': '%s · %d packets',
'discord_opening': 'Opening Discord invite...',
'discord_error': 'Could not open Discord invite'
},
'tr': {
'subtitle': 'v%s · NPC kaydedici',
'switch_language': '🇬🇧 English',
'nearby_npcs': '◆ YAKINDAKİ NPC’LER',
'refresh_npcs': '↻ NPC Listesini Yenile',
'nearby_help': 'Bilgi amaçlı yakındaki NPC listesi',
'record': '◆ KAYIT',
'record_name': 'Kayıt adı',
'start_recording': '● Kaydı Başlat',
'finish_recording': '■ Kaydet & Bitir',
'raw_packets': 'Tüm C→S paketlerini kaydet (gelişmiş)',
'record_help': 'Kaydı başlatıp hedef NPC’ye tıklayın.
Ardından oyun içindeki işlemleri yapın.',
'saved_commands': '▣ KAYITLI NPC KOMUTLARI',
'record_count': '%d kayıt',
'packet_count': '%d paket',
'play_selected': '▶ Seçileni Oynat',
'show_packets': '≡ Paketleri Göster',
'delete_record': '✕ Kaydı Sil',
'cancel_action': '■ İşlemi İptal Et',
'stop_bot': 'Oynatma sırasında botu duraklat',
'packet_preview': 'PAKET ÖNİZLEME',
'ready': 'HAZIR',
'ready_detail': 'NPC listesi yenilenebilir',
'npc_list': 'NPC LİSTESİ',
'nearby_found': '%d yakındaki NPC bulundu',
'error': 'HATA',
'enter_name': 'Kayıt adı girin',
'waiting_npc': 'NPC BEKLENİYOR',
'click_npc': '%s · oyun içinde NPC’ye tıklayın',
'warning': 'UYARI',
'npc_not_selected': 'Henüz oyun içinde bir NPC seçilmedi',
'no_action': 'NPC seçimi dışında işlem yakalanmadı',
'saved': '%s kaydedildi',
'cancelled': 'İPTAL',
'action_stopped': 'Aktif işlem durduruldu',
'select_delete': 'Silmek için bir kayıt seçin',
'deleted': '%s silindi',
'record_not_found': 'Kayıt bulunamadı',
'record_invalid': 'Kayıt bulunamadı veya geçersiz',
'npc_not_nearby': 'Kayıtlı NPC yakında değil',
'playing': 'OYNATILIYOR',
'playing_detail': '%s · %d paket',
'completed': 'TAMAMLANDI',
'play_completed': 'NPC komutu başarıyla oynatıldı',
'select_play': 'Oynatmak için kayıt seçin',
'recording': 'KAYIT',
'recording_npc': '%s · %s',
'recording_packets': '%s · %d paket',
'discord_opening': 'Opening Discord invite...',
'discord_error': 'Could not open Discord invite'
}
}
COLOR_PRIMARY = '#5B57E0'
COLOR_INFO = '#3498DB'
COLOR_SUCCESS = '#1F9D63'
COLOR_WARNING = '#C98A1A'
COLOR_DANGER = '#E74C3C'
COLOR_MUTED = '#7F8C8D'
COLOR_TEXT = '#2B3038'
STATE_IDLE = 'IDLE'
STATE_RECORDING = 'RECORDING'
STATE_PLAYING = 'PLAYING'
# NPC etkileşimlerinde yaygın kullanılan C->S paketleri. "Ham paketler" seçeneği
# kapalıyken hareket, chat ve savaş paketlerinin yanlışlıkla kaydı engellenir.
NPC_OPCODES = set([
0x7034, # inventory operation (buy/sell/move)
0x7045, 0x7046, # select / deselect entity
0x704B, 0x704C, 0x704D, 0x704E, 0x704F,
0x7050, 0x7051, 0x7052, 0x7053, 0x7054, 0x7055,
0x7056, 0x7057, 0x7058, 0x7059, 0x705A,
0x7068, 0x7069, 0x706A, # storage-related variants
0x70B1, 0x70B2, 0x70B3, 0x70B4,
0x70D3, 0x70D4, 0x70D5, 0x70D6,
0x7112, 0x7113, 0x7114, 0x7115
])
MAX_PACKETS = 100
MAX_PACKET_BYTES = 4096
MAX_DELAY_MS = 10000
DEFAULT_DELAY_MS = 1200
SCRIPT_FINISH_BUFFER_MS = 2000
state = STATE_IDLE
record_name = ''
record_npc = None
recorded_packets = []
record_last_time = 0.0
commands = {}
nearby_rows = []
play_packets = []
play_index = 0
play_next_at = 0.0
play_stop_bot = True
play_bot_was_running = False
play_live_uid = 0
play_recorded_uid = 0
play_command_name = ''
play_started_from_script = False
resume_skip_name = ''
status_key = 'ready'
status_detail_key = 'ready_detail'
status_args = ()
status_color = COLOR_INFO
def tr(key):
return TEXT.get(language, TEXT['en']).get(key, TEXT['en'].get(key, key))
def fmt(key, *args):
value = tr(key)
return value % args if args else value
def fixed_width_text(content, width):
return ('
') % (width, content)
def _log(message):
log('[%s] %s' % (pName, message))
def _data_dir():
return os.path.join(get_config_dir(), pName)
def _data_file():
return os.path.join(_data_dir(), 'npc_commands.json')
def _ensure_dir():
folder = _data_dir()
if not os.path.exists(folder):
os.makedirs(folder)
def _safe_int(value, default_value, minimum, maximum):
try:
number = int(str(value).strip())
return max(minimum, min(maximum, number))
except Exception:
return default_value
def _to_hex(data):
return binascii.hexlify(bytes(data)).decode('ascii').upper()
def _from_hex(value):
return binascii.unhexlify(str(value).replace(' ', '').encode('ascii'))
def _set_status(title_key, detail_key='', args=(), color=COLOR_INFO):
global status_key, status_detail_key, status_args, status_color
status_key = title_key
status_detail_key = detail_key
status_args = args
status_color = color
title = tr(title_key)
detail = fmt(detail_key, *args) if detail_key else ''
QtBind.setText(
gui, lblStatus,
'' % (color, title, COLOR_TEXT, detail)
)
def _selected_command_name():
row = QtBind.text(gui, lstCommands)
if not row:
return ''
return row.split(' · ', 1)[0].strip()
def _selected_npc():
index = QtBind.currentIndex(gui, lstNpcs)
if index is None or index < 0 or index >= len(nearby_rows):
return None
return nearby_rows[index]
def _npc_identity(uid, npc):
return {
'uid': int(uid),
'name': str(npc.get('name', '')),
'servername': str(npc.get('servername', '')),
'model': int(npc.get('model', 0) or 0)
}
def _find_live_npc(identity):
npcs = get_npcs() or {}
best = None
for uid, npc in npcs.items():
if identity.get('servername') and npc.get('servername') == identity.get('servername'):
return int(uid), npc
if identity.get('model') and int(npc.get('model', 0) or 0) == identity.get('model'):
best = (int(uid), npc)
elif not best and identity.get('name') and npc.get('name') == identity.get('name'):
best = (int(uid), npc)
return best
def _validate_packet(packet):
if not isinstance(packet, dict):
return False
try:
opcode = int(packet.get('opcode'))
delay = int(packet.get('delay_ms', DEFAULT_DELAY_MS))
raw = _from_hex(packet.get('data', ''))
return 0 <= opcode <= 0xFFFF and 0 <= delay <= MAX_DELAY_MS and len(raw) <= MAX_PACKET_BYTES
except Exception:
return False
def _validate_command(name, command):
if not name or not isinstance(command, dict):
return False
packets = command.get('packets')
npc = command.get('npc')
if not isinstance(packets, list) or not packets or len(packets) > MAX_PACKETS:
return False
if not isinstance(npc, dict):
return False
return all(_validate_packet(packet) for packet in packets)
def _resolve_command_name(name):
"""Return the stored command name using an exact, case-insensitive match."""
if name in commands:
return name
requested = str(name).lower()
matches = [stored_name for stored_name in commands if stored_name.lower() == requested]
if len(matches) == 1:
return matches[0]
if len(matches) > 1:
_log('Aynı isimde birden fazla kayıt bulundu: %s' % name)
return None
def load_commands():
global commands, language
commands = {}
try:
_ensure_dir()
if os.path.exists(_data_file()):
with open(_data_file(), 'r', encoding='utf-8') as handle:
raw = json.load(handle)
source = raw.get('commands', {}) if isinstance(raw, dict) else {}
saved_language = raw.get('language', DEFAULT_LANGUAGE) if isinstance(raw, dict) else DEFAULT_LANGUAGE
language = saved_language if saved_language in ('en', 'tr') else DEFAULT_LANGUAGE
for command_name, command in source.items():
if _validate_command(command_name, command):
commands[command_name] = command
else:
_log('Geçersiz kayıt atlandı: %s' % command_name)
except Exception as ex:
_log('Kayıtlar okunamadı: %s' % ex)
refresh_command_list()
def save_commands():
_ensure_dir()
payload = {'schema_version': 1, 'language': language, 'commands': commands}
target = _data_file()
temporary = target + '.tmp'
try:
with open(temporary, 'w', encoding='utf-8') as handle:
json.dump(payload, handle, ensure_ascii=False, indent=2, sort_keys=True)
os.replace(temporary, target)
return True
except Exception as ex:
_log('Kayıt dosyası yazılamadı: %s' % ex)
try:
if os.path.exists(temporary):
os.remove(temporary)
except Exception:
pass
return False
def refresh_command_list():
QtBind.clear(gui, lstCommands)
for command_name in sorted(commands.keys(), key=lambda value: value.lower()):
command = commands[command_name]
npc_name = command.get('npc', {}).get('name', '?')
QtBind.append(gui, lstCommands, '%s · %s · %s' %
(command_name, fmt('packet_count', len(command.get('packets', []))), npc_name))
QtBind.setText(gui, lblCount, fixed_width_text(
'%s' %
(COLOR_MUTED, fmt('record_count', len(commands))), 120))
def refresh_npcs():
global nearby_rows
nearby_rows = []
QtBind.clear(gui, lstNpcs)
npcs = get_npcs() or {}
ordered = sorted(npcs.items(), key=lambda pair: str(pair[1].get('name', '')).lower())
for uid, npc in ordered:
item = _npc_identity(uid, npc)
nearby_rows.append(item)
QtBind.append(gui, lstNpcs, '%s · UID %s' %
(item['name'], item['uid']))
_set_status('npc_list', 'nearby_found', (len(nearby_rows),), COLOR_INFO)
def start_recording():
global state, record_name, record_npc, recorded_packets, record_last_time
if state != STATE_IDLE:
_log('Önce aktif kayıt/oynatma işlemini durdurun.')
return
name = QtBind.text(gui, tbxName).strip()
if not name:
_set_status('error', 'enter_name', color=COLOR_DANGER)
return
record_name = name
record_npc = None
recorded_packets = []
record_last_time = time.time()
state = STATE_RECORDING
_set_status('waiting_npc', 'click_npc', (name,), COLOR_WARNING)
_log('Kayıt hazır: %s · oyun içinde hedef NPC’ye tıklayın.' % name)
def finish_recording():
global state, record_name, record_npc, recorded_packets
if state != STATE_RECORDING:
_log('Aktif bir kayıt yok.')
return
name = record_name
if record_npc is None:
_set_status('warning', 'npc_not_selected', color=COLOR_WARNING)
return
if len(recorded_packets) <= 1:
_set_status('warning', 'no_action', color=COLOR_WARNING)
return
commands[name] = {
'npc': dict(record_npc),
'packets': list(recorded_packets),
'created_at': time.strftime('%Y-%m-%d %H:%M:%S')
}
if save_commands():
_log('Kayıt kaydedildi: %s (%d paket)' % (name, len(recorded_packets)))
_set_status('ready', 'saved', (name,), COLOR_SUCCESS)
state = STATE_IDLE
record_name = ''
record_npc = None
recorded_packets = []
refresh_command_list()
def cancel_action():
global state, record_name, record_npc, recorded_packets
global play_packets, play_index, play_next_at
was_playing = state == STATE_PLAYING
state = STATE_IDLE
record_name = ''
record_npc = None
recorded_packets = []
play_packets = []
play_index = 0
play_next_at = 0.0
if was_playing and play_stop_bot and play_bot_was_running:
start_bot()
_set_status('cancelled', 'action_stopped', color=COLOR_WARNING)
def delete_command():
name = _selected_command_name()
if not name or name not in commands:
_set_status('error', 'select_delete', color=COLOR_DANGER)
return
del commands[name]
if save_commands():
_log('Kayıt silindi: %s' % name)
_set_status('ready', 'deleted', (name,), COLOR_SUCCESS)
refresh_command_list()
def _bot_is_running():
try:
return str(get_status()).lower() in ('botting', 'training')
except Exception:
return False
def play_command(name, stop_during=True, from_script=False):
global state, play_packets, play_index, play_next_at, play_stop_bot
global play_bot_was_running, play_live_uid, play_recorded_uid
global play_command_name, play_started_from_script
if state != STATE_IDLE:
_log('Başka bir işlem devam ediyor.')
return False
requested_name = name
name = _resolve_command_name(requested_name)
if name is None:
_set_status('error', 'record_not_found', color=COLOR_DANGER)
_log('Kayıt bulunamadı: %s' % requested_name)
return False
command = commands.get(name)
if not _validate_command(name, command):
_set_status('error', 'record_invalid', color=COLOR_DANGER)
_log('Kayıt geçersiz, çalıştırılamadı: %s' % name)
return False
found = _find_live_npc(command['npc'])
if not found:
_set_status('error', 'npc_not_nearby', color=COLOR_DANGER)
_log('NPC bulunamadı: %s' % command['npc'].get('name', '?'))
return False
play_live_uid = found[0]
play_recorded_uid = int(command['npc'].get('uid', 0) or 0)
play_command_name = name
play_started_from_script = bool(from_script)
play_packets = list(command['packets'])
play_index = 0
play_next_at = time.time()
play_stop_bot = bool(stop_during)
play_bot_was_running = _bot_is_running()
if play_stop_bot and play_bot_was_running:
stop_bot()
state = STATE_PLAYING
_set_status('playing', 'playing_detail', (name, len(play_packets)), COLOR_WARNING)
_log('Komut başladı: %s [%s]' % (name, found[1].get('name', '?')))
return True
def _replace_npc_uid(data):
if not play_recorded_uid or play_recorded_uid == play_live_uid:
return data
old_uid = struct.pack('= MAX_PACKETS:
_log('Paket sınırına ulaşıldı; kayıt otomatik tamamlanıyor.')
finish_recording()
return True
if not QtBind.isChecked(gui, cbxRawPackets) and opcode not in NPC_OPCODES:
return True
if len(raw) > MAX_PACKET_BYTES:
_log('Çok büyük paket atlandı: 0x%04X' % opcode)
return True
now = time.time()
elapsed = int((now - record_last_time) * 1000)
delay = _safe_int(elapsed, DEFAULT_DELAY_MS, 0, MAX_DELAY_MS)
recorded_packets.append({
'opcode': int(opcode),
'data': _to_hex(raw),
'delay_ms': delay
})
record_last_time = now
_set_status('recording', 'recording_packets', (record_name, len(recorded_packets)), COLOR_WARNING)
except Exception as ex:
_log('Paket kaydedilemedi: %s' % ex)
return True
def event_loop():
global play_index, play_next_at
if state != STATE_PLAYING or time.time() < play_next_at:
return
if play_index >= len(play_packets):
_finish_playback()
return
try:
packet = play_packets[play_index]
opcode = int(packet['opcode'])
data = _from_hex(packet['data'])
data = _replace_npc_uid(data)
inject_joymax(opcode, data, False)
_log('%d/%d gönderildi: 0x%04X' % (play_index + 1, len(play_packets), opcode))
play_index += 1
if play_index >= len(play_packets):
play_next_at = time.time() + 0.25
else:
delay = _safe_int(play_packets[play_index].get('delay_ms'),
DEFAULT_DELAY_MS, 0, MAX_DELAY_MS)
play_next_at = time.time() + (delay / 1000.0)
except Exception as ex:
_log('Oynatma durduruldu: %s' % ex)
cancel_action()
# Script komutları
# FSH_NPC,kayit_adi[,true|false] -> kaydı oynatır; true botu geçici durdurur.
def FSH_NPC(args):
global resume_skip_name
if len(args) < 2 or len(args) > 3:
_log('Kullanım: FSH_NPC,kayit_adi[,true|false]')
return 0
command_name = str(args[1]).strip()
if resume_skip_name and command_name.lower() == resume_skip_name.lower():
resume_skip_name = ''
_log('Script devam çağrısı onaylandı: %s' % command_name)
return 0
stop_during = True
if len(args) == 3:
stop_during = str(args[2]).strip().lower() not in ('false', '0', 'no', 'off')
if not play_command(command_name, stop_during, True):
return 0
# Bot kapalıysa veya kullanıcı stop_during=false seçtiyse stop_bot çağrılmaz.
# Yürüyüş scripti, asenkron paket oynatımı tamamlanana kadar bekletilir ve
# böylece phBot scripti yeniden başlatmadan doğrudan sonraki satıra geçer.
if not play_bot_was_running or not stop_during:
total_delay = SCRIPT_FINISH_BUFFER_MS
for packet in play_packets[1:]:
total_delay += _safe_int(packet.get('delay_ms'), DEFAULT_DELAY_MS, 0, MAX_DELAY_MS)
return total_delay
return 0
def FSH_SELECT(args):
if len(args) != 2:
_log('Kullanım: FSH_SELECT,npc_adi_veya_servername')
return 0
query = str(args[1]).strip().lower()
for uid, npc in (get_npcs() or {}).items():
if query in (str(npc.get('name', '')).lower(), str(npc.get('servername', '')).lower()):
inject_joymax(0x7045, struct.pack('%s' %
(COLOR_MUTED, fmt('subtitle', pVersion)))
QtBind.setText(gui, btnLanguage, tr('switch_language'))
QtBind.setText(gui, lblNearby, '%s' %
(COLOR_PRIMARY, tr('nearby_npcs')))
QtBind.setText(gui, btnRefresh, tr('refresh_npcs'))
QtBind.setText(gui, lblNearbyHelp, '%s' %
(COLOR_MUTED, tr('nearby_help')))
QtBind.setText(gui, lblRecord, '%s' %
(COLOR_PRIMARY, tr('record')))
QtBind.setText(gui, lblRecordName, '%s' %
(COLOR_MUTED, tr('record_name')))
QtBind.setText(gui, btnStart, tr('start_recording'))
QtBind.setText(gui, btnFinish, tr('finish_recording'))
QtBind.setText(gui, cbxRawPackets, tr('raw_packets'))
QtBind.setText(gui, lblRecordHelp,
'' %
(COLOR_MUTED, tr('record_help')))
QtBind.setText(gui, lblSaved, '%s' %
(COLOR_PRIMARY, tr('saved_commands')))
QtBind.setText(gui, btnPlay, tr('play_selected'))
QtBind.setText(gui, btnPackets, tr('show_packets'))
QtBind.setText(gui, btnDelete, tr('delete_record'))
QtBind.setText(gui, btnCancel, tr('cancel_action'))
QtBind.setText(gui, cbxStopBot, tr('stop_bot'))
QtBind.setText(gui, lblPacketPreview, '%s' %
(COLOR_PRIMARY, tr('packet_preview')))
refresh_command_list()
_set_status(status_key, status_detail_key, status_args, status_color)
def language_clicked():
global language
language = 'tr' if language == 'en' else 'en'
save_commands()
apply_gui_language()
def discord_clicked():
try:
webbrowser.open(DISCORD_URL)
_set_status('ready', 'discord_opening', color=COLOR_SUCCESS)
except Exception as error:
_log('Discord link error: %s' % error)
_set_status('error', 'discord_error', color=COLOR_DANGER)
gui = QtBind.init(__name__, pName)
QtBind.createLabel(gui,
'⚙ FSCRIPT HELPER' % COLOR_PRIMARY, 12, 6)
lblSubtitle = QtBind.createLabel(gui, '', 220, 12)
btnDiscord = QtBind.createButton(gui, 'discord_clicked', u'💬 Discord', 452, 6)
QtBind.createLabel(gui,
u'⚜ Made By FascinaTe' % COLOR_PRIMARY, 555, 11)
btnLanguage = QtBind.createButton(gui, 'language_clicked', '', 355, 6)
QtBind.createLineEdit(gui, '', 12, 31, 716, 1)
lblNearby = QtBind.createLabel(gui, '', 12, 40)
btnRefresh = QtBind.createButton(gui, 'refresh_npcs', '', 12, 60)
lblNearbyHelp = QtBind.createLabel(gui, '', 175, 65)
lstNpcs = QtBind.createList(gui, 12, 88, 420, 91)
lblRecord = QtBind.createLabel(gui, '', 452, 40)
lblRecordName = QtBind.createLabel(gui, '', 452, 65)
tbxName = QtBind.createLineEdit(gui, '', 535, 60, 188, 22)
btnStart = QtBind.createButton(gui, 'start_recording', '', 452, 88)
btnFinish = QtBind.createButton(gui, 'finish_recording', '', 590, 88)
cbxRawPackets = QtBind.createCheckBox(gui, 'noop_checked', '', 452, 119)
QtBind.setChecked(gui, cbxRawPackets, False)
lblRecordHelp = QtBind.createLabel(gui, '', 452, 143)
QtBind.createLineEdit(gui, '', 12, 188, 716, 1)
lblSaved = QtBind.createLabel(gui, '', 12, 197)
lblCount = QtBind.createLabel(gui, '', 250, 199)
lstCommands = QtBind.createList(gui, 12, 220, 420, 100)
btnPlay = QtBind.createButton(gui, 'execute_selected', '', 452, 220)
btnPackets = QtBind.createButton(gui, 'show_packets', '', 590, 220)
btnDelete = QtBind.createButton(gui, 'delete_command', '', 452, 249)
btnCancel = QtBind.createButton(gui, 'cancel_action', '', 590, 249)
cbxStopBot = QtBind.createCheckBox(gui, 'noop_checked', '', 452, 281)
QtBind.setChecked(gui, cbxStopBot, True)
QtBind.createLineEdit(gui, '', 12, 329, 716, 1)
lblPacketPreview = QtBind.createLabel(gui, '', 12, 338)
lstPackets = QtBind.createList(gui, 12, 359, 716, 88)
lblStatus = QtBind.createLabel(gui,
'| ● READY'
' · NPC list can be refreshed |
' %
(COLOR_INFO, COLOR_TEXT), 12, 458)
try:
_ensure_dir()
load_commands()
apply_gui_language()
refresh_npcs()
except Exception as ex:
_log('Başlatma hatası: %s' % ex)
log('[%s] Loaded - ⚜ Made By FascinaTe' % pName)