ndspy.narc: NARC Archives

The ndspy.narc module allows you to open and save NARC archive files.

NARC archives are structured similarly to ROM files, in that each file can be referenced by either ID or by a filename from a filename table. Filename tables work the same as those of ROMs.

Warning

The default value of NARC.endiannessOfBeginning for new NARC objects is '<', but some tools, such as Tinke and narchive, don’t support that type of NARC file. If you’re experiencing problems with them – or if your NARC files aren’t loaded correctly by the game you’re working with – try setting it to '>' instead.

Examples

Load a NARC from a loose file and see what’s inside:

>>> import ndspy.narc
>>> narc = ndspy.narc.NARC.fromFile('mg_trampoline.narc')
>>> print(narc)
<narc endiannessOfBeginning='>'
    0000 mg_trampoline/
    0000     d_2d_mg_bg_trampoline_nsc.bin       b'\x10\0\x10\0-~\xc2\xf0\1\xa4\xf0\x13\xe0%\xa5@'...
    0001     d_2d_mgvs_bg_trampoline_ncg.bin     b'\x10\04\0\0(\xb5\xbbu(\xb5\x8bv\0(\x98'...
    0002     d_2d_mgvs_bg_trampoline_ncl.bin     b'\xc87\xcb]\xece-nnv\0\0\0\0\0\0'...
    0003     d_2d_mgvs_bg_trampoline_nsc.bin     b'\x10\0\x10\0\0\0\xf3\1\xf3\2\xf3~\xc2\0~\xc2'...
    0004     d_2d_mgvs_trampoline_ncl.bin        b'9W\0\0\0\0\xff\x7f:\x1e\xbb2|O-\4'...
    0005     US/
    0005         d_2d_mgvs_trampoline_ncg.bin    b'\x10\0\x18\0\0\0\0\x10\x11\0\0\x113\1\0\x10'...
>
>>>

Load a NARC from within a ROM, replace a file, and save it back into the ROM:

>>> import ndspy.rom, ndspy.narc
>>> rom = ndspy.rom.NintendoDSRom.fromFile('Zelda - Phantom Hourglass.nds')
>>> narc = ndspy.narc.NARC(rom.getFileByName('Effect/effecttex.narc'))
>>> print(narc)
<narc endiannessOfBeginning='>'
    0000 AK_smoke01.ntfi     b'\0@\0@\0@\0@\0@\0@\0@\0@'...
    0001 AK_smoke01.ntfp     b'\0\0\0\0\xbdw\0\0\xff\x7f\0\0c\x0c\0\0'...
    0002 AK_smoke01.ntft     b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'...
    0003 AK_smoke02.ntfi     b'\0@\0@\0@\0@\0@\0@\0@\0@'...
    0004 AK_smoke02.ntfp     b'\0\0\0\0\xde{\0\0\xff\x7f!\4\xe7\x1c\0\0'...
    0005 AK_smoke02.ntft     b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'...
    0006 AK_smoke03.ntfi     b'\0@\0@\0@\0@\0@\0@\0@\0@'...
    0007 AK_smoke03.ntfp     b'\0\0\0\0\xd6Z\0\0{o\0\0\xde{\0\0'...
    0008 AK_smoke03.ntft     b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'...
    0009 smoke01.ntfi        b'\0@\0@\0@\0@\0@\0@\0@\0@'...
    0010 smoke01.ntfp        b'\0\0\0\0\xde{\0\0\xff\x7f\x84\x10\xff\x7f\0\0'...
    0011 smoke01.ntft        b'\xff\xff\xff\xff\xff\xff\xff?\xff\xff\xff\0\xff\xff\xff\0'...
    0012 smoke02.ntfi        b'\0@\0@\0@\1@\2@\3@\4@\0@'...
    0013 smoke02.ntfp        b'\xe7\x1c\0\0{o\0\0\x9cs\0\0J)\0\0'...
    0014 smoke02.ntft        b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff_\x15\xffWU\2'...
    0015 smoke03.ntfi        b'\0@\0@\0@\0@\0@\0@\0@\0@'...
    0016 smoke03.ntfp        b'\0\0\0\0\xde{\0\0\xff\x7f\xc6\x18\xff\x7fc\x0c'...
    0017 smoke03.ntft        b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\3'...
    0018 zds_airflow.ntfp    b'\0\0\xff\x7f'
    0019 zds_airflow.ntft    b'\0\0@UU\1\0\0\0@UUUU\1\0'...
>
>>> narc.setFileByName('zds_airflow.ntfp', b'example data of some kind')
>>> rom.setFileByName('Effect/effecttex.narc', narc.save())
>>> rom.saveToFile('Zelda - Phantom Hourglass - Edited.nds')
>>>

Create a new NARC from scratch, and save it to a file:

>>> import ndspy.narc, ndspy.fnt
>>> with open('a.txt', 'rb') as f:
...    aTxt = f.read()
...
>>> with open('b.bin', 'rb') as f:
...    bBin = f.read()
...
>>> with open('images/c.png', 'rb') as f:
...    cPng = f.read()
...
>>> root = ndspy.fnt.Folder(files=['a.txt', 'b.bin'])
>>> imagesFolder = ndspy.fnt.Folder(files=['c.png'])
>>> # a.txt and b.bin come before the images/ folder. Thus, they'll have
... # IDs 0 and 1, and the images/ folder therefore needs ID 2:
...
>>> imagesFolder.firstID = 2
>>> root.folders = [('images', imagesFolder)]
>>> narc = ndspy.narc.NARC.fromFilesAndNames([aTxt, bBin, cPng], root)
>>> print(narc)
<narc
    0000 a.txt        b'Contents of a.tx'...
    0001 b.bin        b'Contents of b.bi'...
    0002 images/
    0002     c.png    b'\x89PNG\r\n\x1a\n\0\0\0\rIHDR'...
>
>>> narc.saveToFile('things.narc')
>>>

API

class ndspy.narc.NARC([data])

A NARC archive file.

Parameters:

data (bytes) – The data to be read as a NARC file. If this is not provided, the NARC object will initially be empty.

endiannessOfBeginning

The endianness of the first 8 bytes of the NARC file header. The rest of the file is always little-endian.

This option exists because different games use different header endiannesses. For example, The Legend of Zelda: Spirit Tracks uses little-endian NARCs, whereas New Super Mario Bros. uses big-endian. ndspy supports both, and auto-detects it when loading a NARC file, but if you’re creating new NARC objects from scratch, it’s a good idea to set this attribute explicitly to match the game you’re working with.

'<' and '>' (representing little-endian and big-endian, respectively) are the only values this attribute is allowed to take.

Type:

str

Default:

'<'

filenames

The root folder of the NARC’s filename table.

See also

ndspy.fnt – the ndspy module the ndspy.fnt.Folder class resides in.

files – the corresponding list of files that these filenames refer to.

Type:

ndspy.fnt.Folder

Default:

ndspy.fnt.Folder()

files

The list of files in this NARC. Indices are file IDs; that is, “files[0]” is the file with file ID 0, “files[1]” is the file with file ID 1, etc.

See also

filenames – the set of filenames for these files.

Type:

list of bytes

Default:

[]

classmethod fromFilesAndNames(files[, filenames])

Create a NARC archive from a list of files and (optionally) a filename table.

Parameters:
  • files – The initial value for the files attribute.

  • filenames – The initial value for the filenames attribute.

Returns:

The NARC object.

Return type:

NARC

classmethod fromFile(filePath)

Load a NARC archive from a filesystem file. This is a convenience function.

Parameters:

filePath (str or other path-like object) – The path to the NARC file to open.

Returns:

The NARC object.

Return type:

NARC

getFileByName(filename)

Return the data for the file with the given filename (path). This is a convenience function; the following two lines of code are exactly equivalent (apart from some error checking):

fileData = narc.getFileByName(filename)
fileData = narc.files[narc.filenames.idOf(filename)]

See also

setFileByName() – to replace the file data instead of retrieving it.

Parameters:

filename (str) – The name of the file.

Returns:

The file’s data.

Return type:

bytes

setFileByName(filename, data)

Replace the data for the file with the given filename (path) with the given data. This is a convenience function; the following two lines of code are exactly equivalent (apart from some error checking):

narc.setFileByName(filename, fileData)
narc.files[narc.filenames.idOf(filename)] = fileData

See also

getFileByName() – to retrieve the file data instead of replacing it.

Parameters:
  • filename (str) – The name of the file.

  • data (bytes) – The new data for the file.

save()

Generate file data representing this NARC.

Returns:

The NARC archive file data.

Return type:

bytes

saveToFile(filePath)

Generate file data representing this NARC, and save it to a filesystem file. This is a convenience function.

Parameters:

filePath (str or other path-like object) – The path to the NARC archive file to save to.