{"name":"PBO File Format","title":"PBO File Format","type":"topic","url":"https://community.bistudio.com/wiki/PBO_File_Format","revid":373444,"updated":"2025-05-08T11:27:12Z","aliases":["PBO"],"markdown":"> **Disclaimer: This page describes internal undocumented structures of Bohemia Interactive software.**\n>\n> This page contains unofficial information.\n>\n> Some usage of this information may constitute a violation of the rights of Bohemia Interactive and is in no way endorsed or recommended by Bohemia Interactive.\n> Bohemia Interactive is not willing to tolerate use of such tools if it contravenes any general licenses granted to end users of this community wiki or BI products.\n\n**PBO** means \"**p**acked **b**ank **o**f files\". A **.pbo** is identical in purpose to a zip or rar. It is a container for folder(s) and file(s).\n\nThe engine will internally expand any ***.pbo** back out to its original, tree-folder, form.\n\n##### Pbo .ext types\n\ngenerically called 'pbo' there are in fact different extension names employed, dependent on the engine being used. In all cases, the contents ultimately are identical to pbos.\n\n- ifa: A World War2 mod hard wired to only accept ifa files.\n- ebo: initially used by VBS, then by Arma, to encrypt the contents of a pbo and call it a different extension.\n- xbo: Used by vbsLite encrypt with a different algorithm to above.\n  - the encryption method used for vbslite UK and vbslite US is different.\n\n#### Legend\n\nSee Generic FileFormat Data Types.\n\n#### Compression\n\nIn addition to simply packaging all files and folders in a tree into a single file, some, all, or none of the files within can be compressed using Apple's method of LZSS compression.\nWhich type of files are compressed is *entirely* optional. Ones that won't benefit normally are not and some files in the Arma engines cannot be because bis forgot they had this code.\nThe intent behind compression **was** for internet use and, in the 'good old days', simply to reduce hard disk storage requirements.\nThe *actual* use of compression is becoming less 'popular' as it does represent a small load on the engine. Operation Flashpoint: Elite cannot work with *compressed .pbo* files - see Elite PBOs.\n\n#### PboTypes\n\n##### Mission Versus Addon\n\nAcross all engines there are two distinct types of pbo.\n\n- Mission pbos do not contain config.cpps and live in a mission folder (be it campaign, single, or multiplayer). They have no use for a pboPrefix, nor a Properties header.\n- Addon pbos contain a config.cpp/bin which at-the-least identifies the addon name, and, with the exception of the initial cwc engine, rely on a pbo properties header. They live in an addons\\ folder.\n\n##### pbo engine differences\n\n- Cold War Crisis: the very first in the series (disregarding the 'proof of concept' release a year earlier) has no pboProperties header and no checksums. It does however form the core of all other engines. It's format has never been altered\n- Resistance: added a Properties header as the first 'entry' of the pbo.\n- Vbs1: same as Resistance (but encrypted).\n- Xbox Elite: added a 0 plus 4 byte checksum to end of file.\n- Arma: changed Elite's checksum to a 0 plus 20 byte sha key to end of file.\n- VBS2: same as Arma (but encrypted)\n- Dayz: same as Arma\n\nNote also that in almost all cases both the properties header and/or the sha key are optional.\n\n### Main format\n\nThe format of a .pbo contains:\n\n1. a header consisting of 21byte contiguous file name structures called 'entries'. The very first entry might exceed 21 bytes (see below)\n1. one, contiguous data block.\n1. an (optional) 5 byte checksum (Elite) or a 21 byte signature key (Arma)\n\nWith exceptions, each entry defines a file contained in the .pbo, its size, date, name, it is whether it is compressed.\n\nBecause entries and data are contiguous, there is no need for an offset to the 'next' file. Every file, even zero-length ones, are recorded in the header.\n\nHowever, note that there is no provision for, and no ability to, store empty folders. Folders as such are indicated simply by being part of the filename.\nThere are no, folder entries, and consequently, empty folders, cannot be included in a .pbo because there is no filename associated with them.\nPut another way, an empty folder, if it could be stored (and it can't), would appear to be an empty filename when dePbo'd.\n\nThe last header 'entry' is filled with zeroes. The next byte is the beginning of the data block.\n\n### PBO Header Entry\n\nA standard .pbo entry as follows:\n\n```cpp\nstruct entry\n{\n    Asciiz filename;    // a zero terminated string defining the path and filename,\n                        // ''relative to the name of this .pbo'' or it is prefix.\n                        // Zero length filenames ('\\0') indicate the first (optional), or last (non optional) entry in header.\n                        // Other fields in the last entry are filled by zero bytes.\n\n    char[4] MimeType;    // 0x56657273 'Vers' properties entry (only first entry if at all)\n                        // 0x43707273 'Cprs' compressed entry\n                        // 0x456e6372 'Enco' comressed (vbs)\n                        // 0x00000000 dummy last header entry\n\n    ulong OriginalSize;    // Uncompressed: 0 or same value as the DataSize\n                        // compressed: Size of file after unpacking.\n                        // This value is needed for byte boundary unpacking\n                        // since unpacking itself can lead to bleeding of up\n                        // to 7 extra bytes.\n\n    ulong Offset;        // not actually used, always zeros (but vbs = encryption data)\n\n    ulong TimeStamp;    // meant to be the unix filetime of Jan 1 1970 +, but often 0\n\n    ulong DataSize;        // The size in the data block.\n                        // This is also the file size when '''not''' packed\n};\n```\n\n#### Null Entries\n\nEntries with no file name indicate:\n\n1. End of header, content all zeros and ignored regardless.\n1. PboProperties entry as the very first entry of the file (not present for cwc or any mission.pbo)\n\n#### PboProperties\n\n```cpp\nstruct standard entry\n{\n    Asciiz    filename;        // = 0\n    char[4]    MimeType;        // 0x56657273 'Vers' properties entry (only first entry if at all)\n    ulong    OriginalSize;    // = 0\n    ulong    Reserved;        // = 0\n    ulong    TimeStamp;        // = 0\n    ulong    DataSize;        // = 0\n} // end of 'standard' entry\n\nstruct properties\n{\n    Asciiz this1, that1;    // eg this=that;\n}[...];\n\nbyte end; // = 0\n```\n\nThere can be as many contiguos paired Strings as, well, as many as, a piece of string!\n\nThe **LAST** (or only!) String is a zero length [ASCIIZ](https://en.wikipedia.org/wiki/Null-terminated_string) string (eg. '\\0').\n\n##### Operation Flashpoint: Resistance PBO\n\n```cpp\nstruct properties\n{\n    \"product\" = \"OFP: Resistance\"\n};\n```\n\n##### Operation Flashpoint: Elite PBO\n\n```cpp\nstruct properties\n{\n    \"prefix\" = \"Addon\\FOLDER\\Name\"\n};\n```\n\n##### Arma PBO\n\n```cpp\nprefix=Addon\\FOLDER\\Name\nversion=\"123\"\nengine=\"arma3\"\nauthor=I am famous\"\nanything=else that takes your fancy\n```\n\n### End of File Checksum or Sha\n\n#### Operation Flashpoint\n\nNot present for Operation Flashpoint pbos\n\n#### Operation Flashpoint: Elite\n\n```cpp\n{\n    byte always0;\n    int checksum;\n}\n```\n\n#### Arma\n\n```cpp\n{\n    byte always0;\n    byte sha[20]; //std md5 checksum only used for MP play\n}\n```\n\n### Data compression\n\nData compression is a mild, but effective use of Apple Corps LZSS (a variant of liv-zempel & Huffman), allowing (up to) 8k of previous data to repeat itself.\n\nCompression is indicated when a signature of 0x43707273 **and** the file sizes do not match in the entry.\n\nThe following code also applies to the packing method employed in wrp (OPRW) and pac/paa files which have no header info simply a block of known output length that must be decoded.\nIn all cases, the OUTPUT size is known. With .pbo's, the INPUT size is only a boundary definition to the next block of compressed data.\nIt is not used or relevant to decoding data because (up to) 7 residual bytes could exist in the last flag word of the block.\nAs such, only the fixed in concrete output size is relevant.\n\nThe compressed data block is in contiguous 'packets' of different lengths:\n\n```cpp\nblock {packet1}...{packetN} {4 byte checksum}\n\npacket\n{\n    byte flagbits;\n    byte packetdata[...];    // no fixed length\n}\n```\n\nThe contents of the packetdata contain mixtures of raw data that is passed directly to the output, and, 2byte pointers.\n\nFormat: bit values determine what the packetdata is. It is interpeted **lsb** first thus:\n\n```\nBitN = 1    -    append byte directly to file (read single byte)\nBitN = 0    -    pointer (read two bytes)\n```\n\nfor example:\n\nformat byte, is 0x45, binary notation is: 01000101.\n\nThere are three bytes in the block a little further past the format flag that will be passed directly to the output when encountered, and there are FIVE pointers.\n\nIn this example, first byte of packetdata is passed to output, 2 bytes are read to make a pointer, next byte is passed (ultimately) to output and so on.\n\n```\nFor the very last packet in the block, it is almost inevitable that there will be\nexcessive bits. These are ignored (truncated) as the final output length is always\nknown from the Entry. You cannot rely on the ignored bits in the format flag (up to seven\nof them) to be any particular value (0 or 1) and must be ignored.\n```\n\nA pointer consists of a 12 bits address and 4 bit run length.\n\nThe pointer is a reference to somewhere in the previous 4k max of built output. Given Intel's endian word format the bytes b1 and b2 form a short word value B2B1\n\nThe format of B2B1 is unfortunately in Big Endian (motorola) AAAA LLLL AAAAAAAA, requiring some shift and mask fiddling.\n\nThe address refers to the start of some data in the currently rebuilt part of the file. It is a value, relative to the current length of the reconstructed part of the file (FL).\n\nThe run length of the data to be copied, the 'pattern' has 4 bits and therefore, in theory, 0 to 15 bytes can be duplicated.\nIn practice the values are 3..18 bytes because copying 0,1 or 2 bytes makes no sense.\n\nRelative position (rpos) into the currently built output is calculated as\n\n```\nrpos = FL  - ((B2B1 &0x00FF) + (B2B1 & 0xF000)>>4) )\n```\n\nThe length of the data block: rlen\n\n```\nrlen = (B2B1 & 0x0F00>>8) + 3\n```\n\nWith the values of rpos and rlen there are three basic situations possible:\n\n```\nrpos + rlen < FL // bytes to copy are within the existing reconstructed data\nblock is added to the end of the file, giving a new length of FL = FL + rlen.\n```\n\n```\nrpos + rlen > FL // data to copy exceeds what's available\nIn this situation the data block has a length of FL – rpos and it is added to the reconstructed file until FL = rpos + rlen.\n```\n\n```\nrpos + rlen < 0\nThis is a special case where spaces are added to the decoded file until FL = FL,Initial + rlen\n```\n\nThe checksum, the last four bytes of any compressed data block. It is an unsigned long (Intel Little Endian order).\nIt is simply a byte-at-a-time, unsigned additive spillover of the **decompressed** data.\n\nEach and every compressed data block, contains its own, unique checksum.\n\nThere is no checksum or other protective device, employed on a .pbo overall.\nExceptions: Elite and Arma have residual data after the end of contiguous data block that do represent a signature for the file.\n\n### Bibliography\n\n- DosTools: *-dead link-*\n- cpbo: *-dead link-*\n\n### Open Source PBO Libraries\n\n#### C\n\n- JAPM: <https://github.com/RaJiska/JAPM>\n- armake: <https://github.com/KoffeinFlummi/armake>\n- libpbo: <https://github.com/Learath2/libpbo>\n\n#### C++\n\n- libpbo: <https://github.com/StidOfficial/libpbo>\n\n#### C\\#\n\n- SwiftPbo: <https://github.com/headswe/SwiftPbo>\n- PboSharp: <https://github.com/Shix/PBOSharp>\n\n#### Python\n\n- yapbol: <https://github.com/overfl0/yapbol>\n- pbo-fuse: <https://github.com/Dahlgren/python-pbo-fuse/blob/master/pbo.py>\n- arma3pbo: <https://github.com/danielgran/arma3pbo>\n\n#### Rust\n\n- armake2: <https://github.com/KoffeinFlummi/armake2>\n\n#### Java\n\n- ArmaFiles: <https://github.com/Krzmbrzl/ArmaFiles>\n\n#### JavaScript\n\n- Pbo.js: <https://github.com/eelislynne/pbo.js>","sections":[{"heading":null,"level":1,"markdown":"> **Disclaimer: This page describes internal undocumented structures of Bohemia Interactive software.**\n>\n> This page contains unofficial information.\n>\n> Some usage of this information may constitute a violation of the rights of Bohemia Interactive and is in no way endorsed or recommended by Bohemia Interactive.\n> Bohemia Interactive is not willing to tolerate use of such tools if it contravenes any general licenses granted to end users of this community wiki or BI products.\n\n**PBO** means \"**p**acked **b**ank **o**f files\". A **.pbo** is identical in purpose to a zip or rar. It is a container for folder(s) and file(s).\n\nThe engine will internally expand any ***.pbo** back out to its original, tree-folder, form."},{"heading":"Pbo .ext types","level":5,"markdown":"generically called 'pbo' there are in fact different extension names employed, dependent on the engine being used. In all cases, the contents ultimately are identical to pbos.\n\n- ifa: A World War2 mod hard wired to only accept ifa files.\n- ebo: initially used by VBS, then by Arma, to encrypt the contents of a pbo and call it a different extension.\n- xbo: Used by vbsLite encrypt with a different algorithm to above.\n  - the encryption method used for vbslite UK and vbslite US is different."},{"heading":"Legend","level":4,"markdown":"See Generic FileFormat Data Types."},{"heading":"Compression","level":4,"markdown":"In addition to simply packaging all files and folders in a tree into a single file, some, all, or none of the files within can be compressed using Apple's method of LZSS compression.\nWhich type of files are compressed is *entirely* optional. Ones that won't benefit normally are not and some files in the Arma engines cannot be because bis forgot they had this code.\nThe intent behind compression **was** for internet use and, in the 'good old days', simply to reduce hard disk storage requirements.\nThe *actual* use of compression is becoming less 'popular' as it does represent a small load on the engine. Operation Flashpoint: Elite cannot work with *compressed .pbo* files - see Elite PBOs."},{"heading":"PboTypes","level":4,"markdown":""},{"heading":"Mission Versus Addon","level":5,"markdown":"Across all engines there are two distinct types of pbo.\n\n- Mission pbos do not contain config.cpps and live in a mission folder (be it campaign, single, or multiplayer). They have no use for a pboPrefix, nor a Properties header.\n- Addon pbos contain a config.cpp/bin which at-the-least identifies the addon name, and, with the exception of the initial cwc engine, rely on a pbo properties header. They live in an addons\\ folder."},{"heading":"pbo engine differences","level":5,"markdown":"- Cold War Crisis: the very first in the series (disregarding the 'proof of concept' release a year earlier) has no pboProperties header and no checksums. It does however form the core of all other engines. It's format has never been altered\n- Resistance: added a Properties header as the first 'entry' of the pbo.\n- Vbs1: same as Resistance (but encrypted).\n- Xbox Elite: added a 0 plus 4 byte checksum to end of file.\n- Arma: changed Elite's checksum to a 0 plus 20 byte sha key to end of file.\n- VBS2: same as Arma (but encrypted)\n- Dayz: same as Arma\n\nNote also that in almost all cases both the properties header and/or the sha key are optional."},{"heading":"Main format","level":3,"markdown":"The format of a .pbo contains:\n\n1. a header consisting of 21byte contiguous file name structures called 'entries'. The very first entry might exceed 21 bytes (see below)\n1. one, contiguous data block.\n1. an (optional) 5 byte checksum (Elite) or a 21 byte signature key (Arma)\n\nWith exceptions, each entry defines a file contained in the .pbo, its size, date, name, it is whether it is compressed.\n\nBecause entries and data are contiguous, there is no need for an offset to the 'next' file. Every file, even zero-length ones, are recorded in the header.\n\nHowever, note that there is no provision for, and no ability to, store empty folders. Folders as such are indicated simply by being part of the filename.\nThere are no, folder entries, and consequently, empty folders, cannot be included in a .pbo because there is no filename associated with them.\nPut another way, an empty folder, if it could be stored (and it can't), would appear to be an empty filename when dePbo'd.\n\nThe last header 'entry' is filled with zeroes. The next byte is the beginning of the data block."},{"heading":"PBO Header Entry","level":3,"markdown":"A standard .pbo entry as follows:\n\n```cpp\nstruct entry\n{\n    Asciiz filename;    // a zero terminated string defining the path and filename,\n                        // ''relative to the name of this .pbo'' or it is prefix.\n                        // Zero length filenames ('\\0') indicate the first (optional), or last (non optional) entry in header.\n                        // Other fields in the last entry are filled by zero bytes.\n\n    char[4] MimeType;    // 0x56657273 'Vers' properties entry (only first entry if at all)\n                        // 0x43707273 'Cprs' compressed entry\n                        // 0x456e6372 'Enco' comressed (vbs)\n                        // 0x00000000 dummy last header entry\n\n    ulong OriginalSize;    // Uncompressed: 0 or same value as the DataSize\n                        // compressed: Size of file after unpacking.\n                        // This value is needed for byte boundary unpacking\n                        // since unpacking itself can lead to bleeding of up\n                        // to 7 extra bytes.\n\n    ulong Offset;        // not actually used, always zeros (but vbs = encryption data)\n\n    ulong TimeStamp;    // meant to be the unix filetime of Jan 1 1970 +, but often 0\n\n    ulong DataSize;        // The size in the data block.\n                        // This is also the file size when '''not''' packed\n};\n```"},{"heading":"Null Entries","level":4,"markdown":"Entries with no file name indicate:\n\n1. End of header, content all zeros and ignored regardless.\n1. PboProperties entry as the very first entry of the file (not present for cwc or any mission.pbo)"},{"heading":"PboProperties","level":4,"markdown":"```cpp\nstruct standard entry\n{\n    Asciiz    filename;        // = 0\n    char[4]    MimeType;        // 0x56657273 'Vers' properties entry (only first entry if at all)\n    ulong    OriginalSize;    // = 0\n    ulong    Reserved;        // = 0\n    ulong    TimeStamp;        // = 0\n    ulong    DataSize;        // = 0\n} // end of 'standard' entry\n\nstruct properties\n{\n    Asciiz this1, that1;    // eg this=that;\n}[...];\n\nbyte end; // = 0\n```\n\nThere can be as many contiguos paired Strings as, well, as many as, a piece of string!\n\nThe **LAST** (or only!) String is a zero length [ASCIIZ](https://en.wikipedia.org/wiki/Null-terminated_string) string (eg. '\\0')."},{"heading":"Operation Flashpoint: Resistance PBO","level":5,"markdown":"```cpp\nstruct properties\n{\n    \"product\" = \"OFP: Resistance\"\n};\n```"},{"heading":"Operation Flashpoint: Elite PBO","level":5,"markdown":"```cpp\nstruct properties\n{\n    \"prefix\" = \"Addon\\FOLDER\\Name\"\n};\n```"},{"heading":"Arma PBO","level":5,"markdown":"```cpp\nprefix=Addon\\FOLDER\\Name\nversion=\"123\"\nengine=\"arma3\"\nauthor=I am famous\"\nanything=else that takes your fancy\n```"},{"heading":"End of File Checksum or Sha","level":3,"markdown":""},{"heading":"Operation Flashpoint","level":4,"markdown":"Not present for Operation Flashpoint pbos"},{"heading":"Operation Flashpoint: Elite","level":4,"markdown":"```cpp\n{\n    byte always0;\n    int checksum;\n}\n```"},{"heading":"Arma","level":4,"markdown":"```cpp\n{\n    byte always0;\n    byte sha[20]; //std md5 checksum only used for MP play\n}\n```"},{"heading":"Data compression","level":3,"markdown":"Data compression is a mild, but effective use of Apple Corps LZSS (a variant of liv-zempel & Huffman), allowing (up to) 8k of previous data to repeat itself.\n\nCompression is indicated when a signature of 0x43707273 **and** the file sizes do not match in the entry.\n\nThe following code also applies to the packing method employed in wrp (OPRW) and pac/paa files which have no header info simply a block of known output length that must be decoded.\nIn all cases, the OUTPUT size is known. With .pbo's, the INPUT size is only a boundary definition to the next block of compressed data.\nIt is not used or relevant to decoding data because (up to) 7 residual bytes could exist in the last flag word of the block.\nAs such, only the fixed in concrete output size is relevant.\n\nThe compressed data block is in contiguous 'packets' of different lengths:\n\n```cpp\nblock {packet1}...{packetN} {4 byte checksum}\n\npacket\n{\n    byte flagbits;\n    byte packetdata[...];    // no fixed length\n}\n```\n\nThe contents of the packetdata contain mixtures of raw data that is passed directly to the output, and, 2byte pointers.\n\nFormat: bit values determine what the packetdata is. It is interpeted **lsb** first thus:\n\n```\nBitN = 1    -    append byte directly to file (read single byte)\nBitN = 0    -    pointer (read two bytes)\n```\n\nfor example:\n\nformat byte, is 0x45, binary notation is: 01000101.\n\nThere are three bytes in the block a little further past the format flag that will be passed directly to the output when encountered, and there are FIVE pointers.\n\nIn this example, first byte of packetdata is passed to output, 2 bytes are read to make a pointer, next byte is passed (ultimately) to output and so on.\n\n```\nFor the very last packet in the block, it is almost inevitable that there will be\nexcessive bits. These are ignored (truncated) as the final output length is always\nknown from the Entry. You cannot rely on the ignored bits in the format flag (up to seven\nof them) to be any particular value (0 or 1) and must be ignored.\n```\n\nA pointer consists of a 12 bits address and 4 bit run length.\n\nThe pointer is a reference to somewhere in the previous 4k max of built output. Given Intel's endian word format the bytes b1 and b2 form a short word value B2B1\n\nThe format of B2B1 is unfortunately in Big Endian (motorola) AAAA LLLL AAAAAAAA, requiring some shift and mask fiddling.\n\nThe address refers to the start of some data in the currently rebuilt part of the file. It is a value, relative to the current length of the reconstructed part of the file (FL).\n\nThe run length of the data to be copied, the 'pattern' has 4 bits and therefore, in theory, 0 to 15 bytes can be duplicated.\nIn practice the values are 3..18 bytes because copying 0,1 or 2 bytes makes no sense.\n\nRelative position (rpos) into the currently built output is calculated as\n\n```\nrpos = FL  - ((B2B1 &0x00FF) + (B2B1 & 0xF000)>>4) )\n```\n\nThe length of the data block: rlen\n\n```\nrlen = (B2B1 & 0x0F00>>8) + 3\n```\n\nWith the values of rpos and rlen there are three basic situations possible:\n\n```\nrpos + rlen < FL // bytes to copy are within the existing reconstructed data\nblock is added to the end of the file, giving a new length of FL = FL + rlen.\n```\n\n```\nrpos + rlen > FL // data to copy exceeds what's available\nIn this situation the data block has a length of FL – rpos and it is added to the reconstructed file until FL = rpos + rlen.\n```\n\n```\nrpos + rlen < 0\nThis is a special case where spaces are added to the decoded file until FL = FL,Initial + rlen\n```\n\nThe checksum, the last four bytes of any compressed data block. It is an unsigned long (Intel Little Endian order).\nIt is simply a byte-at-a-time, unsigned additive spillover of the **decompressed** data.\n\nEach and every compressed data block, contains its own, unique checksum.\n\nThere is no checksum or other protective device, employed on a .pbo overall.\nExceptions: Elite and Arma have residual data after the end of contiguous data block that do represent a signature for the file."},{"heading":"Bibliography","level":3,"markdown":"- DosTools: *-dead link-*\n- cpbo: *-dead link-*"},{"heading":"Open Source PBO Libraries","level":3,"markdown":""},{"heading":"C","level":4,"markdown":"- JAPM: <https://github.com/RaJiska/JAPM>\n- armake: <https://github.com/KoffeinFlummi/armake>\n- libpbo: <https://github.com/Learath2/libpbo>"},{"heading":"C++","level":4,"markdown":"- libpbo: <https://github.com/StidOfficial/libpbo>"},{"heading":"C\\#","level":4,"markdown":"- SwiftPbo: <https://github.com/headswe/SwiftPbo>\n- PboSharp: <https://github.com/Shix/PBOSharp>"},{"heading":"Python","level":4,"markdown":"- yapbol: <https://github.com/overfl0/yapbol>\n- pbo-fuse: <https://github.com/Dahlgren/python-pbo-fuse/blob/master/pbo.py>\n- arma3pbo: <https://github.com/danielgran/arma3pbo>"},{"heading":"Rust","level":4,"markdown":"- armake2: <https://github.com/KoffeinFlummi/armake2>"},{"heading":"Java","level":4,"markdown":"- ArmaFiles: <https://github.com/Krzmbrzl/ArmaFiles>"},{"heading":"JavaScript","level":4,"markdown":"- Pbo.js: <https://github.com/eelislynne/pbo.js>"}],"categories":["Real Virtuality File Formats"],"groups":["Tools and File Formats"]}