Example: Read contents from a CIA

In this example we will take a homebrew CIA file and extract some data from it. The example in this case will be Checkpoint 3.7.4. Download the example title here.

First we need to import CIAReader and CIASection. The former does the actual reading, the latter is an enum that can be used to access the contents. We will also import json to read a JSON file inside the RomFS.

>>> import json
>>> from pyctr.type.cia import CIAReader, CIASection

Now we can open the file by creating a CIAReader object.

>>> cia = CIAReader('Checkpoint.cia')

This will grant immediate access to all the contents inside, including the tmd, ticket, and NCCH contents. It also loads the data within the NCCH contents, such as the RomFS.

We can now check the Title ID by accessing it through the tmd attribute:

>>> print('Title ID:', cia.tmd.title_id)
Title ID: 000400000bcfff00

Let’s also print the Title Version, using the title_version attribute.

>>> print('Title Version:', '{0.major}.{0.minor}.{0.micro}'.format(cia.tmd.title_version))
Title Version: 3.7.4

Now let’s access the executable content using the contents attribute. Here we’ll use CIASection.Application to read the first (and only) content.

>>> app = cia.contents[CIASection.Application]
>>> app
<NCCHReader program_id: 000400000bcfff00 product_code: CTR-HB-CKPT title_name: 'Checkpoint'>

This has given us an NCCHReader object.

Let’s get the application’s SMDH, which will give us access to the name and publisher shown on the HOME Menu. We’ll get it through the ExeFS and get an SMDH object.

>>> app_title = app.exefs.icon.get_app_title('English')
>>> app_title
AppTitle(short_desc='Checkpoint', long_desc='Fast and simple save manager', publisher='Bernardo Giordano, FlagBrew')
>>> print('Application Title:', app_title.short_desc)
Application Title: Checkpoint
>>> print('Application Description:', app_title.long_desc)
Application Description: Fast and simple save manager
>>> print('Application Publisher:', app_title.publisher)
Application Publisher: Bernardo Giordano, FlagBrew

Next, we will list the contents of the RomFS. The NCCHReader has a romfs attribute that will give us a RomFSReader object.

Using get_info_from_path we will list the contents at the root.

>>> print('Contents in the root:', ', '.join(app.romfs.get_info_from_path('/').contents))
Contents in the root: gfx, cheats, config.json, PKSM.smdh

Using the same method, we can get information about a specific file.

>>> print('Size of /config.json in bytes:', app.romfs.get_info_from_path('/config.json').size)
Size of /config.json in bytes: 183

Finally, we can open the file and parse the JSON inside. We’ll pass an encoding argument so that we get an io.TextIOWrapper object. Then we use json.load() and print a value from it.

>>> f = app.romfs.open('/config.json', encoding='utf-8')
>>> f
<_io.TextIOWrapper encoding='utf-8'>
>>> config = json.load(f)
>>> f.close()
>>> config
{'filter': [], 'favorites': [], 'additional_save_folders': {}, 'additional_extdata_folders': {}, 'nand_saves': False, 'scan_cart': False, 'version': 3}
>>> print('Config version:', config['version'])
Config version: 3

When you’re done, make sure to close the CIAReader. You should also close any open files based on the CIA.

>>> cia.close()

You can also use CIAReader in the form of a context manager.

with CIAReader('Checkpoint.cia') as cia:
    with cia.contents[CIASection.Application].romfs.open('/config.json') as f:
        config = json.load(f)
        print('Config version:', config['version'])