{"id":21498434,"url":"https://github.com/aleokdev/mario-party-editor","last_synced_at":"2025-03-17T12:42:30.531Z","repository":{"id":133660898,"uuid":"278066642","full_name":"aleokdev/mario-party-editor","owner":"aleokdev","description":"Mario Party DS Editor \u0026 Helper","archived":false,"fork":false,"pushed_at":"2020-07-20T08:56:56.000Z","size":272,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-23T22:24:33.747Z","etag":null,"topics":["nds","romhacking"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aleokdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-07-08T11:06:03.000Z","updated_at":"2024-11-01T18:10:45.000Z","dependencies_parsed_at":null,"dependency_job_id":"f1e57377-c534-4685-aba2-6d75e86538c0","html_url":"https://github.com/aleokdev/mario-party-editor","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aleokdev%2Fmario-party-editor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aleokdev%2Fmario-party-editor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aleokdev%2Fmario-party-editor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aleokdev%2Fmario-party-editor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aleokdev","download_url":"https://codeload.github.com/aleokdev/mario-party-editor/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244036647,"owners_count":20387554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nds","romhacking"],"created_at":"2024-11-23T16:38:40.170Z","updated_at":"2025-03-17T12:42:30.518Z","avatar_url":"https://github.com/aleokdev.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"## INTRODUCTION\nI wanted to create a simple editor I could use to change texts in Mario Party DS without messing around\nwith 5000 different files, editors, custom scripts and decompression tools.\nInstead of directly modifying the ROM like other editors, this one generates patch files for every single\nfile change, making single-file revertions and source control much, much easier to accomplish. This also\nmeans you won't need a trillion different NDS files or full patches to distinguish your ROM versions.\n\n## FAQ\nQ: _Does this work on other NDS games apart from Mario Party DS?_\n\nA: Yes, absolutely, although some features, such as the Text Table Editor, are specialized for\nMario Party.\n\nQ: _Will you add support for other NDS game fileformats?_\n\nA: Probably not, but if I do, it'll be for the game _Hotel Dusk: Room 215_.\n\nQ: _Why use this editor instead of other ones, such as Tinke, NSMBe, NDSTool, CrystalTile,\nkiwi.ds...?_\n\nA: To be honest, I didn't create this project to replace any other editors available because I knew\nI was going to create another one to add to the pile. This one is just specialized for my needs.\n\nReal response: Look at the introduction. _Instead of directly modifying the ROM like other editors,\nthis one generates patch files for every single file change, making single-file revertions and source\ncontrol much, much easier to accomplish. This also means you won't need a trillion different NDS\nfiles or full patches to distinguish your ROM versions._\n\n## TECHNICAL STUFF\nIn this section I'll be documenting every single step I have took in order to create proper NDS files\nand how I have solved all the problems I have encountered until now, since I have mediocre memory and\ncan't remember what I've done two days ago.\n\n### GOTCHAS (Important, read this first!)\nQuick TL;DR: We can't change any of the headers, but we can modify the file tables without moving or resizing them.\n\nThe wireless function uses checksums for the arm9, arm7 and general headers. These are encrypted together\nwith SHA-1 to create a key that is checked with nintendo's public key. Decyphering this key is almost\nimpossible so changing the headers is out of the question.\n\nHOWEVER, it is still possible to change stuff like the filetable without modifying the header, as long as\nwe don't change the size of the total filetable or nametable. This is because the FAT/FNT is not stored\ninside of the header, but the size and address of it are.\n\nMore specifically:\n\n   Object       |   Address   |  Size\n----------------|-------------|-------------\nFile Name Table | Header 0x40 | Header 0x44\nFAT             | Header 0x48 | Header 0x4C\n\n[Source](https://github.com/Roughsketch/mdnds/wiki/NDS-Format)\n\nFor simplicity, let's assume these values are constant, since we can't change them anyways. This means that Mario Party DS\nhas these constant values:\n\n   Object       |   Address   |  Size\n----------------|-------------|--------------------------------\nFile Name Table | 0x00308800  | 0x00003479 Bytes (13433 Bytes)\nFAT             | 0x0030BE00  | 0x00001610 Bytes (5648 Bytes)\n\nWe are free to change the values contained in these partitions as much as we want.\n\n### THE FILESYSTEM\nFirst, some source links: The [header](http://svn.blea.ch/thdslib/trunk/thdslib/source/arm9/include/nitrofs.h) and [the source file](http://svn.blea.ch/thdslib/trunk/thdslib/source/arm9/source/nitrofs.c).\n\nNDS files use a filesystem called Nitro to store and keep track of their data. It is composed of a **File Name Table** or\n**FNT** for short that contains the filesystem structure and names, and a **File Allocation Table** or **FAT** that indicates\nwhere each file is stored in the ROM.\n\nFNT Format:\n```protobuf\n// This struct represents the metadata for the directory entry with ID\n// (0xF000 + (offset_from_fnt / sizeof(FNTDirectoryEntry)), where sizeof(FNTDirectoryEntry) == 8.\n// It points to many chained file/directory FNTNamedEntries that are inside of it. The chain ends with a null byte.\nstruct FNTDirectoryEntry {\n    // The FNTNamedEntry address that this directory entry points to, relative to the FNT start.\n    entry_relative_address : uint32,\n    // The entry ID of the first file present in this folder. The second file has this ID + 1, the third has\n    // this ID + 2, etc. This does not affect subdirectories.\n    first_file_id : uint16,\n    // The entry ID of the parent folder\n    // If the parent is the filesystem root, this value doesn't start with 0xF000, and might indicate number of\n    // files?\n    parent_dir_id : uint16\n};\nstruct FNTNamedEntry {\n    is_directory : bool (1 bit),\n    name_length : uint8 (7 bits),\n    name : char8[name_length] // Encoding: UTF-8/ASCII\n    if is_directory =\u003e entry_id : uint16 // Directories always start with 0xF000 in their ID\n    // This struct will repeat for every single file/directory there is available in the directory.\n    // This chain will stop when is_directory + name_length is 0. (Basically, it ends in a null byte.)\n};\n\n// As far as I know, there is no integer present in the metadata containing the number of directory entries.\n// So to figure out how many directory entries there are, keep track of the first entry relative address and read\n// up to that point.\ndirectories : FNTDirectoryEntry[]\n```\nTo look up the editor implementation of the filesystem, look at the NDS*.cs files inside the ROM folder in the\nmain project (More specifically, NDSFilesystem.cs). I tried to make the implementation as simple and easy to\nunderstand as possible.\n\nThe FAT's format is much simpler:\n```protobuf\nstruct FATEntry {\n    // Those are addresses relative to the start of the ROM that mark where an entry's data starts and ends.\n    lower_bound : uint32,\n    upper_bound : uint32\n}\n// Only way that I know of to get the number of FAT entries is to count the number of files in the FNT.\nentries : FATEntry[]\n```\nEach file's FNT entry corresponds to its FAT one, so if for example you want to obtain the contents of\nthe file `0x02` you'd access `entries[0x02]`, get the lower and upper bounds and map those to the ROM.\n\n### LZ77 COMPRESSION\nMost files in the Mario Party DS ROM and most Nintendo-developed DS games use a custom implementation\nof LZ77 compression to optimize the amount of data used in files.\n\nNormally, the compressed files end their filename with \"_LZ.bin\", so it's not hard to identify them.\n\nThe compressed files in Mario Party DS have the following format:\n```protobuf\n// This first byte, as far as I know, is completely unused, and it's not consistent between different\n// files.\nunknown : byte,\n// The uncompressed file size. Can be used for checking if the decompression went right. And yes, it\n// uses 3 bytes... Odd.\nuncompressed_file_size : uint24,\nstruct LZ77Part {\n    decision_byte : byte,\n    // The next content heavily depends on the decision byte, and I'll represent it with some\n    // pseudocode.\n\n    // Go through each bit in decision_byte (High one first, so 1 \u003c\u003c 7 to 1 \u003c\u003c 0)\n    foreach bit in decision_byte:\n        // If the bit is zero, we copy a byte from the input.\n        // If the bit is one, we reference some data from the input.\n        if bit == 0:\n            output.add(input.read_byte())\n        else\n            pointer_data = (input.read_byte() \u003c\u003c 8) | input.read_byte();\n            length = (pointer_data \u003e\u003e 12) + 3;\n            offset = pointer_data \u0026 0xFFF;\n            window_offset = output_index - offset - 1;\n            for pointByte in range(length):\n                output.add(output[windowOffset++])\n}\n\n// Read until there is no more input to read.\nparts : LZ77Part[]\n```\n\n### THE TEXT FORMAT\nText/translation files are compressed with LZ77 and have the following format when uncompressed:\n```protobuf\nnumber_of_texts : uint32,\ntext_addresses : uint32[number_of_texts],\n\nstruct Text {\n    content : char16[] // Encoding: UTF16\n                       // Texts end with a null byte.\n};\n\n// Each text address is aligned with its corresponding index in text_addresses\ntexts : Text[number_of_texts]\n```\nThey're really easy to edit, just remember that the text addresses table has to be updated if the size of any text changes.\n\n### PATCHING\nOkay, so imagine we have a few files we have just edited. Some are bigger than the original size, some are smaller. How do we\npatch and link these without messing up the entire ROM? Well, here's what the editor does.\n\nFirst, it treats separate files individually. If you edit one file, a patch will be generated for that single file, and no\nmodifications will be done to the ROM directly or indirectly via patches. We just keep track of that patch so that we know\nthat that file has been edited.\n\nWhen we actually need the updated, patched ROM, we'll generate it from those patches, which involves changing the FAT so\nthat the file addresses match up, moving the files around, etc. In this case, it's simpler to create the full ROM from\nstart than patch it from the original, so we just create an empty file, take the header and encryption data and patch the\ndata files.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faleokdev%2Fmario-party-editor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faleokdev%2Fmario-party-editor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faleokdev%2Fmario-party-editor/lists"}