Manuale tecnico SWOS ITA Mod
Documentazione per chi vuole capire, ampliare o portare su altri eseguibili le patch del mod: Serie C1 e C2, coppe sudamericane (Libertadores, Supercopa, CONMEBOL), Coppa Intercontinentale in carriera, rose reali 1996-97. Tutto quello che segue è stato ricavato dal disassemblato e verificato con build e partite di prova.
Premessa
Tanto tempo fa - anno 2004 - in una galassia lontana lontana, dove Internet stava appena separandosi dal suono del modem, l'intelligenza artificiale era un argomento da romanzi di fantascienza e i videogiochi si vendevano in eleganti scatole di cartone nei negozi, il sottoscritto - insieme ad Ali Erdinc Koroglu, Jan Wickberg, Martin Binet, Zlatko Karakas, Ross Mayhew e Steve Smith - fondò lo Swos Working Group (web.archive.org) in onore del nostro videogioco preferito di sempre: l'edizione 1996-1997 di Sensible World of Soccer. Eravamo animati da un sogno: rendere ancora più bello quel gioco aggiungendo alcune competizioni la cui mancanza avvertivamo particolarmente amara. La serie C italiana (divisa in C1 e C2), in cui giocavano atleti che sarebbero poi diventati campioni a livello internazionale come Simone Inzaghi e Gianluca Zambrotta, e le coppe sudamericane, su tutte la mitica Copa Libertadores di cui, in un'epoca nella quale il calcio in tv era ancora una novità e non così abbondante, sentivamo il fascino delle cose esotiche e lontane. Ci sarebbe piaciuto giocare queste competizioni particolarmente in modalità carriera, quella preferita in assoluto dai giocatori di Swos: anche perché, nella modalità stagione e competizioni predefinite, eravamo già riusciti ad aggiungerle in una nuova edizione di Swos che si sarebbe dovuta chiamare Swos 2024. Il lavoro fu fatto con il reverse engineering e l'hex editing, ricordo l'emozione nello scoprire le routine di aggiunta delle competizioni ma anche l'amara frustrazione di non riuscire a farle funzionare in modalità carriera, che puntualmente si bloccava. Oggi, anno di grazia 2026, ho voluto chiudere il conto con questa storia personale che durante questi due decenni non mi aveva mai abbandonato, che era il rimpianto di essere arrivati vicino a un sogno e di non esserci mai riusciti. Del resto, mi dicevo per tentare di consolarmi, io alla fine sono diventato uno sviluppatore web e mobile, quello del reverse engineering su un codice x86 del 1996 era stato già un miracolo e per lo più nessuno dei tantissimi modder moderni era riuscito a compiere l'impresa.
Oggi, grazie alla AI, che ha cambiato per sempre il concetto stesso di programmazione, l'impresa è compiuta: potete ora scaricare la patch che aggiunge, in tutte le lingue del gioco, la serie C1 e C2 al campionato italiano, con le rose originali dell'epoca, ma soprattutto aggiunge per il Sud America la Coppa Libertadores, la Conmebol e la Supercopa, con le regole di ammissione di allora. E, come ciliegina sulla torta, non poteva mancare la Coppa Intercontinentale. Quello che mi interessa, però, non è solo il risultato, ma anche la spiegazione di come attraverso la AI ci si è arrivati. Quindi qui pubblichiamo, in inglese e in italiano, la documentazione completa del metodo seguito per creare le patch, che mi ha finalmente svelato cosa non avevamo capito nel lontano 2004 e spero possa ispirare altri sviluppatori e amanti del videogioco più bello del mondo a espanderne ulteriormente la modalità carriera. Un grazie immenso al lavoro di Zlatko Karakas, di cui dopo tanti anni di assenza ho trovato il fondamentale lavoro di disassemblaggio dell'exe originale (github.com).
Questa patch è dedicata alla memoria di Steve Smith, straordinario compilatore di rose aggiornate per Swos, che ci ha lasciati troppo presto. E a tutti quelli che sperano di vedere, prima o poi, realizzato un sogno che può essere lungo anche 22 anni…
— Davide Lorigliola
1Panoramica
Il mod non tocca mai i file originali. Uno script Python legge orig/ITALIAN.EXE e orig/DATA/TEAM.020, applica tutte le modifiche in modo deterministico e scrive i file installati in c/SWOS/. Ogni indirizzo che serve viene cercato nell'eseguibile per firma (sequenze di byte uniche) e verificato con assert: se una firma non si trova o non è unica, il build si ferma invece di produrre un exe sbagliato.
| Area | Cosa fa | Dove | Stato |
|---|---|---|---|
| Italia a 4 divisioni | Serie A 18, B 20, C1 18, C2 18; 4 promozioni e 4 retrocessioni fra livelli adiacenti; risolto il blocco di fine stagione del 1997 | patch.py | provato |
| Squadre C1/C2 | 36 club reali 1996-97 con rose, allenatori e forza calibrata | c1c2.py, c1c2_rosters.py | provato |
| Coppe sudamericane | Libertadores (gironi + eliminazione), Supercopa, CONMEBOL: menu, vista mondo, carriera, qualificate dalle classifiche, regole della detentrice | sacups.py | provato |
| Doppia partecipazione | Club sudamericani: Supercopa nello slot 1 (quello della coppa nazionale, libero in Sudamerica) + coppa continentale nello slot 3 | sacups.py | provato |
| Persistenza | Liste delle coppe sudamericane e finaliste dell'Intercontinentale salvate con la carriera | sacups.py, salists.py | provato |
| Intercontinentale | Finale secca a dicembre fra vincitrice Coppa Campioni e vincitrice Libertadores; finaliste aggiornate a fine stagione; slot 2 oppure slot 4 | sacups.py | slot 2, fine stagione slot 4 |
2Ambiente e build
Cartelle
~/swos-mod/
orig/ ITALIAN.EXE, ENGLISH.EXE, DATA/TEAM.020, swos9697ita.iso (MAI modificare)
c/SWOS/ gioco installato per DOSBox-X: ITALIAN.EXE, DATA/TEAM.020, salvataggi *.CAR
tools/ tutto il codice del mod (Python + sorgenti nasm dentro sacups.py)
ref/swos.asm disassemblato IDA completo (etichette = VA dell'exe INGLESE), da github.com/zlatkok/swos-port
ref/symbols.txt, ref/docs/SWOS/*.txt formati di file e note (teams.txt, career.txt, ...)
notes/STATUS.md registro dettagliato di ogni sessione (fonte primaria)
notes/addrmap.json mappa di delta ENGLISH → ITALIAN per oggetto
docs/ questo manuale (src/ = sorgenti nelle due lingue, tools/mkdocs.py genera le pagine)
Comandi
cd ~/swos-mod/tools && python3 patch.py # scrive c/SWOS/ITALIAN.EXE e c/SWOS/DATA/TEAM.020
python3 tools/salists.py c/SWOS/SALVA.CAR # decodifica le liste SA / Intercontinentale di un salvataggio
ndisasm -b32 -o 0xA1000 file.bin # disassemblatore (Homebrew), nasm per assemblare
Dipendenze: Python 3, nasm e ndisasm nel PATH. Il gioco gira in DOSBox-X con il CD montato dall'ISO originale. Il build è deterministico: stessi input, stesso md5 (il generatore casuale ha seme fisso 1997).
Ordine del build in patch.py:
patch_teams: ricostruisce TEAM.020 (Serie A e B originali + 36 club C1/C2) e restituisce la tabella di rimappatura degli indici.patch_exe: aggiunge una pagina da 4 KB all'oggetto 1 (add_object_page), prepara il pool di stringhe, rimappa i club italiani nelle liste delle coppe europee, applica la patch Italia (patch_italy), poi tuttosacups.patch(coppe, tabelle, carriera, fine stagione), imposta vsize e flag dell'oggetto 1, salva ricostruendo le tabelle dei fixup.
3Regole d'oro
Ognuna di queste regole viene da un bug reale incontrato durante lo sviluppo.
STR_BASE = obj2+0x16F8 (non "aChairmanScenes" come dice il disassemblato inglese). Un nome messo nell'oggetto 1 appare come spazzatura. Per i nomi nuovi c'è strpool.py, che oggi è praticamente pieno.cseg_9487A verifica che la somma dei 2000 byte di leaguesTableCopy sia 0 modulo 256; se non lo è, il gioco si blocca (int 3; jmp $). Qualsiasi dato parcheggiato in tabelle del gioco deve portarsi dietro un byte di bilanciamento.movs, rep). Il registro ES non è garantito uguale a DS nei punti in cui entriamo. Tutte le copie nel codice aggiunto sono cicli byte per byte attraverso DS.D0 = obj2+0x315B7 …). I registri x86 non vengono preservati fra chiamate del gioco.assert. Questo rende possibile il port sugli altri eseguibili (inglese, francese, tedesco) e protegge da errori silenziosi.4Formato LE e fixup
ITALIAN.EXE è un eseguibile DOS/4GW in formato LE (Linear Executable) con due oggetti principali:
| Oggetto | Contenuto | Base di link | Note |
|---|---|---|---|
| obj1 | codice | 0x10000 | vsize originale 0xA09C9 → 0xA1000 (sessione 2) → 0xA2000 (pagina aggiunta). Reso scrivibile (flag 0x2) perché il codice di fine stagione riscrive le liste delle coppe che stanno nella nuova pagina. |
| obj2 | dati | 0xC0000 | tutte le variabili del gioco, i buffer di carriera, le tabelle delle competizioni, le stringhe |
Gli indirizzi nel disassemblato (cseg_8B2D3, dseg_D8CAA) sono VA dell'exe inglese. Per l'italiano: offset nell'oggetto = VA − base, poi si applica il delta di notes/addrmap.json (11 tratti per obj1, 16 per obj2). In pratica, per ogni routine ci si fida della firma trovata nell'exe italiano; la mappa serve solo per orientarsi.
Fixup
Ogni indirizzo assoluto nel file ha un record di fixup interno (oggetto sorgente, offset nella pagina, oggetto destinazione, offset destinazione). Quando si sovrascrive un'istruzione che conteneva un indirizzo, il suo fixup va rimosso (p.remove), altrimenti il loader corrompe i byte nuovi. Quando si aggiunge un indirizzo, va aggiunto il fixup (p.add_ptr). lepatch.save ricostruisce la tabella dei fixup; se cresce oltre lo spazio disponibile sposta in avanti le pagine dati (finora di 0x800 byte) e aggiorna gli offset dell'header.
Pagina aggiunta
add_object_page(data, 1) aggiunge una pagina fisica da 4 KB in fondo al file, la inserisce nella mappa delle pagine subito dopo quelle dell'oggetto 1, aggiunge una voce vuota nella tabella delle pagine dei fixup, sposta di +4/+8 gli offset delle tabelle dell'header e incrementa page_idx dell'oggetto 2. Tutto il codice e i dati delle coppe sudamericane vivono in questa pagina, da obj1+0xA1000. Oggi è occupata fino a circa obj1+0xA1932 (circa 1,7 KB liberi). Lo spazio lasciato libero nella vecchia cavità 0xA0A18–0xA1000 (circa 1,5 KB) è di nuovo disponibile.
5Gli strumenti
- le.py
- Parser LE in sola lettura: oggetti, mappa pagina → file,
obj_bytes(n),fixups()che restituisce (obj sorgente, offset, tipo, obj destinazione, offset destinazione, …). Usato per tutte le verifiche. - lepatch.py
- Classe
LEPatch:put/getper oggetto e offset,target(obj, off)legge dove punta un fixup,add_ptr,remove,retarget,set_vsize,add_flags,save(ricostruzione fixup + spostamento pagine dati). Funzioneadd_object_page. Andata e ritorno senza modifiche = file identico. - nasmcave.py
- Assembla sorgenti nasm (scritti come stringhe dentro
sacups.py) a un indirizzo della cavità. I simboli arrivano come{nome: (oggetto, offset)}. Il sorgente viene assemblato tre volte: normale, con obj1 spostato di 0x01000000 e con obj2 spostato. Ogni dword che cambia è un riferimento assoluto e riceve il fixup verso l'oggetto giusto; i salti relativi dentro obj1 non cambiano e non ne hanno bisogno.labels()legge il file map di nasm per gli indirizzi delle etichette. - strpool.py
- Spazio per nuove stringhe nell'oggetto 2: le stringhe mai referenziate
.COPPA AFRICANA CLUB******eCOPPA SUDAMERICANA*****, più il riempimento con asterischi della secondaCOPPA EUFAe diERRORE DISCO(il testo resta intatto). Contiene COPA LIBERTADORES, SUPERCOPA, COPA CONMEBOL, SERIE C1, SERIE C2 e un nome accorciato per COPPA INTERCONTINENTALE. Pieno: resta 1 byte. - sacups.py
- Il cuore del mod: strutture delle coppe sudamericane e dell'Intercontinentale, tabelle continentali, sorgenti nasm
CAREER_ASM(inizio stagione e caricamento) eQUALIFY_ASM(fine stagione), ricerca di tutte le firme e installazione degli agganci. - patch.py
- Punto d'ingresso del build: Italia (base dei numeri globali, struttura a 4 divisioni), TEAM.020, rimappatura dei club italiani nelle coppe europee.
- c1c2.py / c1c2_rosters.py
- Club di C1/C2 (maglie, divisione), rose reali 1996-97 con allenatori, obiettivi di forza, costruzione dei record squadra (sezione 8).
- salists.py
- Legge un
.CARe stampa il blocco salvato: formato (SA o S2), somma di controllo, liste Libertadores/Supercopa/CONMEBOL, coppia dell'Intercontinentale. - addrmap.py
- Genera
notes/addrmap.jsonconfrontando ENGLISH.EXE e ITALIAN.EXE a finestre mascherate (i byte dei fixup esclusi). - asmbytes.py
- Estrae i byte
dbdi un'etichetta dati daref/swos.asm, utile per cercare le stesse tabelle nell'exe italiano. - mkdocs.py
- Genera
docs/index.html(inglese) edocs/it.html(italiano) per GitHub Pages dai sorgenti indocs/src/.
6Mappa della memoria (exe italiano)
Offset relativi all'oggetto. Tra parentesi il nome nel disassemblato inglese. Tutti trovati per firma nel build.
Oggetto 2: dati
| Offset | Nome | Significato |
|---|---|---|
| 0x16F8 | STR_BASE | base degli offset dei nomi di leghe e coppe |
| 0x71C8 | dseg_C707E | lista delle competizioni internazionali (45 voci) → spostata in obj1 |
| 0x8032 | italyTable | struttura lega Italia originale (42 B) → sostituita da una in obj1 |
| 0x8C66 | competitionsTable | 256 puntatori per nazione/continente |
| 0x9122 | southAmericaTable | tabella del Sudamerica → spostata in obj1 |
| 0x96D6 | careerFileBuffer | inizio del salvataggio carriera (offset 0 del .CAR) |
| 0x16E20 | selTeamNumber | squadra del giocatore (byte basso = file, alto = indice) |
| 0x16E26 | dseg_D6CDC | divisione del giocatore |
| 0x17FB2 | someLeaguesTable | 2000 byte, uno per numero globale di squadra (promozioni/retrocessioni); offset 0xE8DC nel .CAR |
| 0x18DF4 | dseg_D8CAA | puntatori ai contest dei 5 slot (+4 per slot, sezione 11) |
| 0x18E08 | dseg_D8CBE | tipo di coppa europea del giocatore (trofeo, reputazione, premio) |
| 0x18E0A | dseg_D8CC0 | tabella posizioni dei play-off (slot 4) |
| 0x18E54 / 0x199A6 / 0x1A22C / 0x19DE9 | buffer slot 1 / 2 / 3 / 4 | 0x443 byte ciascuno |
| 0x1F640 | competitionFileBuffer | buffer slot 0 (il campionato) |
| 0x1F67D | dseg_DF533 | regole sostituzioni del campionato (3 word: 5, 2, 8) |
| 0x20A79 | dseg_E092F | quale coppa è nello slot 3 (0 CC, 1 CWC, 2 UEFA; il mod aggiunge 3 Lib, 4 Sup, 5 CON; −1 nessuna) |
| 0x315B7 … | D0 D1 D2 … D7 A0 … A4 | pseudo-registri: D0 0x315B7, D1 0x315BB, D2 0x315BF, D7 0x315D3, A0 0x315D7, A1 0x315DB, A2 0x315DF, A3 0x315E3, A4 0x315E7 |
| 0x4EFF3 | DIY_competitionStart | contest "di lavoro": le routine copiano qui uno slot, lo elaborano e lo ricopiano |
| 0x5953C | leaguesTableCopy | copia di someLeaguesTable usata a fine stagione (checksum!) |
| 0xC058B | dseg_18070F | vincitore della Coppa Campioni (detentore); il mod lo usa anche per la Libertadores |
Oggetto 1: codice (routine del gioco usate dal mod)
| Offset | Routine | Ruolo |
|---|---|---|
| 0x7B127 | cseg_8B2D3 | carica un contest in uno slot (D0 = slot, A0 = contest, D1, D2) |
| 0x7D4B5 | cseg_8D661 | il club del giocatore è nella lista di questa coppa? |
| 0x7D917 | cseg_8DAC3 | slot 4: play-off della divisione, con squadre segnaposto |
| 0x7EB02 | cseg_8ECAE | fine campionato: squadre vere nei play-off |
| 0x82BA9 | cseg_92D55 | fine Coppa Campioni: vincitore → dseg_18070F |
| 0x2915F | GetCurrentSeasonPointer | record della stagione (nomi delle competizioni a +1Ah, +1Eh…) |
| VA 0x91345 | assert 80 squadre | quello che bloccava il gioco nel 1997 |
| 0xA09D0 | (nuovo) | struttura lega Italia a 4 divisioni (70 B) |
| 0xA1000–0xA1932 | (nuovo) | pagina aggiunta: coppe SA, Intercontinentale, tabelle, codice carriera e fine stagione |
7Italia a 4 divisioni
Il blocco di fine stagione del 1997
Ogni squadra ha un numero globale (0–1999) usato come indice in someLeaguesTable. L'Italia partiva da 424 con solo 51 numeri: aggiungere squadre sconfinava nella Lettonia (475). Le squadre in più collidevano, e a fine stagione l'assert cseg_91428 (80 squadre nelle liste europee, contatori 16/32/32) finiva in int 3; jmp $. Nel 1997 il mod originale inseriva byte fra la struttura dell'Italia e quella della Lettonia: circa 50 strutture di leghe raggiunte da puntatori fissi venivano lette sfasate e il gioco si bloccava.
La soluzione: la base dell'Italia passa a 1850 (numeri 1794–1999 liberi) nella tabella teamsCountryNumbers, e la nuova struttura della lega va nella cavità di obj1, con il fixup di italyTable reindirizzato (e allargato da 16 a 32 bit).
Struttura di una lega
header 10 byte: [num, type, country, start, end, nameOff, 0, 0, 0, nDiv]
02 03 35
per divisione 6 byte: [squadre, promosse dirette, squadre play-off promozione, retrocesse, play-off retrocessione, offset dati play-off]
00
per divisione 2 dword: offset dei nomi (da STR_BASE)
Il mod usa DIVISIONS in patch.py: A (18, 0, 0, 4), B (20, 4, 0, 4), C1 (18, 4, 0, 4), C2 (18, 4, 0, 0). L'Inghilterra ha già 4 divisioni con lo stesso formato: il motore gestisce 4 livelli senza modifiche al codice.
8Squadre e rose C1/C2
TEAM.020
Il file inizia con un word big-endian (numero squadre) seguito da record da 684 byte. Nel record: +1 ordinale, +2 numero globale (big-endian), +5 nome (17 B), +25 divisione (0 A, 1 B, 2 C1, 3 C2; 4 era "non-league"), +26 prima maglia (5 B), +31 seconda maglia, +36 allenatore (23 B), +60 ordine dei 16 giocatori in formazione, +76 16 giocatori da 38 byte.
| Byte giocatore | Contenuto |
|---|---|
| +0 | nazionalità (18 Italia, 13 Germania, 68 Argentina, 109 Gabon…) |
| +2 | numero di maglia |
| +3 | nome (23 B, terminato da zero: massimo 22 caratteri) |
| +26 | bit 5–7 ruolo (0 G, 1 RB, 2 LB, 3 D, 4 RW, 5 LW, 6 M, 7 A), bit 3–4 carnagione (00 chiara, 10 scura, 01 rossa) |
| +28…+31 | 7 abilità a nibble: passaggio, tiro, colpo di testa, contrasto, controllo, velocità, finalizzazione. Il bit 3 del nibble segna un'abilità principale; il valore reale è v & 7 |
| +32 | prezzo (indice 0–49: 25K … 15M+). Il portiere non ha abilità: il prezzo è la sua bravura |
Il file finale ha 74 squadre (A 18, B 20, C1 18, C2 18), ordinate alfabeticamente, e rimane sotto il limite di circa 93 squadre per nazione (teamFileBuffer da 64014 B). Poiché l'ordine cambia rispetto all'originale, remap_italian_cup_teams corregge gli indici dei club italiani nelle liste delle coppe europee (per nome).
Come si costruisce un club (c1c2.real_team)
- Rosa reale dalla pagina it.wikipedia "<club> 1996-1997" (sezione rosa), divisa in P/D/C/A. I titolari noti vanno per primi; gli altri seguono l'ordine della pagina.
- Si sceglie come modello uno dei 13 club non-league originali con un mix di ruoli compatibile (G/D/M/A contati sulle 16 posizioni), a caso fra i migliori.
- Ogni posizione del modello riceve il prossimo nome del ruolo corrispondente; se il ruolo è esaurito si prende da un ruolo vicino. Per i club che esistevano già, un ruolo mancante (per esempio il secondo portiere) si riempie con un giocatore originale SWOS di quel club.
- Abilità e prezzi del modello si spostano di passi interi verso un obiettivo di prezzo medio ricavato dalla classifica reale (1 passo di abilità = 2 unità di prezzo, limite −3…+2). C1: 15.5–19; C2: 12–15; la Serie B originale ha media 17.9.
- I nomi già presenti nel file (trasferimenti a metà stagione) vanno in fondo alla lista del ruolo, per evitare doppioni. Nomi oltre 22 caratteri: iniziale del nome (
M. DAL COMPARE).
c1c2_rosters.py (l'ordine decide chi entra nei 16) e rilanciare il build. Per cambiare la forza di un club: TARGET.9Struttura delle competizioni
[0] id contest [1] tipo: 1 eliminazione diretta, 2 gironi + eliminazione
[2] nazione (FF = qualsiasi)
[3],[4] mesi: 8 × mese contato da gennaio del primo anno della stagione (≥ 12 = anno dopo)
[5] nomi a +5+[5]: due dword, offset da STR_BASE (stringhe nell'oggetto 2!)
[7] lista squadre a +7+[7]: coppie di byte (numero file TEAM, indice)
[8] 1 = lista fissa
tipo 1: [10] numero squadre, [14..] un byte per turno (0x94 andata e ritorno, 0x14 finale secca), poi 00
tipo 2: [15] numero squadre, gironi da 4
| Id | Competizione | Formato | Mesi |
|---|---|---|---|
| 0x6C | COPA LIBERTADORES | clone della Coppa Campioni: 16 squadre, 4 gironi da 4, poi eliminazione | come le coppe europee (settembre–maggio) |
| 0x6D | SUPERCOPA | 16 squadre, andata e ritorno, finale secca (clone della Coppa delle Coppe) | idem |
| 0x6E | COPA CONMEBOL | idem | idem |
| 0x6F | COPPA INTERCONTINENTALE | tipo 1, 2 squadre, un turno 0x14 (finale secca); default Juventus – River Plate (Tokyo, novembre 1996) | 0x58–0x60: dicembre, finestra fino a gennaio |
Id liberi: 0x70–0x7B (le leghe usano 0x24–0x6B, 0x7C le personalizzate, 0x7D–0xB1 le coppe). Le squadre sudamericane vengono da TEAM.043 (ARG), 045 (BOL), 046 (BRA), 048 (CHI), 049 (COL), 050 (ECU), 064 (PAR), 065 (PER), 071 (URU), 077 (VEN).
10Tabelle continentali e vista mondo
ViewWorldMenu → ChooseCompetitionMenu → SelectTeamsFinalMenu: ogni puntatore nella voce del continente di competitionsTable diventa un pulsante. Le coppe europee non hanno un trattamento speciale, quindi basta aggiungere puntatori.
Scegliendo una competizione, cseg_3AA17 controlla se è uno dei 4 slot del giocatore (stato reale). Altrimenti cseg_3ABB2 la rigenera al volo: seme casuale = id del contest << 8 | stagione, poi simula fino alla data corrente. Le competizioni in cui il giocatore non c'è non hanno stato persistente: per mostrarle basta che esistano struttura e lista squadre.
- Tabella Sudamerica spostata da obj2+0x9122 a obj1+0xA10E4: qualificazioni mondiali, Copa América, Libertadores, Supercopa, CONMEBOL, Intercontinentale.
- Tabella Europa spostata anch'essa in obj1: 252, Coppa Campioni, Coppe, UEFA, Intercontinentale, −1, 43 nazioni. Cinque pulsanti verdi ci stanno.
- Lista internazionale (
dseg_C707E, obj2+0x71C8) spostata a obj1+0xA1154 con le 4 coppe aggiunte: per questi contest il campo DIY 0x3B vale 0 (simulazione,CalculateViewResult).
11Carriera: i 5 slot
In carriera ogni squadra ha 5 slot di competizione. Per ogni slot il gioco tiene un puntatore al contest (dseg_D8CAA + 4 × slot, 0 = vuoto) e un buffer da 0x443 byte con la competizione in corso. Il calendario scorre gli slot e gioca le partite della settimana.
| Slot | Puntatore | Buffer | Uso originale | Uso nel mod |
|---|---|---|---|---|
| 0 | 0x18DF4 | 0x1F640 | campionato | invariato |
| 1 | 0x18DF8 | 0x18E54 | coppa nazionale | Supercopa per i club sudamericani (nessuna coppa nazionale in Sudamerica) |
| 2 | 0x18DFC | 0x199A6 | coppa di lega, oppure amichevoli e tournée | Intercontinentale se lo slot è libero (Italia, tutti i paesi sudamericani) |
| 3 | 0x18E00 | 0x1A22C | coppa europea | anche Libertadores / Supercopa / CONMEBOL |
| 4 | 0x18E04 | 0x19DE9 | play-off della divisione | Intercontinentale per i paesi con coppa di lega, se la divisione non ha play-off |
Intestazione del buffer
+0 tipo (word), +27h puntatore al nome, +2Bh numero dello slot (word), +2Dh id del contest, +3Dh/+3Fh/+41h regole sostituzioni (5 in panchina, 2 cambi, 8).
cseg_8B2D3 (carica un contest in uno slot)
- Scrive il puntatore in
D8CAA[slot]e+2Bh = slot, poi controlla il tipo: slot 1 e 2 accettano solo coppe di tipo 1 (eliminazione), slot 4 solo tipo 1, slot 0 niente tipo 1. Un tipo sbagliato manda inint 3. - Slot ≠ 4: prende le squadre dalla lista del contest (D2 = 0) o dalle squadre selezionate (D2 ≠ 0), costruisce il tabellone e calcola le date (
cseg_8B71C+cseg_8B8B8). - Slot 4: riempie la lista con
careerTeamripetuto (segnaposto) e non calcola le date. A fine campionatocseg_8ECAElegge daD8CC0quali posizioni di classifica vanno ai play-off, sostituisce i segnaposto (cseg_32E15), calcola le date, copia le regole sostituzioni del campionato e impostaD6B62 = 1("durante i play-off non puoi essere esonerato").
12Inizio stagione
ContinueCareerNextSeason (e InitCareer) chiamano InitializeNewSeason: slot 0, 1, 2 dalla tabella della nazione, poi la catena delle coppe europee (cseg_8D661 con D7 = 0, 1, 2 su Coppa Campioni, Coppe, UEFA), infine call cseg_8DAC3 (slot 4, play-off).
flowchart TD
A[InitializeNewSeason: slot 0, 1, 2] --> B{Club in una coppa europea?}
B -- sì --> F[euro_found: int_step, poi E092F = D7]
B -- no --> I[init_sa]
I --> I1{Blocco salvato 'S2' o 'SA'?}
I1 -- no, carriera nuova --> I2[DEFAULTS in liste + INTLIST, lists_out]
I1 -- sì --> I3
I2 --> I3{Slot 1 vuoto e club nella Supercopa?}
I3 -- sì --> I4[Supercopa nello slot 1]
I3 -- no --> I5
I4 --> I5[int_step: Intercontinentale nello slot 2 se libero e club finalista]
I5 --> I6[Slot 3: Libertadores, Supercopa se non già in slot 1, CONMEBOL; D8CBE = D7 - 3]
F --> D[call cseg_8DAC3 dirottata su int4_step]
I6 --> D
D --> D1[cseg_8DAC3: play-off in slot 4 se la divisione li ha]
D1 --> D2{Slot 4 vuoto, club finalista, Intercontinentale non in slot 2?}
D2 -- sì --> D3[Intercontinentale nello slot 4 con lo scambio dei buffer]
D2 -- no --> Z[fine]
D3 --> Z
Intercontinentale nello slot 4: lo scambio dei buffer
Caricare la coppa direttamente nello slot 4 darebbe una partita "tua squadra contro tua squadra" senza date. int4_step invece:
- chiama il vero
cseg_8DAC3; se lo slot 4 è occupato (play-off) si ferma; - azzera l'id del buffer 4 (così un vecchio buffer non viene ripristinato al caricamento);
- copia il buffer dello slot 2 (coppa di lega) nel buffer 4;
- chiama
cseg_8B2D3con slot 2: l'Intercontinentale viene costruita completa, con le finaliste e le date; - scambia i due buffer da 0x443 byte: Intercontinentale nello slot 4, coppa di lega di nuovo intatta nello slot 2;
+2Bh = 4, regole sostituzioni dal campionato (come fa il gioco per i play-off), ripristina il puntatore dello slot 2,D8CBA = INT.
L'aggancio playoffs all'ingresso di cseg_8ECAE esce subito quando lo slot 4 contiene l'Intercontinentale: altrimenti a fine campionato il gioco la ricostruirebbe leggendo una tabella dei play-off inesistente.
13Caricamento di un salvataggio
ProcessCareerFile ricostruisce i puntatori degli slot dalla tabella della nazione (slot 0–2), dal record della divisione (slot 4) e da E092F (slot 3). I buffer degli slot sono nel salvataggio, i puntatori no: il mod li ripristina guardando l'id nei buffer.
load_slot1: legge il blocco salvato (sezione 15) nelle liste; se lo slot 1 è vuoto e il buffer 1 ha id 0x6D → Supercopa; se lo slot 2 è vuoto e il buffer 2 ha id 0x6F → Intercontinentale; se lo slot 4 è vuoto e il buffer 4 ha id 0x6F → Intercontinentale.load_slot3: estende la mappatura diE092F: 3 → Libertadores, 4 → Supercopa, 5 → CONMEBOL.
14Fine stagione
cseg_91428: combinazione delle liste, per la nazione del giocatore e poi per ogni nazione europea simula o legge la classifica (cseg_9153F), promozioni, coppe; cseg_92D55 / 9307A / 9339D chiudono Coppa Campioni, Coppe e UEFA; cseg_93974 aggiunge le qualificate; assert finale 80 squadre (16/32/32); cseg_92BBF riammette i detentori.
Il mod dirotta la chiamata a cseg_92D55 (obj1+0x812C4) su sa_qualify:
[A4] = Libertadoresecall cseg_92D55 + 10(salta la prima istruzione che imposterebbe la Coppa Campioni): chiude la Libertadores (giocata dal giocatore o simulata concseg_3ADAE) e scrive la vincitrice indseg_18070F→LIBWIN.- Per ogni nazione sudamericana: simula la massima divisione con
cseg_915ED(la stessa simulazione della vista mondo, così le classifiche coincidono). Per la divisione del giocatore legge invece lo slot 0. Dal DIY: +6Dh offset delle righe in ordine di classifica (righe da 12h byte), +12Dh numero squadra per riga. - Scrive le qualificate: Libertadores = campioni dei 10 paesi + seconde di ARG, BRA, URU, PAR, COL, CHI (4 gironi misti,
LIB_Q); CONMEBOL = piazzamenti successivi (CON_Q, 8 coppie). Supercopa: lista fissa di ex campioni. - Regole della detentrice (reali 1997): se
LIBWINnon è già in Libertadores prende il posto della seconda del suo paese (altrimenti CHI2), e la squadra esclusa prende il suo posto in CONMEBOL; se non è in Supercopa, ne prende l'ultimo posto. call cseg_92D55vero: chiude la Coppa Campioni. Entrambi i rami finiscono incseg_92F1C, che scrive sempre il vincitore indseg_18070F.INTLIST[0] = [dseg_18070F],INTLIST[1] = LIBWIN(ciascuno saltato se 0FFFFh), poilists_outeret.
flowchart LR S[cseg_91428 fine stagione] --> Q[sa_qualify] Q --> L[Fine Libertadores: LIBWIN] L --> C[Classifiche SA → liste Lib e CON] C --> H[Regole detentrice] H --> E[cseg_92D55 vero: vincitrice CC] E --> N[INTLIST = CC, LIBWIN] N --> O[lists_out: blocco salvato + checksum] O --> R[ritorno a cseg_91428]
15Persistenza nel salvataggio
Il salvataggio contiene someLeaguesTable (2000 byte, uno per numero globale). I numeri 1730–1849 non appartengono a nessuna squadra: il mod ci parcheggia i suoi dati dal numero 1740 (obj2+0x1867E), con una copia speculare in leaguesTableCopy (obj2+0x59C08), perché a fine stagione il gioco copia una tabella sull'altra (cseg_8CC0A e cseg_8CC4E). InitCareer azzera la tabella: una tabella a zero significa carriera nuova.
| Offset | Formato S2 (attuale) | Formato SA (sessione 12) |
|---|---|---|
| +0 | marcatore 'S2' (word 0x3253) | marcatore 'SA' (0x4153) |
| +2 | Libertadores: 16 × (file, indice) | idem |
| +34 | Supercopa | idem |
| +66 | CONMEBOL | idem |
| +98 | Intercontinentale: 2 × (file, indice) | byte di bilanciamento |
| +102 | byte di bilanciamento | — |
Il byte di bilanciamento vale −(somma dei byte precedenti del blocco) mod 256, così la somma dell'intero blocco è 0 e quella della tabella resta 0 per il controllo di cseg_9487A. Lettura: 'S2' tutto dal salvataggio; 'SA' le tre liste più la coppia di default Juventus – River; nessun marcatore → valori del 1997 (DEFAULTS, 100 byte). Il primo fine stagione di una carriera 'SA' la riscrive in 'S2'.
# verifica in Python
blk = save[SAVED:SAVED + 103]
assert blk[:2] == b'S2' and sum(blk) % 256 == 0
16Trofei e reputazione
I bit dei trofei si costruiscono in cseg_39392 dai flag D6C64 (Coppa Campioni, bit 2, reputazione +100), D6C68 (UEFA, bit 3, +50) e D6C66 (Coppe, bit 4, +50). cseg_3D7CB sceglie il flag da dseg_D8CBE, cseg_5DEB9 il premio. Il mod imposta D8CBE = D7 − 3 quando entra una coppa sudamericana:
- Libertadores conta come Coppa Campioni;
- Supercopa nello slot 3 conta come Coppa delle Coppe;
- CONMEBOL conta come UEFA;
- Supercopa nello slot 1 conta come coppa nazionale.
Il riepilogo della stagione (ManagementRecordMenu) mostra già il nome corretto ("COPA LIBERTADORES - WINNERS"). Nomi e bit dedicati (i bit 10–15 sono liberi) richiederebbero nuove stringhe, ma il pool è pieno.
17Tabella degli agganci
Ogni aggancio sovrascrive istruzioni originali con un jmp o cambia il bersaglio di una call; i fixup delle istruzioni sovrascritte vengono rimossi.
| Sito (obj1) | Byte | Originale | Diventa |
|---|---|---|---|
| 0x7D307 | 11 | mov [E092F],-1; jmp done (nessuna coppa europea) | jmp init_sa |
| 0x7D312 | 12 | mov ax,[D7]; mov [E092F],ax (coppa europea trovata) | jmp euro_found |
| 0x7D31E | rel32 | call cseg_8DAC3 | call int4_step |
| 0x237C1 | 10 | cmp [E092F],-1; jz out (ProcessCareerFile) | jmp load_slot1 |
| 0x237FD | 12 | cmp [E092F],2; jz map; jmp out | jmp load_slot3 |
| 0x7EB02 | 13 | cmp dword [D8CBA],0; jz out (cseg_8ECAE) | jmp playoffs |
| 0x812C4 | rel32 | call cseg_92D55 (fine stagione) | call sa_qualify |
Modifiche ai dati: base Italia nella tabella dei numeri globali; fixup di italyTable reindirizzato alla nuova struttura; tabelle Sudamerica, Europa e lista internazionale reindirizzate in obj1; nomi nuovi nel pool di stringhe; indici dei club italiani nelle liste europee; vsize e flag dell'oggetto 1.
init_sa, sa_qualify…) cambiano quando il codice cresce: l'output di patch.py ne stampa i principali, e nasmcave.labels() li restituisce tutti.18Verifica senza giocare
Prima di consegnare una build per le prove, questi controlli vengono fatti ogni volta:
- Il build passa tutti gli
assert(firme uniche, bersagli attesi). - Rilettura con
le.py: le tabelle dei fixup si analizzano senza errori. ndisasmdel codice generato e di ogni sito di aggancio: ogni salto va all'etichetta giusta.- Ogni operando assoluto ha un fixup verso l'oggetto e l'offset corretti (lo script annota ogni riga del disassemblato con i fixup).
- Confronto con la build precedente: i byte cambiano solo nei punti voluti; i fixup cambiati fuori dalla pagina aggiunta devono essere solo quelli attesi.
- Per i dati salvati: emulazione in Python della somma di controllo su una copia di un salvataggio vero.
Se un controllo fallisce e non si risolve, si rimette la build precedente (ITALIAN_S12.EXE è l'ultima provata a fondo).
19Limiti e questioni aperte
- Gironi di C1/C2. Nel 1996-97 la C1 aveva 2 gironi e la C2 3. Il mod usa un girone unico per serie. L'intera realtà (128 squadre) non entra nel limite di circa 93 squadre per nazione. La C1 su due gironi entrerebbe (92), ma servono promozioni e retrocessioni fra divisioni parallele, verifiche sul limite delle 80 squadre di carriera e sui numeri globali: stima 2–3 sessioni.
- Intercontinentale nello slot 4. Una finalista in una divisione con play-off resta senza Intercontinentale. La routine
cseg_8BF89(chiamata dacseg_8B2D3) non è stata analizzata: se tiene dati legati al numero di slot, lo scambio dei buffer potrebbe lasciarli incoerenti. Nel riepilogo stagionale l'Intercontinentale nello slot 4 non ha nome. - Stringhe. Il pool nell'oggetto 2 è pieno: nomi nuovi richiedono altro spazio (altre stringhe inutilizzate, oppure una pagina dati aggiunta all'oggetto 2 con la stessa tecnica usata per l'oggetto 1).
- Trofei dedicati. Le coppe sudamericane usano i trofei delle coppe europee dello stesso rango.
- Salvataggi vecchi. Tengono in cache i dati delle squadre di quando sono stati creati: le rose nuove si vedono solo in una carriera nuova.
- Doppioni. Restano 5 nomi ripetuti (trasferimenti a metà stagione o omonimi). I dati SWOS originali ne avevano 61.
- Panchina inglese (dopo la 1.0). Nella formazione prima della partita le squadre inglesi mostrano 14 giocatori (11 + 3 in panchina). Succede anche con il gioco originale, e
TEAM.008ha 16 giocatori per club: è una regola del gioco. Da capire da dove venga il numero di riserve (regole a +3Dh…41h del buffer, valori 5/2/8 impostati daInitCareer) per portarlo a 5.
20Storico e prossimi passi
| Sessione | Contenuto | Prova |
|---|---|---|
| 1–2 | Strumenti LE; causa del blocco del 1997; Italia a 4 divisioni con base 1850 | passata |
| 5 | Club reali C1/C2 1996-97 | passata |
| 9 | Coppe sudamericane nei menu e nella vista mondo; pool di stringhe | passata |
| 10 | Club sudamericani nelle coppe in carriera; qualificate dalle classifiche; detentrice | passata |
| 11 | Supercopa nello slot 1; pagina aggiunta all'oggetto 1 | passata |
| 12 | Persistenza delle liste con checksum; scambio CONMEBOL; trofei; Intercontinentale nei menu e nello slot 2 | persistenza, slot 2, CONMEBOL trofei |
| 13 | Finaliste dell'Intercontinentale a fine stagione; formato S2; Intercontinentale nello slot 4 | fine stagione, S2 slot 4 |
| 14 | Rose reali 1996-97 e forza calibrata per tutti i 36 club di C1/C2 | passata |
Roadmap
- Prove delle sessioni 12–14 e correzioni.
- Port su ENGLISH/FRENCH/GERMAN.EXE dello stesso CD. Da rendere generici:
STR_BASE, gli offset degli pseudo-registri scritti nelle espressioni regolari disacups.py, le stringhe sorgente del pool (testi italiani), i nomi visualizzati, gli indirizzi degli slot (tutti trovati per firma: da ricontrollare uno per uno). - Patcher in un unico file HTML/JS che distribuisce solo differenze: l'utente sceglie i propri file originali, il patcher controlla gli md5, applica le differenze e scarica i file modificati; README in italiano e inglese; per disinstallare si tengono gli originali.
- Dopo la 1.0: panchina da 5 per le squadre inglesi; prova dell'Intercontinentale nello slot 4; C1 su due gironi, dopo una sessione di sola analisi.
Crediti: disassemblato e documentazione dei formati da swos-port (zlatkok). Mod: Davide Lorigliola, con Claude.