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, unique_id, 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)
unique_id (int | None)
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, ...] | None)
- uid¶
The id fmsave reads for the player’s record and joins every table on. It is not the game’s Unique ID (that is unique_id): it is the Unique ID of the person stored just before him, so it differs between new games started from the same database (unconfirmed).
- Type:
- unique_id¶
The player’s Unique ID in the game database, the same in every new game started from that database. The game can give the id of a person it has deleted to one it creates later. None when the save’s bytes cannot tell it (unconfirmed).
- Type:
int | None
- 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_nation_ids¶
Save-local ids of nations the player is considered home grown for.
- 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_nation_id¶
Denormalised save-local league nation id of club_uid (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 current contract, or None when a current contract cannot be established.
- 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. None when the FM27 reader cannot establish suspension coverage.
- Type:
tuple[fmsave.models.suspensions.PlayerSuspension, …] | 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. A record that carries its terms always wins over one that does not, even one at the player’s own club, so a player on loan has the contract of the club that loaned him out. Among the records of the winning kind it is the latest to start of those at the player’s own club that have started and have not ended; failing that, the latest running record at the club his earliest running record names; failing that, 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¶
Save-local 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¶
Save-local 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, unique_id, 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, job, 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 is `job`, read on FM27 only, and `unknown[“r4”]` is not it. That byte in the ability block was once the candidate, 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. So r4 keeps its raw number for good. On FM27 the job comes from the person’s own contract record, or from the record of a role he holds with no contract.
On FM27 a contract offer is not a row. A person can hold contract records for jobs he has been offered, his own club’s offer to renew among them; those are left out, so a row is a job the person holds. FM26 layouts read every contract record as a job.
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)
unique_id (int | None)
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)
job (CodedValue[StaffJob] | 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 id fmsave reads for the person’s object and joins every table on. It is not the game’s Unique ID (that is unique_id): it is the Unique ID of the person stored just before him, so it differs between new games started from the same database (unconfirmed).
- Type:
- unique_id¶
The person’s Unique ID in the game database, the same in every new game started from that database. The game can give the id of a person it has deleted to one it creates later. None when the save’s bytes cannot tell it (unconfirmed).
- Type:
int | None
- birth_date¶
Date of birth (unconfirmed).
- Type:
datetime.date | None
- team_id¶
The team his contract registers him with, or on FM27 the team of a role he holds with no contract; None when neither is read.
- Type:
int | None
- team_slot¶
That team’s slot in the club’s team list, None wherever team_id is (unconfirmed).
- Type:
int | None
- job¶
His job at the club, None where no record of it is read, which is every row on FM26 (unconfirmed).
- Type:
fmsave.models.common.CodedValue[fmsave.models.staff.StaffJob] | 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, unique_id=None, gender=None)[source]¶
A club from the save’s game database.
Club names are not unique: look clubs up by uid.
- Parameters:
- uid¶
fmsave’s join key for this club within the save. The value 0 is valid. Use unique_id to match clubs across careers (unconfirmed).
- Type:
- fa_nation_id¶
Save-local 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
- last_league_position¶
League position at the end of the last completed season, or None when it cannot be read. It is not the position in the current table.
- Type:
int | None
- unique_id¶
The club’s Unique ID in the game database, for matching across separate careers. None when it cannot be read (unconfirmed).
- Type:
int | None
- gender¶
The club’s stored gender code, or None when no team list parses. Unknown codes keep their raw value (unconfirmed).
- 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. Every code from 0 to 20 carries a word, and a code outside that range is UNKNOWN with the raw number kept. A club that stores no rating reads NONE with raw 0, as its screen shows.
- class fmsave.Stadium(uid, name, all_seater_capacity, seated_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)
seated_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
- all_seater_capacity¶
The ground’s total capacity, seated and standing together: the first figure the game’s facilities screen shows. Despite the name it is not the seated figure, which is seated_capacity; the two are equal on a ground the screen calls all-seater.
- Type:
- seated_capacity¶
How many of the total are seats: the figure the facilities screen shows in brackets as seated, and the same as the total on an all-seater ground. It is at or below the total on all but one ground across the saves measured, and it is zero on roughly two grounds in five, where what the game shows has not been compared (unconfirmed).
- Type:
- expansion_capacity¶
The capacity the ground would hold expanded, which is at or above the total 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. It is at or below the total on all but a handful of grounds and equals it on most that store one; which figure the game uses it for has not been compared with a screen (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. None means the fixture calendar is unavailable, not that no club uses the ground (unconfirmed).
- home_club_names¶
Denormalised names of home_club_uids, in the same order; None when the fixture calendar is unavailable (unconfirmed).
- pitch_length_dm¶
The pitch’s length in decimetres. The facilities screen shows whole metres and drops the rest, so a stored 1005 shows as 100 m. A few grounds store a length past pitch_max_length_dm, and the screen shows the maximum for such a ground; the value is returned as stored.
- Type:
- pitch_width_dm¶
The pitch’s width in decimetres. As with the length, a few grounds store a width past pitch_max_width_dm, and it is returned as stored.
- Type:
- built_date¶
The date the ground was built, or None when the save stores none. Grounds built in the 1800s keep their year, as the game’s facilities screen shows it; a year before 1800 reads as 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. “u17” is the first capacity-like word, which seated_capacity now carries; it stays here, with the same value, so code that reads it keeps working (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:
- balance¶
The club’s balance at the end of the month. On most clubs whose series begins with the career, the oldest row instead holds the balance before that month’s net: the next row’s balance adds both months’ nets to it.
- 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. Only the codes an FM27 screen named carry a label; every other code, and every FM26 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. On most running contracts it is the annual value times the contract’s whole years. The screen’s Duration column is total_value / annual_value in years, which can differ from the span between start and end, so do not work a duration out from the dates.
- Type:
- annual_value¶
What a running contract pays a year. A contract that has ended can still carry a non-zero annual value, often equal to its total value, so neither a zero nor a non-zero value says whether the contract is still running; compare end with the save’s date for that. A running contract is not always counted in the screen’s sponsorship income either: on one FM27 club an unexpired kind 6 row was left out of it.
- 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:
Nations¶
nations() pairs each save-local nation_id with its game database_id. Join other
tables’ nation fields to nation_id, then use database_id with an external name map.
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.
- 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, 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 no unambiguous score was decoded. Empty goals on a played match do not mean it finished goalless. Score coverage varies between saves.
- Type:
int | None
- away_goals¶
Goals the away side scored, or None when no unambiguous score was decoded, as for home_goals.
- Type:
int | None
- is_neutral_venue¶
Whether the match was played away from the home club’s usual grounds. A team’s usual ground is the one it used most at home in this season, and each of the club’s teams has its own, so a reserve side’s ground counts too. False when the match is at a usual ground of any of the club’s teams, True when it is not and the home team has a usual ground, and None when the ground is unknown or the home team played too few home matches this season to say. 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 team played shares one vote; a team 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 a stage-keyed result. A score recovered only from a match summary or calendar packet carries no such byte (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, which Suspension.competition_id also uses; 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: 20% to 28% 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. On FM27 some cup group stages count matches in played that are none of won, drawn or lost.
- 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. So are they on a played slot whose own counters do not describe one match whose result its score bears out, such as a slot counting two meetings at once; the save holds such slots almost only in rows whose slots do not add up to the row’s totals, and fmsave leaves them empty rather than report numbers that are not one match’s. fmsave validate counts them.
- 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.
- 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 | None)
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
- played¶
Matches played in total. On FM27 some cup group stages count matches here that are none of won, drawn or lost.
- Type:
- points¶
Points taken in total, after any adjustment such as a points deduction, so it can differ from the home and away points added together and can be below zero.
- Type:
- 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; None where the save stores the row without match slots, which it does for some tables (unconfirmed).
- Type:
int | None
- matches¶
One entry per match slot, in stored order, including slots never played; empty where the save stores the row without them.
- 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.95 to 0.99 of a linked block’s dated rounds fall on a date its competition plays a fixture on, against 0.66 to 0.79 when each block is linked to the run stored before it instead. 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
- match_count¶
Unidentified u32 retained unchanged under its legacy field name. It is not a dependable fixture count: the same dated row can change from 2 to 10 as a career advances, and larger values such as 256 and 8192 occur in complete calendars. No bit masks or flag meanings are inferred (unconfirmed).
- Type:
- 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.
These rows are retained match histories, not guaranteed season or career totals. Older matches can be missing, and a row does not mark its history as truncated. Rows also cover club friendlies, internationals and matches for a club’s other teams, so filter on competition_id before adding rows up.
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. It is stored on the record itself rather than reached through a stage.
- Type:
- opponent_team_id¶
The opposing team’s id, exactly as stored. It is a team id and never a club index, and a reserve or youth side has its own.
- 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 record holds statistics for the match. A record without 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. On the saves measured, such a record is a match of his team that he did not play in (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:
Season statistics¶
player_season_stats(): each player’s current season, one row per competition type and team.
- class fmsave.PlayerSeasonStats(player_uid, player_name, kind, team_id, club_uid, club_name, club_short_name, team_slot, starts, substitute_appearances, minutes, rated_appearances, average_rating, player_of_the_match, goals, assists, expected_goals, non_penalty_expected_goals, expected_assists, shots, shots_on_target, shots_outside_box, goals_outside_box, free_kick_shots, penalties_taken, penalties_scored, passes_attempted, passes_completed, progressive_passes, key_passes, open_play_key_passes, clear_cut_chances_created, crosses_attempted, crosses_completed, open_play_crosses_attempted, open_play_crosses_completed, dribbles, offsides, distance_km, high_intensity_sprints, aerial_challenges_attempted, headers_won, key_headers, tackles_attempted, tackles_completed, key_tackles, interceptions, possession_won, possession_lost, pressures_attempted, pressures_completed, blocks, shots_blocked, clearances, fouls_made, fouls_against, yellow_cards, red_cards, mistakes_leading_to_goal, clean_sheets, goals_allowed, saves_held, saves_parried, saves_tipped, shots_on_target_faced, expected_goals_prevented)[source]¶
One player’s stats for one competition type and one team this season.
A competition type with no games has no row. Goalkeepers get saves_held, saves_parried, saves_tipped and shots_on_target_faced; everyone else gets aerial_challenges_attempted, headers_won, open_play_crosses_completed, blocks and clearances. The other set is None. A player counts as a goalkeeper when GK is one of their natural positions.
Rates: most counts have a _per_90 version (such as goals_per_90), plus pass_completion_percent, cross_completion_percent, open_play_cross_completion_percent, headers_won_percent, tackle_completion_percent, shots_on_target_percent, conversion_percent, save_percent, expected_goals_per_shot and headers_lost_per_90. A rate is None when there is nothing to divide by. COMPUTED_FIELDS lists them all.
- Parameters:
player_uid (int)
player_name (str | None)
kind (SeasonStatsKind)
team_id (int | None)
club_uid (int | None)
club_name (str | None)
club_short_name (str | None)
team_slot (int | None)
starts (int)
substitute_appearances (int)
minutes (int)
rated_appearances (int)
average_rating (float | None)
player_of_the_match (int)
goals (int)
assists (int)
expected_goals (float)
non_penalty_expected_goals (float)
expected_assists (float)
shots (int)
shots_on_target (int)
shots_outside_box (int)
goals_outside_box (int | None)
free_kick_shots (int)
penalties_taken (int)
penalties_scored (int)
passes_attempted (int)
passes_completed (int)
progressive_passes (int)
key_passes (int)
open_play_key_passes (int)
clear_cut_chances_created (int)
crosses_attempted (int)
crosses_completed (int)
open_play_crosses_attempted (int)
open_play_crosses_completed (int | None)
dribbles (int)
offsides (int)
distance_km (float)
high_intensity_sprints (int)
aerial_challenges_attempted (int | None)
headers_won (int | None)
key_headers (int)
tackles_attempted (int)
tackles_completed (int)
key_tackles (int)
interceptions (int)
possession_won (int)
possession_lost (int)
pressures_attempted (int | None)
pressures_completed (int | None)
blocks (int | None)
shots_blocked (int)
clearances (int | None)
fouls_made (int)
fouls_against (int)
yellow_cards (int)
red_cards (int)
mistakes_leading_to_goal (int)
clean_sheets (int)
goals_allowed (int)
saves_held (int | None)
saves_parried (int | None)
saves_tipped (int | None)
shots_on_target_faced (int | None)
expected_goals_prevented (float)
- kind¶
The competition type the row covers.
- team_id¶
The team the row is for: the player’s current team, or another team they played for this season.
- Type:
int | None
- club_uid¶
Uid of the club whose team list holds team_id, or None when no club lists it.
- Type:
int | None
- 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
- non_penalty_expected_goals¶
Expected goals from shots other than penalties (npxG) (unconfirmed).
- Type:
- goals_outside_box¶
Goals from outside the box. Always None for now: the stored count runs above goals and shots on target on some rows, so it is withheld until its meaning is known (unconfirmed).
- Type:
int | None
- clear_cut_chances_created¶
Clear-cut chances created. The game’s “chances created” figure counts the same thing.
- Type:
- open_play_crosses_completed¶
Crosses completed in open play; None on a goalkeeper’s line (unconfirmed).
- Type:
int | None
- goals_allowed¶
Goals conceded as goalkeeper; usually 0 for an outfield player (unconfirmed).
- Type:
- shots_on_target_faced¶
Shots on target faced as goalkeeper; None for an outfield player (unconfirmed).
- Type:
int | None
- expected_goals_prevented¶
Expected goals prevented (xGP); negative when a goalkeeper conceded more than expected (unconfirmed).
- Type:
- property pass_completion_percent: float | None¶
Passes completed, as a percentage of passes attempted.
- property cross_completion_percent: float | None¶
Crosses completed, as a percentage of crosses attempted.
- property open_play_cross_completion_percent: float | None¶
Open-play crosses completed, as a percentage of those attempted.
- property headers_won_percent: float | None¶
Headers won, as a percentage of aerial challenges attempted.
- property tackle_completion_percent: float | None¶
Tackles completed, as a percentage of tackles attempted.
- property save_percent: float | None¶
Saves held, parried and tipped, as a percentage of shots on target faced.
- property non_penalty_expected_goals_per_90: float | None¶
Non-penalty expected goals per 90 minutes.
- property open_play_crosses_attempted_per_90: float | None¶
Open-play crosses attempted per 90 minutes.
- property open_play_crosses_completed_per_90: float | None¶
Open-play crosses completed per 90 minutes.
player_season_history(): archived rows with the same counts and rates. Candidate year and
competition-reference meanings remain unconfirmed; stored order need not be chronological.
See exporting for sparsity and scope details.
- class fmsave.PlayerSeasonHistory(player_uid, player_name, kind, team_id, club_uid, club_name, club_short_name, team_slot, starts, substitute_appearances, minutes, rated_appearances, average_rating, player_of_the_match, goals, assists, expected_goals, non_penalty_expected_goals, expected_assists, shots, shots_on_target, shots_outside_box, goals_outside_box, free_kick_shots, penalties_taken, penalties_scored, passes_attempted, passes_completed, progressive_passes, key_passes, open_play_key_passes, clear_cut_chances_created, crosses_attempted, crosses_completed, open_play_crosses_attempted, open_play_crosses_completed, dribbles, offsides, distance_km, high_intensity_sprints, aerial_challenges_attempted, headers_won, key_headers, tackles_attempted, tackles_completed, key_tackles, interceptions, possession_won, possession_lost, pressures_attempted, pressures_completed, blocks, shots_blocked, clearances, fouls_made, fouls_against, yellow_cards, red_cards, mistakes_leading_to_goal, clean_sheets, goals_allowed, saves_held, saves_parried, saves_tipped, shots_on_target_faced, expected_goals_prevented, season_year, competition_reference)[source]¶
One archived statistics line for a player and team.
Inherits the statistics and computed rates of PlayerSeasonStats. The team is the archived team, which may differ from the player’s current team. Goalkeeper fields use the player’s current natural positions; the archive does not identify their past role. Imported history can omit detailed counts, so a stored zero does not prove no event occurred. Rows keep their stored order, which need not be chronological.
- Parameters:
player_uid (int)
player_name (str | None)
kind (SeasonStatsKind)
team_id (int | None)
club_uid (int | None)
club_name (str | None)
club_short_name (str | None)
team_slot (int | None)
starts (int)
substitute_appearances (int)
minutes (int)
rated_appearances (int)
average_rating (float | None)
player_of_the_match (int)
goals (int)
assists (int)
expected_goals (float)
non_penalty_expected_goals (float)
expected_assists (float)
shots (int)
shots_on_target (int)
shots_outside_box (int)
goals_outside_box (int | None)
free_kick_shots (int)
penalties_taken (int)
penalties_scored (int)
passes_attempted (int)
passes_completed (int)
progressive_passes (int)
key_passes (int)
open_play_key_passes (int)
clear_cut_chances_created (int)
crosses_attempted (int)
crosses_completed (int)
open_play_crosses_attempted (int)
open_play_crosses_completed (int | None)
dribbles (int)
offsides (int)
distance_km (float)
high_intensity_sprints (int)
aerial_challenges_attempted (int | None)
headers_won (int | None)
key_headers (int)
tackles_attempted (int)
tackles_completed (int)
key_tackles (int)
interceptions (int)
possession_won (int)
possession_lost (int)
pressures_attempted (int | None)
pressures_completed (int | None)
blocks (int | None)
shots_blocked (int)
clearances (int | None)
fouls_made (int)
fouls_against (int)
yellow_cards (int)
red_cards (int)
mistakes_leading_to_goal (int)
clean_sheets (int)
goals_allowed (int)
saves_held (int | None)
saves_parried (int | None)
saves_tipped (int | None)
shots_on_target_faced (int | None)
expected_goals_prevented (float)
season_year (int)
competition_reference (int)
- season_year¶
Candidate season year; whether it identifies the start or end of a season is unknown (unconfirmed).
- Type:
- competition_reference¶
Raw candidate competition reference, without a confirmed mapping to competition entities or database ids (unconfirmed).
- Type:
- property aerial_challenges_attempted_per_90: float | None¶
Aerial challenges attempted per 90 minutes.
- property cross_completion_percent: float | None¶
Crosses completed, as a percentage of crosses attempted.
- property headers_won_percent: float | None¶
Headers won, as a percentage of aerial challenges attempted.
- property non_penalty_expected_goals_per_90: float | None¶
Non-penalty expected goals per 90 minutes.
- property open_play_cross_completion_percent: float | None¶
Open-play crosses completed, as a percentage of those attempted.
- property open_play_crosses_attempted_per_90: float | None¶
Open-play crosses attempted per 90 minutes.
- property open_play_crosses_completed_per_90: float | None¶
Open-play crosses completed per 90 minutes.
- property pass_completion_percent: float | None¶
Passes completed, as a percentage of passes attempted.
- property save_percent: float | None¶
Saves held, parried and tipped, as a percentage of shots on target faced.
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(), selections(), 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, balance=None, scouting_budget=None, scouting_budget_remaining=None)[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:
- manager_name¶
The human manager’s name as the save summary stores it, or None when the summary does not name the manager next to the club (unconfirmed).
- Type:
str | None
- manager_staff_uid¶
Uid of the human manager’s own record, which is a Staff.uid and joins to the staff table; None when it cannot be identified exactly. manager_name is this person’s name (unconfirmed).
- Type:
int | None
- balance¶
The club’s balance on the save’s date, the finances screen’s Overall Balance, in the same stored unit as FinanceMonth. It is not the last FinanceMonth.balance, which closes the previous month: this one also carries the current month’s money so far (unconfirmed). None on a build with no layout for it (every FM26 build so far), or when the club has no monthly finance chain or its sponsor prefix does not decode.
- Type:
int | None
- scouting_budget¶
The club’s scouting budget for the season, in the same unit (unconfirmed). It is the season’s total, not what is left to spend: it stays the same while scouting_budget_remaining falls. None on a build with no layout for it (every FM26 build so far), on a save with more than one human manager, or when it cannot be found exactly once.
- Type:
int | None
- 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; a bit no screen has shown reads UNKNOWN, and the raw value is always 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 relate to the position’s role, but two slots the game shows with different roles can store the same bits, so they are not the role on their own. 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 unnamed.
- 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 stored routines, 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.Selection(club_uid, club_name, team_id, team_slot, substitute, slot, player_uid, player_name)[source]¶
One player picked in one team’s stored selection: a starter or a substitute.
A team stores one selection of eleven starter places and fifteen substitute places. A row is one place that holds a pick; an empty place has no row, so a team with nothing picked has no rows at all. Only the manager’s own teams store a selection in this form.
A starter’s place is a tactic slot. The starter in slot n plays the position of TacticSlot.number n, but only in the tactic the team has selected, and nothing stored says which of a team’s tactics that is.
The substitutes are the stored list, not the next match’s bench. On one FM27 career the stored starters and the first thirteen substitutes matched the tactics screen in its order. The substitute places can keep players past the bench size of the next match, so compare their count with the competition’s bench size before treating them all as picks.
- 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 place within the starters or within the substitutes, counting from zero in stored order; a starter’s is the tactic slot it fills, and a substitute’s 0 is the first substitute the game lists (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, ...] | None)
- 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, or None when reading the library is unsupported. An empty tuple means an observed empty library. One library is stored per save rather than per team, so every row carries the same one. 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:
tuple[fmsave.models.training.TrainingSchedule, …] | None
- 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, days=())[source]¶
One week of a team’s training calendar.
- Parameters:
week_start (date | None)
schedule_name (str)
days (tuple[TrainingDay, ...])
- week_start¶
The day the week starts, None when the stored date does not decode.
- Type:
datetime.date | None
- schedule_name¶
Name of the schedule the week runs, exactly as the save holds it. A schedule is a user’s own file, so this is whatever it was called.
- Type:
- days¶
The week’s seven days in calendar order, starting on week_start. Empty when a week is built from only its start and name.
- Type:
- class fmsave.TrainingDay(day, sessions)[source]¶
One day of a week of a team’s training calendar.
- Parameters:
day (date | None)
sessions (tuple[CodedValue[TrainingSession], ...])
- day¶
The date, which is the week’s start plus the day’s place in the week; None when the week’s start does not decode.
- Type:
datetime.date | None
- sessions¶
The day’s three sessions in the order the Training screen lists them, top to bottom. On an FM26 save every session is UNKNOWN with its raw code (unconfirmed).
- 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: the game’s default wording with its number, or the name the group was given in game.
- Type:
- member_uids¶
Uid of each member, in stored order, None for a member whose stored selector names no player record.