Editing PARAM.PFD

Jump to navigation Jump to search
Warning: You are not logged in. Your IP address will be publicly visible if you make any edits. If you log in or create an account, your edits will be attributed to your username, along with other benefits.

The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.

Latest revision Your text
Line 1: Line 1:
{{Wikify}}
[[Category:Software]]
[[File:PFD Structure.png|thumbnail|Overview on the PARAM.PFD file structure]]
Alot of this page is filled with speculation and in need of cleanup, making room for proper structured information
 


==Description==
==Description==
'''PFD''' ('''P'''rotected '''F'''iles '''D'''atabase) are responsible to prevent tampering of other files of the same folder, the only purpose is the security of the folder contents. Contains the cryptographic signatures of other files of the same folder (not all, but the ones that developers decided to be important enought to be secured).
This files are responsible to prevent tampering of other files of the same folder, the only purpose is the security of the folder contents.


Contains the cryptographic signatures of other files of the same folder (not all, but the ones that developers decided to be important enought to be secured).


Its used in several paths of the PS3, usually to secure data related with the user profile e.g:
Its used in several folders of the PS3, usually to controll data related with the user profile e.g:


  /dev_hdd0/home/0000000*/savedata/SAVEDATA_DIRECTORY/PARAM.PFD <---- in all save game folders
  /dev_hdd0/home/0000000*/savedata/SAVEDATA_DIRECTORY/PARAM.PFD <---- in all save game folders
  /dev_hdd0/home/0000000*/trophy/NPCOMMID/PARAM.PFD <--- in all trophy folders
  /dev_hdd0/home/0000000*/trophy/NPCOMMID/PARAM.PFD <--- in all trohpye folders
  /dev_hdd0/home/0000000*/trophy/_TROPSYS_/PARAM.PFD <---- in the "user trophy index" folder
  /dev_hdd0/home/0000000*/trophy/_TROPSYS_/PARAM.PFD <---- in the trophy "index" folder
/Maybe other places ?


Usually PARAM.SFO is one of the files listed (and supervised) by PARAM.PFD but this can vary.
Usually PARAM.SFO is one of the files listed (and supervised) by PARAM.PFD but this can vary.


Every time one of the files listed in the '''Protected files table''' inside the PARAM.PFD is updated... the PARAM.PFD itself needs to be updated to store the new signature of the updated file. e.g:
e.g. the folder _TROPSYS_ only contains 2 files: TROPSYS.DAT &  PARAM.PFD (the first is the trophy index for this user, and the second supervises it).
 
Every time one of the files listed inside the .PFD is updated... the .PFD itself needs to be updated to store the new "signatures" of the other updated files e.g:


*When you save a game there is some text that changes inside PARAM.SFO with your current "gameplay time", "character level", etc...
When you save a game there is some text that changes inside PARAM.SFO with your current "gameplay time", "character level", etc...
*When you unlock a new trophy the file TROPUSR.DAT is updated with this new data
When you unlock a new trophye the file TROPUSR.DAT is updated with this new data
*When you install new trophies (with a "trophy installer" in .TRP format) from a new game the TROPSYS.DAT is updated to index the new trophy installation
When you install new trophyes (with a "trophy installer" in .TRP format) from a new game the TROPSYS.DAT is updated to index the new trophy installation
... In all this cases, there is a PARAM.PFD in the same folder that has been updated to store the new "signatures" of the other updated files
... In all this cases, there is a PARAM.PFD that has been updated to store the new "signatures" of the other updated files


More info on other related file formats: [[PARAM.SFO]] [[Game Saves]] [[Trophy files]].
More info on other related file formats: [[PARAM.SFO]] [[Game Saves]] [[Trophy files]].


==Structure==
==Structure==
'''PFD Header''' (96 bytes)
  Total file size = 32768 bytes
'''Tables Header''' (24 bytes)
'''X table''' (456 bytes)
'''Protected files table'''(31008 bytes)
'''Y table''' (1140 bytes)
'''Padding''' (44 bytes)
  Total file size = 96+24+456+31008+1140+44 = 32768 bytes (0x8000)
 
'''C Code By Jakes625, Credits to flatz for original source'''
<code>
    typedef unsigned long int u64;
    typedef unsigned char byte;
 
    typedef struct
    {
        u64 magic; //PDFB
        u64 version; //3 or 4 else error
    } pfd_header_t;
 
    typedef struct
    {
        byte bottom_hash[20];
        byte top_hash[20];
        byte hash_key[20]; //use the decrypted hash_key for sha1 hmac hashing (in sig table)
        byte padding[4];
    } header_table_t;
 
    typedef struct
    {
        u64 num_reserved; //# of entries reserved in entry table (0x39)
        u64 num_total;  //# of total entries (0x72)
        u64 num_used; //# of used entries (the number of files in the folder with pfd)
        u64 hash_entries[39]; //num reserved (* if you prefer)
    } pfd_hash_table_t;


    typedef struct
The file can be divided in 5 chunks: header 120 bytes, X table 456 bytes, file table 31008 bytes, Y table 1140 bytes, and padding 44 bytes, for a total of 120+456+31008+1140+44 = 32768 bytes (0x8000 in hexadecimal)
    {
        u64 addition_entry;
        char file_name[65]; //c string
        byte padding_0[7];
        union
        {
            struct
            {
                byte key[64]; //used for file decryption/encryption and sig table hashing
                byte file_hashes[4][20]; //4 hashes from hmac sha1 following the key
                byte padding_1[40];
                u64 file_size;
            };
            byte file_certificate[192];
        }
    } pfd_entry_table_t;


    typedef struct
The size seems to be fixed because the number of entries in both "X table" & "Y table" is 57 (when the entry is not used his position is reserved and marked as "not-used"). In the same way... the "files table" has the space reserved for a maximun of 114 entries (usually most of them are not used and are filled with zeroes)
    {
        pfd_header_t header;
        byte header_table_iv[0xF]; //key used for aes cbc decryption of header table
        pfd_header_table_t header_table;
        pfd_hash_table_t hash_table;
        pfd_entry_table_t entry_table;
        byte sig_table[39][20]; //array of 39 signatures (corresponding to hash table) and each signature is 20 bytes long (hmac sha1)
        byte end_padding[44]; //out of memory I think it's 44
    } pfd_file_t;</code>


The size of the file is fixed because the number of entries in both '''X table''' & '''Y table''' is 57 (when the entry is not used their position is marked as "not-used"). In the same way... the '''Protected files table''' has an area reserved for a maximum of 114 entries (not used entries are filled with zeroes). As result the file contains the maximum number of possible entries
As result the file contains the maximun number of possible entries


*Useful game saves used in this page as examples
Note that entries in the "file table" (114) is exactly the double than the entries in "X table" (57) & "Y table" (57)
**http://www.gamefaqs.com/ps3/941950-mirrors-edge/saves (the first one region North America). Mirror: http://www.multiupload.nl/SWJ4Y4H7ER
**http://www.gamefaqs.com/ps3/933123-heavy-rain/saves


The structure of the PARAM.PFD used in Mirror's edge game save can be considered "rare", this wiki page uses this file for explaining the structure because is perfect to understand how it works, the structure contains all the "problems" not present in "standard" files that can be considered unknown... there are other game saves that can be used (MotoGP10, Heavy rain)... but the list of protected files in heavy rain contains more than 100 files (use heavy rain if you are coding an app as a "stress test")
===Header===
From 0x0 to 0x78
Size = 120 bytes


===PFD Header===
The end of the header is not clear at first sight, but it can be deduced by counting the number of entries in "X table" & "Y table" and comparing the positions used in both
From 0x00 to 0x60
Size = 96 bytes


First, you need to decrypt the header table itself (you can decrypt it by call ''Decrypt with portability''). The 128-bit key for it is stored at the 0x10 (it is an initialization vector). These bytes are randomly generated when the file is created and never changes after it.
e.g. look for one used entry in the "Y table" and count his position in this table... then look for other used entry in the "X" (both tables matches in the used entries). Then count towards behind to find the first entry (the start of the first entry is the start of the "X table")


After decrypting the header table you can grab a third key from the table at offset 0x48. These bytes are randomly generated. This key is used as HMAC key to authenticate many data. An empty data gives us a default HMAC hash.
So the start of the "X table" is the end offset of the header


You can decrypt the hash table on your PC too. Grab the second key from page [[Keys]] at section '''sc_iso module 1.00-4.00'''. It will be an AES-128 key, and IV is stored at 0x10 offset in .PFD. The algorithm is AES in CBC mode.
{| border="1" cellspacing="0" cellpadding="5" border="#999" class="wikitable" style="border:1px solid #999; border-collapse: collapse;"  
 
|- bgcolor="#cccccc"
The header table is encrypted and always changes when the file is updated, is decrypted by vtrm
! Offset !! Size !! Value !! Description
 
*Mirror's edge game save '''Encrypted PFD Header'''
 
{| class="wikitable"
|-
! Area !! Offset !! Size !! Value !! Description !! Notes
|-
|-
| rowspan="3" style="background-color:#DDDDDD;" | || 0x00 || 0x08 (8 bytes) || 0000000050464442 || '''magic''' || '''PFDB''' (in ASCII)
| 0x00 || 04 bytes || 00000000 || ???why this padding???
|-
|-
| 0x08 || 0x08 (8 bytes) || 0000000000000003 || '''version''' || Must be 3
| 0x04 || 04 bytes || PFDB || Magic value in ASCII
|-
|-
| 0x10 || 0x10 (16 bytes) || 69152C97812B25C52AD4FA18E4B816A8 || '''header_table_iv''' || 128-bits initialization vector. Used to en/decrypt '''header_table'''
| 0x08 || 08 bytes || 0000000000000003 || ???file type???
|-
|-
| rowspan="4" style="background-color:#DDDDDD;" | '''header <br /> table''' || 0x20 || 0x14 (20 bytes) || 7C1F5C28A7EE4D3922ADC828E6CD7888980F33B6 || '''Y-Table SHA1-HMAC hash''' || Used only occupied space, i.e. without padding
| 0x10 || 80 bytes || ???Public key??? || ???Unknown???
|-
|-
| 0x34 || 0x14 (20 bytes) || 2394EEE99706774E71912413A7CFDBE5E38E8D0C || '''Entry Table Header & X-Table SHA1-HMAC hash''' || Data started at 0x60
| 0x60 || 08 bytes || 0000000000000039 || Number of reserved entries in the "X table" & "Y table" (57 in decimal)
|-
|-
| 0x48 || 0x14 (20 bytes)|| 5BCF880FCB7B59C4C5A3D983904B6CEEDE27152 || '''file_hmac_key''' ||
| 0x68 || 08 bytes || 0000000000000072 || Number of reserved entries in the "file table" (114 in decimal)
|-
|-
| 0x5C || 0x04 (4 bytes) || AA9C2F85 || ''padding'' ||
| 0x70 || 08 bytes || 00000000000000** || Number of used entries in the "file table" (it can vary from 1 to 114 in decimal)
|-
|-
|}
|}
:


0x0000  00 00 00 00 50 46 44 42  00 00 00 00 00 00 00 03 |....PFDB........|
===X table===
0x0010  69 15 2C 97 81 2B 25 C5  2A D4 FA 18 E4 B8 16 A8 |i.,..+%.*.......|
0x0020  7C 1F 5C 28 A7 EE 4D 39  22 AD C8 28 E6 CD 78 88 ||.\(..M9"..(..x.|
0x0030  98 0F 33 B6 23 94 EE E9  97 06 77 4E 71 91 24 13 |..3.#.....wNq.$.|
0x0040  A7 CF DB E5 E3 8E 8D 0C  5B CF 88 07 FC B7 B5 9C |........[.......|
0x0050  4C 5A 3D 98 39 04 B6 CE  ED E2 71 52 AA 9C 2F 85 |LZ=.9.....qR../.|
 
*Mirror's edge game save '''Decrypted PFD Header'''
0x0000  00 00 00 00 50 46 44 42  00 00 00 00 00 00 00 03 |....PFDB........|
0x0010  69 15 2C 97 81 2B 25 C5  2A D4 FA 18 E4 B8 16 A8 |i.,..+%.*.......|
0x0020  2F 0C F2 BA 8B 59 F3 48  7D 7E A4 8D 63 77 6A 9A |/....Y.H}~..cwj.|
0x0030  20 1A D8 75 CD 70 9C 6B  B4 99 B8 6E 61 D7 49 6B | ..u.p.k...na.Ik|
0x0040  B6 3D 86 FC F0 42 79 15  F4 47 42 26 C2 8D 46 D4 |.=...By..GB&..F.|
0x0050  C6 67 A4 E6 B9 C5 9F 3D  65 C2 33 14 1C 1D 5F 8E |.g.....=e.3..._.|
 
===Tables Header===
From 0x60 to 0x78
Size = 24 bytes
 
This header defines the structure of the tables
 
*Mirror's edge game save '''Tables Header'''
0x0060  00 00 00 00 00 00 00 39  00 00 00 00 00 00 00 72 |.......9.......r|
0x0070  00 00 00 00 00 00 00 21                          |.......!
 
{| class="wikitable"
|-
! Offset !! Size !! Value !! Description !! Notes
|-
| 0x60 || 0x08 bytes || 0000000000000039 || '''X and Y tables reserved entries''' || '''57''' (in decimal). Seems correct in all the examples found, but is speculation ???
|-
| 0x68 || 0x08 bytes || 0000000000000072 || '''Protected files table reserved entries''' || '''114''' (in decimal). Seems correct in all the examples found, but is speculation ???
|-
| 0x70 || 0x08 bytes || 0000000000000021 || '''Protected files table used entries''' || '''33''' (in decimal). Can vary, from 1 to 114 in decimal (or from 0x00 to 0x71)
|-
|}
 
===X-Table===


  From 0x78 to 0x240
  From 0x78 to 0x240
  Size = 456 bytes
  Size = 456 bytes
  Entry length = 8 bytes
  Entry lenght = 8 bytes
  Number of entries = 57
  Number of entries = 57


Each entry can contain a "file index" (from 0x00 to 0x71... or 1 to 114 in decimal) that is pointing (or is assigned) to one of the files in the '''Protected files table'''. How this assignation works is unknown
This table has a total of 57 entries, with 8 bytes each, for a total of 456 bytes (57*8=456)


If the "file index" is 72 (position 115) it means that is pointing out of the '''Protected files table''' (not used)
Each entry can contain a "file index" that points to the "file table" from 0 to 71 in hexadecimal (or 1 to 114 in decimal)


The first "file index" (nº0x00) is always located at 0xb8 (entry nº9 of '''X-Table''', marked in blue in the example). In complex PARAM.PFD like the one used as example the value is replaced by another "file index", this seems to indicate that there are more "file index" spreaded in the '''Protected files table''' and "file index" nº0x00 is located in this new position
If the "file index" is 72 it means that is pointing out of the "file table" (not used)


The table does not fills with entries from top to bottom, usually the first entries are not used (marked as 72) followed by entries with whatever number from 0 to 71 (by now the entries used seems random), and mixed with more 72's entries
The table does not fills with entries from top to bottom, usually the first entries are not used (marked as 72) followed by entries with whatever number from 0 to 71, and mixed with more 72's entries


Used entries has a number assigned by their position in the table e.g:
Used entries has a number asigned by his position in the table e.g:


*Mirror's edge game save '''X table'''
Example of a "non-standard" "X table" with a "entry lenght" of 1 byte, and 8 entries
0070                          00 00 00 00 00 00 00 72          .......r|
  72 01 72 72 00 72 72 02
0080  00 00 00 00 00 00 00 '''01'''  00 00 00 00 00 00 00 72  |...............r|
   
0090  00 00 00 00 00 00 00 '''0C'''  00 00 00 00 00 00 00 72  |...............r|
  This "X table" can be translated as:
00a0  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 '''1F'''  |.......r........|
  -File index nº 1 (00) assigned as position 5
00b0  00 00 00 00 00 00 00 '''10'''  <span style="background:#6666ff;">00 00 00 00 00 00 00 '''1B'''</span>  |................|
  -File index nº 2 (01) assigned as position 2
  00c0  00 00 00 00 00 00 00 72 00 00 00 00 00 00 00 '''14'''  |.......r........|
  -File index nº 3 (02) assigned as position 8
00d0  00 00 00 00 00 00 00 72 00 00 00 00 00 00 00 72  |.......r.......r|
00e0  00 00 00 00 00 00 00 '''0B'''  00 00 00 00 00 00 00 '''0D'''  |................|
00f0  00 00 00 00 00 00 00 72 00 00 00 00 00 00 00 72 |.......r.......r|
0100  00 00 00 00 00 00 00 '''15'''  00 00 00 00 00 00 00 '''19'''  |................|
0110  00 00 00 00 00 00 00 72 00 00 00 00 00 00 00 '''13'''  |.......r........|
  0120  00 00 00 00 00 00 00 '''17'''  00 00 00 00 00 00 00 72  |...............r|
  0130  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 '''0E'''  |.......r........|
  0140  00 00 00 00 00 00 00 '''11'''  00 00 00 00 00 00 00 72  |...............r|
  0150  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 '''18'''  |.......r........|
0160  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 72  |.......r.......r|
0170  00 00 00 00 00 00 00 '''1A'''  00 00 00 00 00 00 00 '''12'''  |................|
0180  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 72  |.......r.......r|
0190  00 00 00 00 00 00 00 '''04'''  00 00 00 00 00 00 00 72  |...............r|
01a0  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 '''1C'''  |.......r.......r|
01b0  00 00 00 00 00 00 00 '''07'''  00 00 00 00 00 00 00 72  |...............r|
01c0  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 72  |.......r.......r|
01d0  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 72  |.......r.......r|
01e0  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 '''03'''  |.......r........|
01f0  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 72  |.......r.......r|
  0200  00 00 00 00 00 00 00 '''1D'''  00 00 00 00 00 00 00 72  |...............r|
0210  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 '''20'''  |.......r........|
0220  00 00 00 00 00 00 00 '''0F'''  00 00 00 00 00 00 00 72  |...............r|
0230  00 00 00 00 00 00 00 72  00 00 00 00 00 00 00 '''1E'''  |.......r........|
In this table there are 25 entries used


{| class="wikitable"
{| border="1" cellspacing="0" cellpadding="5" border="#999" class="wikitable" style="border:1px solid #999; border-collapse: collapse;"  
|-
|- bgcolor="#cccccc"
! Position in table !! Virtual Index ID ?
! Some possible entries !! Description
|-
| 1 || {{No}}
|-
| 2 || 0x01
|-
| 3 || {{No}}
|-
| 4 || 0x0C
|-
| 5 || {{No}}
|-
| 6 || {{No}}
|-
| 7 || 0x1F
|-
| 8 || 0x10
|-style="background:#6666ff;"
| 9 || 0x1B
|-
| 10 || {{No}}
|-
| 11 || 0x14
|-
| 12 || {{No}}
|-
| 13 || {{No}}
|-
| 14 || 0x0B
|-
| 15 || 0x0D
|-
| 16 || {{No}}
|-
| 17 || {{No}}
|-
| 18 || 0x15
|-
| 19 || 0x19
|-
| 20 || {{No}}
|-
| 21 || 0x13
|-
| 22 || 0x17
|-
| 23 || {{No}}
|-
| 24 || {{No}}
|-
| 25 || 0x0E
|-
| 26 || 0x11
|-
| 27 || {{No}}
|-
| 28 || {{No}}
|-
| 29 || 0x18
|-
| 30 || {{No}}
|-
| 31 || {{No}}
|-
| 32 || 0x1A
|-
| 33 || 0x12
|-
| 34 || {{No}}
|-
| 35 || {{No}}
|-
| 36 || 0x04
|-
| 37 || {{No}}
|-
| 38 || {{No}}
|-
| 39 || 0x1C
|-
| 40 || 0x07
|-
| 41 || {{No}}
|-
| 42 || {{No}}
|-
| 43 || {{No}}
|-
| 44 || {{No}}
|-
| 45 || {{No}}
|-
| 46 || {{No}}
|-
| 47 || 0x03
|-
| 48 || {{No}}
|-
| 49 || {{No}}
|-
| 50 || 0x1D
|-
| 51 || {{No}}
|-
| 52 || {{No}}
|-
| 53 || 0x20
|-
| 54 || 0x0F
|-
| 55 || {{No}}
|-
| 56 || {{No}}
|-
| 57 || 0x1E
|-
|}
 
{| class="wikitable"
|-
! Other possible values !! Description
|-
|-
| 0000000000000000 || File index nº 1
| 0000000000000000 || File index nº 1
Line 351: Line 105:
| 0000000000000071 || File index nº 114
| 0000000000000071 || File index nº 114
|-
|-
| 0000000000000072 || Out of '''Protected files table''' (not used)
| 0000000000000072 || Out of file table (not used)
|-
|-
|}
|}
:


===Protected files table===
===File table===
  From 0x240 to 0x7B60
  From 0x240 to 0x7B60
  Size = 31008 bytes
  Size = 31008 bytes
  Entry length = 272 bytes
  Entry lenght = 272 bytes
  Number of reserved entries = 114
  Number of entries = variable from 1 to 114
Number of used entries = variable from 1 to 114


*The table contains 114 reserved entries, each used entry has this structure
This table has a total of 114 entries, with 272 bytes each, for a total of 31008 bytes (114*272=31008)
{| class="wikitable"
|-
! Size !! Value !! Name !! Description
|-
| 0x08 (8 bytes) || 00000000000000** || '''Virtual Index ID ?''' || Valid values from 0x00 to 0x71 (and 0x72 = Not used)
|-
| 0x41 (65 bytes) || EXAMPLE.WTF || '''File Name''' || Name of the file included the point and the extension in ASCII (Null-terminated)
|-
| 0x07 (7 bytes) || :Wtf-o0- || '''Random garbage''' || Usually zeroed (crysis2 savedata uses '''CELL''' and heavenly sword '''s:Music''') (random stack data because of an uninitialized variable?)
|-
| 0xC0 (192 bytes) || ????????... || '''File Certificate?''' || Encrypted file info? A part of it is decrypted by VTRM, key is unknown yet. Last 8 bytes of it is '''File Size'''.
|}


*Notes
The used entries fills the table from top to bottom, not-used entries are placed at the end of the table filled with zeroes
Certificate: The first 64 bytes are the entry key used to decrypt PARAM.SFO (or whatever entry the file is) and the next 80 bytes are the 4 hmac sha1 hashes. PARAM.SFO has more than other files because it goes through a different hashing process using the param sfo encryption key as do others such as trophies and console id etc. Then 40 bytes of padding, followed by (u64) (8 bytes) of the file size of the file entry


Encrypted by VTRM using the same portability algorithm. Key is unknown yet. The first 0x10 and the second 0x10 bytes are randomly generated (key/iv?).
The first 8 bytes of each entry (file index) works in the same way that the entries in the "X table", usually not used (72) and when used are randmonly placed asigning an "file index" to the entry


Each entry can store the signature of one of the files in the folder, there is always an entry used to store the signature of [[PARAM.SFO]], this gives a maximum number of protected files generated by the game of 113. Used entries fills the table from top to bottom, not-used entries are placed at the end of the table filled with zeroes
This "file index" does not matches with the position of the entry in the "file table" itself... so seems that this "indexes files" are ???virtually reordered???


The first 8 bytes of each entry (Virtual Index ID) works in the same way than the entries in the '''X table''', when not used are marked as "72" and when used are randomly placed assigning a "file index" to the entry. This "file index" does not matches with the position of the entry in the '''Protected files table''' itself... (e.g: indexed 0x06 is in position 14)
In fact, for a theoricall file with alll entries used, half of the "file index" numbers will be spreaded between the "X table" (can only contain 57) ant the first 8 bytes of some entries in the "file table"


For a theoretical file with all entries used (114 protected files), half of the "file index" numbers will be spreaded between the '''X table''' (can only contain 57) ant the first 8 bytes of some entries in the '''Protected files table''' (the other 57)
{| border="1" cellspacing="0" cellpadding="5" border="#999" class="wikitable" style="border:1px solid #999; border-collapse: collapse;"  
 
|- bgcolor="#cccccc"
*Mirror's edge game save '''Protected Files Table'''
! Size !! Value !! Description
The table contains 33 used entries, 8 of them are "indexed"
{| class="wikitable"
|-
! Position in table !! Virtual Index ID ? !! File Name !! Unknown ? !! Certificate ? !! File Size
|-
| 1 || {{No}} || PARAM.SFO || 00568CD8 || longer than others || 00000AB0
|-
| 2 || {{No}} || SAVTOC0.BIN || 00568CD8 || || 0000058C
|-
| 3 || {{No}} || SAV33.BIN || 00568CD8 || || 00000584
|-
| 4 || {{No}} || SAV14.BIN || 00568CD8 || || 00009543
|-
| 5 || {{No}} || SAV3.BIN || 00568CD8 || || 0000848F
|-
| 6 || {{No}} || SAV22.BIN || 00568CD8 || || 00008221
|-
| 7 || {{No}} || SAV21.BIN || 00568CD8 || || 00009D66
|-
| 8 || {{No}} || SAV13.BIN || 00568CD8 || || 00009EF5
|-
| 9 || {{No}} || SAV5.BIN || 00568CD8 || || 0000CD1B
|-
| 10 || {{No}} || SAV6.BIN || 00568CD8 || || 0000B3AF
|-
| 11 || {{No}} || SAV7.BIN || 00568CD8 || || 00008266
|-
| 12 || {{No}} || SAV8.BIN || 00568CD8 || || 00007618
|-
| 13 || {{No}} || SAV16.BIN || 00568CD8 || || 0000B95A
|-
| 14 || 0x06 || SAV0.BIN || 00568CD8 || || 00009F4D
|-
| 15 || {{No}} || SAV19.BIN || 00568CD8 || || 0000A11B
|-
| 16 || {{No}} || SAV15.BIN || 00568CD8 || || 0000CC00
|-
| 17 || {{No}} || SAV20.BIN || 00568CD8 || || 000109F0
|-
| 18 || {{No}} || SAV11.BIN || 00568CD8 || || 00009C0A
|-
| 19 || {{No}} || SAV12.BIN || 00568CD8 || || 0000948D
|-
| 20 || {{No}} || SAV9.BIN || 00568CD8 || || 0000C712
|-
| 21 || {{No}} || SAV17.BIN || 00568CD8 || || 0000E4FD
|-
| 22 || 0x02 || SAV18.BIN || 00568CD8 || || 0000AE4D
|-
| 23 || {{No}} || SAV2.BIN || 00568CD8 || || 0000CD55
|-
| 24 || 0x05 || SAV1.BIN || 00568CD8 || || 000098B3
|-
| 25 || 0x16 || SAV23.BIN || 00568CD8 || || 00004FBE
|-
| 26 || {{No}} || SAV10.BIN || 00568CD8 || || 0000ADBD
|-
| 27 || {{No}} || SAV35.BIN || 00568CD8 || || 00008E2E
|-style="background:#6666ff;"
| 28 || 0x00 || SAVTOC1.BIN || 00568CD8 || || 00000108
|-
| 29 || {{No}} || SAV36.BIN || 00568CD8 || || 00009CBE
|-
|-
| 30 || 0x08 || SAV26.BIN || 00568CD8 || || 0000B4D7
| 008 bytes || 00000000000000** || File index (value 72 = Not indexed)
|-
|-
| 31 || 0x09 || SAV27.BIN || 00568CD8 || || 00009FD1
| 064 bytes || EXAMPLE.WTF || Name of the file included the point and the extension in ASCII (Null-terminated)
|-
|-
| 32 || 0x0A || SAV28.BIN || 00568CD8 || || 00007D73
| 008 bytes || :Wtf-o0- || In ASCII, Usually zeroed (crysis2 savedata contains the string "CELL" and heavenly sword contains "s:Music") ???Unknown???
|-
|-
| 33 || {{No}} || SAV38.BIN || 00568CD8 || || 0000D401
| 188 bytes || ????????... || Certificate for the file. When the file is PARAM.SFO then the certificate is bigger in size and uses imput data from the attribute "PARAMS" and/or "ACCOUNT_ID" inside PARAM.SFO. Method unknown (Null-terminated)
|-
|-
| Up to 114 || {{No}} || {{No}} || {{No}} || {{No}} || {{No}}
| 004 bytes || 1A2B3C4D|| Size of the file in bytes
|-
|-
|}
|}
:


===Y-table===
===Y table===
  From 0x7B60 to 0x7FD4
  From 0x7B60 to 0x7FD4
  Size = 1140 bytes
  Size = 1140 bytes
  Entry length = 20 bytes (SHA-1 HMAC digest)
  Entry lenght = 20 bytes (some kind of sha-1 hash??)
  Number of entries = 57
  Number of entries = 57


Is directly related with the '''X table''', both matches in the total number of entries (57) and which ones are used (e.g. when the '''X table''' has a entry in position 12... the '''Y table''' has position 12 used)  
This table has a total of 57 entries, with 20 bytes each, for a total of 1140 bytes (57*20=1140)


Only used entries contains a real SHA1-HMAC hash. It can be the SHA1-HMAC hash of entry (a concatenation of 0x41 bytes of '''File Name''' and 0xC0 bytes of '''File certificate''' is used as data) or some unknown SHA1-HMAC hash (hash of file data related to the entry?). Unused entries have a "default" hash (SHA1-HMAC of empty data). In a theoretical PARAM.PFD with no files listed, the '''Y table''' would have all their 57 entries with a default hash).
All the entries contains the same "20 bytes string", only the used ones has a different "20 bytes string"
in the other "X table" (in a theoricall PARAM.PFD with no files listed, the "Y table" would have all his 57 entries with the same string)


The '''Y table''' has a repeating pattern so an entry for each potential file with the blank entry (I.E. no file) being represented by the repeating byte pattern.
Is directly related with the "X table", both matches in the total number of entries (57) and wich ones are used (e.g. when the "index table" has a entry in position 12... the "weird table" has position 12 used)  


*Mirror's edge game save '''Y table'''
The "Y table" has a repeating pattern so an entry for each potential file with the blank entry (I.E. no file)
When a entry in this table is not used is "marked" with a "standard" value... for mirror's edge game save is: '''5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0'''
being represented by the repeating byte pattern.
 
{| class="wikitable"
|-
! Position in table !! Hash ???
|-
| 1 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 2 || 83C16E92 F5F5FAAF 19A00810 186E82A9 313AADAD
|-
| 3 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 4 || 28458FC1 3B00ED44 EF5FA5BE D55D016E 94066099
|-
| 5 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 6 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 7 || 42CBF813 4469B91F 541DCF76 CC4934E9 F6CDEDC6
|-
| 8 || 6B88527E002E78DB1D915573DD44951F0CBE6A3C
|-style="background:#6666ff;"
| 9 || 703F1A6F0A576A8D85E8EB35B30FE5DAB7689988
|-
| 10 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 11 || EFAF21CD389AC84662601AC5B449AE12CCF739F9
|-
| 12 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 13 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 14 || C45F773837AE76FAF3D662974438CE7FD620D2D0
|-
| 15 || 6BE399A6B97F6158C6194ED6C8CC6A1B2AD6ADDB
|-
| 16 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 17 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 18 || 933E85E3651EC32C05929CAD2CD23081BC975E9C
|-
| 19 || 323A62857BAD6BD1D3B1ED8584723CC36C4D915D
|-
| 20 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 21 || 1B9491E3F006528C387F4A8FAD75455E9780EF0D
|-
| 22 || A69EC11F680BCDC8260CDF38FA47C3FCD1D16495
|-
| 23 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 24 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 25 || 41534126B047CE6AADC6606C24766246C4F712D7
|-
| 26 || 08F26808FB5A47B4E35E71DDAA3167CD5CD08D75
|-
| 27 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 28 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 29 || 71A20B8F36D2853E4E84AB998AD201A0AC8C629C
|-
| 30 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 31 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 32 || 18BA71B3CEC1CCCDAD3395323C4259D72B1EF5E0
|-
| 33 || B08D1FAB5A5FA370B5FF2930024CE7CAC9F08234
|-
| 34 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 35 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 36 || E81D24D0EF67CDC849260668B2A99F982C545929
|-
| 37 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 38 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 39 || 6347B31E98FA9A58195AB5D9ED1548A46CFE6FCE
|-
| 40 || 4CB48133E388EDBCF0E0AD0DB6CD55020CB03342
|-
| 41 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 42 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 43 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 44 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 45 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 46 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 47 || D5097670CAF4186E89009EF031FD721FE3C3E083
|-
| 48 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 49 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 50 || 49481F76A9C591E03CA1B115D0FC9737A48837A3
|-
| 51 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 52 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 53 || 389ED925282AFEC432D2A1F042E0043D0E91F785
|-
| 54 || E40D4A3ABB1EEB92A1BEF55B7730AB6BB5CDDB2D
|-
| 55 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 56 || style="background:Red;" | 5A7EAF6D 029992D0 7580EBCA 42698114 9F2C5DF0
|-
| 57 || C1CE8AF3 7CA63059 B835B2D6 CD64E506 B2E55397
|-
|}
 
A screencapture of this table with the width fixed (each entry is one row)... there is a total of 57 entries, 25 of them used. http://img6.imagebanana.com/img/q82sh1dj/Ytablemirrorsedge.jpg


===Padding===
===Padding===
  Size = 44 bytes
  Size = 44 bytes
Not much to say about this padding, is an area filled with zeroes to increase the size of the file to 32768 bytes (0x8000)
----
----
----
==Cryptography and Speculation==
===How to blank the tables===
In the header of the file there are 3 hashes that belongs to the 3 tables (X table, protected files table, and Y table). The first step needed to modify the file is to be able to generate this hashes for our "new" modifyed tables... but in fact, this hashes are the "only" thing needed to pwn the whole file security, because we can "clean-up" all the tables (in other words, we dont need to understand how this tables are built, how the protected files signatures are generated, or what this ID's means, because we can erase all this data):
*1.- First is needed to look at "X table" to find an entry marked as 0x72 (not used), then go to the "Y table" and using this same position make a copy of the entry (this will be needed later in step 5 when cleaning-up "Y table" by pasting this value repeatedly)
*2.- Fill with zeroes the value "Protected files table used entries" (from 0x70 to 0x78)
*3.- Fill "X table" with "not-used" entryes (from 0x78 to 0x240... paste 57 times the entry 0000000000000072)
*4.- Fill with zeroes all the "protected files table" (from 0x240 to 0x7B60)
*5.- Fill "Y table" with "not-used" entries (from 0x7B60 to 0x7FD4... paste 57 times the "not used" value for this table copyed previously)
After cleaning-up the tables and regenerating its hashes, the behaviour of the PS3 when trying to load this "BLANKED.PFD" file can give some possible results:
*File is valid, is loaded, but there is "something" that states this file originally had X "protected files" listed.... but now there is none = epic fail
*File is valid, is loaded, and PARAM.SFO is added to the list of protected files (this is something that seems to happen "by default"), consequently the PARAM.PFD is updated, both files are valid = partial win
*File is valid, is loaded, no protected files are added to the list = epic win
It depends of the function that creates/updates the .PFD which files needs to be added to the list of "protected files" (this is done the first time when the file is created), but probably games can add more files for their game saves
The theory is simple... is a valid file with empty tables, it's something not tested yet, but if somebody finds a way to generate the header hashes... this theory is the next test that worths a try
===The "virtual index"===
Mirror's edge has a total of 33 "protected files" (listed in the protected files table)... this number matches with the 33 "indexed" numbers that are spreading between "X table" and "protected files table" itself... if we place all the entries from all tables together we can reorder all by using their ID number in a "virtual index"
{| class="wikitable"
|-
! Index ID !! Index ID position in "X Table" !! Index ID position in "Protected Files Table" (File Name asociated) !! Y table hash ? (same position than X table)
|-
| style="background:#6666ff;" | 0x00 (nº1)  || {{no}} || style="background:#6666ff;" | 28 (SAVTOC1.BIN) || {{no}} (default)
|-
| 0x01 (nº2) || 2 || {{No}} || 83C16E92F5F5FAAF19A00810186E82A9313AADAD
|-
| 0x02 (nº3) || {{no}} || 22 (SAV18.BIN ) || {{no}} (default)
|-
| 0x03 (nº4) || 47 || {{No}} || D5097670CAF4186E89009EF031FD721FE3C3E083
|-
| 0x04 (nº5) || 36 || {{No}} || E81D24D0EF67CDC849260668B2A99F982C545929
|-
| 0x05 (nº6) || {{no}} || 24 (SAV1.BIN ) || {{no}} (default)
|-
| 0x06 (nº7) || {{no}} || 14 (SAV0.BIN ) || {{no}} (default)
|-
| 0x07 (nº8) || 40 || {{No}} || 4CB48133E388EDBCF0E0AD0DB6CD55020CB03342
|-
| 0x08 (nº9) || {{no}} || 30 (SAV26.BIN ) || {{no}} (default)
|-
| 0x09 (nº10) || {{no}} || 31 (SAV27.BIN ) || {{no}} (default)
|-
| 0x0A (nº11) || {{no}} || 32 (SAV28.BIN ) || {{no}} (default)
|-
| 0x0B (nº12) || 14 || {{No}} || C45F773837AE76FAF3D662974438CE7FD620D2D0
|-
| 0x0C (nº13) || 4 || {{No}} || 28458FC13B00ED44EF5FA5BED55D016E94066099
|-
| 0x0D (nº14) || 15 || {{No}} || 6BE399A6B97F6158C6194ED6C8CC6A1B2AD6ADDB
|-
| 0x0E (nº15) || 25 || {{No}} || 41534126B047CE6AADC6606C24766246C4F712D7
|-
| 0x0F (nº16) || 54 || {{No}} || E40D4A3ABB1EEB92A1BEF55B7730AB6BB5CDDB2D
|-
| 0x10 (nº17) || 8 || {{No}} || 6B88527E002E78DB1D915573DD44951F0CBE6A3C
|-
| 0x11 (nº18) || 26 || {{No}} || 08F26808FB5A47B4E35E71DDAA3167CD5CD08D75
|-
| 0x12 (nº19) || 33 || {{No}} || B08D1FAB5A5FA370B5FF2930024CE7CAC9F08234
|-
| 0x13 (nº20) || 21 || {{No}} || 1B9491E3F006528C387F4A8FAD75455E9780EF0D
|-
| 0x14 (nº21) || 11 || {{No}} || EFAF21CD389AC84662601AC5B449AE12CCF739F9
|-
| 0x15 (nº22) || 18 || {{No}} || 933E85E3651EC32C05929CAD2CD23081BC975E9C
|-
| 0x16 (nº23) || {{no}} || 25 (SAV23.BIN ) || {{no}} (default)
|-
| 0x17 (nº24) || 22 || {{No}} || A69EC11F680BCDC8260CDF38FA47C3FCD1D16495
|-
| 0x18 (nº25) || 29 || {{No}} || 71A20B8F36D2853E4E84AB998AD201A0AC8C629C
|-
| 0x19 (nº26) || 19 || {{No}} || 323A62857BAD6BD1D3B1ED8584723CC36C4D915D
|-
| 0x1A (nº27) || 32 || {{No}} || 18BA71B3CEC1CCCDAD3395323C4259D72B1EF5E0
|-
| 0x1B (nº28) || style="background:#6666ff;" | 9 || {{No}} || style="background:#6666ff;" | 703F1A6F0A576A8D85E8EB35B30FE5DAB7689988
|-
| 0x1C (nº29) || 39 || {{No}} || 6347B31E98FA9A58195AB5D9ED1548A46CFE6FCE
|-
| 0x1D (nº30) || 50 || {{No}} || 49481F76A9C591E03CA1B115D0FC9737A48837A3
|-
| 0x1E (nº31) || 57 || {{No}} || C1CE8AF37CA63059B835B2D6CD64E506B2E55397
|-
| 0x1F (nº32) || 7 || {{No}} || 42CBF8134469B91F541DCF76CC4934E9F6CDEDC6
|-
| 0x20 (nº33) || 53 || {{No}} || 389ED925282AFEC432D2A1F042E0043D0E91F785
|-
|}
*Jumps theory
**When the file is loaded, the firmware tryes to find ID 0x00 at position nº9 in "X-table" (at offset 0xB8)... in mirror's edge this value is not ID 0x00... has been replaced by ID 0x1B (position 28 in decimal)
**Then to locate ID 0x00 the firmware "jumps" +28 positions in "Protected files table"
**This has not been fully verifyed yet, and is most notable in the first "jump"... also is not clear if this ID's are in fact ID's, just a displacement, or both
**The next "jump" seems to be based in timestamps/filenames (see "talk" page)


===More brainstorming===
==Cryptography==
*The maximum number of entries in the '''Protected files table''' (114) is exactly the double than the maximum number of entries in '''X table''' (57) & '''Y table''' (57). Or from other point of view 114 = 57 + 57
Unknown by now, but some questions rises...
**Seems obvious that there is an "index number" assigned to each "protected file", but their positions are "scrambled"




*The order of the files in the "protected files table" is based on timestamps (not alphabetically, not by size). Or in other words... tt's related with how the PARAM.PFD was generated for first time
Why the files are listed in this order and not in other in the "files table" ?
**The original order when the file was created for first time (based on these timestamps), when the files in the list are updated by the ps3 (its timestamps change) keep their original position. When new files are added to the "protected files list table"... they are added to the end of the list, based on these timestamps.
**When several "protected files" have the same timestamp (something very probable because can be generated by the game very fast)... the order is alphabetical
**So when ordering them, the timestamp is preferent... and when a timestamp matches it uses their name (alphabetically)


Because are not listed alphabetically, neither by size


*Indexed files in the '''X table''' has a different number for each one, never repeats, but there is not direct relationship between the number of entries in '''X table''' & '''Y table''' (both are fixed to 57) and the number of files listed in the '''Protected files table''' (114)... the most logical explain if that this 114 "protected files" can be linked to both tables (57 each)... but in fact the only table that stores crypto is the '''Y table''' (limited to 57)... so what trick they used ? hmmmm




*What are this index in the '''X table''' and in the '''Protected files table''' itself?, its positions seems to be random (but the number of indexed files matches with the number of "protected files"), seems like an old school "lucas arts games" anticheat card where you pick 2 values and by mixing them you get the unlock code :D
Indexes files (in the "index table") seems to have different number for every one, never repeats, but there is not direct relationship between the number of entries in "index table" & "weird table" (both are fixed to 57) and the numer of files listed in the "file table" (114)... the most logicall explain if that this 114 files can be linked to
**Seems to be "jumps" from one table to the other. The files are read in order by the ps3 by "jumping" using its "index nº" to locate the next one... "Index nº0" seems to be located always at position 8 in the "X-Table" (offset 0xB8). In most complex PARAM.SFO (e.g: heavy rain, mirror's edge or motogp10/11) the value at this position is replaced by another "index nº" (by now seems random) that is pointing to the "protected files table" index area of each entry (where index nº0 is located). How this "jumps" works is partially unknown and by now is only notable in the first jump
both tables (57 each)... but in fact the only table that stores crypto is the "weird table" (limited to 57)... so what trick they used ? hmmmm


What are this index in the "index table" and in the "file table" itself?, his positions seems to be random, seems like an old school "lucas arts games" anticheat card where you pick 2 values and by mixing them you get the unlock code :D


But here what is random is the positions, and index numbers of the entries in the "index table", and the indexed files in the "file table" ??? 2 index ???




Discussion thread ---> http://www.ps3hax.net/showthread.php?p=392684#post392684


==Mechanism Diagrams==
These diagrams illustrate the mechanisms used by the PARAM.PFD to protect the files. '''For the PARAM.SFO there seems to be a slightly different mechanism!'''
===Decryption / Encryption of protected files===


[[File:PFD Decrypt.png|boderless|The decryption mechanism of the PARAM.PFD]]
[[File:PFD Encrypt.png|boderless|The Encryption mechanism of the PARAM.PFD]]


===PARAM.PFD file modification process===
After you have modified a decrypted file using a hex-editor or whatever you encrypt it back. Now you need to do these steps in this order to update the PARAM.PFD:


# update the file hashes in the protected files table entries
# update the y table signatures
# calculate the y table HMACSHA1
# calculate the tables_header + x table HMACSHA1
# encrypt the header_table
[[File:PFD Filehashes.png|boderless| update the file hashes in the protected files table entries]]
[[File:PFD Ytablesignatures.png|boderless| update the y table signatures]]
[[File:PFD XytableHMAC.png|boderless| calculate the xy table HMACSHA1]]
[[File:PFD Header table.png|boderless| encrypt the header_table]]


==Related Work==
[https://github.com/BuXXe/PARAM.PFD-PS3-Demons-Souls-Savegame-Tool Github for a Python 2.7 PARAM.PFD (decryption/encryption/signing) for Demon's Souls]


{{File Formats}}<noinclude>[[Category:Main]]</noinclude>
Source: http://ps3dev.wikispaces.com/PARAM.PFD
Please note that all contributions to PS3 Developer wiki are considered to be released under the GNU Free Documentation License 1.2 (see PS3 Developer wiki:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!

To protect the wiki against automated edit spam, we kindly ask you to solve the following hCaptcha:

Cancel Editing help (opens in new window)