.. Copyright 2019 RoadrunnerWMC This file is part of ndspy. ndspy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ndspy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ndspy. If not, see . ``ndspy.rom``: ROMs =================== .. py:module:: ndspy.rom ``ndspy.rom`` provides a class that represents a Nintendo DS ROM file. .. note:: Unlike most ndspy documentation, the attributes of :py:class:`NintendoDSRom` aren't arranged mostly alphabetically. Rather, they're sorted in the same order as in ROM header data. Examples -------- Change a ROM's name and resave it: .. code-block:: python >>> import ndspy.rom >>> rom = ndspy.rom.NintendoDSRom.fromFile('nsmb.nds') >>> print(rom.name) bytearray(b'NEW MARIO') >>> rom.name = b'Example Name' >>> rom.saveToFile('nsmb_edited.nds') >>> Get a file from a ROM by file ID: .. code-block:: python >>> import ndspy.rom >>> rom = ndspy.rom.NintendoDSRom.fromFile('nsmb.nds') >>> soundDataSDAT = rom.files[134] >>> with open('sound_data.sdat', 'wb') as f: ... f.write(soundDataSDAT) ... 4839008 >>> Replace a file in a ROM by filename, and resave it: .. code-block:: python >>> import ndspy.rom >>> rom = ndspy.rom.NintendoDSRom.fromFile('nsmb.nds') >>> with open('sound_data.sdat', 'rb') as f: ... soundDataSDAT = f.read() ... >>> rom.setFileByName('sound_data.sdat', soundDataSDAT) >>> rom.saveToFile('nsmb_edited.nds') >>> Print the names of all NSBTX files, found by looking for their file data magics rather than file extensions: .. code-block:: python >>> import ndspy.rom >>> rom = ndspy.rom.NintendoDSRom.fromFile('nsmb.nds') >>> rom.setFileByName('sound_data.sdat', soundDataSDAT) >>> for i, file in enumerate(rom.files): ... if file.startswith(b'BTX0'): ... print(rom.filenames.filenameOf(i)) ... enemy/b_lift.nsbtx enemy/d_bridge.nsbtx enemy/I_do_hahen_l.nsbtx enemy/I_do_hahen_r.nsbtx enemy/I_kaiten_ami.nsbtx [snip] polygon_unit/wire_netting8.nsbtx polygon_unit/wire_netting9.nsbtx >>> Load a ROM's ARM9 code file and overlays: .. code-block:: python >>> arm9 = rom.loadArm9() >>> print(arm9) > >>> overlays = rom.loadArm9Overlays() >>> for id, overlay in overlays.items(): ... print(id, overlay) ... 0 1 2 3 4 5 [snip] 129 130 >>> API --- .. py:class:: NintendoDSRom([data]) A Nintendo DS ROM file (.nds). :param bytes data: The data to be read as a ROM file. If not provided, the ROM will use default values. .. py:attribute:: name The ROM's name. This is usually a short ASCII string containing the name of the software. This can be up to 12 bytes long; longer values will be truncated when saving. This is at offset 0x000 in the ROM header. :type: :py:class:`bytes` (12-byte limit) :default: ``b''`` .. py:attribute:: idCode The four-byte ID code of the software. Usually, this is ASCII, and the fourth character is a region identifier ("E" for North America, "P" for Europe, or "J" for Japan). This is at offset 0x00C in the ROM header. :type: :py:class:`bytes` (exactly 4 bytes long) :default: ``b'####'`` .. py:attribute:: developerCode An identifier for the developer of the software. Usually two ASCII characters; for example, Nintendo is "01". This is at offset 0x010 in the ROM header. :type: :py:class:`bytes` (exactly 2 bytes long) :default: ``b'\0\0'`` .. py:attribute:: unitCode The systems this ROM supports: * 0: Nintendo DS (DSi only in compatibility mode) * 2: Both Nintendo DS and Nintendo DSi * 3: Nintendo DSi only This is at offset 0x012 in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: encryptionSeedSelect The seed number to use when decrypting the ROM. The actual seed values are built into the DS's hardware; this is only an index into a table. Valid values are 0 through 7, inclusive. This is at offset 0x013 in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: deviceCapacity A number representing the storage capacity of the hardware this ROM is intended to be placed on. The formula is ``2 ^ (17 + X)`` bytes; for example, a value of 7 means 16 MB. This is at offset 0x014 in the ROM header. .. note:: This can optionally be recalculated for you automatically upon saving the ROM. For more information about this, see the documentation for the :py:func:`save` function. :type: :py:class:`int` :default: 9 .. py:attribute:: pad015 The value of the padding byte at 0x015 in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: pad016 The value of the padding byte at 0x016 in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: pad017 The value of the padding byte at 0x017 in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: pad018 The value of the padding byte at 0x018 in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: pad019 The value of the padding byte at 0x019 in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: pad01A The value of the padding byte at 0x01A in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: pad01B The value of the padding byte at 0x01B in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: pad01C The value of the padding byte at 0x01C in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: region The region this ROM is intended to be used in: * 0x00: most regions * 0x40: Korea * 0x80: China This is at offset 0x01D in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: version The version number for this ROM. It's unclear exactly what this means. This is at offset 0x01E in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: autostart A value related to how the ROM should be loaded. If ":py:attr:`autostart` & 4" is set, the "Press Button" message after the Health and Safety screen will be skipped. This is at offset 0x01F in the ROM header. :type: :py:class:`int` :default: 0 .. py:attribute:: arm9EntryAddress The RAM address that ARM9 execution should begin at, after the ARM9 code has been loaded into RAM at :py:attr:`arm9RamAddress`. This is at offset 0x024 in the ROM header. .. seealso:: :py:attr:`arm9` -- the code data that this entry address should reference. :type: :py:class:`int` :default: 0x02000800 .. py:attribute:: arm9RamAddress The RAM address that the ARM9 code should be loaded to. This is at offset 0x028 in the ROM header. .. seealso:: :py:attr:`arm9` -- the code data that will be loaded to this address. :type: :py:class:`int` :default: 0x02000000 .. py:attribute:: arm7EntryAddress The RAM address that ARM7 execution should begin at, after the ARM7 code has been loaded into RAM at :py:attr:`arm7RamAddress`. This is at offset 0x034 in the ROM header. .. seealso:: :py:attr:`arm7` -- the code data that this entry address should reference. :type: :py:class:`int` :default: 0x02380000 .. py:attribute:: arm7RamAddress The RAM address that the ARM7 code should be loaded to. This is at offset 0x038 in the ROM header. .. seealso:: :py:attr:`arm7` -- the code data that will be loaded to this address. :type: :py:class:`int` :default: 0x02380000 .. py:attribute:: normalCardControlRegisterSettings The "port 0x040001A4 setting for normal commands". For more information, see `the section about this value on GBATEK `__ (subheader "40001A4h - NDS7/NDS9 - ROMCTRL - Gamecard Bus ROMCTRL (R/W)"). This is at offset 0x060 in the ROM header. :type: :py:class:`int` :default: 0x00416657 .. py:attribute:: secureCardControlRegisterSettings The "port 0x040001A4 setting for KEY1 commands". For more information, see `the section about this value on GBATEK `__ (subheader "40001A4h - NDS7/NDS9 - ROMCTRL - Gamecard Bus ROMCTRL (R/W)"). This is at offset 0x064 in the ROM header. :type: :py:class:`int` :default: 0x081808f8 .. py:attribute:: secureAreaChecksum The checksum of the encrypted "secure area" of the ROM. This is at offset 0x06C in the ROM header. .. todo:: This should be calculated automatically when saving the ROM instead of being an attribute. :type: :py:class:`int` :default: 0x0000 .. py:attribute:: secureTransferDelay A delay value of some kind related to encryption commands. Measured in units of 130.912kHz each. For more information, see `the section about this value on GBATEK `__ (subheader "Secure Area Delay"). This is at offset 0x06E in the ROM header. :type: :py:class:`int` :default: 0x0D7E .. py:attribute:: arm9CodeSettingsPointerAddress The address in RAM (plus 4) of a pointer to the "code settings" structure in ARM9's main code file. This defines things like the SDK version used to compile the ROM, whether the code is compressed or not, and the list of ARM9 code "sections" and where they should be placed in memory. If this value is 0, then either there is no code settings block in ARM9 or its location is unspecified. This is at offset 0x070 in the ROM header. .. note:: You have to subtract 4 from this value to get the actual address of the pointer to the code settings block. :type: :py:class:`int` :default: 0 .. py:attribute:: arm7CodeSettingsPointerAddress The address in RAM (plus 4) of a pointer to the "code settings" structure in ARM7's main code file. This defines things like the SDK version used to compile the ROM, whether the code is compressed or not, and the list of ARM7 code "sections" and where they should be placed in memory. If this value is 0, then either there is no code settings block in ARM7 or its location is unspecified. This is at offset 0x074 in the ROM header. .. note:: You have to subtract 4 from this value to get the actual address of the pointer to the code settings block. :type: :py:class:`int` :default: 0 .. py:attribute:: secureAreaDisable This value disables the encrypted "secure area" of the ROM, allowing one to use that area without encryption. To do this, the value must be set to "NmMdOnly", encrypted. This is probably impossible without Nintendo's private keys. This is at offset 0x078 in the ROM header. :type: :py:class:`bytes` (exactly 8 bytes long) :default: ``b'\0\0\0\0\0\0\0\0'`` .. py:attribute:: pad088 Padding area beginning at 0x088 in the ROM header. :type: :py:class:`bytes` (exactly 0x38 bytes long) :default: ``b'\0' * 0x38`` .. py:attribute:: nintendoLogo A compressed image of the Nintendo logo. The DS will refuse to load the ROM if this is modified in any way. This is at offset 0x0C0 in the ROM header. :type: :py:class:`bytes` (exactly 0x9C bytes long) :default: (the correct value) .. py:attribute:: debugRomAddress The address where the "debug rom" should be loaded to in RAM, if present. It's unclear what exactly this is. This is at offset 0x168 in the ROM header. .. seealso:: :py:attr:`debugRom` -- the data this refers to. :type: :py:class:`int` :default: 0 .. py:attribute:: pad16C Padding area beginning at 0x16C in the ROM header. :type: :py:class:`bytes` (exactly 0x94 bytes long) :default: ``b'\0' * 0x94`` .. py:attribute:: pad200 Padding after the ROM header, beginning at 0x200. :type: :py:class:`bytes` :default: ``b'\0' * 0x3E00`` .. py:attribute:: rsaSignature The ROM's RSA signature data. Not every ROM has is cryptographically signed, but for those that are, this is stored at the very end of the ROM. Since most methods of playing games from ROM files these days bypass the RSA verification step, this attribute probably isn't very useful for most purposes. :type: :py:class:`bytes` (either 0 or 0x88 bytes long) :default: ``b''`` .. py:attribute:: arm9 The main ARM9 executable binary to be loaded to :py:attr:`arm9RamAddress`. .. seealso:: :py:class:`ndspy.code.MainCodeFile` -- the ndspy class you can use to load this data. :py:attr:`arm9RamAddress` -- the address this will be loaded to in RAM. :py:attr:`arm9EntryAddress` -- the address in RAM where ARM9 execution will begin. :type: :py:class:`bytes` :default: ``b''`` .. py:attribute:: arm9PostData A small amount of extra data immediately following :py:attr:`arm9` in the ROM data. It is unclear what this is for. :type: :py:class:`bytes` :default: ``b''`` .. py:attribute:: arm7 The main ARM7 executable binary to be loaded to :py:attr:`arm7RamAddress`. .. seealso:: :py:class:`ndspy.code.MainCodeFile` -- the ndspy class you can use to load this data. :py:attr:`arm7RamAddress` -- the address this will be loaded to in RAM. :py:attr:`arm7EntryAddress` -- the address in RAM where ARM7 execution will begin. :type: :py:class:`bytes` :default: ``b''`` .. py:attribute:: arm9OverlayTable The table containing information about ARM9 overlays. .. seealso:: :py:func:`ndspy.code.loadOverlayTable` -- the ndspy function you can use to load this data. :type: :py:class:`bytes` :default: ``b''`` .. py:attribute:: arm7OverlayTable The table containing information about ARM7 overlays. .. seealso:: :py:func:`ndspy.code.loadOverlayTable` -- the ndspy function you can use to load this data. :type: :py:class:`bytes` :default: ``b''`` .. py:attribute:: iconBanner A structure containing the game's icon data, and its title in multiple languages. For more information, see `the section about this value on GBATEK `__. The required length of this :py:class:`bytes` object depends on its version number (which is its first two bytes), according to the following table: ============== ============================ Version number :py:attr:`iconBanner` length ============== ============================ 0x0001 0x0840 0x0002 0x0940 0x0003 0x1240 0x0103 0x23C0 ============== ============================ An empty bytes object (``b''``) is also allowed, which corresponds to a null icon/banner data offset in the ROM's header. :type: :py:class:`bytes` (required length depends on the version number). :default: ``b''`` .. py:attribute:: debugRom Some optional data related to debugging; it's unclear what exactly this is. .. seealso:: :py:attr:`debugRomAddress` -- the address in RAM this will be loaded to. :type: :py:class:`bytes` :default: ``b''`` .. py:attribute:: filenames The root folder of the ROM's filename table. These filenames usually do not cover all files in the ROM (for example, overlays are usually unnamed). .. seealso:: :py:mod:`ndspy.fnt` -- the ndspy module the :py:class:`ndspy.fnt.Folder` class resides in. :py:attr:`files` -- the corresponding list of files that these filenames refer to. :type: :py:class:`ndspy.fnt.Folder` :default: ``ndspy.fnt.Folder()`` .. py:attribute:: files The list of files in this ROM. Indices are file IDs; that is, ":py:attr:`files`\[0]" is the file with file ID 0, ":py:attr:`files`\[1]" is the file with file ID 1, etc. .. seealso:: :py:attr:`filenames` -- the set of filenames for these files. :type: :py:class:`list` of :py:class:`bytes` :default: ``[]`` .. py:attribute:: sortedFileIds For unknown reasons, ROMs sometimes store files in an order other than that of ascending file IDs. To preserve this order, this list contains file IDs in the order in which they should be saved in the ROM data. This is automatically populated when opening a ROM, and you should never really need to change this. (You can empty it to force files to be saved in order, though.) If any file IDs are missing from this list, they will be placed in order of ascending file IDs after the files that are in the list. If this is empty, all files will be saved in order of ascending file IDs. .. seealso:: :py:attr:`files` -- the list of files these indices refer to. :type: :py:class:`list` of :py:class:`int` :default: ``[]`` .. py:classmethod:: fromFile(filePath) Load a ROM from a filesystem file. This is a convenience function. :param filePath: The path to the ROM file to open. :type filePath: :py:class:`str` or other path-like object :returns: The ROM object. :rtype: :py:class:`NintendoDSRom` .. py:function:: 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): .. code-block:: python fileData = rom.getFileByName(filename) fileData = rom.files[rom.filenames.idOf(filename)] .. seealso:: :py:func:`setFileByName` -- to replace the file data instead of retrieving it. :param str filename: The name of the file. :returns: The file's data. :rtype: :py:class:`bytes` .. py:function:: 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): .. code-block:: python rom.setFileByName(filename, fileData) rom.files[rom.filenames.idOf(filename)] = fileData .. seealso:: :py:func:`getFileByName` -- to retrieve the file data instead of replacing it. :param str filename: The name of the file. :param bytes data: The new data for the file. .. py:function:: loadArm7() Create a :py:class:`ndspy.code.MainCodeFile` object representing the main ARM7 code file in this ROM. .. seealso:: :py:attr:`arm7` -- depending on what you're trying to do, it may be more appropriate to just use this raw data attribute directly instead. :returns: The ARM7 code file. :rtype: :py:class:`ndspy.code.MainCodeFile` .. py:function:: loadArm7Overlays([idsToLoad]) Create a dictionary of this ROM's ARM7 :py:class:`ndspy.code.Overlay`\s. .. seealso:: :py:attr:`arm7OverlayTable` -- if you just want the raw overlay table data, you can access it from this attribute instead. This avoids the side effect of decompressing all of the overlay data (which can be slow). :param idsToLoad: A specific set of overlay IDs to load. You can use this to avoid loading overlays you don't actually care about, in order to improve your application's performance. :type idsToLoad: :py:class:`set` of :py:class:`int` :returns: A :py:class:`dict` of overlays. :rtype: :py:class:`dict`: ``{overlayID: overlay}`` (where ``overlayID`` is of type :py:class:`int` and ``overlay`` is of type :py:class:`Overlay`) .. py:function:: loadArm9() Create a :py:class:`ndspy.code.MainCodeFile` object representing the main ARM9 code file in this ROM. .. seealso:: :py:attr:`arm9` -- depending on what you're trying to do, it may be more appropriate to just use this raw data attribute directly instead. :returns: The ARM9 code file. :rtype: :py:class:`ndspy.code.MainCodeFile` .. py:function:: loadArm9Overlays([idsToLoad]) Create a dictionary of this ROM's ARM9 :py:class:`ndspy.code.Overlay`\s. .. seealso:: :py:attr:`arm9OverlayTable` -- if you just want the raw overlay table data, you can access it from this attribute instead. This avoids the side effect of decompressing all of the overlay data (which can be slow). :param idsToLoad: A specific set of overlay IDs to load. You can use this to avoid loading overlays you don't actually care about, in order to improve your application's performance. :type idsToLoad: :py:class:`set` of :py:class:`int` :returns: A :py:class:`dict` of overlays. :rtype: :py:class:`dict`: ``{overlayID: overlay}`` (where ``overlayID`` is of type :py:class:`int` and ``overlay`` is of type :py:class:`Overlay`) .. py:function:: save(*[, updateDeviceCapacity=False]) Generate file data representing this ROM. :param bool updateDeviceCapacity: If this is ``True``, :py:attr:`deviceCapacity` will be updated based on the size of the output file data. It will be set to match the capacity of the smallest cartridge that would be able to hold the data. :default: ``False`` :returns: The ROM file data. :rtype: :py:class:`bytes` .. py:function:: saveToFile(filePath, *[, updateDeviceCapacity=False]) Generate file data representing this ROM, and save it to a filesystem file. This is a convenience function. :param filePath: The path to the ROM file to save to. :type filePath: :py:class:`str` or other path-like object :param bool updateDeviceCapacity: If this is ``True``, :py:attr:`deviceCapacity` will be updated based on the size of the output file data. It will be set to match the capacity of the smallest cartridge that would be able to hold the data. :default: ``False``