Records¶
The dataclass each reader’s Table holds, grouped by the reader family that produces it.
Table itself, and the value-wrapper and metadata types shared across every reader, live on
their own pages: see Table and Enums.
People¶
players(), contracts(), suspensions(), staff() and staff_lists().
- class fmsave.Player(uid, name, first_name, last_name, common_name, full_name, legal_name, birth_date, age, nation_id, second_nation_ids, home_grown_nation_ids, home_grown_club_uids, home_grown_club_names, height_cm, ability, reputation, club_uid, club_name, club_short_name, club_nation_id, club_fa_nation_id, club_reputation, club_last_league_position, team_id, team_slot, team_club_uid, team_club_name, club_join_date, natural_positions, accomplished_positions, personality, attributes, raw_attributes, left_foot, right_foot, raw_left_foot, raw_right_foot, positions, transfer_value, transfer_value_state, raw_condition, raw_match_sharpness, traits, trait_bits, on_loan, loan_parent_club_uid, loan_parent_club_name, loan_start, loan_end, contract, suspensions)[source]¶
A player record from the save’s game database.
Person fields (name, birth date, nationality, home-grown ids, personality and traits) come from the player’s person block; they stay None or empty when no block validates.
- Parameters:
uid (int)
name (str | None)
first_name (str | None)
last_name (str | None)
common_name (str | None)
full_name (str | None)
legal_name (str | None)
birth_date (date | None)
age (int | None)
nation_id (int | None)
height_cm (int)
ability (Ability)
reputation (Reputation)
club_uid (int | None)
club_name (str | None)
club_short_name (str | None)
club_nation_id (int | None)
club_fa_nation_id (int | None)
club_reputation (int | None)
club_last_league_position (int | None)
team_id (int | None)
team_slot (int | None)
team_club_uid (int | None)
team_club_name (str | None)
club_join_date (date | None)
personality (Personality | None)
attributes (Attributes)
raw_attributes (Attributes)
left_foot (int)
right_foot (int)
raw_left_foot (int)
raw_right_foot (int)
positions (Positions)
transfer_value (int | None)
transfer_value_state (TransferValueState)
raw_condition (int)
raw_match_sharpness (int)
traits (tuple[CodedValue[Trait], ...])
trait_bits (int | None)
on_loan (bool | None)
loan_parent_club_uid (int | None)
loan_parent_club_name (str | None)
loan_start (date | None)
loan_end (date | None)
contract (Contract | None)
suspensions (tuple[PlayerSuspension, ...])
- name¶
Display name: common name first, else first plus last name, else legal name (unconfirmed).
- Type:
str | None
- full_name¶
First name plus last name, or None when either is missing (unconfirmed).
- Type:
str | None
- legal_name¶
The save’s stored legal name, or None when the save stores none (unconfirmed).
- Type:
str | None
- birth_date¶
Date of birth (unconfirmed).
- Type:
datetime.date | None
- age¶
Age at the save’s in-game clock date, or None when birth_date or the clock date is unreadable (unconfirmed).
- Type:
int | None
- home_grown_club_uids¶
Uids of clubs the player is considered home grown for, in relation-list order; an entry is None when its club index does not resolve, but still keeps its position. They are Club.uid values and carry its status (unconfirmed).
- home_grown_club_names¶
Denormalised names for home_grown_club_uids, in the same order; None wherever home_grown_club_uids is None (unconfirmed).
- ability¶
Current and potential ability.
- reputation¶
Reputation figures; never threshold on reputation.bucket.
- club_uid¶
Uid of the club the player belongs to: the club fielding his registered team, which for a team an affiliate club stores is the club controlling it. None for a free agent or an unresolved team (unconfirmed).
- Type:
int | None
- club_last_league_position¶
Denormalised last completed season league position of club_uid.
- Type:
int | None
- team_id¶
Id of the player’s registered team, or None for a free agent (unconfirmed).
- Type:
int | None
- team_slot¶
The team’s slot in club_uid’s team list, which counts that club’s own slots first and then the teams it controls at affiliate clubs; None when team_id does not resolve to a club (unconfirmed).
- Type:
int | None
- team_club_uid¶
Uid of the club whose record stores the player’s registered team, when another club controls that team, else None (unconfirmed).
- Type:
int | None
- team_club_name¶
Denormalised full name of team_club_uid; None wherever team_club_uid is (unconfirmed).
- Type:
str | None
- club_join_date¶
Date the player joined his current club; it disagrees with the contract start date for about half of players, so it is not a substitute for it (unconfirmed).
- Type:
datetime.date | None
- accomplished_positions¶
Position codes rated 15 to 17, best first. This is an fmsave classifier label, not text the game itself shows.
- personality¶
Personality profile, or None when no person block validates.
- Type:
- attributes¶
The 52 non-foot attributes, on the 1 to 20 display scale.
- raw_attributes¶
The same 52 attributes, on the 1 to 100 raw scale.
- positions¶
The 15 position ratings.
- transfer_value¶
Transfer value in the save’s base currency, set only when transfer_value_state is OK.
- Type:
int | None
- transfer_value_state¶
How transfer_value was read from the save (unconfirmed).
- raw_condition¶
Condition, on the 0 to 10000 scale the save stores it on. The game shows condition as a percentage, which is this divided by 100.
- Type:
- raw_match_sharpness¶
Match sharpness, on the 0 to 10000 scale the save stores it on. The game shows it as a percentage, which is this divided by 100.
- Type:
- traits¶
Named player traits; an unnamed bit is Trait.UNKNOWN with raw set to the bit number.
- on_loan¶
Whether the player is on loan from the club of his contract in effect: True when he is registered with another club’s team and the save holds a loan for him there that has not ended, False when it does not, and None when his club or his contract in effect is unknown. A player registered with a team his own club controls, such as a B team, is a player of that club, not on loan.
- Type:
bool | None
- loan_parent_club_uid¶
Uid of the club of the contract in effect when on_loan is True, else None. It is the contract’s club_uid under another name, so it carries the same status (unconfirmed).
- Type:
int | None
- loan_start¶
Date the loan began when on_loan is True, else None. It is also None when the loan’s stored start does not decode as a game date, which keeps an unreadable start from costing the player his loan; no save read so far holds such a loan.
- Type:
datetime.date | None
- loan_end¶
Date the loan ends when on_loan is True, else None.
- Type:
datetime.date | None
- contract¶
The player’s assembled contract, or None when no chain record and no fallback dates were found.
- Type:
- suspensions¶
The player’s unserved suspensions, in the order the save stores them, including bans the game no longer displays; () when the player has none.
- class fmsave.Attributes(crossing, dribbling, finishing, heading, long_shots, marking, off_the_ball, passing, penalty_taking, tackling, vision, handling, aerial_reach, command_of_area, communication, kicking, throwing, anticipation, decisions, one_on_ones, positioning, reflexes, first_touch, technique, flair, corners, teamwork, work_rate, long_throws, eccentricity, rushing_out, punching, acceleration, free_kick_taking, strength, stamina, pace, jumping_reach, leadership, dirtiness, balance, bravery, consistency, aggression, agility, important_matches, injury_proneness, versatility, natural_fitness, determination, composure, concentration)[source]¶
52 non-foot player attributes, shared by the display and raw scales.
- Parameters:
crossing (int)
dribbling (int)
finishing (int)
heading (int)
long_shots (int)
marking (int)
off_the_ball (int)
passing (int)
penalty_taking (int)
tackling (int)
vision (int)
handling (int)
aerial_reach (int)
command_of_area (int)
communication (int)
kicking (int)
throwing (int)
anticipation (int)
decisions (int)
one_on_ones (int)
positioning (int)
reflexes (int)
first_touch (int)
technique (int)
flair (int)
corners (int)
teamwork (int)
work_rate (int)
long_throws (int)
eccentricity (int)
rushing_out (int)
punching (int)
acceleration (int)
free_kick_taking (int)
strength (int)
stamina (int)
pace (int)
jumping_reach (int)
leadership (int)
dirtiness (int)
balance (int)
bravery (int)
consistency (int)
aggression (int)
agility (int)
important_matches (int)
injury_proneness (int)
versatility (int)
natural_fitness (int)
determination (int)
composure (int)
concentration (int)
- class fmsave.Ability(current, potential, potential_range_code)[source]¶
A player’s current and potential ability.
- class fmsave.Reputation(bucket, home, current, world)[source]¶
A player’s reputation figures.
Never threshold on bucket: it is a coarse, lossy bucket that mis-orders players. Use current to rank players instead.
- class fmsave.Positions(gk, sw, dl, dc, dr, dm, ml, mc, mr, aml, amc, amr, stc, wbl, wbr)[source]¶
The 15 position ratings, in the player rating array order.
This is not the tactic or per-match position mask order.
- Parameters:
- class fmsave.Personality(adaptability, ambition, loyalty, pressure, professionalism, sportsmanship, temperament, controversy)[source]¶
A player’s personality profile.
- Parameters:
- class fmsave.Contract(player_uid, player_name, club_uid, club_name, team_id, wage, start, end, end_source, squad_status, kind, clauses, on_loan, loan_parent_club_uid, loan_parent_club_name, loan_start, loan_end, event_count, chain, chain_club_uids, chain_club_names, chain_club_uids_with_terms, chain_club_names_with_terms, unknown)[source]¶
A player’s contract as it stands at the save’s in-game date.
Every field below that a chain record fills comes from one record: the contract in effect, which is the record at the player’s own club that has started and has not ended, or else the record with the latest start on or before the in-game date. Records that start later are agreed future moves, such as pre-contracts and completed transfers that take effect later; they stay in chain but never fill these fields. When no record has started, those fields are None or empty and chain still lists every record.
- Parameters:
player_uid (int)
player_name (str | None)
club_uid (int | None)
club_name (str | None)
team_id (int | None)
wage (int | None)
start (date | None)
end (date | None)
end_source (ContractEndSource)
squad_status (CodedValue[SquadStatus] | None)
kind (CodedValue[ContractType] | None)
on_loan (bool | None)
loan_parent_club_uid (int | None)
loan_parent_club_name (str | None)
loan_start (date | None)
loan_end (date | None)
event_count (int | None)
chain (tuple[ContractChainEntry, ...])
- club_uid¶
Uid of the contracting club of the record in effect, or None when no record is in effect or its team does not resolve (unconfirmed).
- Type:
int | None
- team_id¶
Id of the contracting team of the record in effect, or None (unconfirmed).
- Type:
int | None
- wage¶
Weekly wage from the record in effect, in the save’s base currency (unconfirmed).
- Type:
int | None
- start¶
Contract start date, from the record in effect (unconfirmed).
- Type:
datetime.date | None
- end¶
Contract end date.
- Type:
datetime.date | None
- end_source¶
Where end was read from.
- squad_status¶
Squad status from the terms of the record in effect, the role agreed in the contract rather than how much the player plays, or None when that record gives no terms up.
- kind¶
The contract’s type from the terms of the record in effect, or None when it gives no terms up.
- clauses¶
Clauses from the terms of the record in effect, or () when it gives no terms up.
- Type:
- on_loan¶
Whether the player is on loan from the club of the contract in effect: True when he is registered with another club’s team and the save holds a loan for him there that has not ended, False when it does not, and None when his club or the contract in effect is unknown. A player registered with a team his own club controls, such as a B team, is a player of that club, not on loan.
- Type:
bool | None
- loan_parent_club_uid¶
Uid of the club of the contract in effect when on_loan is True, else None. It is club_uid under another name, so it carries the same status (unconfirmed).
- Type:
int | None
- loan_start¶
Date the loan began when on_loan is True, else None. It is also None when the loan’s stored start does not decode as a game date, which keeps an unreadable start from costing the player his loan; no save read so far holds such a loan.
- Type:
datetime.date | None
- loan_end¶
Date the loan ends when on_loan is True, else None.
- Type:
datetime.date | None
- event_count¶
Contract event count from the terms of the record in effect, or None (unconfirmed).
- Type:
int | None
- chain¶
Every chain record, oldest first (unconfirmed).
- chain_club_uids¶
Uid of the contracting club for each chain record, in the same order as chain; None where the team does not resolve (unconfirmed).
- chain_club_names¶
Denormalised names for chain_club_uids, in the same order (unconfirmed).
- chain_club_uids_with_terms¶
Uids of the clubs of the chain records that carry their agreed terms and whose team resolves, in chain order (unconfirmed).
- chain_club_names_with_terms¶
Denormalised names for chain_club_uids_with_terms, in the same order (unconfirmed).
- unknown¶
Numeric fields with no known meaning, from the terms of the record in effect; a key is present only when its value was read (unconfirmed).
- Type:
- class fmsave.Clause(kind, parameter, value)[source]¶
One clause attached to a contract, such as a release fee, an extension option or a bonus.
Every named kind is confirmed in game, and so is what a clause’s value holds for each of them (see ClauseKind). A status covers a field across every kind, so parameter still ships unconfirmed: a minority of MINIMUM_FEE_RELEASE clauses carry one whose meaning no in-game reading pins.
- Parameters:
kind (CodedValue[ClauseKind])
parameter (int | None)
value (int | None)
- kind¶
The clause’s kind.
- class fmsave.ContractChainEntry(club_uid, club_name, team_id, wage, start, end, has_terms)[source]¶
One record in a player’s registration chain.
- Parameters:
- club_uid¶
Uid of the contracting club, or None when the team does not resolve (unconfirmed).
- Type:
int | None
- start¶
Start date of this spell (unconfirmed).
- Type:
datetime.date | None
- end¶
End date from this record’s own terms, or None when it gives no terms up or stores no end.
- Type:
datetime.date | None
- class fmsave.PlayerSuspension(scope, scope_code, competition_id, competition_name, nation_id, issued_date)[source]¶
One unserved suspension, as listed on its player.
- Parameters:
- scope¶
What the ban covers: one competition, a whole nation, or UNKNOWN for a scope code never measured.
- scope_code¶
The scope code exactly as the save stores it: 1 for a competition-wide ban, 5, 6 or 10 for a nation-wide one. What separates 5, 6 and 10 is not known.
- Type:
- competition_id¶
Id of the competition the ban applies to, in the stage id space, so it joins stages(), competitions(), fixtures(), league_tables() and player_match_stats(). None unless the scope is COMPETITION.
- Type:
int | None
- competition_name¶
Denormalised name of competition_id, which is None unless a name map is supplied, because the save stores no competition names. A competition the game itself created during the career has a database id no name source outside the save carries, so some rows stay unnamed even with a full map (unconfirmed).
- Type:
str | None
- nation_id¶
Id of the nation whose competitions the ban covers, in the same id space as Club.nation_id and Player.nation_id. None unless the scope is NATION.
- Type:
int | None
- issued_date¶
Date the ban was issued.
- Type:
- class fmsave.Suspension(player_uid, player_name, club_uid, club_name, scope, scope_code, competition_id, competition_name, nation_id, issued_date, unknown)[source]¶
One unserved suspension, with its player and the club the player belongs to.
A ban the game no longer displays is listed here too.
- Parameters:
- club_uid¶
Uid of the club the player belongs to, which for a player registered with a team another club controls is that controlling club; None for a free agent or a team that does not resolve to a club. Copied from the decoded player (unconfirmed). It is the club the player belongs to now, which for an old ban need not be the club he was banned at.
- Type:
int | None
- scope¶
What the ban covers: one competition, a whole nation, or UNKNOWN for a scope code never measured.
- scope_code¶
The scope code exactly as the save stores it: 1 for a competition-wide ban, 5, 6 or 10 for a nation-wide one. What separates 5, 6 and 10 is not known.
- Type:
- competition_id¶
Id of the competition the ban applies to, in the stage id space, so it joins stages(), competitions(), fixtures(), league_tables() and player_match_stats(). None unless the scope is COMPETITION.
- Type:
int | None
- competition_name¶
Denormalised name of competition_id, which is None unless a name map is supplied, because the save stores no competition names. A competition the game itself created during the career has a database id no name source outside the save carries, so some rows stay unnamed even with a full map (unconfirmed).
- Type:
str | None
- nation_id¶
Id of the nation whose competitions the ban covers, in the same id space as Club.nation_id and Player.nation_id. None unless the scope is NATION.
- Type:
int | None
- issued_date¶
Date the ban was issued.
- Type:
- unknown¶
Numeric fields with no known meaning (unconfirmed).
- Type:
- class fmsave.Staff(uid, is_human_manager, name, first_name, last_name, common_name, full_name, legal_name, birth_date, age, nation_id, club_uid, club_name, team_id, team_slot, listed_club_uid, listed_club_name, in_club_lists, list_indexes, has_contract, wage, contract_start, contract_end, ability, personality, attributes, preferences, unknown)[source]¶
One member of staff at one club.
A person listed by a club’s affiliate side and paid by the club itself is one row at the club, with the side that lists him in listed_club_uid, the same rule a player on a B team follows. A person a club lists but pays nothing has no contract to read, so wage, contract_start and contract_end are None rather than zero.
unknown[“contract_e36”] and the three codes beside it are not player squad statuses: they come from the same bytes but take different values on staff, and no displayed label has named one, so they ship as raw numbers and never as a SquadStatus.
The job title is not readable, and `unknown[“r4”]` is not it. That byte was the one candidate for it, and a club’s staff screen ruled it out: over 78 displayed rows, 8 of the 16 codes on show carried two or more different job titles, 7 titles appeared under two or three different codes, and one code carried seven titles on its own. So r4 keeps its raw number for good rather than becoming a named role, and the codes that happen to sit on one title at one club are not evidence of a meaning the field demonstrably does not have.
The human manager is a row with is_human_manager true. His object is laid out differently, so ability, preferences and every unknown key read from the ability block are absent for him, while his name, birth date, personality and contract read normally.
- Parameters:
uid (int)
is_human_manager (bool)
name (str | None)
first_name (str | None)
last_name (str | None)
common_name (str | None)
full_name (str | None)
legal_name (str | None)
birth_date (date | None)
age (int | None)
nation_id (int | None)
club_uid (int)
club_name (str)
team_id (int | None)
team_slot (int | None)
listed_club_uid (int | None)
listed_club_name (str | None)
in_club_lists (bool)
has_contract (bool)
wage (int | None)
contract_start (date | None)
contract_end (date | None)
ability (Ability | None)
personality (Personality | None)
attributes (StaffAttributes | None)
preferences (StaffPreferences | None)
- uid¶
The person’s unique id, as the save stores it; whether the game shows this value or the one above it has not been read (unconfirmed).
- Type:
- birth_date¶
Date of birth (unconfirmed).
- Type:
datetime.date | None
- team_slot¶
That team’s slot in the club’s team list, None without a contract (unconfirmed).
- Type:
int | None
- listed_club_uid¶
The club whose own staff list holds him, which is club_uid unless an affiliate side lists him; None when no list holds him (unconfirmed).
- Type:
int | None
- listed_club_name¶
Denormalised full name of listed_club_uid; None wherever listed_club_uid is (unconfirmed).
- Type:
str | None
- list_indexes¶
Which of the three lists hold him, 0 to 2, empty when none does (unconfirmed).
- wage¶
What the contract pays, per week, in the save’s base currency; None without a contract.
- Type:
int | None
- contract_start¶
The day the contract started.
- Type:
datetime.date | None
- contract_end¶
The day it ends, None when the save stores none (unconfirmed).
- Type:
datetime.date | None
- ability¶
Current and potential ability, None when the object carries no readable ability block, as the human manager’s does not.
- Type:
- personality¶
The eight personality values, None without a name block.
- Type:
- attributes¶
The one rated attribute located, None without a name block.
- Type:
- preferences¶
The named preferences, None when the object carries no readable ability block.
- Type:
- unknown¶
Numeric fields with no known meaning (unconfirmed). The ability-block keys (entry_count, r4 to r12 and every preference_slot_*) are absent when that block does not read, and the contract_* keys when no contract was read.
- Type:
- class fmsave.StaffAttributes(adaptability)[source]¶
The one rated staff attribute located in the save.
A staff profile rates about twenty attributes; only this one has been found, and it is the same byte as Staff.personality.adaptability, so the two always agree.
- Parameters:
adaptability (int)
- class fmsave.StaffPreferences(attacking, business, directness, interference, patience, trigger_press, resources, buying_players, mind_games, flexibility, hardness_of_training, squad_rotation, tempo, width)[source]¶
How a member of staff prefers a team to be run, each on a 1 to 20 scale.
These are preferences, not rated attributes: a high directness says what the person wants from a side, never how good he is at anything. Fourteen of the save’s twenty-six slots have a name; the rest ship as Staff.unknown[“preference_slot_<i>”].
- Parameters:
- class fmsave.StaffList(club_uid, club_name, list_index, staff_uids, staff_names)[source]¶
One of the three staff lists a club record holds, for one club.
A club that lists nobody has no row here at all; a club that lists somebody has all three rows, empty lists included. The lists split a club’s staff into groups, and the codes their members carry differ from list to list.
The three lists look like the three departments a club’s staff screen shows. On one club, the screen’s medical, coaching and recruitment panels and the club’s senior rows in lists 0, 1 and 2 held the same people, person for person by name, save for the human manager and one contracted person no list holds; that makes list 0 medical, list 1 coaching and list 2 recruitment. It is arithmetic on one club rather than a label on a list, so it is recorded here and list_index stays a number.
People a list holds who turn out to be players are dropped, and so are the few whose object cannot be told from another’s, so a list’s people are those staff() also has a row for.
- Parameters:
- staff_uids¶
The uid of each person the list holds, in stored order. They are Staff.uid values and join to the staff table (unconfirmed).
Clubs¶
clubs(), facilities(), stadiums(), finances(), sponsorships(), affiliates() and
job_vacancies().
- class fmsave.Club(uid, name, short_name, nation_id, fa_nation_id, city_id, teams, parent_club_uid, parent_club_name, reputation, last_league_position)[source]¶
A club from the save’s game database.
Club names are not unique: look clubs up by uid.
- Parameters:
- fa_nation_id¶
Id of the nation whose football association the club belongs to. It differs from nation_id for a club that plays in another nation’s league (unconfirmed).
- Type:
- city_id¶
Id of the club’s city, or None when the save stores the missing-id marker instead (unconfirmed).
- Type:
int | None
- teams¶
The club’s own teams in stored order, including slots no player is registered with, then the teams of the affiliate clubs it controls; an empty tuple when the team list cannot be read (unconfirmed).
- Type:
- parent_club_uid¶
Uid of the club that controls this one, when another club lists one of this club’s teams among its affiliate teams, else None (unconfirmed).
- Type:
int | None
- parent_club_name¶
Denormalised full name of parent_club_uid; None wherever parent_club_uid is (unconfirmed).
- Type:
str | None
- class fmsave.Team(team_id, slot, club_uid, is_affiliate)[source]¶
One team in a club’s team list.
A club’s team list holds its own stored team slots, in stored order, then the teams it fields at the clubs it controls, such as a B or C team stored as a club of its own.
- slot¶
Position in the club’s team list, 0 for the first entry. Which side each own slot holds, such as a reserve or youth team, is not known (unconfirmed).
- Type:
- club_uid¶
Uid of the club whose record stores this team: the club listing it for its own teams, and the affiliate club itself for an affiliate team (unconfirmed).
- Type:
- class fmsave.ClubFacilities(club_uid, club_name, corporate_facilities)[source]¶
One club’s facility ratings, as its own record stores them.
- Parameters:
club_uid (int)
club_name (str)
corporate_facilities (CodedValue[CorporateFacilities])
- corporate_facilities¶
How good the club’s corporate facilities are. Seventeen of the twenty codes carry a word, and the three no club in the save carries are UNKNOWN with the raw number kept.
- class fmsave.Stadium(uid, name, all_seater_capacity, expansion_capacity, capacity, owner_club_uid, owner_club_name, home_club_uids, home_club_names, pitch_length_dm, pitch_width_dm, pitch_min_length_dm, pitch_min_width_dm, pitch_max_length_dm, pitch_max_width_dm, built_date, rebuilt_date, unknown)[source]¶
One ground the save’s database holds.
- Parameters:
uid (int)
name (str | None)
all_seater_capacity (int)
expansion_capacity (int)
capacity (int | None)
owner_club_uid (int | None)
owner_club_name (str | None)
pitch_length_dm (int)
pitch_width_dm (int)
pitch_min_length_dm (int)
pitch_min_width_dm (int)
pitch_max_length_dm (int)
pitch_max_width_dm (int)
built_date (date | None)
rebuilt_date (date | None)
- uid¶
The ground’s uid, which is the stored value plus one, the same convention club uids follow. A stadium uid that equals a club uid is a coincidence: the two are separate id spaces and must never be joined on (unconfirmed).
- Type:
- name¶
The ground’s name as the save stores it inline, or None when the save stores none, which is the case for nearly every ground: the game takes the name from its installed database instead (unconfirmed).
- Type:
str | None
- expansion_capacity¶
The capacity the ground would hold expanded, which is at or above the all-seater capacity on all but a handful of grounds.
- Type:
- capacity¶
A further capacity the save stores, empty where the save stores zero, which is about four grounds in five (unconfirmed).
- Type:
int | None
- owner_club_uid¶
Uid of the club that owns the ground, or None when the save names no owner or names a club it does not list. A ground with no stored owner is the council’s: the game’s own facilities screen shows one such ground as owned by the council, so an empty owner is an answer rather than a gap. Owning a ground is not the same as playing at it (unconfirmed).
- Type:
int | None
- owner_club_name¶
Denormalised name of owner_club_uid, or None when there is no owner or the owner does not resolve (unconfirmed).
- Type:
str | None
- home_club_uids¶
Uids of the clubs whose home ground this is, in ascending order. It comes from the fixture calendar, not from any stored link: a club has a home ground once the calendar records enough first-team home matches for it, and that ground is the one it used most. It is empty for a ground no club uses often enough, and a ground several clubs share lists each of them (unconfirmed).
- home_club_names¶
Denormalised names of home_club_uids, in the same order (unconfirmed).
- built_date¶
The date the ground was built, or None when the save stores none. A few grounds carry a date after the save’s own clock.
- Type:
datetime.date | None
- rebuilt_date¶
The date the ground was last rebuilt, or None when the save stores none.
- Type:
datetime.date | None
- unknown¶
Numeric fields with no known meaning: two capacity-like words, a small code, a third date as one little-endian int, and the whole flags byte whose 0x10 bit is what says the row carries its name inline (unconfirmed).
- Type:
- class fmsave.FinanceMonth(club_uid, club_name, month, balance, transfer_budget_allocated, transfer_budget_remaining, wage_budget_weekly, wage_payroll_weekly, income_excluding_transfers, net_transfers, wage_bill, net, expenditure_excluding_transfers, total_income, total_expenditure)[source]¶
One club’s money in one month.
- Parameters:
club_uid (int)
club_name (str)
month (date)
balance (int)
transfer_budget_allocated (int)
transfer_budget_remaining (int)
wage_budget_weekly (int)
wage_payroll_weekly (int)
income_excluding_transfers (int)
net_transfers (int)
wage_bill (int)
net (int)
expenditure_excluding_transfers (int)
total_income (int)
total_expenditure (int)
- month¶
First day of the month the row covers, worked out from the save’s own date: the last row of a club is the month before the save’s month, and a club’s two oldest rows landed on the months its own finance screen labelled them with.
- Type:
- transfer_budget_allocated¶
A transfer figure with no confirmed meaning (unconfirmed). It is not the budget the board allocated: a club’s own finances screen showed a budget well above this figure with almost none of it spent, and this figure is below transfer_budget_remaining on about a quarter of rows.
- Type:
- expenditure_excluding_transfers¶
The month’s expenditure other than transfer fees (unconfirmed).
- Type:
- class fmsave.Sponsorship(club_uid, club_name, kind, start, end, total_value, annual_value, unknown)[source]¶
One sponsorship contract of one club, running or ended.
- Parameters:
- kind¶
What kind of sponsorship it is; every code is UNKNOWN (unconfirmed).
- start¶
The day the contract starts.
- Type:
datetime.date | None
- end¶
The day it ends.
- Type:
datetime.date | None
- total_value¶
The contract’s whole value, which is the annual value times its whole years on about 19 contracts in 20.
- Type:
- unknown¶
Numeric fields with no known meaning (unconfirmed).
- Type:
- class fmsave.AffiliateGroup(group_index, club_uids, club_names)[source]¶
One stored group of clubs, with its members resolved to clubs where they resolve.
- Parameters:
- group_index¶
The group’s own place in the section, counting from zero in stored order. It is fmsave’s own index and not a value the save keeps: the groups carry no id of their own.
- Type:
- club_uids¶
Uid of each member, in stored order, None for a member whose stored club index no club record claims, which is about one member in sixty (unconfirmed).
- class fmsave.JobVacancy(team_id, club_uid, club_name, team_slot, advertised_date, competition_id, competition_name, league_position, unknown)[source]¶
One job the save’s job-centre feed holds.
- Parameters:
- club_uid¶
Uid of the club that fields team_id, None for a team no club lists and wherever team_id is.
- Type:
int | None
- team_slot¶
The team’s place in that club’s own team list, 0 for its first team; None when the team resolves to no club (unconfirmed).
- Type:
int | None
- advertised_date¶
Date the vacancy was advertised, None when the stored date does not decode. The time of day the save stores with it is in unknown[“advertised_slot”].
- Type:
datetime.date | None
- competition_id¶
Id of the competition the job’s team plays in, in the stage id space; None where the record names none (unconfirmed).
- Type:
int | None
- competition_name¶
Denormalised name of competition_id, None unless the save was opened with a name map that names it (unconfirmed).
- Type:
str | None
- league_position¶
The team’s current position in that competition’s table, None where the record stores none, which is about a quarter of rows (unconfirmed).
- Type:
int | None
- unknown¶
Numeric fields with no known meaning: the role code the vacancy’s job title comes from, the advertised date’s time-of-day slot, a second date whose meaning is not settled (its raw four bytes as one little-endian int, not a decoded date), an attribute of the competition, and a 0/1 flag (unconfirmed).
- Type:
Competitions¶
stages(), competitions(), fixtures(), league_tables(), transfer_windows(),
competition_rules() and player_match_stats().
- class fmsave.Stage(id, competition_id, competition_name, group_id, round, previous_stage_id, unknown)[source]¶
One stage of one competition: a league season, a cup round, or one leg of a tie.
- Parameters:
id (int)
competition_id (int | None)
competition_name (str | None)
group_id (int | None)
round (CodedValue[CompetitionRound] | None)
previous_stage_id (int | None)
- id¶
The stage id, which every fixture, league-table group and per-match record joins through.
- Type:
- competition_id¶
Id of the competition this stage belongs to, in the stage id space; None when the row stores no competition, and None for the one out-of-band id every save carries, which is a marker rather than a competition.
- Type:
int | None
- competition_name¶
Denormalised name of competition_id, which is None unless a name map is supplied, because the save stores no competition names (unconfirmed).
- Type:
str | None
- group_id¶
Id of the group or section within the competition, or None when the stage has none.
- Type:
int | None
- round¶
The stage’s round, or None when the stage has no round, as a league matchday and a league phase do not. Codes with no confirmed meaning keep their raw number and are labelled UNKNOWN.
- previous_stage_id¶
The stage id the row claims comes before this one; None when the row stores the missing value. It equals id - 1 on every row seen, and the reader’s own row test requires that, so it is not independent evidence of a chain between stages (unconfirmed).
- Type:
int | None
- unknown¶
Numeric fields with no known meaning. “s25” is data, not a sentinel: it takes small values on several hundred rows per save. “s29” is the missing value on almost every row and carries small numbers on the rest (unconfirmed).
- Type:
- class fmsave.Competition(id, database_id, name, stage_ids)[source]¶
One competition, as the stage table names it.
- id¶
The competition id, in the stage id space (not the id space a suspension’s competition id belongs to).
- Type:
- database_id¶
The competition’s id in the game’s own editor database, which is stable across saves and is what an external name source is keyed on; None when the save holds no such link (unconfirmed).
- Type:
int | None
- class fmsave.Fixture(stage_id, competition_id, competition_name, round, round_index, date, kick_off_time, season_start_year, home_team_id, home_club_uid, home_club_name, home_club_short_name, home_team_slot, away_team_id, away_club_uid, away_club_name, away_club_short_name, away_team_slot, home_goals, away_goals, played, is_neutral_venue, stadium_uid, stadium_name, match_record_id, match_rules_template, unknown)[source]¶
One match in the save’s fixture calendar.
- Parameters:
stage_id (int | None)
competition_id (int | None)
competition_name (str | None)
round (CodedValue[CompetitionRound] | None)
round_index (int | None)
date (date | None)
kick_off_time (time | None)
season_start_year (int | None)
home_team_id (int)
home_club_uid (int | None)
home_club_name (str | None)
home_club_short_name (str | None)
home_team_slot (int | None)
away_team_id (int)
away_club_uid (int | None)
away_club_name (str | None)
away_club_short_name (str | None)
away_team_slot (int | None)
home_goals (int | None)
away_goals (int | None)
played (bool)
is_neutral_venue (bool | None)
stadium_uid (int | None)
stadium_name (str | None)
match_record_id (int | None)
- stage_id¶
The stage this match belongs to, which is the only route to its competition; None when the record stores no stage.
- Type:
int | None
- competition_id¶
Id of the competition, in the stage id space (not the id space a suspension’s competition id belongs to), reached through the stage; None when the record has no stage, the stage is not in the stage table, or the stage names no competition.
- Type:
int | None
- competition_name¶
Denormalised name of competition_id, which is None unless a name map is supplied, because the save stores no competition names (unconfirmed).
- Type:
str | None
- round¶
The stage’s round, or None when the match has no stage or its stage carries no round, as a league matchday and a league phase do not. Codes with no confirmed meaning keep their raw number and are labelled UNKNOWN.
- round_index¶
The match’s zero-based index within its round, so a matchday is this plus one; None when the record stores the no-round value.
- Type:
int | None
- date¶
The kick-off date, or None when the stored date does not decode.
- Type:
datetime.date | None
- kick_off_time¶
The kick-off time of day, or None when the stored slot does not land inside a day.
- Type:
datetime.time | None
- season_start_year¶
The calendar year the season started in, or None when the record stores none, as some friendlies do.
- Type:
int | None
- home_club_uid¶
Uid of the club fielding the home team, or None when no club lists that team.
- Type:
int | None
- home_club_name¶
Denormalised name of home_club_uid, or None when the team does not resolve.
- Type:
str | None
- home_club_short_name¶
Denormalised short name of home_club_uid, or None when the team does not resolve.
- Type:
str | None
- home_team_slot¶
The home team’s slot in its club’s team list, 0 for the first entry, or None when the team does not resolve.
- Type:
int | None
- away_club_uid¶
Uid of the club fielding the away team, or None when no club lists that team.
- Type:
int | None
- away_club_name¶
Denormalised name of away_club_uid, or None when the team does not resolve.
- Type:
str | None
- away_club_short_name¶
Denormalised short name of away_club_uid, or None when the team does not resolve.
- Type:
str | None
- away_team_slot¶
The away team’s slot in its club’s team list, 0 for the first entry, or None when the team does not resolve.
- Type:
int | None
- home_goals¶
Goals the home side scored, or None when the save no longer holds the score. A played match with no goals means the score was not retained, not that the match finished goalless. The calendar itself stores no score: it is read from separate records the save keeps for only part of a career, so about a quarter of played matches carry one.
- Type:
int | None
- away_goals¶
Goals the away side scored, or None when the save no longer holds the score, as for home_goals.
- Type:
int | None
- is_neutral_venue¶
Whether the match was played away from the home club’s usual ground: True when its ground differs from the one that club used most in this season, False when it matches, and None when the ground is unknown or the club played too few home matches this season to say which ground is usual. This is about the ground and not about which club counts as the home one: the home club is home_team_id whether the ground is neutral or not, and LeagueTableMatch.home_or_away is the field that says which side of a match a club was. The season is the stored season_start_year, which some friendlies leave empty, and every such match a club played shares one vote; a club that moved ground mid-career therefore has its earlier seasonless matches judged against the later ground.
- Type:
bool | None
- stadium_uid¶
Uid of the ground the match is played at, from the stadium table; None when the record stores no ground or one the table does not hold. The join itself is exact, but a stadium uid is not confirmed by any displayed label, so this field can be no stronger than the uid it carries (unconfirmed).
- Type:
int | None
- stadium_name¶
Denormalised name of stadium_uid. Only a couple of hundred grounds store a name, so this is None on nearly every match even where stadium_uid is filled in; Stadium.name says why (unconfirmed).
- Type:
str | None
- match_record_id¶
Id of the match record the save keeps for a played match, or None when the match is unplayed (unconfirmed).
- Type:
int | None
- match_rules_template¶
The three bytes naming the shared match-rules template this match uses. It is a template many competitions share and is never a competition id (unconfirmed).
- unknown¶
Numeric fields with no known meaning. “result_r22” is the byte stored beside the score, so only a match whose score the save still holds carries one (unconfirmed).
- Type:
- class fmsave.LeagueTable(competition_id, competition_name, club_count, rows)[source]¶
One live table: every club the save groups together, with its rows.
The rows nest, so a CSV export puts the whole list in one JSON cell. to_dicts() and the JSON writers are the useful exports for tables.
- Parameters:
competition_id (int | None)
competition_name (str | None)
club_count (int)
rows (tuple[LeagueTableRow, ...])
- competition_id¶
The competition the table was voted to belong to, in the stage id space (not the id space a suspension’s competition id belongs to); None when the vote could not settle it, which on the corpus is one table in a thousand. Read that beside the shape of the tables: 30% to 43% of them hold one block, and a one-block table meets the vote’s half-the-members rule on a majority of one, so it resolves trivially; over the tables of two blocks or more resolution is 0.998 to 1.000. One competition names several tables, because a cup’s group stage is one competition holding a table per group. The save stores no link from a table to its competition, so this is a vote on the fixture calendar rather than a stored value (unconfirmed).
- Type:
int | None
- competition_name¶
Denormalised name of competition_id, which is None unless a name map is supplied, because the save stores no competition names (unconfirmed).
- Type:
str | None
- rows¶
One row per club, in the order the save stores the group.
- class fmsave.LeagueTableSplit(played, won, drawn, lost, goals_for, goals_against, points)[source]¶
One of a row’s four partial records: home, away, first half or second half.
Every field is unconfirmed. The save’s own arithmetic holds on all four (played equals won plus drawn plus lost, and home and away add up to the total, as do the two halves), which is what the block acceptance checks require, but no in-game screen has yet been read back against them, so none of the seven is verified.
- Parameters:
- class fmsave.LeagueTableMatch(slot, home_or_away, opponent_team_id, opponent_club_uid, opponent_club_name, opponent_club_short_name, goals_for, goals_against, outcome, points)[source]¶
One match slot of one club’s row: a played match, or a slot never played.
A slot the club has not played keeps its place in the list so the shape of the season is never lost: its opponent fields, goals, outcome and points are all None.
- Parameters:
- home_or_away¶
Which side of the match the row’s club was, the home one or the away one, which the save stores as the slot’s parity: the even slots are the home ones. That is checked against the fixture calendar’s own stored home team on every table whose rows account for exactly one season of it, and it holds on 99.76% to 99.87% of the slots the calendar can settle by itself, against a fifth of a percent were the parity the other way round; the handful that disagree are consistent with rescheduled or neutral-ground meetings. A slot never played carries a side too, since the parity belongs to the slot rather than to what happened in it. It is None only where the build fmsave read the save with has not settled the parity. This is the club’s side and not the ground: Fixture.is_neutral_venue is the field that says whether a match was played away from the home club’s usual ground, and a match at a neutral ground still has a home club and an away club.
- Type:
- opponent_team_id¶
The opponent’s team id exactly as stored, or None for an unplayed slot.
- Type:
int | None
- opponent_club_uid¶
Uid of the club fielding the opponent, or None when the slot is unplayed or no club lists that team.
- Type:
int | None
- opponent_club_name¶
Denormalised name of opponent_club_uid, or None when it does not resolve.
- Type:
str | None
- opponent_club_short_name¶
Denormalised short name of opponent_club_uid, or None when it does not resolve.
- Type:
str | None
- outcome¶
Whether the club won, drew or lost, or None for an unplayed slot and for a row whose counters name no single result.
- Type:
- class fmsave.LeagueTableRow(position, team_id, club_uid, club_name, club_short_name, team_slot, played, won, drawn, lost, goals_for, goals_against, points, home, away, first_half, second_half, rounds_per_venue, matches, unknown)[source]¶
One club’s row in one live table, with its splits and its match slots.
The seven top-level counters are the block’s TOTAL aggregate; home, away, first_half and second_half are the other four, in the order the save stores them.
- Parameters:
position (int)
team_id (int)
club_uid (int | None)
club_name (str | None)
club_short_name (str | None)
team_slot (int | None)
played (int)
won (int)
drawn (int)
lost (int)
goals_for (int)
goals_against (int)
points (int)
home (LeagueTableSplit)
away (LeagueTableSplit)
first_half (LeagueTableSplit)
second_half (LeagueTableSplit)
rounds_per_venue (int)
matches (tuple[LeagueTableMatch, ...])
- position¶
The club’s place in the table, counting from one, which is the order the save stores. Read it as the league position and leave the rows alone: a sort by points and goal difference reproduces it on about 92% of tables and disagrees only where the game’s own first tie-break, the results between the clubs level on points, decides an order these fields cannot reconstruct.
- Type:
- club_short_name¶
Denormalised short name of club_uid, or None when the team does not resolve.
- Type:
str | None
- team_slot¶
The team’s slot in its club’s team list, 0 for the first entry, or None when the team does not resolve (unconfirmed).
- Type:
int | None
- home¶
The club’s home record (every field unconfirmed).
- away¶
The club’s away record (every field unconfirmed).
- first_half¶
The club’s record in the first half of the season (every field unconfirmed).
- second_half¶
The club’s record in the second half of the season (every field unconfirmed).
- rounds_per_venue¶
How many rounds the group plays at each venue, so the row holds twice this many match slots (unconfirmed).
- Type:
- matches¶
One entry per match slot, in stored order, including slots never played.
- unknown¶
The 19 bytes stored in front of the block, one int per byte. The first of them is the row’s own place in its table, which is what tells one table from the next and is already reported as position; it is kept here so the blob ships whole. The other 18 are undecoded, and they are the only fields in which the save’s several copies of a block differ (unconfirmed).
- Type:
- class fmsave.TransferWindow(start_day, start_month, start_season_year_offset, end_day, end_month, end_season_year_offset, unknown)[source]¶
One transfer window: when it opens and closes, relative to the season start.
The window carries no name. The save’s rules database does hold description strings, but none of them belongs to a window record: on the saves measured only six windows in fifty-four have a description ending anywhere near the record, and the rest sit hundreds of thousands of bytes away. Attaching the nearest one would put an unrelated label on a window, so no label ships and the dates are the whole of what a window says.
A save holds more rows than it has distinct windows. On the saves measured the reader returns 54 rows of which 22 are distinct: 17 of those appear twice and 5 appear four times. Rows that repeat are equal in every field this record carries, so a caller who wants one row per distinct window can drop the repeats and lose nothing it is able to see. A record carries a mapping and so is not hashable, which rules out a set; equality works as usual, so keep the first of each instead:
distinct_windows: list[TransferWindow] = [] for window in career_save.transfer_windows(): if window not in distinct_windows: distinct_windows.append(window)
Why the rules database repeats a window is not established. What was measured is that a repeat carries no key of its own that fmsave drops: of the 32 repeated records, 26 repeat the whole underlying record exactly, tag for tag and value for value, including the id each of its two date sub-lists carries, and the other 6 differ only in a description and two unidentified tags. Nothing inside a window record tells one copy from another, so whatever separates them, if anything does, sits outside the record and this reader does not read it. In particular nothing measured here makes them per-competition or per-nation copies; that remains a guess. The count is the anchored walk’s own, not a decode that counts a record twice, and every save measured gives the same 54 rows and the same 22 distinct windows.
- Parameters:
- start_season_year_offset¶
Which season year the window opens in: 0 is the season’s own start year and 1 the calendar year after it. There is no calendar date without a season, so this is an offset and never a date.
- Type:
- end_season_year_offset¶
Which season year the window closes in, as start_season_year_offset.
- Type:
- unknown¶
Numeric fields with no known meaning. “close_time” is the value stored against the window’s closing-time tag, which looks like an hour and minute on a 24-hour clock but is not named, because the game’s own Rules screen shows no time of day to pin it to. “window_type” is a small code the record carries on about two windows in five and no displayed label explains (unconfirmed).
- Type:
- class fmsave.CompetitionRules(kind, competition_id, competition_name, fixtures_per_club, promotion_places, playoff_places, relegation_places, prize_money, rounds, unknown)[source]¶
One competition-rules block: promotion and relegation, prize money and the calendar.
The block stores no competition. Its competition comes from where the save keeps it. No field inside a block names one: every u32 in the 256 bytes before the marker and the 256 bytes after the block’s end was tested against the competition ids the stage table holds, over 200 blocks of every save measured, and no offset named a known competition on even 90% of blocks while varying from block to block. What does work is position. The span alternates rules blocks and league-table blocks, and the run of table blocks stored after a block is the table that block’s rules govern: on the saves measured about seven blocks in ten have such a run, nearly all of those runs are exactly one league_tables() table’s set of clubs, and nearly all of those tables carry a voted competition. A run matching two tables at once names neither.
That link is corroborated, and is no stronger than the vote behind it. 0.74 to 0.78 of a linked block’s dated rounds fall on a date its competition plays a fixture on, against 0.55 to 0.58 when each block is linked to the run stored before it instead, and about 0.13 against a competition drawn at random for each block. On the one division whose Rules screen was read in game, the linked block holds 20 clubs, 38 rounds and 3 relegation places and its last round is the date that screen gives as the season’s end. The competition it hands over is itself a vote on the fixture calendar rather than a stored value, so these two fields are exactly as strong as LeagueTable.competition_id: unconfirmed.
The other field meanings are weakly sourced. They come from checks against one division’s Rules screen in another tool, never re-verified here. The link above does now reach the one division that screen describes, and the values of that block match it, but a shape that matches a screen is corroboration rather than a field read back, so every field here is unconfirmed.
A block’s club count and its points deduction for administration are not read: no fixed position in the block carries either, so no field ships for them rather than a field that is empty on every row. The linked table’s own club_count is where a club count comes from.
The squad and financial rules the save’s rules database holds (a home-grown minimum, a maximum squad size, a salary cap and a wage-bill percentage) are not read. They live in tagged groups that nothing readable ties to a competition – seven routes were measured, including the positional one this record uses, and a group is not stored beside a table – and whose content is database content, identical on every save of one installed database. So fmsave ships no field for them rather than a field whose meaning rests on another tool’s guess. Adding them later adds columns and renames nothing.
Transfer windows are their own table: see Save.transfer_windows().
- Parameters:
- kind¶
Which structure the row was read from. Every row is PREAMBLE, since the tagged rules groups are not read (unconfirmed).
- competition_id¶
The competition of the league table the save stores right after this block, in the stage id space; None where that run is not exactly one table with a competition of its own, which is 32% to 45% of rows. Not a stored link: a position in the span, resolved through a vote on the fixture calendar (unconfirmed).
- Type:
int | None
- competition_name¶
Denormalised name of competition_id, which is None unless a name map is supplied, because the save stores no competition names (unconfirmed).
- Type:
str | None
- fixtures_per_club¶
How many matches each club plays, which is the number of rounds the block holds; None when no round decoded (unconfirmed).
- Type:
int | None
- promotion_places¶
How many clubs are promoted, or None when the block’s two copies of the promotion quad differ (unconfirmed).
- Type:
int | None
- playoff_places¶
How many clubs enter a promotion play-off, or None as promotion_places (unconfirmed).
- Type:
int | None
- relegation_places¶
How many clubs are relegated, or None as promotion_places (unconfirmed).
- Type:
int | None
- prize_money¶
Prize money by finishing position, in the save’s base currency, in finishing order. Empty on most blocks: only about one block in eight carries a prize list at all (unconfirmed).
- rounds¶
The competition’s calendar, in stored order (unconfirmed).
- Type:
- unknown¶
Numeric fields with no known meaning. “promotion_byte2” is the third byte of the promotion quad. “tie_break_00” to “tie_break_15” are the tie-break codes the block lists, in order, and are absent past the end of that list; no displayed label names any of them, so they ship as raw numbers (unconfirmed).
- Type:
- class fmsave.RulesRound(number, date, match_count, unknown)[source]¶
One round of a competition’s calendar, as the rules block stores it.
- number¶
The round’s number, counting from one. A round the save does not number stores a value that reads here as 256 rather than as a guess (unconfirmed).
- Type:
- date¶
The date the round is played on, or None when the stored date does not decode (unconfirmed).
- Type:
datetime.date | None
- unknown¶
Numeric fields with no known meaning. “kind” and “b5” are the two unidentified bytes the round record carries (unconfirmed).
- Type:
- class fmsave.PlayerMatchStats(player_uid, player_name, date, competition_id, opponent_team_id, opponent_club_uid, opponent_club_name, opponent_club_short_name, opponent_team_slot, has_stats, position, minutes, left_at_minute, goals, assists, rating, passes_attempted, passes_completed, stats_in_range, unknown)[source]¶
One player’s record of one match the save still holds.
Never a whole season and never a career. The save keeps about twenty matches per player per spell at a team, counted across all competitions at once rather than per competition, and drops the oldest as new ones arrive. A per-competition total summed from these rows is therefore short without saying so, and nothing in a row marks it as truncated. Friendlies, internationals and youth matches are kept apart from these records and are not here at all.
competition_id belongs to the stage id space, the same space stages(), fixtures(), the league tables and Suspension.competition_id use, so all of them join.
- Parameters:
player_uid (int)
player_name (str | None)
date (date)
competition_id (int)
opponent_team_id (int)
opponent_club_uid (int | None)
opponent_club_name (str | None)
opponent_club_short_name (str | None)
opponent_team_slot (int | None)
has_stats (bool)
position (CodedValue[MatchPosition] | None)
minutes (int | None)
left_at_minute (int | None)
goals (int | None)
assists (int | None)
rating (float | None)
passes_attempted (int | None)
passes_completed (int | None)
stats_in_range (bool)
- player_uid¶
Uid of the player the record belongs to, which is the player whose object holds it (unconfirmed).
- Type:
- date¶
The date the match was played.
- Type:
- competition_id¶
Id of the competition, in the stage id space (not the id space a suspension’s competition id belongs to). It is stored on the record itself rather than reached through a stage.
- Type:
- opponent_team_id¶
The opposing side’s first-team id, exactly as stored. It is a team id and never a club index.
- Type:
- opponent_club_uid¶
Uid of the club fielding the opposing team, or None when no club lists that team.
- Type:
int | None
- opponent_club_name¶
Denormalised name of opponent_club_uid, or None when the team does not resolve.
- Type:
str | None
- opponent_club_short_name¶
Denormalised short name of opponent_club_uid, or None when the team does not resolve.
- Type:
str | None
- opponent_team_slot¶
The opposing team’s slot in its club’s team list, 0 for the first entry, or None when the team does not resolve (unconfirmed).
- Type:
int | None
- has_stats¶
Whether the save still holds this match’s statistics. A save that has dropped them keeps the date, the competition and the opponent and nothing else, so every field below that can be None is None when this is false (unconfirmed).
- Type:
- position¶
The position played, as a coded value over the mask the record stores. Only the goalkeeper bit is named; every other mask keeps its raw value and reads as UNKNOWN. None when the save holds no statistics for the match.
- minutes¶
Minutes played, or None when the save holds no statistics for the match. No value the game displays has been matched to this number; what says it is minutes played is its shape. On the saves measured about half the matches with statistics hold exactly 90, over 0.993 hold 90 or less, none holds more than 130, and the rest are spread over the values below 90 rather than clustered (unconfirmed).
- Type:
int | None
- left_at_minute¶
The minute the player left the pitch, whether he was substituted or sent off. None when he was on it at the final whistle, which the save stores as a zero, and None as well when the save holds no statistics for the match; has_stats tells those two apart. It is never 0, so a filter such as left_at_minute < 10 selects the early departures and nobody else (unconfirmed).
- Type:
int | None
- assists¶
Assists, or None when the save holds no statistics for the match. This is a hypothesis: no displayed label has been matched to this byte, and the count shown on a player’s season statistics screen is what would confirm it (unconfirmed).
- Type:
int | None
- rating¶
The match rating, which is the stored number divided by ten and so exact to one decimal. None when the save holds no statistics for the match, and None as well when the game rated nobody in it, which the save stores as a zero; has_stats tells those two apart, and the stored zero is kept as unknown[“rating_raw”] so nothing is dropped. A rating is never 0.0, so an average over this column is an average of the ratings the game actually gave (unconfirmed).
- Type:
float | None
- passes_attempted¶
Passes attempted, or None when the save holds no statistics for the match (unconfirmed).
- Type:
int | None
- passes_completed¶
Passes completed, or None when the save holds no statistics for the match (unconfirmed).
- Type:
int | None
- stats_in_range¶
fmsave’s own sanity flag, not a value the save stores: true when the save holds this match’s statistics and its minutes, rating and goals are all inside the bounds a match can reach. A false flag never blanks a value; every number is kept exactly as stored so a caller can see what the save holds (unconfirmed).
- Type:
- unknown¶
Numeric fields with no known meaning. “role_code” moves with the position played but its meaning is not established, and it is absent from the mapping for a match the save holds no statistics for; “tag” is stored beside the date and is always read; “rating_raw” is the stored rating, kept only where it is zero, which is the one value rating itself cannot carry (unconfirmed).
- Type:
Injuries¶
injury_types() and injuries().
- class fmsave.InjuryType(id, name, unknown)[source]¶
One named injury the game can hand out.
- name¶
The game’s own name for the injury, in the language the save was written in (unconfirmed).
- Type:
- unknown¶
Numeric fields with no known meaning: a flag byte, and a second id in an id space of its own (unconfirmed).
- Type:
- class fmsave.InjuryRecord(kind, player_uid, player_name, date, team_id, club_uid, club_name, team_slot, type_id, type_name, cause, severity, unknown)[source]¶
One injury the save remembers, of either kind.
- Parameters:
kind (InjuryRecordKind)
player_uid (int | None)
player_name (str | None)
date (date | None)
team_id (int | None)
club_uid (int | None)
club_name (str | None)
team_slot (int | None)
type_id (int | None)
type_name (str | None)
cause (CodedValue[InjuryCause] | None)
severity (CodedValue[InjurySeverity] | None)
- kind¶
Which store the row came from: HISTORY for an injury that happened, TYPED for a row carrying an injury type.
- player_uid¶
Uid of the person the row is stored against, or None when the save no longer keeps that person as a player, which is about one HISTORY row in twenty (unconfirmed).
- Type:
int | None
- player_name¶
Denormalised display name of player_uid; None wherever player_uid is (unconfirmed).
- Type:
str | None
- date¶
When the injury happened, on a HISTORY row. On a TYPED row it is the day the player is expected back rather than the day he was hurt, which is why it is often ahead of the in-game date; it is None when the save stores no date at all.
- Type:
datetime.date | None
- team_id¶
Id of the team the person was registered with at the time, on a HISTORY row; None on every TYPED row, which carries no team.
- Type:
int | None
- club_uid¶
Uid of the club that fields team_id, which for a team another club controls is the controlling club; None when no club lists that team and on every TYPED row.
- Type:
int | None
- team_slot¶
The team’s slot in club_uid’s team list, counting that club’s own teams first and then the teams it controls; None wherever club_uid is (unconfirmed).
- Type:
int | None
- type_id¶
The injury’s type code, on a TYPED row; None on every HISTORY row, which carries no type.
- Type:
int | None
- type_name¶
Denormalised name of type_id from Save.injury_types, None for the codes that table has no entry for and on every row of a save with no per-match file (unconfirmed).
- Type:
str | None
- cause¶
Whether the injury happened in training or in a match, on a HISTORY row; None on every TYPED row. Both stored codes carry the cause a screen displayed for them, except on an illness row, which displays its own vocabulary.
- severity¶
How bad the injury was, on a HISTORY row; None on every TYPED row. All four stored codes carry the severity a screen displayed for them, except on an illness row, which displays its own vocabulary.
- unknown¶
Numeric fields with no known meaning, each present only on the kind of row that carries it: r0 a byte that is 1 on every row of every save, hi7_date the time-slot bits of the row’s date word, and, on a TYPED row, r11 and r12 (unconfirmed).
- Type:
Managed-club-only¶
managed_clubs(), tactics(), set_pieces(), training() and mentoring(): rows here exist
only for the club the save’s human manager runs.
- class fmsave.ManagedClub(club_uid, club_name, club_short_name, manager_name, manager_staff_uid)[source]¶
A club run by one of the save’s human managers.
The link from a human manager to a club is proven only on saves with a single human manager. On a save with several human managers, only the first one may be listed. A manager with no current club, for example one between jobs, has no row.
- Parameters:
- class fmsave.Tactic(club_uid, club_name, team_id, team_slot, index, name, style_name, mentality, slots, unknown)[source]¶
One stored copy of one tactic, belonging to one team.
- Parameters:
- club_uid¶
Uid of the club the team plays for, None for a team no club lists (unconfirmed).
- Type:
int | None
- team_slot¶
The team’s place in that club’s own team list, 0 for its first team; None when the team resolves to no club (unconfirmed).
- Type:
int | None
- index¶
The copy’s place among the team’s own tactics, counting from zero in stored order. Nothing stored says which of them is selected (unconfirmed).
- Type:
- style_name¶
The tactical style shown with the tactic, as stored text. No screen has confirmed that this label is the style (unconfirmed).
- Type:
- mentality¶
The mentality the tactic is set to; the seven codes the game offers each carry the word it shows for them, and any other code reads UNKNOWN.
- slots¶
The eleven positions, in stored order.
- Type:
- unknown¶
The 19 team-instruction bytes, one int per byte, and the tactic’s four-byte style code as one little-endian int. Byte 2 of the instructions is the mentality and is shipped as mentality as well; bytes 0, 3, 4, 5, 7, 17 and 18 are the same on every record measured, and bytes 11 to 15 look bit-packed rather than numeric (unconfirmed).
- Type:
- class fmsave.TacticSlot(number, in_possession_positions, in_possession_mask, in_possession_setting_count, in_possession_settings, out_of_possession_positions, out_of_possession_mask, out_of_possession_setting_count, out_of_possession_settings, unknown)[source]¶
One position of a tactic, in possession and out of possession.
A tactic holds eleven of these in stored order. The mask is the field the positions come from: its low bits are positions and its two high bits flag which of a pair of central or wide slots is on which side, so the flags are in *_mask but never in *_positions.
- Parameters:
number (int)
in_possession_positions (tuple[CodedValue[TacticPosition], ...])
in_possession_mask (int)
in_possession_setting_count (int)
in_possession_settings (tuple[TacticSettingUnit, ...])
out_of_possession_positions (tuple[CodedValue[TacticPosition], ...])
out_of_possession_mask (int)
out_of_possession_setting_count (int)
out_of_possession_settings (tuple[TacticSettingUnit, ...])
- in_possession_positions¶
The positions the in-possession mask sets, in ascending bit number; every label is UNKNOWN and the raw value is the bit number.
- in_possession_setting_count¶
How many setting units the in-possession block holds (unconfirmed).
- Type:
- in_possession_settings¶
Those units, in stored order (unconfirmed).
- Type:
- out_of_possession_positions¶
The same for the out-of-possession block, which differs from the in-possession one on some slots of some saves.
- out_of_possession_settings¶
Those units, in stored order (unconfirmed).
- Type:
- unknown¶
The two bit fields each block carries besides its units, and the index byte the out-of-possession block carries. The role bits hold exactly one bit and are most likely the position’s role, and the index bytes of a record are a permutation of 0 to 10 which is the identity on all but one record measured (unconfirmed).
- Type:
- class fmsave.TacticSettingUnit(head_byte, first_bits, second_bits_low, second_bits_middle, second_bits_high)[source]¶
One 24-byte setting unit of a tactic slot, as two bit fields.
A slot holds between none and about twenty of these, and they are what the player instructions of that position most likely are, but the save stores no name for either field, so both ship as the numbers they are. Every field is unconfirmed.
- Parameters:
- head_byte¶
The byte after the unit’s lead bytes, which is 0 on most units and 2 or 10 on the rest (unconfirmed).
- Type:
- first_bits¶
The unit’s seven-byte bit field as one little-endian int; between none and four of its bits are set (unconfirmed).
- Type:
- class fmsave.SetPieceRoutine(club_uid, club_name, team_id, team_slot, slot, name)[source]¶
One set-piece routine slot of one team, named or empty.
- Parameters:
- club_uid¶
Uid of the club the team plays for, None for a team no club lists (unconfirmed).
- Type:
int | None
- team_slot¶
The team’s place in that club’s own team list, 0 for its first team; None when the team resolves to no club (unconfirmed).
- Type:
int | None
- slot¶
The routine’s place in the team’s twenty slots, counting from zero in stored order. Which set-piece situation a slot is for is not stored as text, so the slot number is all there is to go on (unconfirmed).
- Type:
- class fmsave.TeamTraining(team_id, club_uid, club_name, team_slot, weeks, schedule_library)[source]¶
One team’s training calendar, with the library of schedules the save holds beside it.
- Parameters:
team_id (int)
club_uid (int | None)
club_name (str | None)
team_slot (int | None)
weeks (tuple[TrainingWeek, ...])
schedule_library (tuple[TrainingSchedule, ...])
- club_uid¶
Uid of the club that fields team_id, None for a team no club lists. For a team the managed club fields at a club it controls, this is the managed club.
- Type:
int | None
- team_slot¶
The team’s place in that club’s own team list, 0 for its first team; None when the team resolves to no club. Which side each slot is, such as a reserve or a youth team, is not known (unconfirmed).
- Type:
int | None
- weeks¶
Every week of the calendar, in stored order, running from before the save’s date to well after it.
- Type:
- schedule_library¶
The schedules the manager has saved, in stored order. It is one library per save rather than per team, so every row carries the same one, and it holds only saved schedules: the game’s own built-in ones are not in it, although a week may well name one. The save may store a schedule more than once: most saves measured hold twice as many entries as the Schedules screen lists, the same schedules over under two runs of ids, so count the distinct (folder, name) pairs rather than the entries to compare with a screen (unconfirmed).
- Type:
- class fmsave.TrainingSchedule(folder, name, unknown)[source]¶
One schedule the save keeps in the manager’s own library of saved schedules.
- unknown¶
Numeric fields with no known meaning: the id the save stores with the schedule, which is not the key a week’s name is matched on. Where the save stores the same schedule twice, the copies differ only in this id (unconfirmed).
- Type:
- class fmsave.TrainingWeek(week_start, schedule_name)[source]¶
One week of a team’s training calendar.
- week_start¶
The day the week starts, None when the stored date does not decode.
- Type:
datetime.date | None
- class fmsave.MentoringGroup(team_id, club_uid, club_name, team_slot, group_number, label, member_uids, member_names)[source]¶
One mentoring group of one team of the managed club.
- Parameters:
- team_slot¶
The team’s place in that club’s own team list, 0 for its first team; None when the team resolves to no club (unconfirmed).
- Type:
int | None
- group_number¶
The number the save stores with the group, which runs from one up within a team (unconfirmed).
- Type:
- label¶
The group’s label as the save stores it. Every group of every save measured carries the game’s default wording with its number, and whether renaming a group in game changes the stored text has not been checked.
- Type:
- member_uids¶
Uid of each member, in stored order, None for a member whose stored selector names no player record.