{"id":42557616,"url":"https://github.com/SunakazeKun/pyjmap","last_synced_at":"2026-03-05T00:01:06.640Z","repository":{"id":44542648,"uuid":"469803029","full_name":"SunakazeKun/pyjmap","owner":"SunakazeKun","description":"Python library for Nintendo's BCSV/JMap format.","archived":true,"fork":false,"pushed_at":"2023-05-27T22:15:23.000Z","size":115,"stargazers_count":5,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-14T09:29:23.622Z","etag":null,"topics":["bcsv","jmap","jsystem","modding-tools","nintendo","nintendo-hacking","nintendo-wii","python","super-mario-galaxy"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SunakazeKun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-03-14T15:54:25.000Z","updated_at":"2025-11-21T20:36:08.000Z","dependencies_parsed_at":"2023-02-15T18:15:59.028Z","dependency_job_id":null,"html_url":"https://github.com/SunakazeKun/pyjmap","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/SunakazeKun/pyjmap","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SunakazeKun%2Fpyjmap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SunakazeKun%2Fpyjmap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SunakazeKun%2Fpyjmap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SunakazeKun%2Fpyjmap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SunakazeKun","download_url":"https://codeload.github.com/SunakazeKun/pyjmap/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SunakazeKun%2Fpyjmap/sbom","scorecard":{"id":135607,"data":{"date":"2025-08-11","repo":{"name":"github.com/SunakazeKun/pyjmap","commit":"cdb985f236933fd97a024335ba5b269d577bffa4"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/19 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"project is archived","details":["Warn: Repository is archived."],"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: GNU General Public License v3.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}}]},"last_synced_at":"2025-08-16T06:25:01.095Z","repository_id":44542648,"created_at":"2025-08-16T06:25:01.095Z","updated_at":"2025-08-16T06:25:01.095Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30101639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-04T23:59:36.199Z","status":"ssl_error","status_checked_at":"2026-03-04T23:56:48.556Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["bcsv","jmap","jsystem","modding-tools","nintendo","nintendo-hacking","nintendo-wii","python","super-mario-galaxy"],"created_at":"2026-01-28T20:00:26.601Z","updated_at":"2026-03-05T00:01:06.625Z","avatar_url":"https://github.com/SunakazeKun.png","language":"Python","funding_links":[],"categories":["🔧 Middleware \u0026 SDKs"],"sub_categories":["JSYSTEM (GameCube/Wii)"],"readme":"\n# pyjmap\n**pyjmap** is a high-level implementation of Nintendo's homemade BCSV/JMap data format. This includes methods to construct, analyze, manipulate, deserialize and serialize proper JMap data. Conversion between CSV and BCSV files is also supported. The reverse-engineered specifications of the file format can be accessed on the [Luma's Workshop wiki](https://luma.aurumsmods.com/wiki/BCSV_(File_format)). This flatbuffer-like data type was used in first-party GameCube and Wii games. As the field/column names are hashed, a lookup table needs to be used to retrieve proper field names. For this, the library provides hashtable implementations for *Super Mario Galaxy*, *Super Mario Galaxy 2*, *Luigi's Mansion* and *Donkey Kong Jungle Beat*.\n\n## Setup\nThis library requires **Python 3.6 or newer**. You can use pip to install *pyjmap*:\n```sh\npip install pyjmap\n```\n\n## Command usage\nCommand line operations to convert between JMap and CSV files are supported. The CSV files are required to be in a special format that has been found in some leftover source files from *Super Mario Galaxy 2*. That format is described down below.\n\nYou can dump the contents of a BCSV/JMap file to a CSV file using:\n```sh\npyjmap tocsv [-le] [-jmapenc JMAP_ENCODING] [-csvenc CSV_ENCODING] {smg,dkjb,lm} JMAP_FILE_PATH CSV_FILE_PATH\n```\n\nProper CSV files can be converted back to BCSV/JMap files using:\n```sh\npyjmap tojmap [-le] [-jmapenc JMAP_ENCODING] [-csvenc CSV_ENCODING] {smg,dkjb,lm} CSV_FILE_PATH JMAP_FILE_PATH\n```\n\nIf ``le`` is set, the data is expected to be stored using little-endian byte order. ``jmapenc`` specifies the encoding of strings in the JMap data and it defaults to ``shift_jisx0213``. ``csvenc`` is the encoding of the CSV file and it uses ``utf-8`` by default. The hash lookup table is specified by ``HASHTABLE``. Supported values are ``smg`` for *Super Mario Galaxy*, ``lm`` for *Luigi's Mansion*, ``sms`` for *Super Mario Sunshine* and ``dkjb`` for *Donkey Kong Jungle Beat*.\n\n## Library usage\nThe library provides various high-level operations to deal with JMap data. Below is some example code showing the fundamentals of *pyjmap*. Look at [jmap.py](pyjmap/jmap.py) for more information about the different methods.\n\n```python\nimport pyjmap\n\n# A hash lookup table is required to retrieve the proper names for hashed fields:\nhashtbl_smg = pyjmap.SuperMarioGalaxyHashTable()    # Lookup table for Super Mario Galaxy 1/2\nhashtbl_sms = pyjmap.SuperMarioSunshineHashTable()  # Lookup table for Super Mario Sunshine\nhashtbl_lm = pyjmap.LuigisMansionHashTable()        # Lookup table for Luigi's Mansion\nhashtbl_dkjb = pyjmap.JungleBeatHashTable()         # Lookup table for Donkey Kong Jungle Beat\n\n# Create JMapInfo data from files and print number of entries\ninfo = pyjmap.from_file(hashtbl_smg, \"GalaxySortIndexTable.bcsv\", big_endian=True)  # Big-endian is True by default\ninfo_from_csv = pyjmap.from_csv(hashtbl_smg, \"GalaxySortIndexTable.csv\")            # Load data from CSV file\nprint(\"Number of entries: %d\" % len(info))                                          # \u003e\u003e Number of entries: 55\n\n# Print fields\nfor field in info.fields:\n    print(field)  # \u003e\u003e name\n                  # \u003e\u003e MapPaneName\n                  # \u003e\u003e OpenCondition0\n                  # \u003e\u003e OpenCondition1\n                  # \u003e\u003e OpenCondition2\n                  # \u003e\u003e PowerStarNum\n                  # \u003e\u003e GrandGalaxyNo\n\n# Checking if a field exists\nprint(\"MapPaneName\" in info)  # \u003e\u003e True\nprint(\"StageName\" in info)    # \u003e\u003e False\n\n# Getting information about a field\nfield = info.get_field(\"MapPaneName\")  # Get field by name\nfield = info.get_field(0x7991F36F)     # Get field by hash\n\nprint(\"[%08X]\" % field.hash)  # \u003e\u003e [7991F36F]\nprint(field.name)             # \u003e\u003e MapPaneName\nprint(field.type)             # \u003e\u003e JMapFieldType.STRING_OFFSET\nprint(\"0x%08X\" % field.mask)  # \u003e\u003e 0xFFFFFFFF\nprint(field.shift)            # \u003e\u003e 0\n\n# Manually-specified offsets and bit-packed data\ncollision_pa = pyjmap.JMapInfo(hashtbl_smg)\ncollision_pa.manual_offsets = True\ncollision_pa.create_field(\"camera_id\", pyjmap.JMapFieldType.LONG, 0, mask=0x000000FF, shift_amount=0, offset=0)\ncollision_pa.create_field(\"Sound_code\", pyjmap.JMapFieldType.LONG, 0, mask=0x00007F00, shift_amount=8, offset=0)\ncollision_pa.create_field(\"Floor_code\", pyjmap.JMapFieldType.LONG, 0, mask=0x001F8000, shift_amount=15, offset=0)\ncollision_pa.create_field(\"Wall_code\", pyjmap.JMapFieldType.LONG, 0, mask=0x01E00000, shift_amount=21, offset=0)\ncollision_pa.create_field(\"Camera_through\", pyjmap.JMapFieldType.LONG, 0, mask=0x02000000, shift_amount=25, offset=0)\n\n# Creating an exact copy of the data\ncopied = info.copy()\n\n# The following creates a new field called CometMedalNum which uses the LONG data type. The field's default value\n# that is applied to all fields is -1. The optional bitmask and shift amount are 0xFFFFFFFF and 0, respectively.\ncopied.create_field(\"CometMedalNum\", pyjmap.JMapFieldType.LONG, -1, mask=0xFFFFFFFF, shift_amount=0)\n\n# This removes the field OpenCondition2 and its data in all entries.\ncopied.drop_field(\"OpenCondition2\")\n\n# Accessing entries directly\nfirst = copied[0] # Get first entry from copied data\nlast = copied[-1] # Get last entry from copied data\n\n# Adding and deleting entries\nnew_entry = copied.create_entry()  # Creates a new entry with default data for all fields\ndel copied[-3:]                    # Delete the last three entries from the copied data\n\n# Iterate over all entries and set GrandGalaxyNo to 0\nfor entry in copied:\n    entry[\"GrandGalaxyNo\"] = 0\n\n# Sort entries by name in lexicographic descending order\ninfo.sort_entries(lambda e: e[\"name\"].lower(), reverse=True)\n\n# Get all entries whose name start with \"Koopa\"\nfor entry in filter(lambda e: e[\"name\"].startswith(\"Koopa\"), info):\n    print(entry)  # \u003e\u003e {'name': 'KoopaJrShipLv1Galaxy', ... }\n                  # \u003e\u003e {'name': 'KoopaBattleVs3Galaxy', ... }\n                  # \u003e\u003e {'name': 'KoopaBattleVs2Galaxy', ... }\n                  # \u003e\u003e {'name': 'KoopaBattleVs1Galaxy', ... }\n\n# Write data to files\npyjmap.write_file(info, \"GalaxySortIndexTable_edited.bcsv\", big_endian=True)  # Pack and write binary\npyjmap.dump_csv(copied, \"GalaxySortIndexTable_copied.csv\", encoding=\"utf-8\")  # Dump CSV content\n\n# Pack as little-endian buffer\npacked_copied = pyjmap.pack_buffer(copied, big_endian=False)\n```\n\n# Data types\nThe following field data types are supported:\n\n| Identifier | CSV type | Description |\n| - | - | - |\n| ``JMapFieldType.LONG`` | ``Int`` | 32-bit integer |\n| ``JMapFieldType.UNSIGNED_LONG`` | ``UnsignedInt`` | 32-bit unsigned integer |\n| ``JMapFieldType.SHORT`` | ``Short`` | 16-bit integer |\n| ``JMapFieldType.CHAR`` | ``Char`` | 8-bit integer |\n| ``JMapFieldType.FLOAT`` | ``Float`` | single-precission float |\n| ``JMapFieldType.STRING`` | ``EmbeddedString`` | embedded SJIS string (occupies 31 bytes at max) |\n| ``JMapFieldType.STRING_OFFSET`` | ``String`` | SJIS string (**not supported in Luigi's Mansion**) |\n\n# CSV format\nThe CSV format is based on the format of known source files that were left in the files of *Super Mario Galaxy 2*:\n* All CSV files are comma-delimited and use quote-marks for quoted cell strings. Quoting is only used when necessary.\n* The first CSV-row contains the field descriptors. A field descriptor always consists of three components that are separated by double-colons: the field's name, the data type and the default value. All existing CSV types are described in the previous section and are case sensitive!\n* The default value for strings is 0 and is always ignored. It is only kept for syntax.\n* If an entry's field data is empty, the default value will be used.\n* The field name may be a hash if it's a hex-string encapsulated between two square brackets (for example ``[DEADBEEF]``)\n\nHere is an example of a properly-formated CSV file:\n```csv\nname:String:0,MapPaneName:String:0,OpenCondition0:String:0,OpenCondition1:String:0,OpenCondition2:String:0,PowerStarNum:Char:0,GrandGalaxyNo:Char:0\nAstroGalaxy,dummy,,,,0,0\nAstroDome,dummy,,,,0,0\nLibraryRoom,dummy,,,,0,0\nPeachCastleGardenGalaxy,dummy,,,,0,0\nEpilogueDemoStage,dummy,,,,0,0\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSunakazeKun%2Fpyjmap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSunakazeKun%2Fpyjmap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSunakazeKun%2Fpyjmap/lists"}