(10-28-2026, 08:22 AM)Ermolli91 escribió: Gran avance, se esta dando bastante rápido. Bien ahí.
Si parce, le estoy dando duro en las noches que tengo tiempo libre.
Ya practicamente esta el editor, se ve muy rudimentario, muy retro jaja
pero bueno, la idea era aprender y eso lo estoy haciendo dia a dia con este juego, por ejemplo, me he llevado una grata sorpresa, este juego solo cuenta con 1 molde de player 
Los archivos estan mejor distribuidos, y no se que tiene ese Motor Grafico, pero es mas NITIDO, Mas Claro, como con mas pixeles jaj
Pienso que una vez terminado bien el editor, en lo proximo que debo hacer es importar los datos de SoFIFA para que quede mas facil el desarrollo, se que a dia de hoy, hay tools que facilitan mucho eso, pero primero es terminar el editor, despues le agregamos o quitamos detalles de la tool
Dejo el codigo por si lo desean estudiar:
Toda este desarrollo por: Pino, Pato_Lukas18 & Gemini, aprendiendo tambien de eso de la IA, aunque ya voy de salida, algo se aprende!
Codigo Python:
Código PHP:
import struct
import tkinter as tk
from tkinter import colorchooser, messagebox, ttk
# ==========================================
# RUTAS Y OFFSETS (WE6FE SLPM_622.68 / parameter.ovl)
# ==========================================
SLPM_PATH = "SLPM_622.68"
OVL_PATH = "parameter.ovl"
# Offsets de Jugadores
SLPM_BASE_OFFSET = 0x1F24B0
PLAYER_SIZE = 44
NAMES_BASE_OFFSET = 0x668
NAME_SIZE = 32
CLUB_NAMES_BASE_OFFSET = 0x17A90
NUMBERS_BASE_OFFSET = 0x338D0
NUMBERS_RECORD_SIZE = 23
CLUB_NUMBERS_OFFSET = 0x33F80
CLUB_RECORD_SIZE = 23
SHIRT_NAMES_OFFSET = 0x116C0
SHIRT_NAME_SIZE = 16
NUM_PLAYERS = 500
MAX_DATABASE_PLAYERS = 3000
# Offsets de Equipos y Plantillas (Separación Nacionales vs Clubes)
TEAM_NAMES_OFFSET = 0x23F8C8
TEAM_NAME_LIMIT = 16
NUM_TEAMS = 64
NUM_NAT_TEAMS = 32 # Los primeros 32 equipos son Selecciones Nacionales
# Offsets y Tamaños de Vínculos exactos
NAT_LINKS_OFFSET = 0x20C230 # En SLPM_622.68
CLUB_LINKS_OFFSET = 0x353B0 # En parameter.ovl
TEAM_RECORD_SIZE = 80 # 80 bytes por registro de equipo
# Lista de atributos mapeados en el SLPM
ATTRIBUTES_DEF = [
("Ataque", 2), ("Defensa", 3), ("Balance", 4), ("Stamina", 5),
("Top Speed", 6), ("Aceleración", 7), ("Respuesta", 8), ("Agilidad", 9),
("Dribble Acc", 10), ("Dribble Vel", 11), ("P. Corto Acc", 12), ("P. Corto Vel", 13),
("P. Largo Acc", 14), ("P. Largo Vel", 15), ("Tiro Acc", 16), ("Tiro Potencia", 17),
("Tiro Téc", 18), ("Falta / T.Libre", 19), ("Curva", 20), ("Cabezazo", 21),
("Salto", 22), ("Técnica", 23), ("Agresividad", 24), ("Mentalidad", 25),
("Habil. Arquero", 26), ("Trabajo Equipo", 27), ("Condición", 28),
("Pie Bueno Frec", 29), ("Pie Bueno", 30)
]
class WE6FE_EditorApp:
def __init__(self, root):
self.root = root
self.root.title("WE6FE Database Editor & Engine Analyzer - By WE11world, Pato_Lucas18 & Gemini")
self.root.geometry("1220x880")
self.root.config(bg="#1e1e1e")
self.current_player_index = 0
self.current_team_index = 0
self.current_roster_team_index = 0
self.current_source_team_index = 0
self.entries = {}
self.team_name_entry = None
self.team_listbox = None
self.lbl_power_att = None
self.lbl_power_def = None
self.lbl_power_ovr = None
self.kit_team_combobox = None
self.kit_canvases = {}
# --- ESTILO GENERAL Y DE PESTAÑAS ---
style = ttk.Style()
style.theme_use('default')
style.configure('TNotebook', background='#1e1e1e', borderwidth=0)
style.configure('TNotebook.Tab', background='#2d2d2d', foreground='#ffffff', font=('Arial', 10, 'bold'), padding=[12, 6])
style.map('TNotebook.Tab', background=[('selected', '#007acc')], foreground=[('selected', '#ffffff')])
style.configure("Treeview", background="#2a2a2a", foreground="#ffffff", fieldbackground="#2a2a2a", font=("Consolas", 9), rowheight=22)
style.configure("Treeview.Heading", background="#3c3c3c", foreground="#00ffcc", font=("Arial", 9, "bold"))
style.map("Treeview", background=[('selected', '#007acc')], foreground=[('selected', '#ffffff')])
self.notebook = ttk.Notebook(root)
self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.tab_players = tk.Frame(self.notebook, bg="#1e1e1e")
self.tab_teams = tk.Frame(self.notebook, bg="#1e1e1e")
self.tab_rosters = tk.Frame(self.notebook, bg="#1e1e1e")
self.tab_kits = tk.Frame(self.notebook, bg="#1e1e1e")
self.notebook.add(self.tab_players, text=" ⚽ Edición de Jugadores ")
self.notebook.add(self.tab_teams, text=" ?️ Edición de Equipos ")
self.notebook.add(self.tab_rosters, text=" ? Plantillas y Traspasos ")
self.notebook.add(self.tab_kits, text=" ? Kits & Colores (C Engine) ")
self.build_players_tab()
self.build_teams_tab()
self.build_rosters_tab()
self.build_kits_tab()
def is_valid_name(self, text):
if not text or len(text) < 1:
return False
valid_chars = sum(1 for c in text if c.isalnum() or c.isspace() or c in ".-'áéíóúÁÉÍÓÚñÑ")
if (valid_chars / len(text)) < 0.75:
return False
return True
def get_clean_player_info(self, f_ovl, pid):
p_name = ""
shirt_name = ""
try:
if pid < 150:
pos = NAMES_BASE_OFFSET + (pid * NAME_SIZE)
shirt_pos = SHIRT_NAMES_OFFSET + (pid * SHIRT_NAME_SIZE)
else:
base_club_id = 1451
index_offset = pid - base_club_id if pid >= 1451 else (pid - 150)
pos = CLUB_NAMES_BASE_OFFSET + (index_offset * NAME_SIZE)
f_ovl.seek(pos)
raw_name = f_ovl.read(NAME_SIZE)
extracted_name = raw_name.split(b'\x00')[0].decode('latin-1', errors='ignore').strip()
if self.is_valid_name(extracted_name):
p_name = extracted_name
if pid < 150:
f_ovl.seek(shirt_pos)
raw_shirt = f_ovl.read(SHIRT_NAME_SIZE)
extracted_shirt = raw_shirt.split(b'\x00')[0].decode('latin-1', errors='ignore').strip()
if self.is_valid_name(extracted_shirt):
shirt_name = extracted_shirt
elif p_name:
shirt_name = p_name.upper()[:16]
else:
if p_name:
shirt_name = p_name.upper()[:16]
if not p_name:
if shirt_name:
p_name = shirt_name
else:
p_name = f"Jugador #{pid}"
except Exception:
p_name = f"Jugador #{pid}"
return p_name, shirt_name
def get_team_file_and_info(self, team_index):
if team_index < NUM_NAT_TEAMS:
return SLPM_PATH, NAT_LINKS_OFFSET, 23
else:
return OVL_PATH, CLUB_LINKS_OFFSET, 23
# ==========================================
# PESTAÑA 1: JUGADORES
# ==========================================
def build_players_tab(self):
main_frame = tk.Frame(self.tab_players, bg="#1e1e1e")
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
left_frame = tk.Frame(main_frame, bg="#2d2d2d", width=280)
left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
tk.Label(left_frame, text="Plantilla de Jugadores", bg="#2d2d2d", fg="#ffffff", font=("Arial", 11, "bold")).pack(pady=10)
list_scroll = tk.Scrollbar(left_frame)
list_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.player_listbox = tk.Listbox(left_frame, yscrollcommand=list_scroll.set, bg="#1e1e1e", fg="#00ffcc", selectbackground="#007acc", font=("Consolas", 10), height=36)
self.player_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.player_listbox.bind('<<ListboxSelect>>', self.on_player_select)
list_scroll.config(command=self.player_listbox.yview)
right_frame = tk.Frame(main_frame, bg="#2d2d2d")
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
top_edit_frame1 = tk.Frame(right_frame, bg="#2d2d2d")
top_edit_frame1.pack(fill=tk.X, padx=15, pady=(10, 5))
tk.Label(top_edit_frame1, text="Nombre:", bg="#2d2d2d", fg="#00ffcc", font=("Arial", 10, "bold")).pack(side=tk.LEFT, padx=(0, 5))
self.name_entry = tk.Entry(top_edit_frame1, bg="#3c3c3c", fg="#ffffff", insertbackground="white", font=("Consolas", 10, "bold"), width=18)
self.name_entry.pack(side=tk.LEFT, padx=(0, 15))
tk.Label(top_edit_frame1, text="Nom. Camiseta:", bg="#2d2d2d", fg="#00ffcc", font=("Arial", 10, "bold")).pack(side=tk.LEFT, padx=(0, 5))
self.shirt_name_entry = tk.Entry(top_edit_frame1, bg="#3c3c3c", fg="#ffffff", insertbackground="white", font=("Consolas", 10, "bold"), width=16)
self.shirt_name_entry.pack(side=tk.LEFT)
top_edit_frame2 = tk.Frame(right_frame, bg="#2d2d2d")
top_edit_frame2.pack(fill=tk.X, padx=15, pady=(0, 10))
tk.Label(top_edit_frame2, text="D.Nac:", bg="#2d2d2d", fg="#00ffcc", font=("Arial", 10, "bold")).pack(side=tk.LEFT, padx=(0, 3))
self.dorsal_entry = tk.Entry(top_edit_frame2, bg="#3c3c3c", fg="#ffffff", insertbackground="white", font=("Consolas", 10, "bold"), width=4)
self.dorsal_entry.pack(side=tk.LEFT, padx=(0, 15))
tk.Label(top_edit_frame2, text="D.Club:", bg="#2d2d2d", fg="#00ffcc", font=("Arial", 10, "bold")).pack(side=tk.LEFT, padx=(0, 3))
self.club_dorsal_entry = tk.Entry(top_edit_frame2, bg="#3c3c3c", fg="#ffffff", insertbackground="white", font=("Consolas", 10, "bold"), width=4)
self.club_dorsal_entry.pack(side=tk.LEFT)
canvas = tk.Canvas(right_frame, bg="#2d2d2d", highlightthickness=0)
scrollbar = tk.Scrollbar(right_frame, orient="vertical", command=canvas.yview)
self.form_frame = tk.Frame(canvas, bg="#2d2d2d")
for col_idx in range(4):
self.form_frame.grid_columnconfigure(col_idx, weight=1)
self.form_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
canvas.create_window((0, 0), window=self.form_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
row = 0
col = 0
for name, byte_idx in ATTRIBUTES_DEF:
lbl = tk.Label(self.form_frame, text=name + ":", bg="#2d2d2d", fg="#d4d4d4", font=("Arial", 9, "bold"), anchor="e")
lbl.grid(row=row, column=col*2, sticky="e", padx=5, pady=4)
entry = tk.Entry(self.form_frame, width=8, bg="#3c3c3c", fg="#ffffff", insertbackground="white", font=("Consolas", 10))
entry.grid(row=row, column=col*2+1, sticky="w", padx=5, pady=4)
self.entries[byte_idx] = entry
col += 1
if col > 1:
col = 0
row += 1
btn_save = tk.Button(right_frame, text="Guardar Cambios de Jugador", bg="#007acc", fg="#ffffff", font=("Arial", 10, "bold"), command=self.save_player_data)
btn_save.pack(pady=15)
self.load_players_list()
# ==========================================
# PESTAÑA 2: EQUIPOS
# ==========================================
def build_teams_tab(self):
main_frame = tk.Frame(self.tab_teams, bg="#1e1e1e")
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
left_frame = tk.Frame(main_frame, bg="#2d2d2d", width=320)
left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
tk.Label(left_frame, text="Listado de Equipos", bg="#2d2d2d", fg="#ffffff", font=("Arial", 11, "bold")).pack(pady=10)
list_scroll = tk.Scrollbar(left_frame)
list_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.team_listbox = tk.Listbox(left_frame, yscrollcommand=list_scroll.set, bg="#1e1e1e", fg="#ffcc00", selectbackground="#007acc", font=("Consolas", 10), height=36)
self.team_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.team_listbox.bind('<<ListboxSelect>>', self.on_team_select)
list_scroll.config(command=self.team_listbox.yview)
right_frame = tk.Frame(main_frame, bg="#2d2d2d")
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
edit_container = tk.Frame(right_frame, bg="#2d2d2d")
edit_container.pack(pady=40, padx=20, fill=tk.X)
tk.Label(edit_container, text="Nombre del Equipo:", bg="#2d2d2d", fg="#ffcc00", font=("Arial", 11, "bold")).pack(anchor="w", pady=(0, 8))
self.team_name_entry = tk.Entry(edit_container, bg="#3c3c3c", fg="#ffffff", insertbackground="white", font=("Consolas", 12, "bold"), width=25)
self.team_name_entry.pack(anchor="w", pady=(0, 20))
btn_save_team = tk.Button(edit_container, text="Guardar Nombre de Equipo en SLPM", bg="#007acc", fg="#ffffff", font=("Arial", 10, "bold"), command=self.save_team_data)
btn_save_team.pack(anchor="w")
self.load_teams_list()
# ==========================================
# PESTAÑA 3: PLANTILLAS Y TRASPASOS (Doble Panel Inteligente - 23 Slots)
# ==========================================
def build_rosters_tab(self):
main_frame = tk.Frame(self.tab_rosters, bg="#1e1e1e")
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
top_control_frame = tk.Frame(main_frame, bg="#2d2d2d", padx=10, pady=10)
top_control_frame.pack(fill=tk.X, pady=(0, 10))
team_names_list = [f"Equipo #{i} {'(Selección)' if i < NUM_NAT_TEAMS else '(Club)'}" for i in range(NUM_TEAMS)]
tk.Label(top_control_frame, text="Origen:", bg="#2d2d2d", fg="#00ffcc", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5))
self.source_team_combobox = ttk.Combobox(top_control_frame, values=team_names_list, width=26, state="readonly")
self.source_team_combobox.pack(side=tk.LEFT, padx=(0, 15))
self.source_team_combobox.current(1 if NUM_TEAMS > 1 else 0)
self.source_team_combobox.bind("<<ComboboxSelected>>", self.on_source_team_combo_select)
tk.Label(top_control_frame, text="Destino:", bg="#2d2d2d", fg="#ffcc00", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5))
self.roster_team_combobox = ttk.Combobox(top_control_frame, values=team_names_list, width=26, state="readonly")
self.roster_team_combobox.pack(side=tk.LEFT, padx=(0, 15))
self.roster_team_combobox.current(0)
self.roster_team_combobox.bind("<<ComboboxSelected>>", self.on_roster_team_combo_select)
self.lbl_power_att = tk.Label(top_control_frame, text="ATK: --", bg="#2d2d2d", fg="#00ffcc", font=("Consolas", 9, "bold"))
self.lbl_power_att.pack(side=tk.LEFT, padx=4)
self.lbl_power_def = tk.Label(top_control_frame, text="DEF: --", bg="#2d2d2d", fg="#00ffcc", font=("Consolas", 9, "bold"))
self.lbl_power_def.pack(side=tk.LEFT, padx=4)
self.lbl_power_ovr = tk.Label(top_control_frame, text="OVR: --", bg="#2d2d2d", fg="#ff3366", font=("Consolas", 9, "bold"))
self.lbl_power_ovr.pack(side=tk.LEFT, padx=4)
tables_frame = tk.Frame(main_frame, bg="#1e1e1e")
tables_frame.pack(fill=tk.BOTH, expand=True)
columns_def = ("id", "slot", "name", "shirt")
# --- TABLA IZQUIERDA: Plantilla Origen ---
left_container = tk.LabelFrame(tables_frame, text=" Plantilla Origen (23 Slots) ", bg="#2d2d2d", fg="#00ffcc", font=("Arial", 10, "bold"), padx=5, pady=5)
left_container.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))
self.source_tree = ttk.Treeview(left_container, columns=columns_def, show="headings", height=22)
self.source_tree.heading("id", text="ID")
self.source_tree.heading("slot", text="Slot")
self.source_tree.heading("name", text="Name")
self.source_tree.heading("shirt", text="Shirt Name")
self.source_tree.column("id", width=50, anchor="center")
self.source_tree.column("slot", width=45, anchor="center")
self.source_tree.column("name", width=140, anchor="w")
self.source_tree.column("shirt", width=120, anchor="w")
source_scroll = ttk.Scrollbar(left_container, orient="vertical", command=self.source_tree.yview)
self.source_tree.configure(yscrollcommand=source_scroll.set)
self.source_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
source_scroll.pack(side=tk.RIGHT, fill=tk.Y)
# --- BOTON DE TRASPASO ---
middle_frame = tk.Frame(tables_frame, bg="#1e1e1e", width=70)
middle_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5)
btn_transfer_to_team = tk.Button(middle_frame, text=" ➔ \nFichar", bg="#007acc", fg="#ffffff", font=("Arial", 9, "bold"), command=self.transfer_player_to_slot)
btn_transfer_to_team.pack(expand=True, fill=tk.X, pady=5)
# --- TABLA DERECHA: Plantilla Destino ---
right_container = tk.LabelFrame(tables_frame, text=" Plantilla Destino (23 Slots) ", bg="#2d2d2d", fg="#ffcc00", font=("Arial", 10, "bold"), padx=5, pady=5)
right_container.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0))
self.dest_tree = ttk.Treeview(right_container, columns=columns_def, show="headings", height=22)
self.dest_tree.heading("id", text="ID")
self.dest_tree.heading("slot", text="Slot")
self.dest_tree.heading("name", text="Name")
self.dest_tree.heading("shirt", text="Shirt Name")
self.dest_tree.column("id", width=50, anchor="center")
self.dest_tree.column("slot", width=45, anchor="center")
self.dest_tree.column("name", width=140, anchor="w")
self.dest_tree.column("shirt", width=120, anchor="w")
dest_scroll = ttk.Scrollbar(right_container, orient="vertical", command=self.dest_tree.yview)
self.dest_tree.configure(yscrollcommand=dest_scroll.set)
self.dest_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
dest_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.load_roster_teams_list_v2()
def load_roster_teams_list_v2(self):
try:
team_names = []
with open(SLPM_PATH, "rb") as f_slpm:
for i in range(NUM_TEAMS):
f_slpm.seek(TEAM_NAMES_OFFSET + (i * TEAM_RECORD_SIZE))
name_data = f_slpm.read(TEAM_NAME_LIMIT)
t_name = name_data.decode('latin-1', errors='ignore').rstrip('\x00').strip()
if not t_name:
t_name = f"Equipo #{i}"
t_type = "Selección" if i < NUM_NAT_TEAMS else "Club"
team_names.append(f"#{i} - {t_name} ({t_type})")
if team_names:
if hasattr(self, 'source_team_combobox'):
self.source_team_combobox['values'] = team_names
self.source_team_combobox.current(1 if len(team_names) > 1 else 0)
if hasattr(self, 'roster_team_combobox'):
self.roster_team_combobox['values'] = team_names
self.roster_team_combobox.current(0)
self.on_source_team_combo_select(None)
self.on_roster_team_combo_select(None)
except Exception:
pass
def on_source_team_combo_select(self, event):
self.current_source_team_index = self.source_team_combobox.current()
self.source_tree.delete(*self.source_tree.get_children())
file_path, base_offset, num_slots = self.get_team_file_and_info(self.current_source_team_index)
try:
with open(file_path, "rb") as f:
record_offset = base_offset + (self.current_source_team_index * TEAM_RECORD_SIZE)
f.seek(record_offset)
data = f.read(TEAM_RECORD_SIZE)
if len(data) >= (num_slots * 2):
player_ids = struct.unpack(f"<{num_slots}H", data[:num_slots * 2])
for idx, pid in enumerate(player_ids):
with open(OVL_PATH, "rb") as f_ovl:
if 0 < pid < MAX_DATABASE_PLAYERS:
p_name, shirt_name = self.get_clean_player_info(f_ovl, pid)
self.source_tree.insert("", tk.END, values=(pid, idx, p_name, shirt_name))
else:
self.source_tree.insert("", tk.END, values=(pid, idx, "[ Slot Vacío / Nulo ]", ""))
except Exception as e:
messagebox.showerror("Error", f"Error al cargar plantilla origen: {e}")
def on_roster_team_combo_select(self, event):
self.current_roster_team_index = self.roster_team_combobox.current()
self.dest_tree.delete(*self.dest_tree.get_children())
current_team_pids = []
file_path, base_offset, num_slots = self.get_team_file_and_info(self.current_roster_team_index)
try:
with open(file_path, "rb") as f:
record_offset = base_offset + (self.current_roster_team_index * TEAM_RECORD_SIZE)
f.seek(record_offset)
data = f.read(TEAM_RECORD_SIZE)
if len(data) >= (num_slots * 2):
player_ids = struct.unpack(f"<{num_slots}H", data[:num_slots * 2])
current_team_pids = list(player_ids)
for idx, pid in enumerate(player_ids):
with open(OVL_PATH, "rb") as f_ovl:
if 0 < pid < MAX_DATABASE_PLAYERS:
p_name, shirt_name = self.get_clean_player_info(f_ovl, pid)
self.dest_tree.insert("", tk.END, values=(pid, idx, p_name, shirt_name))
else:
self.dest_tree.insert("", tk.END, values=(pid, idx, "[ Slot Vacío / Nulo ]", ""))
p_att, p_def, p_ovr = self.calculate_team_power(current_team_pids)
self.lbl_power_att.config(text=f"ATK: {p_att}")
self.lbl_power_def.config(text=f"DEF: {p_def}")
self.lbl_power_ovr.config(text=f"OVR: {p_ovr}")
except Exception as e:
messagebox.showerror("Error", f"Error al cargar plantilla destino: {e}")
def transfer_player_to_slot(self):
selected_source = self.source_tree.selection()
selected_dest = self.dest_tree.selection()
if not selected_source or not selected_dest:
messagebox.showwarning("Selección Incompleta", "Por favor selecciona un jugador origen y un slot destino.")
return
try:
# CORRECCIÓN DIRECTA: Extraemos el primer valor (ID) de la tupla de manera segura
new_id = int(self.source_tree.item(selected_source, "values")[0])
dest_index = self.dest_tree.index(selected_dest)
# Escribir el cambio en el archivo SLPM_622.68
SLPM_PATH = "SLPM_622.68"
TEAM_RECORD_SIZE = 46 # 23 slots * 2 bytes
# Dirección base en hexadecimal para la alineación del primer equipo (Irlanda)
SLPM_BASE_ROSTER_OFFSET = 0x0023BF00
slot_offset = SLPM_BASE_ROSTER_OFFSET + (self.current_roster_team_index * TEAM_RECORD_SIZE) + (dest_index * 2)
with open(SLPM_PATH, "r+b") as f:
f.seek(slot_offset)
f.write(struct.pack("<H", new_id))
f.flush()
import os
os.fsync(f.fileno())
messagebox.showinfo("Éxito", f"¡Traspaso grabado directamente en el ejecutable {SLPM_PATH}!\nSlot {dest_index} actualizado.")
self.on_roster_team_combo_select(None)
self.on_source_team_combo_select(None)
except Exception as e:
# Este es el bloque que Python estaba reclamando que faltaba abajo
messagebox.showerror("Error de Escritura", f"No se pudo sobreescribir el ejecutable: {e}")
# ==========================================
# PESTAÑA 4: KITS Y COLORES
# ==========================================
def build_kits_tab(self):
main_frame = tk.Frame(self.tab_kits, bg="#1e1e1e")
main_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=15)
tk.Label(main_frame, text="? Gestión de Uniformes y Paleta de Colores (Estilo Waterloo Tool)", bg="#1e1e1e", fg="#00ffcc", font=("Arial", 12, "bold")).pack(anchor="w", pady=(0, 10))
select_frame = tk.Frame(main_frame, bg="#2d2d2d", padx=10, pady=10)
select_frame.pack(fill=tk.X, pady=(0, 15))
tk.Label(select_frame, text="Seleccionar Equipo:", bg="#2d2d2d", fg="#ffcc00", font=("Arial", 10, "bold")).pack(side=tk.LEFT, padx=(0, 10))
team_names_list = [f"Equipo #{i}" for i in range(NUM_TEAMS)]
self.kit_team_combobox = ttk.Combobox(select_frame, values=team_names_list, width=35, state="readonly")
self.kit_team_combobox.pack(side=tk.LEFT, padx=(0, 10))
self.kit_team_combobox.current(0)
self.kit_team_combobox.bind("<<ComboboxSelected>>", self.on_kit_team_select)
colors_container = tk.LabelFrame(main_frame, text=" Configuración de Kits (Player & Goalkeeper - Home / Away) ", bg="#2d2d2d", fg="#ffcc00", font=("Arial", 10, "bold"), padx=10, pady=10)
colors_container.pack(fill=tk.BOTH, expand=True)
headers = ["Kit Type", "Edit Back Number Color", "Edit Back Name Color", "Edit Short Number Color", "Base Color"]
for col_idx, h_text in enumerate(headers):
tk.Label(colors_container, text=h_text, bg="#2d2d2d", fg="#00ffcc", font=("Arial", 9, "bold"), anchor="center").grid(row=0, column=col_idx, padx=5, pady=5, sticky="nsew")
kit_rows = [
("Player (Home)", "p_home"),
("Player (Away)", "p_away"),
("Goalkeeper (Home)", "gk_home"),
("Goalkeeper (Away)", "gk_away")
]
self.kit_canvases = {}
for row_idx, (row_label, row_key) in enumerate(kit_rows, start=1):
tk.Label(colors_container, text=row_label, bg="#2d2d2d", fg="#ffffff", font=("Arial", 9, "bold"), anchor="w").grid(row=row_idx, column=0, padx=10, pady=8, sticky="w")
self.kit_canvases[row_key] = {}
props = [
("back_num", "5"),
("back_name", "AB"),
("short_num", "5"),
("base_col", "")
]
for col_idx, (prop_key, display_text) in enumerate(props, start=1):
c = tk.Canvas(colors_container, width=60, height=35, bg="#1a3b5c", relief="sunken", bd=2)
c.grid(row=row_idx, column=col_idx, padx=15, pady=5)
if display_text:
c.create_text(30, 17, text=display_text, fill="#ffcc00", font=("Consolas", 10, "bold"))
c.bind("<Button-1>", lambda e, r=row_key, p=prop_key: self.pick_kit_color(r, p))
self.kit_canvases[row_key][prop_key] = c
btn_save_kits = tk.Button(colors_container, text="? Guardar Cambios de Kits en SLPM", bg="#007acc", fg="#ffffff", font=("Arial", 10, "bold"), command=self.save_kit_colors)
btn_save_kits.grid(row=5, column=0, columnspan=5, pady=15, sticky="ew")
self.update_kit_team_names()
def update_kit_team_names(self):
try:
team_names = []
with open(SLPM_PATH, "rb") as f_slpm:
for i in range(NUM_TEAMS):
f_slpm.seek(TEAM_NAMES_OFFSET + (i * TEAM_RECORD_SIZE))
name_data = f_slpm.read(TEAM_NAME_LIMIT)
t_name = name_data.decode('latin-1', errors='ignore').rstrip('\x00').strip()
if not t_name:
t_name = f"Equipo #{i}"
team_names.append(f"#{i} - {t_name}")
if team_names and self.kit_team_combobox:
self.kit_team_combobox['values'] = team_names
except Exception:
pass
def on_kit_team_select(self, event):
selected_idx = self.kit_team_combobox.current()
if selected_idx < 0:
return
try:
base_offset = 0x240000 + (selected_idx * 0x40)
with open(SLPM_PATH, "rb") as f:
f.seek(base_offset)
kit_keys = ["p_home", "p_away", "gk_home", "gk_away"]
props = ["back_num", "back_name", "short_num", "base_col"]
for row_key in kit_keys:
for prop_key in props:
raw_color_bytes = f.read(2)
if len(raw_color_bytes) == 2:
raw_color = struct.unpack('<H', raw_color_bytes)[0]
r = ((raw_color >> 0) & 0x1F) << 3
g = ((raw_color >> 5) & 0x1F) << 3
b = ((raw_color >> 10) & 0x1F) << 3
hex_color = f"#{r:02x}{g:02x}{b:02x}"
if prop_key in self.kit_canvases[row_key]:
self.kit_canvases[row_key][prop_key].config(bg=hex_color)
except Exception:
pass
def pick_kit_color(self, kit_type, prop_type):
color_code = colorchooser.askcolor(title=f"Seleccionar Color ({kit_type} - {prop_type})")[1]
if color_code:
if prop_type in self.kit_canvases[kit_type]:
self.kit_canvases[kit_type][prop_type].config(bg=color_code)
def save_kit_colors(self):
selected_idx = self.kit_team_combobox.current()
if selected_idx < 0:
messagebox.showerror("Error", "Por favor, selecciona un equipo primero.")
return
try:
base_offset = 0x240000 + (selected_idx * 0x40)
kit_keys = ["p_home", "p_away", "gk_home", "gk_away"]
props = ["back_num", "back_name", "short_num", "base_col"]
with open(SLPM_PATH, "r+b") as f:
f.seek(base_offset)
for row_key in kit_keys:
for prop_key in props:
hex_color = self.kit_canvases[row_key][prop_key].cget("bg")
hex_color = hex_color.lstrip("#")
r = int(hex_color[0:2], 16)
g = int(hex_color[2:4], 16)
b = int(hex_color[4:6], 16)
r_5 = r >> 3
g_5 = g >> 3
b_5 = b >> 3
raw_color = (b_5 << 10) | (g_5 << 5) | r_5
f.write(struct.pack('<H', raw_color))
messagebox.showinfo("Éxito", "¡Los colores de los kits se han guardado correctamente en el archivo!")
except Exception as e:
messagebox.showerror("Error", f"No se pudo guardar la configuración de kits: {str(e)}")
def calculate_team_power(self, player_ids):
if not player_ids:
return 0, 0, 0
att_scores = []
def_scores = []
try:
with open(SLPM_PATH, "rb") as f_slpm:
for pid in player_ids:
if 0 < pid < MAX_DATABASE_PLAYERS:
f_slpm.seek(SLPM_BASE_OFFSET + (pid * PLAYER_SIZE))
p_data = f_slpm.read(PLAYER_SIZE)
if len(p_data) >= PLAYER_SIZE:
unpacked = struct.unpack("<31B 13s", p_data)
att = unpacked[2]
deff = unpacked[3]
att_scores.append(att)
def_scores.append(deff)
if not att_scores:
return 0, 0, 0
att_scores.sort(reverse=True)
def_scores.sort(reverse=True)
top_att = sum(att_scores[:11]) / max(len(att_scores[:11]), 1)
top_def = sum(def_scores[:11]) / max(len(def_scores[:11]), 1)
power_att = min(int(top_att * 1.02), 99)
power_def = min(int(top_def * 1.02), 99)
power_ovr = min(int((power_att + power_def) / 2), 99)
return power_att, power_def, power_ovr
except Exception:
return 0, 0, 0
def load_players_list(self):
try:
with open(SLPM_PATH, "rb") as f_slpm, open(OVL_PATH, "rb") as f_ovl:
for i in range(NUM_PLAYERS):
p_name, _ = self.get_clean_player_info(f_ovl, i)
self.player_listbox.insert(tk.END, f"#{i} - {p_name}")
if NUM_PLAYERS > 0:
self.player_listbox.selection_set(0)
self.on_player_select(None)
except FileNotFoundError:
pass
def on_player_select(self, event):
selection = self.player_listbox.curselection()
if not selection:
return
self.current_player_index = selection[0]
try:
with open(OVL_PATH, "rb") as f_ovl:
p_name, shirt_name = self.get_clean_player_info(f_ovl, self.current_player_index)
self.name_entry.delete(0, tk.END)
self.name_entry.insert(0, p_name)
self.shirt_name_entry.delete(0, tk.END)
self.shirt_name_entry.insert(0, shirt_name)
f_ovl.seek(NUMBERS_BASE_OFFSET + (self.current_player_index * NUMBERS_RECORD_SIZE))
record_bytes = f_ovl.read(NUMBERS_RECORD_SIZE)
self.dorsal_entry.delete(0, tk.END)
self.dorsal_entry.insert(0, str(record_bytes[1] if len(record_bytes) > 1 else 0))
f_ovl.seek(CLUB_NUMBERS_OFFSET + (self.current_player_index * CLUB_RECORD_SIZE))
club_bytes = f_ovl.read(CLUB_RECORD_SIZE)
self.club_dorsal_entry.delete(0, tk.END)
self.club_dorsal_entry.insert(0, str(club_bytes[0] if len(club_bytes) > 0 else 255))
with open(SLPM_PATH, "rb") as f_slpm:
f_slpm.seek(SLPM_BASE_OFFSET + (self.current_player_index * PLAYER_SIZE))
unpacked = struct.unpack("<31B 13s", f_slpm.read(PLAYER_SIZE))
for byte_idx, entry in self.entries.items():
entry.delete(0, tk.END)
entry.insert(0, str(unpacked[byte_idx]))
except Exception as e:
messagebox.showerror("Error", f"Error al leer jugador: {e}")
def save_player_data(self):
try:
with open(OVL_PATH, "r+b") as f_ovl:
new_name = self.name_entry.get().strip()
f_ovl.seek(NAMES_BASE_OFFSET + (self.current_player_index * NAME_SIZE))
f_ovl.write(new_name.encode('latin-1', errors='ignore')[:NAME_SIZE].ljust(NAME_SIZE, b'\x00'))
new_shirt = self.shirt_name_entry.get().strip()
f_ovl.seek(SHIRT_NAMES_OFFSET + (self.current_player_index * SHIRT_NAME_SIZE))
f_ovl.write(new_shirt.encode('latin-1', errors='ignore')[:SHIRT_NAME_SIZE].ljust(SHIRT_NAME_SIZE, b'\x00'))
dorsal_val = int(self.dorsal_entry.get())
f_ovl.seek(NUMBERS_BASE_OFFSET + (self.current_player_index * NUMBERS_RECORD_SIZE))
rec = bytearray(f_ovl.read(NUMBERS_RECORD_SIZE))
rec[1] = dorsal_val
f_ovl.seek(NUMBERS_BASE_OFFSET + (self.current_player_index * NUMBERS_RECORD_SIZE))
f_ovl.write(rec)
club_val = int(self.club_dorsal_entry.get())
f_ovl.seek(CLUB_NUMBERS_OFFSET + (self.current_player_index * CLUB_RECORD_SIZE))
crec = bytearray(f_ovl.read(CLUB_RECORD_SIZE))
crec[0] = club_val
f_ovl.seek(CLUB_NUMBERS_OFFSET + (self.current_player_index * CLUB_RECORD_SIZE))
f_ovl.write(crec)
with open(SLPM_PATH, "rb") as f_slpm:
f_slpm.seek(SLPM_BASE_OFFSET + (self.current_player_index * PLAYER_SIZE))
p_bytes = bytearray(f_slpm.read(PLAYER_SIZE))
for byte_idx, entry in self.entries.items():
p_bytes[byte_idx] = int(entry.get())
with open(SLPM_PATH, "r+b") as f_slpm:
f_slpm.seek(SLPM_BASE_OFFSET + (self.current_player_index * PLAYER_SIZE))
f_slpm.write(p_bytes)
self.player_listbox.delete(self.current_player_index)
self.player_listbox.insert(self.current_player_index, f"#{self.current_player_index} - {new_name}")
self.player_listbox.selection_set(self.current_player_index)
messagebox.showinfo("Éxito", "¡Jugador actualizado correctamente!")
except Exception as e:
messagebox.showerror("Error", f"No se pudo guardar: {e}")
def load_teams_list(self):
try:
with open(SLPM_PATH, "rb") as f_slpm:
for i in range(NUM_TEAMS):
f_slpm.seek(TEAM_NAMES_OFFSET + (i * TEAM_RECORD_SIZE))
data = f_slpm.read(TEAM_NAME_LIMIT)
team_name = data.decode('latin-1', errors='ignore').rstrip('\x00').strip()
if not team_name:
team_name = f"Equipo #{i}"
self.team_listbox.insert(tk.END, f"#{i} - {team_name}")
if NUM_TEAMS > 0:
self.team_listbox.selection_set(0)
self.on_team_select(None)
except FileNotFoundError:
pass
def on_team_select(self, event):
selection = self.team_listbox.curselection()
if not selection:
return
self.current_team_index = selection[0]
try:
with open(SLPM_PATH, "rb") as f_slpm:
f_slpm.seek(TEAM_NAMES_OFFSET + (self.current_team_index * TEAM_RECORD_SIZE))
data = f_slpm.read(TEAM_NAME_LIMIT)
team_name = data.decode('latin-1', errors='ignore').rstrip('\x00').strip()
self.team_name_entry.delete(0, tk.END)
self.team_name_entry.insert(0, team_name)
except Exception as e:
messagebox.showerror("Error", f"Error al leer equipo: {e}")
def save_team_data(self):
try:
new_team_name = self.team_name_entry.get().strip()
name_bytes = new_team_name.encode('latin-1', errors='ignore')[:TEAM_NAME_LIMIT].ljust(TEAM_NAME_LIMIT, b'\x00')
with open(SLPM_PATH, "r+b") as f_slpm:
record_offset = TEAM_NAMES_OFFSET + (self.current_team_index * TEAM_RECORD_SIZE)
f_slpm.seek(record_offset)
full_record = bytearray(f_slpm.read(TEAM_RECORD_SIZE))
full_record[:TEAM_NAME_LIMIT] = name_bytes
f_slpm.seek(record_offset)
f_slpm.write(full_record)
self.team_listbox.delete(self.current_team_index)
self.team_listbox.insert(self.current_team_index, f"#{self.current_team_index} - {new_team_name}")
self.team_listbox.selection_set(self.current_team_index)
messagebox.showinfo("Éxito", f"¡Equipo #{self.current_team_index} actualizado a '{new_team_name}' en el SLPM!")
except Exception as e:
messagebox.showerror("Error", f"No se pudo guardar el equipo: {e}")
if __name__ == "__main__":
root = tk.Tk()
app = WE6FE_EditorApp(root)
root.mainloop()
Enjoy


