Diving into the FDI Files: Building a PC Calcio 7 Player Editor with Go and Fyne
As a long-time fan of retro football management games, PC Calcio 7 holds a special place in my heart. Recently, I embarked on a personal project to explore its data files, specifically the .fdi
files that I suspected contained player information. This exploration led to the development of a dedicated player editor using the Go programming language and the Fyne.io cross-platform UI toolkit. In this article, I'll walk you through the journey of reverse-engineering the file format and building the editor.
Unraveling the Mystery of the .FDI File
My first step was to dive into the raw bytes of an .fdi
file. A simple Go program ( see: https://github.com/mcesarini/fdi-analyzer ) allowed me to read and analyze the contents. It quickly became apparent that the file had a structured format. The initial findings revealed:
- A header block, with the first few bytes clearly stating
"DMFIv1.0"
. This likely identifies the file format version, providing a crucial starting point for understanding the file's structure. - Following the header, I observed repeating patterns of what appeared to be offsets and lengths. This hinted at an index or table section, likely pointing to the actual data blocks within the file.
Based on this initial analysis, I hypothesized the .fdi
file structure to be something like this:
[Header (Format Version, Table Counts?)]
[Index/Table Section (Pointers to Data Blocks)]
[Data Blocks (Player Information)]
Decoding Player Data in Go
With this structural understanding, the next challenge was to decipher the actual player data. This involved meticulously examining the byte sequences within the data blocks to identify fields like player names, surnames, and other attributes. Through careful analysis, I was able to map the byte encodings used for names and surnames.
var CharMap = map[byte]rune{
// Row 0
0x00: 'a', 0x01: '`', 0x02: 'c', 0x03: 'b', 0x04: 'e', 0x05: 'd', 0x06: 'g', 0x07: 'f',
0x08: 'i', 0x09: 'h', 0x0A: 'k', 0x0B: 'j', 0x0C: 'm', 0x0D: 'l', 0x0E: 'o', 0x0F: 'n',
// Row 1
0x10: 'q', 0x11: 'p', 0x12: 's', 0x13: 'r', 0x14: 'u', 0x15: 't', 0x16: 'w', 0x17: 'v',
0x18: 'y', 0x19: 'x', 0x1A: '{', 0x1B: 'z', 0x1C: '}', 0x1D: '|', 0x1E: ' ', 0x1F: '~',
// Row 2
0x20: 'A', 0x21: '@', 0x22: 'C', 0x23: 'B', 0x24: 'E', 0x25: 'D', 0x26: 'G', 0x27: 'F',
0x28: 'I', 0x29: 'H', 0x2A: 'K', 0x2B: 'J', 0x2C: 'M', 0x2D: 'L', 0x2E: 'O', 0x2F: 'N',
// Row 3
0x30: 'Q', 0x31: 'P', 0x32: 'S', 0x33: 'R', 0x34: 'U', 0x35: 'T', 0x36: 'W', 0x37: 'V',
0x38: 'Y', 0x39: 'X', 0x3A: '[', 0x3B: 'Z', 0x3C: ']', 0x3D: '\\', 0x3E: '_', 0x3F: '^',
// Row 4
0x40: '!', 0x41: ' ', 0x42: '#', 0x43: '"', 0x44: '%', 0x45: '$', 0x46: '\'', 0x47: '&',
0x48: ')', 0x49: '(', 0x4A: '+', 0x4B: '*', 0x4C: '-', 0x4D: ',', 0x4E: '/', 0x4F: '.',
// Row 5
0x50: '1', 0x51: '0', 0x52: '3', 0x53: '2', 0x54: '5', 0x55: '4', 0x56: '7', 0x57: '6',
0x58: '9', 0x59: '8', 0x5A: ';', 0x5B: ':', 0x5C: '=', 0x5D: '<' , 0x5E: '?', 0x5F: '>',
// Row 6: 0x60-0x6F - Control characters represented as runes
// Note: These would typically be handled specially in actual code
0x60: 'S', 0x61: 'N', 0x62: 'E', 0x63: 'S', 0x64: 'E', 0x65: 'E', 0x66: 'B', 0x67: 'A',
0x68: 'H', 0x69: 'B', 0x6A: 'V', 0x6B: 'L', 0x6C: 'C', 0x6D: 'F', 0x6E: 'S', 0x6F: 'S',
// Row 7: 0x70-0x7F - Control characters represented as runes
0x70: 'D', 0x71: 'D', 0x72: 'D', 0x73: 'D', 0x74: 'N', 0x75: 'D', 0x76: 'E', 0x77: 'S',
0x78: 'E', 0x79: 'C', 0x7A: 'E', 0x7B: 'S', 0x7C: 'G', 0x7D: 'F', 0x7E: 'U', 0x7F: 'R',
// Row 8 (Accented characters)
0x80: 'á', 0x81: 'à', 0x82: 'ã', 0x83: 'â', 0x84: 'å', 0x85: 'ä', 0x86: 'ç', 0x87: 'æ',
0x88: 'é', 0x89: 'è', 0x8A: 'ë', 0x8B: 'ê', 0x8C: 'í', 0x8D: 'ì', 0x8E: 'ï', 0x8F: 'î',
// Row 9
0x90: 'ñ', 0x91: 'ð', 0x92: 'ó', 0x93: 'ò', 0x94: 'õ', 0x95: 'ô', 0x96: '÷', 0x97: 'ö',
0x98: 'ù', 0x99: 'ø', 0x9A: 'û', 0x9B: 'ú', 0x9C: 'ý', 0x9D: 'ü', 0x9E: 'ÿ', 0x9F: 'þ',
// Row A
0xA0: 'Á', 0xA1: 'À', 0xA2: 'Ã', 0xA3: 'Â', 0xA4: 'Å', 0xA5: 'Ä', 0xA6: 'Ç', 0xA7: 'Æ',
0xA8: 'É', 0xA9: 'È', 0xAA: 'Ë', 0xAB: 'Ê', 0xAC: 'Í', 0xAD: 'Ì', 0xAE: 'Ï', 0xAF: 'Î',
// Row B
0xB0: 'Ñ', 0xB1: 'Ð', 0xB2: 'Ó', 0xB3: 'Ò', 0xB4: 'Õ', 0xB5: 'Ô', 0xB6: '×', 0xB7: 'Ö',
0xB8: 'Ù', 0xB9: 'Ø', 0xBA: 'Û', 0xBB: 'Ú', 0xBC: 'Ý', 0xBD: 'Ü', 0xBE: 'ß', 0xBF: 'Þ',
// Row C
0xC0: '¡', 0xC1: ' ', 0xC2: '£', 0xC3: '¢', 0xC4: '¥', 0xC5: '¤', 0xC6: '§', 0xC7: '¦',
0xC8: '©', 0xC9: '¨', 0xCA: '«', 0xCB: 'ª', 0xCC: '­', 0xCD: '¬', 0xCE: '¯', 0xCF: '®',
// Row D
0xD0: '±', 0xD1: '°', 0xD2: '³', 0xD3: '²', 0xD4: 'µ', 0xD5: '´', 0xD6: '·', 0xD7: '¶',
0xD8: '¹', 0xD9: '¸', 0xDA: '»', 0xDB: 'º', 0xDC: '½', 0xDD: '¼', 0xDE: '¿', 0xDF: '¾',
// Row F
0xF0: '‘', 0xF1: '', 0xF2: '“', 0xF3: '’', 0xF4: '•', 0xF5: '”', 0xF6: '—', 0xF7: '–',
0xF8: '™', 0xF9: '˜', 0xFA: '›', 0xFB: 'š', 0xFC: '', 0xFD: 'œ', 0xFE: 'Ÿ', 0xFF: 'ž',
}
This led to the definition of Go structs to represent player data within my program. For example, a basic player struct might look something like this:
type Player struct {
Begin string
Id string
Number int
SurNameDigits int
SurName string
FullNameDigits int
FullName string
Position int
Role1 string
Role2 string
Role3 string
Role4 string
Role5 string
Role6 string
Nationality string
SkinColor string
HairColor string
PlayerType string
DayOfBirth int
MonthOfBirth int
Year1OfBirth int
Height int
Weight int
NationOfBirth string
Speed int
Stamina int
Aggression int
Quality int
Finishing int
Dribbling int
Passing int
Shooting int
Tackling int
Handling int
PreferredFoot string
Penalties int
LeftCorner int
RightCorner int
LeftFreeKick int
RightFreeKick int
RawData []byte
}
Once the data structures were defined, I wrote Go functions to:
- Locate all players: These functions would traverse the index section and extract the pointers to each player's data block.
- Modify player data: These functions would take a player object and update the corresponding bytes in the
.fdi
file, allowing for editing of player attributes.
Building the User Interface with Fyne
With the backend logic in place, the next exciting step was to create a user-friendly interface. I chose Fyne.io, a Go-based UI toolkit that allows for the creation of cross-platform applications with relative ease.
Using Fyne, I implemented:
- A way to load and browse
.fdi
files. - A visual representation of the player data, allowing users to view and edit names, surnames, and all other attributes.
- Functionality to save the modified data back to the
.fdi
file.
What's Next?
This project is still ongoing, and there are many potential avenues for future development. This could include adding support for more player attributes, implementing more sophisticated data validation and teams modifications.