├── .config └── dotnet-tools.json ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── build-and-release.yml │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── .gitmodules ├── .prettierrc.yaml ├── .vscode ├── header-cs.code-snippets ├── launch.json ├── settings.json └── tasks.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── GitReleaseManager.yaml ├── GitVersion.yml ├── LICENSE ├── README.md ├── SECURITY.md ├── build └── orchestrator │ ├── BuildContext.cs │ ├── BuildSystem.csproj │ ├── BuildSystem.sln │ ├── DownloadTestFilesTask.cs │ └── Program.cs ├── docs ├── .gitignore ├── api │ └── .gitignore ├── articles │ ├── changelog.md │ ├── dev │ │ ├── features │ │ │ └── cartridge.md │ │ ├── toc.yml │ │ └── tutorial.md │ └── specs │ │ ├── cartridge │ │ ├── banner.md │ │ ├── cartridge.md │ │ ├── filesystem.md │ │ ├── header.md │ │ ├── program.md │ │ └── security.md │ │ └── toc.yml ├── docfx.json ├── images │ ├── favicon.png │ ├── logo_128.png │ └── logo_48.png ├── index.md ├── template │ └── public │ │ ├── main.css │ │ └── main.js └── toc.yml └── src ├── .idea └── .idea.Ekona │ └── .idea │ ├── .gitignore │ ├── .name │ ├── indexLayout.xml │ └── vcs.xml ├── Directory.Build.props ├── Directory.Packages.props ├── Ekona.Examples ├── Cartridge.cs ├── Ekona.Examples.csproj └── QuickStart.cs ├── Ekona.PerformanceTests ├── Binary2NitroRomTest.cs ├── Ekona.PerformanceTests.csproj ├── FilePathInfo.cs ├── NitroBlowfishTest.cs ├── Program.cs └── ResourceManager.cs ├── Ekona.Tests ├── Assertions │ ├── BinaryFormatAssertions.cs │ ├── NodeAssertions.cs │ └── StreamAssertions.cs ├── BinaryInfo.cs ├── Containers │ └── Rom │ │ ├── Binary2BannerTests.cs │ │ ├── Binary2NitroRomTests.cs │ │ ├── Binary2RomHeaderTests.cs │ │ └── ProgramInfoTests.cs ├── Ekona.Tests.csproj ├── NodeContainerInfo.cs ├── Resources │ ├── .gitignore │ └── Containers │ │ ├── banner.txt │ │ ├── header.txt │ │ └── rom.txt ├── Security │ ├── NitroBlowfishTests.cs │ └── NitroKey1EncryptionTests.cs ├── TestDataBase.cs └── YarhlTestExtensions.cs ├── Ekona.sln ├── Ekona.sln.DotSettings ├── Ekona ├── Containers │ └── Rom │ │ ├── AgeRating.cs │ │ ├── ArmEncodeFieldFinder.cs │ │ ├── Banner.cs │ │ ├── Banner2Binary.cs │ │ ├── Binary2Banner.cs │ │ ├── Binary2NitroRom.cs │ │ ├── Binary2RomHeader.cs │ │ ├── DeviceUnitKind.cs │ │ ├── DsiCryptoMode.cs │ │ ├── DsiProgramInfo.cs │ │ ├── DsiRomFeatures.cs │ │ ├── GlobalMemoryBankSettings.cs │ │ ├── IconAnimation2AnimatedImage.cs │ │ ├── IconAnimationFrame.cs │ │ ├── IconAnimationSequence.cs │ │ ├── LocalMemoryBankSettings.cs │ │ ├── MemoryBankProcessor.cs │ │ ├── ModcryptTargetKind.cs │ │ ├── NitroProgramCodeParameters.cs │ │ ├── NitroRom.cs │ │ ├── NitroRom2Binary.cs │ │ ├── NitroRom2BinaryParams.cs │ │ ├── OverlayInfo.cs │ │ ├── ProgramInfo.cs │ │ ├── ProgramRegions.cs │ │ ├── ProgramStartJumpKind.cs │ │ ├── RomHeader.cs │ │ ├── RomHeader2Binary.cs │ │ ├── RomSectionInfo.cs │ │ ├── ScfgExtendedFeaturesArm7.cs │ │ └── TwilightAccessControl.cs ├── Ekona.csproj └── Security │ ├── DsiKeyStore.cs │ ├── HashInfo.cs │ ├── HashStatus.cs │ ├── IOExtensions.cs │ ├── Modcrypt.cs │ ├── NitroBlowfish.cs │ ├── NitroCrcGenerator.cs │ ├── NitroKey1Encryption.cs │ ├── TwilightHMacGenerator.cs │ └── TwilightSigner.cs ├── Tests.runsettings └── nuget.config /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "gitversion.tool": { 6 | "version": "5.12.0", 7 | "commands": [ 8 | "dotnet-gitversion" 9 | ] 10 | }, 11 | "thirdlicense": { 12 | "version": "1.3.1", 13 | "commands": [ 14 | "thirdlicense" 15 | ] 16 | }, 17 | "dotnet-reportgenerator-globaltool": { 18 | "version": "5.2.0", 19 | "commands": [ 20 | "reportgenerator" 21 | ] 22 | }, 23 | "docfx": { 24 | "version": "2.75.2", 25 | "commands": [ 26 | "docfx" 27 | ] 28 | }, 29 | "gitreleasemanager.tool": { 30 | "version": "0.16.0", 31 | "commands": [ 32 | "dotnet-gitreleasemanager" 33 | ] 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | ## Description 10 | 11 | TODO: describe the issue 12 | 13 | ## Reproducer 14 | 15 | TODO: steps to reproduce the behavior. 16 | 17 | ## Expected behavior 18 | 19 | TODO: description of the expected behavior. 20 | 21 | ## Report info 22 | 23 | TODO: if applicable, the full exception stacktrace that you get. 24 | 25 | TODO: if applicable, add screenshots to help explain your problem. 26 | 27 | TODO: describe your environment like OS, app/lib version 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | ## Goal 10 | 11 | TODO: describe with user stories or a short text the goal of the feature. 12 | 13 | ## Description 14 | 15 | TODO: describe the motivation behind of the idea and what it should do. 16 | 17 | ## Proposed solution 18 | 19 | TODO: add any ideas for the implementation or how it should look like. 20 | 21 | ## Acceptance criteria 22 | 23 | TODO: list of expectations it should pass to close the request. 24 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | TODO: description of the PR work. 2 | 3 | This PR closes # 4 | 5 | ## Quality check list 6 | 7 | - [ ] Related code has been tested automatically or manually 8 | - [ ] Related documentation is updated 9 | - [ ] I acknowledge I have read and filled this checklist and accept the 10 | [developer certificate of origin](https://developercertificate.org/) 11 | 12 | ## Acceptance criteria 13 | 14 | TODO: list of expectations it has passed from the related issue. 15 | 16 | ## Follow-up work 17 | 18 | TODO: describe any missing or future required work. 19 | 20 | ## Example 21 | 22 | TODO: small code-snippet or screenshot of the work 23 | -------------------------------------------------------------------------------- /.github/workflows/build-and-release.yml: -------------------------------------------------------------------------------- 1 | name: Build and release 2 | 3 | on: 4 | # Dev 5 | workflow_dispatch: 6 | pull_request: 7 | push: 8 | # Preview 9 | branches: [ main ] 10 | # Stable 11 | tags: [ "v*" ] 12 | 13 | jobs: 14 | build: 15 | name: "Build" 16 | uses: ./.github/workflows/build.yml 17 | with: 18 | dotnet_version: '8.0.204' 19 | secrets: 20 | test_resources: ${{ secrets.TEST_RESOURCES_URI_V1 }} 21 | 22 | # Preview release on push to main only 23 | # Stable release on version tag push only 24 | deploy: 25 | name: "Deploy" 26 | if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') 27 | needs: build 28 | uses: ./.github/workflows/deploy.yml 29 | with: 30 | dotnet_version: '8.0.204' 31 | azure_nuget_feed: 'https://pkgs.dev.azure.com/SceneGate/SceneGate/_packaging/SceneGate-Preview/nuget/v3/index.json' 32 | secrets: 33 | nuget_preview_token: "az" # Dummy values as we use Azure DevOps onlyg 34 | nuget_stable_token: "${{ secrets.NUGET_FEED_TOKEN }}" 35 | azure_nuget_token: ${{ secrets.ADO_NUGET_FEED_TOKEN }} 36 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: "Build" 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | dotnet_version: 7 | required: true 8 | type: string 9 | secrets: 10 | test_resources: 11 | required: true 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | matrix: 17 | os: [ ubuntu-latest, macos-latest, windows-latest ] 18 | include: 19 | # By default they are no "main build" but if it matches "os" then yes. 20 | - os: ubuntu-latest 21 | is_main_build: true 22 | name: "${{ matrix.os }}" 23 | runs-on: ${{ matrix.os }} 24 | env: 25 | test_resources: ${{ secrets.test_resources }} 26 | steps: 27 | - name: "Checkout" 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 0 # We need full history for version number 31 | 32 | - name: "Setup .NET SDK" 33 | uses: actions/setup-dotnet@v4 34 | with: 35 | dotnet-version: ${{ inputs.dotnet_version }} 36 | 37 | - name: "Setup .NET 6 SDK" 38 | uses: actions/setup-dotnet@v4 39 | with: 40 | dotnet-version: 6.0.421 41 | 42 | - if: ${{ env.test_resources != '' }} 43 | name: "Build and run FULL tests" 44 | run: dotnet run --project build/orchestrator -- --resource-uri=${{ env.test_resources }} --target=Default --dotnet-configuration=Release 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | 48 | # External PR will run basic tests due to the complexity to make it work with secrets 49 | - if: ${{ env.test_resources == '' }} 50 | name: "Build and run basic tests" 51 | run: dotnet run --project build/orchestrator -- --target=Default --dotnet-configuration=Release 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | 55 | - name: "Bundle" 56 | if: ${{ matrix.is_main_build }} 57 | run: dotnet run --project build/orchestrator -- --target=Bundle --dotnet-configuration=Release 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | 61 | - name: "Publish artifacts to CI" 62 | if: ${{ matrix.is_main_build }} 63 | uses: actions/upload-artifact@v4 64 | with: 65 | name: "Artifacts" 66 | retention-days: 7 67 | path: | 68 | build/artifacts/ 69 | !build/artifacts/docs 70 | 71 | - name: Publish docs artifact to CI 72 | if: ${{ matrix.is_main_build }} 73 | uses: actions/upload-pages-artifact@v3 74 | with: 75 | path: build/artifacts/docs 76 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: "Deploy" 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | dotnet_version: 7 | required: true 8 | type: string 9 | azure_nuget_feed: 10 | required: false 11 | type: string 12 | secrets: 13 | nuget_preview_token: 14 | required: false 15 | nuget_stable_token: 16 | required: false 17 | azure_nuget_token: 18 | required: false 19 | 20 | jobs: 21 | upload_doc: 22 | name: "Documentation" 23 | runs-on: "ubuntu-latest" 24 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 25 | permissions: 26 | pages: write # to deploy to Pages 27 | id-token: write # to verify the deployment originates from an appropriate source 28 | # Deploy to the github-pages environment 29 | environment: 30 | name: github-pages 31 | url: ${{ steps.deployment.outputs.page_url }} 32 | steps: 33 | - name: Deploy to GitHub Pages 34 | id: deployment 35 | uses: actions/deploy-pages@v4 36 | 37 | push_artifacts: 38 | name: "Artifacts" 39 | runs-on: "ubuntu-latest" 40 | steps: 41 | - name: "Checkout" 42 | uses: actions/checkout@v4 43 | with: 44 | fetch-depth: 0 # We need full history for version number 45 | 46 | - name: "Download artifacts" 47 | uses: actions/download-artifact@v4 48 | with: 49 | name: "Artifacts" 50 | path: "./build/artifacts/" 51 | 52 | - name: "Setup .NET SDK" 53 | uses: actions/setup-dotnet@v4 54 | with: 55 | dotnet-version: ${{ inputs.dotnet_version }} 56 | 57 | # Weird way to authenticate in Azure DevOps Artifacts 58 | # Then, we need to setup VSS_NUGET_EXTERNAL_FEED_ENDPOINTS 59 | - name: "Install Azure Artifacts Credential Provider" 60 | run: wget -qO- https://aka.ms/install-artifacts-credprovider.sh | bash 61 | 62 | - name: "Deploy artifacts" 63 | run: dotnet run --project build/orchestrator -- --target=Deploy 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | STABLE_NUGET_FEED_TOKEN: ${{ secrets.nuget_stable_token }} 67 | PREVIEW_NUGET_FEED_TOKEN: ${{ secrets.nuget_preview_token }} 68 | VSS_NUGET_EXTERNAL_FEED_ENDPOINTS: '{"endpointCredentials": [{"endpoint":"${{ inputs.azure_nuget_feed }}", "username":"", "password":"${{ secrets.azure_nuget_token }}"}]}' 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build outputs 2 | obj/ 3 | bin/ 4 | build/artifacts/ 5 | build/temp/ 6 | /CHANGELOG.md 7 | /CHANGELOG.NEXT.md 8 | 9 | # IDEs 10 | .vs/ 11 | *.csproj.user 12 | *.DotSettings.user 13 | launchSettings.json 14 | 15 | # Test results 16 | BenchmarkDotNet.Artifacts/ 17 | 18 | # Test resources 19 | resources/ 20 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SceneGate/Ekona/5864da78cb3bf45df8307476afd7acf542436871/.gitmodules -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | overrides: 2 | - files: "*.md" 3 | options: 4 | proseWrap: always 5 | -------------------------------------------------------------------------------- /.vscode/header-cs.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Header C#": { 3 | "scope": "csharp", 4 | "prefix": "header", 5 | "description": "Insert the C# file header", 6 | "body": [ 7 | "// Copyright (c) 2023 SceneGate", 8 | "", 9 | "// Permission is hereby granted, free of charge, to any person obtaining a copy", 10 | "// of this software and associated documentation files (the \"Software\"), to deal", 11 | "// in the Software without restriction, including without limitation the rights", 12 | "// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", 13 | "// copies of the Software, and to permit persons to whom the Software is", 14 | "// furnished to do so, subject to the following conditions:", 15 | "", 16 | "// The above copyright notice and this permission notice shall be included in all", 17 | "// copies or substantial portions of the Software.", 18 | "", 19 | "// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", 20 | "// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", 21 | "// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", 22 | "// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", 23 | "// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", 24 | "// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", 25 | "// SOFTWARE.", 26 | "namespace SceneGate.Ekona.$1;", 27 | "", 28 | "$0", 29 | "" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Attach", 9 | "type": "coreclr", 10 | "request": "attach", 11 | "processId": "${command:pickProcess}" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.rulers": [ 3 | 80, 4 | 120 5 | ], 6 | "editor.renderWhitespace": "boundary", 7 | "editor.defaultFormatter": "esbenp.prettier-vscode", 8 | "[markdown]": { 9 | "editor.formatOnSave": true, 10 | }, 11 | "[csharp]": { 12 | "editor.defaultFormatter": "ms-dotnettools.csharp", 13 | "editor.formatOnType": true 14 | }, 15 | "cSpell.words": [ 16 | "Agcb", 17 | "cref", 18 | "Diagnoser", 19 | "Ekona", 20 | "Encryptor", 21 | "ESRB", 22 | "firmwares", 23 | "HMAC", 24 | "HMACSHA", 25 | "Itcm", 26 | "Modcrypt", 27 | "Modcrypted", 28 | "NAND", 29 | "nitrocode", 30 | "nitrorom", 31 | "Pegi", 32 | "scenegate", 33 | "SCFG", 34 | "Texim", 35 | "ulong", 36 | "unlaunch", 37 | "Unswizzle", 38 | "ushort", 39 | "WRAM" 40 | ], 41 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "process", 6 | "command": "dotnet", 7 | "args": [ 8 | "run", 9 | "--project", 10 | "build/orchestrator/", 11 | ], 12 | "group": { 13 | "kind": "build", 14 | "isDefault": true 15 | }, 16 | "problemMatcher": ["$msCompile"], 17 | "label": "Build" 18 | }, 19 | { 20 | "type": "process", 21 | "command": "dotnet", 22 | "args": [ 23 | "run", 24 | "--project", 25 | "build/orchestrator/", 26 | "--", 27 | "--target=Bundle" 28 | ], 29 | "group": { 30 | "kind": "build", 31 | }, 32 | "problemMatcher": ["$msCompile"], 33 | "label": "Bundle" 34 | }, 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of 9 | experience, nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or reject 41 | comments, commits, code, wiki edits, issues, and other contributions that are 42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 43 | contributor for other behaviors that they deem inappropriate, threatening, 44 | offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . The project 59 | team will review and investigate all complaints, and will respond in a way that 60 | it deems appropriate to the circumstances. The project team is obligated to 61 | maintain confidentiality with regard to the reporter of an incident. Further 62 | details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 71 | version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | Thanks for taking the time to contribute! :sparkles: 4 | 5 | In this document you will find all the information you need to make sure that 6 | the projects continues to be consistent and with great quality! 7 | 8 | > [!NOTE] 9 | > By contributing in this repository you accept the 10 | > [developer certificate of origin](https://developercertificate.org/). 11 | 12 | ## Reporting features and issues 13 | 14 | ### Issues 15 | 16 | When reporting a problem, be as specific as possible. Ideally, you should 17 | provide an small snippet of code that reproduces the issue. 18 | 19 | Please fill the default template so we can have all the required information to 20 | address the issue. 21 | 22 | ### Features 23 | 24 | Features are requested and handled as GitHub _issues_. 25 | 26 | If you want to ask for a new feature, first make sure it hasn't been reported 27 | yet by using the search box in the issue tab. Make sure that the feature aligns 28 | with the direction of the project. 29 | 30 | **Do not ask for tools for games or translations**. 31 | 32 | ## Pull Request 33 | 34 | Before starting a pull request, create an issue 35 | [requesting the feature](#features) you would like to see and implement. If you 36 | are fixing a bug, create also an issue to be able to track the problem. 37 | 38 | In the issue or feature request specify that that you would like to work on it. 39 | The team will reply as soon as possible to discuss the proposal. This guarantee 40 | the Pull Request implementation match the direction the project is going. 41 | 42 | In general, the process to create a pull request is: 43 | 44 | 1. Create an issue describing the bug or feature and state you would like to 45 | work on that. 46 | 2. The team will cheer you and/or discuss with you the issue. 47 | 3. Fork the project (if not done already). 48 | 4. Clone your forked project and create a git branch. 49 | 5. Make the necessary code changes in as many commits as you want. The commit 50 | message should follow this convention: 51 | 52 | ```plain 53 | :emoji: Short description #IssueID 54 | 55 | Long description if needed. 56 | ``` 57 | 58 | 6. Create a pull request. After reviewing your changes and making any new 59 | commits if needed, the team will approve and merge it. 60 | 61 | For a complete list of emoji description see 62 | [this repository](https://github.com/slashsBin/styleguide-git-commit-message#suggested-emojis). 63 | 64 | ## Code Guidelines 65 | 66 | The project includes a `.editorconfig` file that ensures the code style is 67 | consistent. It is supported in any modern IDE. 68 | 69 | In general, we follow the following standard guidelines with custom changes: 70 | 71 | - [Mono Code Guidelines](https://raw.githubusercontent.com/mono/website/gh-pages/community/contributing/coding-guidelines.md). 72 | - [Microsoft Framework Design Guidelines](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/) 73 | - [Microsoft C# Coding Convetions](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions). 74 | 75 | And as the 76 | [mono team says](https://www.mono-project.com/community/contributing/coding-guidelines/#performance-and-readability): 77 | 78 | - It is more important to be correct than to be fast. 79 | - It is more important to be maintainable than to be fast. 80 | - Fast code that is difficult to maintain is likely going to be looked down 81 | upon. 82 | 83 | Make sure to follow these tips: 84 | 85 | - :heavy_check_mark: **DO** write documentation for any public type and method. 86 | - :heavy_check_mark: **DO** write a test for all the possible code branches of 87 | your methods. Use a TDD approach. 88 | - :heavy_check_mark: **DO** seek for the maximum test coverage. 89 | - :heavy_check_mark: **DO** clean compiler warning. 90 | -------------------------------------------------------------------------------- /GitReleaseManager.yaml: -------------------------------------------------------------------------------- 1 | # Configuration values used when creating new releases 2 | create: 3 | include-footer: false 4 | include-sha-section: false 5 | allow-update-to-published: false 6 | 7 | # Configuration values used when exporting release notes 8 | export: 9 | include-created-date-in-title: true 10 | created-date-string-format: MMMM dd, yyyy 11 | perform-regex-removal: false 12 | 13 | # Configuration values used when closing a milestone 14 | close: 15 | use-issue-comments: false 16 | # issue-comment: |- 17 | # :tada: This issue has been resolved in version {milestone} :tada: 18 | # 19 | # The release is available on: 20 | # 21 | # - [GitHub release](https://github.com/{owner}/{repository}/releases/tag/{milestone}) 22 | # 23 | # Your **[GitReleaseManager](https://github.com/GitTools/GitReleaseManager)** bot :package::rocket: 24 | 25 | # The labels that will be used to include issues in release notes. 26 | issue-labels-include: 27 | - Breaking 28 | - Bug 29 | - Duplicate 30 | - Documentation 31 | - Enhancement 32 | - Feature 33 | - Improvement 34 | - Question 35 | 36 | # The labels that will NOT be used when including issues in release notes. 37 | issue-labels-exclude: 38 | - Internal Refactoring 39 | 40 | # Overrides default pluralization and header names for specific labels. 41 | issue-labels-alias: 42 | - name: Documentation 43 | header: Documentation 44 | plural: Documentation 45 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: ContinuousDeployment 2 | branches: 3 | main: 4 | tag: preview 5 | increment: Patch 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 SceneGate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ekona 2 | 3 | 4 |

5 | 6 | Stable version 7 | 8 |   9 | 10 | GitHub commits since latest release (by SemVer) 11 | 12 |   13 | 14 | Build and release 15 | 16 |   17 | 18 | MIT License 19 | 20 |   21 |

22 | 23 | _Ekona_ is a library part of the [_SceneGate_](https://github.com/SceneGate) 24 | framework that provides support for **DS and DSi file formats.** 25 | 26 | ## Supported formats 27 | 28 | - :video_game: DS cartridge: 29 | - :file_folder: Filesystem: read and write 30 | - :information_source: Header: read and write, including extended header 31 | - :framed_picture: Banner and icon: read and write. 32 | - :closed_lock_with_key: ARM9 secure area encryption and decryption (KEY1). 33 | - :video_game: DSi cartridge: 34 | - :file_folder: Filesystem: read and write `arm9i` and `arm7i` programs. 35 | - :information_source: Extended header: read and write 36 | - :framed_picture: Animated banner icons 37 | - :closed_lock_with_key: Modcrypt encryption and decryption 38 | - :lock_with_ink_pen: HMAC validation and generation when keys are provided. 39 | - :lock_with_ink_pen: Signature validation when keys are provided. 40 | 41 | ## Getting started 42 | 43 | Check-out the 44 | [getting started guide](https://scenegate.github.io/Ekona/docs/dev/tutorial.html) 45 | to start using _Ekona_ in no time! Below you can find an example that shows how 46 | to open a DS/DSi ROM file (cartridge dump). 47 | 48 | ```csharp 49 | // Create Yarhl node from a file (binary format). 50 | Node game = NodeFactory.FromFile("game.nds", FileOpenMode.Read); 51 | 52 | // Use the `Binary2NitroRom` converter to convert the binary format 53 | // into node containers (virtual file system tree with files and directories). 54 | game.TransformWith(); 55 | 56 | // And it's done! 57 | // Now we can access to every game file. For instance, we can export one file 58 | Node items = Navigator.SearchNode(game, "data/Items.dat"); 59 | items.Stream.WriteTo("dump/Items.dat"); 60 | ``` 61 | 62 | ## Usage 63 | 64 | The project provides the following .NET libraries (NuGet packages in nuget.org). 65 | The libraries works on supported versions of .NET: 6.0 and 8.0. 66 | 67 | - [![SceneGate.Ekona](https://img.shields.io/nuget/v/SceneGate.Ekona?label=SceneGate.Ekona&logo=nuget)](https://www.nuget.org/packages/SceneGate.Ekona) 68 | - `SceneGate.Ekona.Containers.Rom`: DS and DSi cartridge (ROM) format. 69 | - `SceneGate.Ekona.Security`: hash and encryption algorithms 70 | 71 | Preview releases can be found in this 72 | [Azure DevOps package repository](https://dev.azure.com/SceneGate/SceneGate/_packaging?_a=feed&feed=SceneGate-Preview). 73 | To use a preview release, create a file `nuget.config` in the same directory of 74 | your solution file (.sln) with the following content: 75 | 76 | ```xml 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | ``` 96 | 97 | ## Documentation 98 | 99 | You can get full details about how to use library from the 100 | [documentation](https://scenegate.github.io/Ekona/docs/dev/features/cartridge.html) 101 | website. 102 | 103 | Don't miss the 104 | [formats specifications](https://scenegate.github.io/Ekona/docs/specs/cartridge/cartridge.html) 105 | in case you need to do further research. 106 | 107 | And don't hesitate to ask questions in the 108 | [project Discussion site!](https://github.com/SceneGate/Ekona/discussions) 109 | 110 | ## Build 111 | 112 | The project requires to build .NET 8.0 SDK. 113 | 114 | To build, test and generate artifacts run: 115 | 116 | ```sh 117 | # Build and run tests 118 | dotnet run --project build/orchestrator 119 | 120 | # (Optional) Create bundles (nuget, zips, docs) 121 | dotnet run --project build/orchestrator -- --target=Bundle 122 | ``` 123 | 124 | To build the documentation only, run: 125 | 126 | ```sh 127 | dotnet docfx docs/docfx.json --serve 128 | ``` 129 | 130 | To run the performance test with memory and CPU traces: 131 | 132 | ```sh 133 | dotnet run --project src/Ekona.PerformanceTests/ -c Release -- -f "**" -m -p EP --maxWidth 60 134 | ``` 135 | 136 | ## Special thanks 137 | 138 | The DS / DSi cartridge format was based on the amazing reverse engineering work 139 | of Martin Korth at [GBATek](https://problemkaputt.de/gbatek.htm). Its 140 | specifications of the hardware of the video controller and I/O ports was also a 141 | great help in additional reverse engineering. 142 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest stable version is actively supported with critical bug fixed and 6 | security issues. 7 | 8 | ## Reporting a Vulnerability 9 | 10 | Vulnerabilities can be reported to my personal email address (you can check it 11 | from my [GitHub profile](https://github.com/pleonex)). 12 | 13 | All the security issues will be analyzed and a reply will be given in two 14 | working days. Once the issue is accepted it will be fixed in the current 15 | development branch and for the latest version. A new version would be released. 16 | -------------------------------------------------------------------------------- /build/orchestrator/BuildContext.cs: -------------------------------------------------------------------------------- 1 | using Cake.Core; 2 | using Cake.Frosting.PleOps.Recipe; 3 | 4 | namespace BuildSystem; 5 | 6 | public class BuildContext : PleOpsBuildContext 7 | { 8 | public BuildContext(ICakeContext context) : base(context) 9 | { 10 | TestResourceUri = string.Empty; 11 | } 12 | 13 | public string TestResourceUri { get; set; } 14 | 15 | public override void ReadArguments() 16 | { 17 | base.ReadArguments(); 18 | 19 | Arguments.SetIfPresent("resource-uri", x => TestResourceUri = x); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /build/orchestrator/BuildSystem.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | 6 | net8.0 7 | $(MSBuildProjectDirectory)/../.. 8 | 9 | enable 10 | enable 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /build/orchestrator/BuildSystem.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildSystem", "BuildSystem.csproj", "{C3AF4C5B-294F-4EA3-BCC9-3E6B2AA4D569}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(SolutionProperties) = preSolution 14 | HideSolutionNode = FALSE 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {C3AF4C5B-294F-4EA3-BCC9-3E6B2AA4D569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {C3AF4C5B-294F-4EA3-BCC9-3E6B2AA4D569}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {C3AF4C5B-294F-4EA3-BCC9-3E6B2AA4D569}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {C3AF4C5B-294F-4EA3-BCC9-3E6B2AA4D569}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /build/orchestrator/DownloadTestFilesTask.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Text.Json; 3 | using Cake.Common.IO; 4 | using Cake.Common.Net; 5 | using Cake.Core.Diagnostics; 6 | using Cake.Frosting; 7 | 8 | namespace BuildSystem; 9 | 10 | [TaskName("Download-TestFiles")] 11 | [TaskDescription("Download the test resource files")] 12 | [IsDependeeOf(typeof(Cake.Frosting.PleOps.Recipe.Dotnet.TestTask))] 13 | public class DownloadTestFilesTask : FrostingTask 14 | { 15 | public override void Run(BuildContext context) 16 | { 17 | string resourcesPath = Path.GetFullPath("./resources"); 18 | Environment.SetEnvironmentVariable("SCENEGATE_TEST_DIR", resourcesPath); 19 | 20 | if (Directory.Exists("resources")) { 21 | context.Log.Information("Test files already exists, skipping download."); 22 | return; 23 | } 24 | 25 | if (string.IsNullOrEmpty(context.TestResourceUri)) { 26 | context.Log.Information("Test resource uri is not present, skipping download."); 27 | return; 28 | } 29 | 30 | var jsonInfoPath = context.DownloadFile(context.TestResourceUri); 31 | string jsonInfoText = File.ReadAllText(jsonInfoPath.FullPath); 32 | IEnumerable? resources = JsonSerializer.Deserialize>(jsonInfoText); 33 | if (resources is null) { 34 | throw new Exception("Failed to read json info file"); 35 | } 36 | 37 | foreach (TestResource resource in resources) { 38 | var compressedResources = context.DownloadFile(resource.uri); 39 | context.Unzip(compressedResources, resource.path); 40 | } 41 | } 42 | 43 | private sealed record TestResource(string uri, string path); 44 | } 45 | -------------------------------------------------------------------------------- /build/orchestrator/Program.cs: -------------------------------------------------------------------------------- 1 | using BuildSystem; 2 | using Cake.Core; 3 | using Cake.Frosting; 4 | using Cake.Frosting.PleOps.Recipe; 5 | 6 | return new CakeHost() 7 | .AddAssembly(typeof(PleOpsBuildContext).Assembly) 8 | .UseContext() 9 | .UseLifetime() 10 | .Run(args); 11 | 12 | public sealed class BuildLifetime : FrostingLifetime 13 | { 14 | public override void Setup(BuildContext context, ISetupContext info) 15 | { 16 | // Do not enforce coverage as everyone may not have all test resources 17 | context.DotNetContext.CoverageTarget = 0; 18 | 19 | context.ReadArguments(); 20 | 21 | context.DotNetContext.PreviewNuGetFeed = "https://pkgs.dev.azure.com/SceneGate/SceneGate/_packaging/SceneGate-Preview/nuget/v3/index.json"; 22 | 23 | context.Print(); 24 | } 25 | 26 | public override void Teardown(BuildContext context, ITeardownContext info) 27 | { 28 | // Save the info from the existing artifacts for the next execution (e.g. deploy job) 29 | context.DeliveriesContext.Save(); 30 | } 31 | } 32 | 33 | [TaskName("Default")] 34 | [IsDependentOn(typeof(Cake.Frosting.PleOps.Recipe.Common.SetGitVersionTask))] 35 | [IsDependentOn(typeof(Cake.Frosting.PleOps.Recipe.Common.CleanArtifactsTask))] 36 | [IsDependentOn(typeof(Cake.Frosting.PleOps.Recipe.Dotnet.DotnetTasks.BuildProjectTask))] 37 | public sealed class DefaultTask : FrostingTask 38 | { 39 | } 40 | 41 | [TaskName("Bundle")] 42 | [IsDependentOn(typeof(Cake.Frosting.PleOps.Recipe.Common.SetGitVersionTask))] 43 | [IsDependentOn(typeof(Cake.Frosting.PleOps.Recipe.GitHub.ExportReleaseNotesTask))] 44 | [IsDependentOn(typeof(Cake.Frosting.PleOps.Recipe.Dotnet.DotnetTasks.BundleProjectTask))] 45 | [IsDependentOn(typeof(Cake.Frosting.PleOps.Recipe.DocFx.BuildTask))] 46 | public sealed class BundleTask : FrostingTask 47 | { 48 | } 49 | 50 | [TaskName("Deploy")] 51 | [IsDependentOn(typeof(Cake.Frosting.PleOps.Recipe.Common.SetGitVersionTask))] 52 | [IsDependentOn(typeof(Cake.Frosting.PleOps.Recipe.Dotnet.DotnetTasks.DeployProjectTask))] 53 | [IsDependentOn(typeof(Cake.Frosting.PleOps.Recipe.GitHub.UploadReleaseBinariesTask))] 54 | public sealed class DeployTask : FrostingTask 55 | { 56 | } 57 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # folder # 3 | ############### 4 | /**/DROP/ 5 | /**/TEMP/ 6 | /**/packages/ 7 | /**/bin/ 8 | /**/obj/ 9 | _site 10 | 11 | # DrawIO 12 | .$*.drawio* 13 | -------------------------------------------------------------------------------- /docs/api/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # temp file # 3 | ############### 4 | *.yml 5 | .manifest 6 | -------------------------------------------------------------------------------- /docs/articles/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | To be filled on preview and stable builds. 4 | -------------------------------------------------------------------------------- /docs/articles/dev/toc.yml: -------------------------------------------------------------------------------- 1 | - name: ✨ Getting started 2 | - name: Introduction 3 | href: ../../index.md 4 | - name: Getting started guide 5 | href: tutorial.md 6 | 7 | - name: ♻️ Converters 8 | - name: Cartridge 9 | href: features/cartridge.md 10 | 11 | - name: 📃 API docs 12 | - name: Namespaces 13 | href: ../../api/toc.yml 14 | -------------------------------------------------------------------------------- /docs/articles/dev/tutorial.md: -------------------------------------------------------------------------------- 1 | # Getting started guide 2 | 3 | Welcome to _Ekona_, a library part of the _SceneGate_ framework that provides 4 | support for DS and DSi file formats. 5 | 6 | ## Usage 7 | 8 | The project provides the following .NET libraries (NuGet packages in nuget.org). 9 | The libraries works on supported versions of .NET: 6.0 and 8.0. 10 | 11 | - [![SceneGate.Ekona](https://img.shields.io/nuget/v/SceneGate.Ekona?label=SceneGate.Ekona&logo=nuget)](https://www.nuget.org/packages/SceneGate.Ekona) 12 | - `SceneGate.Ekona.Containers.Rom`: DS and DSi cartridge (ROM) format. 13 | - `SceneGate.Ekona.Security`: hash and encryption algorithms 14 | 15 | ## Quick start 16 | 17 | ### Cartridge file system 18 | 19 | Let's start by opening a game (ROM) and accessing to its files. We can virtually 20 | _unpack_ the ROM by using the converter 21 | [`Binar2NitroRom`](xref:SceneGate.Ekona.Containers.Rom.Binary2NitroRom). It will 22 | create a tree of _nodes_ that we can use to access to the files. 23 | 24 | [!code-csharp[OpenGame](../../../src/Ekona.Examples/QuickStart.cs?name=OpenGame)] 25 | 26 | > [!NOTE] 27 | > The converter will not write any file to the disk. It will create a tree of 28 | > nodes that points to different parts of the original game file. If it needs to 29 | > decrypt a file (like `arm9i`), it will create a new _stream_ on memory. 30 | 31 | Now we can quickly modify our file even by code! 32 | 33 | [!code-csharp[ModifyFile](../../../src/Ekona.Examples/QuickStart.cs?name=ModifyFile)] 34 | 35 | Finally, to generate a new game (ROM) file we just need one more line of code to 36 | use the [`NitroRom2Binary`](xref:SceneGate.Ekona.Containers.Rom.NitroRom2Binary) 37 | converter. 38 | 39 | [!code-csharp[WriteGame](../../../src/Ekona.Examples/QuickStart.cs?name=WriteGame)] 40 | 41 | > [!TIP] 42 | > Check-out the [cartridge](features/cartridge.md) section to learn about the 43 | > optional parameters of these converters! 44 | 45 | ### Cartridge information 46 | 47 | Once we have opened a game, we can access to all the information from its header 48 | easily via the `system/info` node. 49 | 50 | [!code-csharp[HeaderInfo](../../../src/Ekona.Examples/QuickStart.cs?name=HeaderInfo)] 51 | 52 | In a similar way, you can access to the information from the banner like the 53 | game title in different languages: 54 | 55 | [!code-csharp[BannerTitle](../../../src/Ekona.Examples/QuickStart.cs?name=BannerTitle)] 56 | 57 | You can also export the game icon. If it's a DSi game, it may even have an 58 | animated icon that you can export as GIF! 59 | 60 | [!code-csharp[ExportIcon](../../../src/Ekona.Examples/QuickStart.cs?name=ExportIcon)] 61 | -------------------------------------------------------------------------------- /docs/articles/specs/cartridge/banner.md: -------------------------------------------------------------------------------- 1 | # Cartridge banner format 2 | 3 | The banner contains the program title in different languages and the icon. There 4 | are different versions of the banner that support additional content. The 5 | section start offset is defined in the [header](header.md) at `0x68`. Additional 6 | in DSi-enhanced or exclusive games, the header also contains the banner length 7 | at `0x208`. 8 | 9 | ## Binary format 10 | 11 | | Offset | Format | Description | 12 | | ------ | ----------- | ------------------------------------------------------ | 13 | | 0x00 | ushort | Version (major.minor) | 14 | | 0x02 | ushort | CRC-16 basic banner [0x20..0x840) | 15 | | 0x04 | ushort | (Version >= 0.1) CRC-16 with Chinese [0x20..0x940) | 16 | | 0x06 | ushort | (Version >= 0.2) CRC-16 with Korean [0x20..0xA40) | 17 | | 0x08 | ushort | (Version >= 1.3) CRC-16 animated icon [0x1240..0x23C0) | 18 | | 0x0A | byte[22] | Reserved | 19 | | 0x20 | Pixel[1024] | 4 bpp indexed tiled pixels for 32x32 icon | 20 | | 0x220 | Color[16] | 16 BGR-555 colors | 21 | | 0x240 | char[256] | Japanese title | 22 | | 0x340 | char[256] | English title | 23 | | 0x440 | char[256] | French title | 24 | | 0x540 | char[256] | German title | 25 | | 0x640 | char[256] | Italian title | 26 | | 0x740 | char[256] | Spanish title | 27 | | 0x840 | char[256] | (Version >= 0.1) Chinese title | 28 | | 0x940 | char[256] | (Version >= 0.2) Korean title | 29 | | 0xA40 | byte[0x800] | Reserved | 30 | | 0x1240 | Pixel[8192] | 4 bpp indexed tiled pixels animated bitmap 0..7 | 31 | | 0x2240 | Color[128] | 16 x 8 BGR-555 colors | 32 | | 0x2340 | byte[0x80] | [Animation sequence](#animated-icon) | 33 | 34 | The encoding for the title text is UTF-16. Titles may be empty, filled with 35 | `0x00`. 36 | 37 | ## Icon format 38 | 39 | The icon is an indexed bitmap of 32 x 32 pixels. The format is 4 bpp, each pixel 40 | byte points to two pixel colors in the palette (4-bits for each pixel). The 41 | pixels follow a tile order. This means they are not lineal, but each block of 64 42 | pixels defines a 8x8 pixels block of the image. 43 | 44 | The palette colors have the format `BGR555`. Each color is defined in an 45 | unsigned 16-bits values. There are 5-bits per component (higher bit is unused). 46 | 47 | ## Animated icon 48 | 49 | The animation sequence defines each frame of the animation. It points to one of 50 | the 8 bitmaps and one of the 8 palettes. It also defines the duration of the 51 | frame. The animation sequence are 16-bits values defined as: 52 | 53 | | Bits | Description | 54 | | ----- | ----------------------------- | 55 | | 0-7 | Frame duration in 60 Hz units | 56 | | 8-10 | Bitmap index | 57 | | 11-13 | Palette index | 58 | | 14 | Horizontal flip | 59 | | 15 | Vertical flip | 60 | -------------------------------------------------------------------------------- /docs/articles/specs/cartridge/cartridge.md: -------------------------------------------------------------------------------- 1 | # Cartridge format 2 | 3 | The binary cartridge format (also known as `SRL` or _ROM_) is the way to pack 4 | the information and files for a DS and DSi program. This is the format that 5 | physical cartridge carts have, but also the format used by digital content like 6 | DSiWare and utility programs from the DSi firmware. 7 | 8 | > [!NOTE] 9 | > These documents refer to DS/DSi programs. This also includes games. 10 | 11 | ## Cartridge memory map 12 | 13 | The raw binary format of the cartridge is based on four different regions. It 14 | maps the different commands we need to send to the cartridge chip to retrieve 15 | the data: 16 | 17 | | Offset | Length | Description | 18 | | --------- | ------ | --------------------------------------------------- | 19 | | 0x00 | 0x1000 | Header | 20 | | 0x1000 | 0x3000 | Unknown, not readable | 21 | | 0x4000 | 0x8000 | Secure area, first 2 KB with _KEY1_ encryption | 22 | | 0x8000 | ... | Data area | 23 | | 0xZZ00000 | 0x3000 | Unknown, not readable | 24 | | 0xZZ03000 | 0x4000 | ARM9i secure area, usually with modcrypt encryption | 25 | | 0xZZ07000 | ... | DSi data area | 26 | 27 | > [!NOTE] 28 | > For details in _KEY1_ and modcrypt encryptions see section 29 | > [security](security.md). 30 | 31 | ## DS / DSi program sections 32 | 33 | Practically speaking, official DS / DSi program maps these memory map areas to 34 | the following sections: 35 | 36 | - [Header](header.md) 37 | - [Unknown, not readable](#unknown-regions), usually `0x00` 38 | - [ARM9 processor program](program.md) (actual program code): 39 | - Program code: starts at `0x4000` so first 2 KB are _KEY1_ encrypted. 40 | - [Program extended parameters](program.md#program-parameters) (DS only) 41 | - [Overlay table info](program.md#overlay-information-table) 42 | - [Overlay files](program.md#overlays) 43 | - [ARM7 processor program](program.md) (system code): 44 | - Program code 45 | - [Overlay table info](program.md#overlay-information-table) 46 | - [Overlay files](program.md#overlays) (usually empty) 47 | - [File name table](filesystem.md#file-name-table) 48 | - [File access table](filesystem.md#file-access-table) 49 | - [Banner](banner.md) 50 | - Files data 51 | - Additionally, for DSi programs: 52 | - [Digest HMACs](security.md) 53 | - [Unknown, not readable](#unknown-regions) 54 | - [ARM9i program](program.md) 55 | - [ARM7i program](program.md) 56 | 57 | ### Padding 58 | 59 | Every section, including each file data is padded to blocks of 512 bytes with 60 | the byte `0xFF`. 61 | 62 | On DS programs, the last file is not padded. 63 | 64 | A DS program region (nitro) ends after the _digest HMACs_. The additional DSi 65 | program region (twilight) starts with the _unknown region_. Between these two 66 | regions there is a special padding to fill the last block of 1 MB with the byte 67 | `0xFF`. 68 | 69 | ### Unknown regions 70 | 71 | The header contains `0x3000` unknown bytes. These bytes cannot be read as the 72 | cartridge protocol does not support getting data from this address. On digital 73 | programs like firmware utilities (like the launcher), this area seems to have 74 | random bytes. 75 | 76 | This is similar to what happen at the DSi program region. It starts with 77 | `0x3000` unknown bytes. These bytes cannot be dumped because the cartridge 78 | protocol silently redirect any read command from these region to `0x8000`. These 79 | means that if we try to dump these `0x3000` bytes, we will get from the physical 80 | cart three times `0x1000` bytes of data from `0x8000` (arm9 after secure area). 81 | -------------------------------------------------------------------------------- /docs/articles/specs/cartridge/filesystem.md: -------------------------------------------------------------------------------- 1 | # Cartridge file system format 2 | 3 | The cartridge format contains a file system with a files and directories to 4 | define the boundaries of the data area. Files and directory have also a name and 5 | ID that the game uses to access. 6 | 7 | The file system is defined in two sections: 8 | 9 | - _File Name Table_ (FNT): defines the hierarchy of directories and files and 10 | their name. 11 | - _File Access Table_ (FAT): defines the offset and length of each file. 12 | 13 | ## File Access Table 14 | 15 | The start and length of this section is defined in the 16 | [cartridge header](header.md) at `0x48` and `0x4C`. 17 | 18 | The table consists of a set of start and end offset for each file. This also 19 | includes the overlays for ARM9 and ARM7 but it does not define the offsets for 20 | the ARM9/ARM7 programs (defined in the header). 21 | 22 | The access of the table is by ID defined in the 23 | [File Name Table](#file-name-table) 24 | 25 | For each file, there is: 26 | 27 | | Offset | Format | Description | 28 | | ------ | ------ | -------------------------- | 29 | | 0x00 | uint | Start position of the file | 30 | | 0x04 | uint | End position of the file | 31 | 32 | Tips: 33 | 34 | - Dividing the section length by 8 we can get the total number of files. 35 | - Subtracting the second value to the first one we can get the file length. 36 | - File start offsets have a padding to the cartridge padding block: 512 bytes. 37 | The padding byte in the file data content is `0xFF`. 38 | 39 | ## File Name Table 40 | 41 | The start and length of this section is defined in the 42 | [cartridge header](header.md) at `0x40` and `0x44`. 43 | 44 | The section actually contains two tables: 45 | 46 | - Directory definition table: defines the hierarchy between directories and its 47 | files 48 | - Name entry table: defines the names for files and directories. 49 | 50 | ### Directory definition table 51 | 52 | There are 8 bytes per directory. The entries are sorted by directory ID. To get 53 | the information for the directory ID `0xFnnn`, we would go to 54 | `fnt_offset + ((dir_id & 0x0FFF) * 8)`. 55 | 56 | | Offset | Format | Description | 57 | | ------ | ------ | ------------------------------------------------------- | 58 | | 0x00 | uint | Name entry relative offset for directory and file names | 59 | | 0x04 | ushort | First file ID for the directory, or 0 if empty. | 60 | | 0x06 | ushort | Directory parent ID or number of directories for root | 61 | 62 | ### Name entry table 63 | 64 | The section is a sequence of names with metadata with no random order access. 65 | After finding the directory in the 66 | [directory table](#directory-definition-table), jump to this section with the 67 | relative offset and keep reading names until finding an empty token (`0x00`). 68 | 69 | The format of the entries is: 70 | 71 | | Offset | Format | Description | 72 | | ------ | ------ | ----------------------------------------- | 73 | | 0x00 | byte | Node type (see below) | 74 | | 0x01 | string | Name | 75 | | .. | ushort | Only for directories, ID of the directory | 76 | 77 | The _node type_ byte is defined as: 78 | 79 | - Bit 7: if set, the next bytes apply to a directory, otherwise it's a file. 80 | - Bits 0-6: length of the name 81 | 82 | The name of the files are in order. This means that the first file name is for 83 | the file ID defined in the second field of the 84 | [directory table](#directory-definition-table). 85 | 86 | After every file and directory names of the current directory entry, the byte 87 | `0x00` must be present. 88 | 89 | The text encoding for the name is Shift-JIS. As it's a Japanese encoding, names 90 | may contain Japanese characters and cannot contain any European (Latin-1) or 91 | other non-English language characters. 92 | 93 | ## Files ID and order 94 | 95 | The files ID are assigned in order following a _depth-first_ recursion of the 96 | file system hierarchy. The overlays for ARM9 (if any) must always be the first 97 | ones, following the ID defined in the overlay table, but their file data is 98 | written in a different section. 99 | 100 | The order of the file data in the data area seems to not follow any pattern. It 101 | does not follow the order of the file IDs. Most likely the official SDK was able 102 | to do incremental builds and new files during development were being placed at 103 | the end each time. 104 | 105 | > [!TIP] 106 | > In order to create small patches, tools should try to replicate the original 107 | > order of the files. This is the case of _Ekona_, it adds the tag 108 | > `scenegate.ekona.physical_id` to each file node. 109 | -------------------------------------------------------------------------------- /docs/articles/specs/toc.yml: -------------------------------------------------------------------------------- 1 | - name: 📁 Cartridge 2 | - name: Cartridge 3 | href: cartridge/cartridge.md 4 | - name: Header 5 | href: cartridge/header.md 6 | - name: File system 7 | href: cartridge/filesystem.md 8 | - name: Banner 9 | href: cartridge/banner.md 10 | - name: Programs (armX) 11 | href: cartridge/program.md 12 | - name: Security 13 | href: cartridge/security.md 14 | -------------------------------------------------------------------------------- /docs/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "files": [ 7 | "Ekona/*.csproj" 8 | ], 9 | "src": "../src" 10 | } 11 | ], 12 | "dest": "api", 13 | "includePrivateMembers": false, 14 | "disableGitFeatures": false, 15 | "disableDefaultFilter": false, 16 | "noRestore": false, 17 | "namespaceLayout": "flattened", 18 | "memberLayout": "samePage", 19 | "EnumSortOrder": "alphabetic", 20 | "allowCompilationErrors": false 21 | } 22 | ], 23 | "build": { 24 | "content": [ 25 | { 26 | "files": [ 27 | "api/**.yml", 28 | "api/index.md" 29 | ] 30 | }, 31 | { "files": "**/*.{md,yml}", "src": "articles", "dest": "docs" }, 32 | { "files": [ "toc.yml", "*.md" ] } 33 | ], 34 | "resource": [ 35 | { 36 | "files": [ "**/images/**", "**/resources/**" ], 37 | "exclude": [ "_site/**", "obj/**" ] 38 | } 39 | ], 40 | "output": "_site", 41 | "globalMetadata": { 42 | "_appTitle": "Ekona, SceneGate library for DS and DSi formats", 43 | "_appName": "Ekona", 44 | "_appFooter": "Copyright © 2021 SceneGate.
Generated by DocFX using Material (Oscar Vásquez) and Mathew (Mathew Sachin) templates.
", 45 | "_appLogoPath": "images/logo_48.png", 46 | "_appFaviconPath": "images/favicon.png", 47 | "_enableSearch": true, 48 | "_enableNewTab": true, 49 | "_lang": "en" 50 | }, 51 | "fileMetadataFiles": [], 52 | "template": [ 53 | "default", 54 | "modern", 55 | "template" 56 | ], 57 | "postProcessors": [], 58 | "keepFileLink": false, 59 | "disableGitFeatures": false, 60 | "sitemap": { 61 | "baseUrl": "https://scenegate.github.io/Ekona/", 62 | "priority": 0.5, 63 | "changefreq": "monthly" 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /docs/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SceneGate/Ekona/5864da78cb3bf45df8307476afd7acf542436871/docs/images/favicon.png -------------------------------------------------------------------------------- /docs/images/logo_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SceneGate/Ekona/5864da78cb3bf45df8307476afd7acf542436871/docs/images/logo_128.png -------------------------------------------------------------------------------- /docs/images/logo_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SceneGate/Ekona/5864da78cb3bf45df8307476afd7acf542436871/docs/images/logo_48.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Ekona [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://choosealicense.com/licenses/mit/) 2 | 3 | _Ekona_ is a library part of the [_SceneGate_](https://github.com/SceneGate) 4 | framework that provides support for DS and DSi file formats. 5 | 6 | ## Supported formats 7 | 8 | - :video_game: DS cartridge: 9 | - :file_folder: Filesystem: read and write 10 | - :information_source: Header: read and write, including extended header 11 | - :framed_picture: Banner and icon: read and write. 12 | - :closed_lock_with_key: ARM9 secure area encryption and decryption (KEY1). 13 | - :video_game: DSi cartridge: 14 | - :file_folder: Filesystem: read and write `arm9i` and `arm7i` programs. 15 | - :information_source: Extended header: read and write 16 | - :framed_picture: Animated banner icons 17 | - :closed_lock_with_key: Modcrypt encryption and decryption 18 | - :lock_with_ink_pen: HMAC validation and generation when keys are provided. 19 | - :lock_with_ink_pen: Signature validation when keys are provided. 20 | 21 | ## Getting started 22 | 23 | Check-out the 24 | [getting started guide](https://scenegate.github.io/Ekona/docs/dev/tutorial.html) 25 | to start using _Ekona_ in no time! Below you can find an example that shows how 26 | to open a DS/DSi ROM file (cartridge dump). 27 | 28 | ```csharp 29 | // Create Yarhl node from a file (binary format). 30 | Node game = NodeFactory.FromFile("game.nds", FileOpenMode.Read); 31 | 32 | // Use the `Binary2NitroRom` converter to convert the binary format 33 | // into node containers (virtual file system tree with files and directories). 34 | game.TransformWith(); 35 | 36 | // And it's done! 37 | // Now we can access to every game file. For instance, we can export one file 38 | Node items = Navigator.SearchNode(game, "data/Items.dat"); 39 | items.Stream.WriteTo("dump/Items.dat"); 40 | ``` 41 | 42 | ## Usage 43 | 44 | The project provides the following .NET libraries (NuGet packages in nuget.org). 45 | The libraries works on supported versions of .NET: 6.0 and 8.0. 46 | 47 | - [![SceneGate.Ekona](https://img.shields.io/nuget/v/SceneGate.Ekona?label=SceneGate.Ekona&logo=nuget)](https://www.nuget.org/packages/SceneGate.Ekona) 48 | - `SceneGate.Ekona.Containers.Rom`: DS and DSi cartridge (ROM) format. 49 | - `SceneGate.Ekona.Security`: hash and encryption algorithms 50 | 51 | ## Special thanks 52 | 53 | The DS / DSi cartridge format was based on the amazing reverse engineering work 54 | of Martin Korth at [GBATek](https://problemkaputt.de/gbatek.htm). Its 55 | specifications of the hardware of the video controller and I/O ports was also a 56 | great help in additional reverse engineering. 57 | -------------------------------------------------------------------------------- /docs/template/public/main.css: -------------------------------------------------------------------------------- 1 | /* Changing the site font */ 2 | @import url("https://fonts.googleapis.com/css2?family=Nunito:wght@100;400;700&display=swap"); 3 | @import url("https://fonts.googleapis.com/css2?family=Fira Code&display=swap"); 4 | 5 | :root { 6 | --bs-font-sans-serif: "Nunito"; 7 | --bs-font-monospace: "Fira Code"; 8 | } 9 | 10 | /* Hide breadcrum bar on large screen as it only links to itself */ 11 | @media (min-width: 768px) { 12 | .actionbar { 13 | display: none !important; 14 | } 15 | } 16 | 17 | /* Give more space for section separation in a doc */ 18 | h2 { 19 | margin-top: 2rem !important; 20 | } 21 | 22 | /* Improve TOC with a line for categories (entries without link) */ 23 | .toc span.name-only { 24 | border-bottom-color: var(--bs-tertiary-color) !important; 25 | border-bottom-width: 2px !important; 26 | border-bottom-style: solid !important; 27 | margin-bottom: 0 !important; 28 | margin-top: 0.6rem !important; 29 | } 30 | 31 | .toc span.name-only:first() { 32 | margin-top: 0.4rem !important; 33 | } 34 | -------------------------------------------------------------------------------- /docs/template/public/main.js: -------------------------------------------------------------------------------- 1 | export default { 2 | iconLinks: [ 3 | { 4 | icon: "github", 5 | href: "https://github.com/SceneGate/Ekona", 6 | title: "GitHub", 7 | }, 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /docs/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Dev guides 2 | href: articles/dev/ 3 | 4 | - name: Format specs 5 | href: articles/specs/ 6 | 7 | - name: Changelog 8 | href: articles/changelog.md 9 | 10 | - name: GitHub 11 | href: https://github.com/SceneGate/Ekona 12 | -------------------------------------------------------------------------------- /src/.idea/.idea.Ekona/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /modules.xml 6 | /contentModel.xml 7 | /projectSettingsUpdater.xml 8 | /.idea.Ekona.iml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /src/.idea/.idea.Ekona/.idea/.name: -------------------------------------------------------------------------------- 1 | Ekona -------------------------------------------------------------------------------- /src/.idea/.idea.Ekona/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ../../Ekona 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/.idea/.idea.Ekona/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | SceneGate 4 | scenegate 5 | None 6 | Copyright (C) 2021 SceneGate 7 | 8 | true 9 | 10 | 12 | false 13 | 14 | 15 | 16 | MIT 17 | https://scenegate.github.io/Ekona/ 18 | https://github.com/SceneGate/Ekona 19 | icon.png 20 | README.md 21 | reverse-engineering;scenegate;nds 22 | 23 | 24 | 25 | 26 | 27 | true 28 | 29 | 30 | true 31 | 32 | 38 | embedded 39 | 40 | 41 | true 42 | true 43 | 44 | 45 | 46 | 47 | true 48 | true 49 | latest 50 | true 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Ekona.Examples/Cartridge.cs: -------------------------------------------------------------------------------- 1 | using SceneGate.Ekona.Containers.Rom; 2 | using SceneGate.Ekona.Security; 3 | using Texim.Animations; 4 | using Texim.Formats; 5 | using Yarhl.FileFormat; 6 | using Yarhl.FileSystem; 7 | using Yarhl.IO; 8 | 9 | namespace SceneGate.Ekona.Examples; 10 | 11 | public class Cartridge 12 | { 13 | public void DecryptEncryptArm9(Node rom, DsiKeyStore keyStore) 14 | { 15 | #region DecryptEncryptArm9 16 | var programInfo = Navigator.SearchNode(rom, "system/info").GetFormatAs(); 17 | var key1Encryption = new NitroKey1Encryption(programInfo.GameCode, keyStore); 18 | 19 | DataStream arm9 = Navigator.SearchNode(rom, "system/arm9").Stream!; 20 | bool isEncrypted = key1Encryption.HasEncryptedArm9(arm9); 21 | if (isEncrypted) { 22 | DataStream decryptedArm9 = key1Encryption.DecryptArm9(arm9); 23 | } else { 24 | DataStream encryptedArm9 = key1Encryption.EncryptArm9(arm9); 25 | } 26 | #endregion 27 | } 28 | 29 | public void ExportGif(Node rom) 30 | { 31 | #region ExportIconGif 32 | NodeContainerFormat animated = Navigator.SearchNode(rom, "system/banner/animated") 33 | .GetFormatAs(); 34 | 35 | AnimatedFullImage animatedImage = new IconAnimation2AnimatedImage().Convert(animated); 36 | using BinaryFormat gifData = new AnimatedFullImage2Gif().Convert(animatedImage); 37 | 38 | gifData.Stream.WriteTo("icon.gif"); 39 | #endregion 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Ekona.Examples/Ekona.Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SceneGate.Ekona.Examples 5 | Examples for the documentation. 6 | 7 | net8.0 8 | 9 | SceneGate.Ekona.Examples 10 | false 11 | enable 12 | disable 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Ekona.Examples/QuickStart.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using SceneGate.Ekona.Containers.Rom; 21 | using Texim.Animations; 22 | using Texim.Formats; 23 | using Texim.Images; 24 | using Yarhl.FileFormat; 25 | using Yarhl.FileSystem; 26 | using Yarhl.IO; 27 | using Yarhl.Media.Text; 28 | 29 | namespace SceneGate.Ekona.Examples; 30 | 31 | public class QuickStart 32 | { 33 | public void OpenWriteGame(string gameFilePath) 34 | { 35 | #region OpenGame 36 | // Create node from file with binary format. 37 | Node game = NodeFactory.FromFile(gameFilePath, FileOpenMode.Read); 38 | 39 | // Use the `Binary2NitroRom` converter to convert the binary format 40 | // into node containers (virtual file system tree). 41 | game.TransformWith(); 42 | 43 | // And it's done! 44 | // Now we can access to every game file. For instance, we can export one file 45 | Node gameFile = game.Children["data"].Children["Items.dat"]; 46 | gameFile.Stream.WriteTo("dump/items.dat"); 47 | #endregion 48 | 49 | #region ModifyFile 50 | // Read the file by converting from binary format into PO (translation format) 51 | Node itemsFile = Navigator.SearchNode(game, "data/Items.dat"); 52 | Po items = itemsFile.TransformWith().GetFormatAs(); 53 | 54 | // Let's modify the first entry 55 | items.Entries[0].Translated = "Hello world!"; 56 | 57 | // Convert back from PO format into binary (write into a new memory stream) 58 | itemsFile.TransformWith(); 59 | #endregion 60 | 61 | #region WriteGame 62 | game.TransformWith(); 63 | game.Stream.WriteTo("output/new_game.nds"); 64 | #endregion 65 | } 66 | 67 | public void AccessHeaderInfo(string gameFilePath) 68 | { 69 | #region HeaderInfo 70 | Node game = NodeFactory.FromFile(gameFilePath, FileOpenMode.Read) 71 | .TransformWith(); 72 | 73 | ProgramInfo info = game.Children["system"].Children["info"] 74 | .GetFormatAs(); 75 | 76 | Console.WriteLine($"Game title: {info.GameTitle} [{info.GameCode}]"); 77 | #endregion 78 | 79 | #region BannerTitle 80 | Banner bannerInfo = Navigator.SearchNode(game, "system/banner/info").GetFormatAs(); 81 | Console.WriteLine($"Japanese title: {bannerInfo.JapaneseTitle}"); 82 | Console.WriteLine($"English title: {bannerInfo.EnglishTitle}"); 83 | #endregion 84 | 85 | #region ExportIcon 86 | IndexedPaletteImage icon = Navigator.SearchNode(game, "system/banner/icon") 87 | .GetFormatAs(); 88 | 89 | // Using Texim converters, create a PNG image 90 | var converterParameters = new IndexedImageBitmapParams { Palettes = icon }; 91 | var bitmapConverter = new IndexedImage2Bitmap(converterParameters); 92 | 93 | using BinaryFormat binaryPng = bitmapConverter.Convert(icon); 94 | binaryPng.Stream.WriteTo("dump/icon.png"); 95 | 96 | // For DSi-enhanced games we can export its animated icon as GIF 97 | if (bannerInfo.SupportAnimatedIcon) { 98 | var animatedNode = Navigator.SearchNode(game, "system/banner/animated") 99 | .GetFormatAs(); 100 | 101 | var animatedImage = new IconAnimation2AnimatedImage().Convert(animatedNode); 102 | using var binaryGif = new AnimatedFullImage2Gif().Convert(animatedImage); 103 | binaryGif.Stream.WriteTo("dump/icon.gif"); 104 | } 105 | #endregion 106 | } 107 | 108 | private sealed class BinaryItems2Po : IConverter 109 | { 110 | public Po Convert(IBinary source) => new Po(); 111 | } 112 | 113 | private sealed class Po2BinaryItems : IConverter 114 | { 115 | public BinaryFormat Convert(Po source) => new BinaryFormat(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Ekona.PerformanceTests/Binary2NitroRomTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using BenchmarkDotNet.Attributes; 21 | using SceneGate.Ekona.Containers.Rom; 22 | using SceneGate.Ekona.Security; 23 | using Yarhl.FileFormat; 24 | using Yarhl.IO; 25 | 26 | namespace SceneGate.Ekona.PerformanceTests; 27 | 28 | public class Binary2NitroRomTest 29 | { 30 | private BinaryFormat binaryRom = null!; 31 | private DsiKeyStore keyStore = null!; 32 | private NitroRom root = null!; 33 | private DataStream outputStream = null!; 34 | 35 | public static IEnumerable RomPaths => ResourceManager.GetRoms(); 36 | 37 | [ParamsAllValues] 38 | public bool UseKeys { get; set; } 39 | 40 | [ParamsSource(nameof(RomPaths))] 41 | public FilePathInfo RomPath { get; set; } = null!; 42 | 43 | [GlobalSetup] 44 | public void Setup() 45 | { 46 | keyStore = ResourceManager.GetDsiKeyStore(); 47 | binaryRom = new BinaryFormat(DataStreamFactory.FromFile(RomPath.Path, FileOpenMode.Read)); 48 | root = binaryRom.ConvertWith(new Binary2NitroRom()); 49 | outputStream = new DataStream(); 50 | } 51 | 52 | [GlobalCleanup] 53 | public void Cleanup() 54 | { 55 | root?.Dispose(); 56 | binaryRom?.Dispose(); 57 | outputStream?.Dispose(); 58 | } 59 | 60 | [Benchmark] 61 | public NitroRom ReadRom() 62 | { 63 | var converter = UseKeys ? new Binary2NitroRom() : new Binary2NitroRom(keyStore); 64 | return converter.Convert(binaryRom); 65 | } 66 | 67 | [Benchmark] 68 | public BinaryFormat WriteRom() 69 | { 70 | DsiKeyStore? runKeys = UseKeys ? keyStore : null; 71 | var parameters = new NitroRom2BinaryParams { 72 | KeyStore = runKeys, 73 | OutputStream = outputStream, 74 | }; 75 | 76 | var converter = new NitroRom2Binary(parameters); 77 | return converter.Convert(root); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Ekona.PerformanceTests/Ekona.PerformanceTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | Ekona.PerformanceTests 6 | Tests for Ekona. 7 | 8 | net8.0 9 | 10 | SceneGate.Ekona.PerformanceTests 11 | false 12 | enable 13 | enable 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Ekona.PerformanceTests/FilePathInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | namespace SceneGate.Ekona.PerformanceTests; 21 | 22 | public class FilePathInfo 23 | { 24 | public FilePathInfo(string path) => Path = path ?? throw new ArgumentNullException(nameof(path)); 25 | 26 | public string Path { get; set; } 27 | 28 | public override string ToString() => System.IO.Path.GetFileName(Path); 29 | } 30 | -------------------------------------------------------------------------------- /src/Ekona.PerformanceTests/NitroBlowfishTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using BenchmarkDotNet.Attributes; 21 | using SceneGate.Ekona.Security; 22 | using Yarhl.IO; 23 | 24 | namespace SceneGate.Ekona.PerformanceTests; 25 | 26 | [MemoryDiagnoser] 27 | public class NitroBlowfishTest 28 | { 29 | private readonly NitroBlowfish blowfish = new NitroBlowfish(); 30 | private readonly byte[] buffer = new byte[16 * 1024]; 31 | private byte[] key = null!; 32 | 33 | public int Level => 3; 34 | 35 | public int Modulo => 8; 36 | 37 | [GlobalSetup] 38 | public void Setup() 39 | { 40 | var random = new Random(); 41 | key = new byte[NitroBlowfish.KeyLength]; 42 | random.NextBytes(key); 43 | } 44 | 45 | [Benchmark] 46 | [Arguments(8)] 47 | [Arguments(2 * 1024)] 48 | public void EncryptArray(int dataLength) 49 | { 50 | var data = buffer[..dataLength]; 51 | 52 | blowfish.Initialize("YYYY", Level, Modulo, key); 53 | blowfish.Encrypt(data); 54 | } 55 | 56 | [Benchmark] 57 | [Arguments(8)] 58 | [Arguments(2 * 1024)] 59 | public void EncryptStream(int dataLength) 60 | { 61 | using var stream = DataStreamFactory.FromArray(buffer, 0, dataLength); 62 | 63 | blowfish.Initialize("YYYY", Level, Modulo, key); 64 | blowfish.Encrypt(stream); 65 | } 66 | 67 | [Benchmark] 68 | public void Encrypt64Bits() 69 | { 70 | uint data0 = 0x89ABCDEF; 71 | uint data1 = 0x01234567; 72 | 73 | blowfish.Initialize("YYYY", Level, Modulo, key); 74 | blowfish.Encrypt(ref data0, ref data1); 75 | } 76 | 77 | [Benchmark] 78 | public void Initialization() 79 | { 80 | blowfish.Initialize("YYYY", Level, Modulo, key); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Ekona.PerformanceTests/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using BenchmarkDotNet.Running; 21 | 22 | namespace SceneGate.Ekona.PerformanceTests; 23 | 24 | public static class Program 25 | { 26 | public static void Main(string[] args) => 27 | BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); 28 | } 29 | -------------------------------------------------------------------------------- /src/Ekona.PerformanceTests/ResourceManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using SceneGate.Ekona.Security; 21 | using YamlDotNet.Serialization; 22 | 23 | namespace SceneGate.Ekona.PerformanceTests; 24 | 25 | public static class ResourceManager 26 | { 27 | public static string ResourceDirectory { 28 | get { 29 | string? envVar = Environment.GetEnvironmentVariable("SCENEGATE_TEST_DIR"); 30 | if (string.IsNullOrEmpty(envVar)) { 31 | throw new InvalidOperationException("Missing environment variable"); 32 | } 33 | 34 | return envVar; 35 | } 36 | } 37 | 38 | public static DsiKeyStore GetDsiKeyStore() 39 | { 40 | string keysPath = Path.Combine(ResourceDirectory, "dsi_keys.yml"); 41 | if (!File.Exists(keysPath)) { 42 | throw new InvalidOperationException("Missing keys file"); 43 | } 44 | 45 | string keysYaml = File.ReadAllText(keysPath); 46 | return new DeserializerBuilder() 47 | .Build() 48 | .Deserialize(keysYaml); 49 | } 50 | 51 | public static IEnumerable GetRoms() 52 | { 53 | string containerDir = Path.Combine(ResourceDirectory, "Containers"); 54 | string filePath = Path.Combine(containerDir, "rom.txt"); 55 | if (!File.Exists(filePath)) { 56 | return Array.Empty(); 57 | } 58 | 59 | return File.ReadAllLines(filePath) 60 | .Select(line => line.Trim()) 61 | .Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith('#')) 62 | .Select(l => Path.Combine(containerDir, l.Split(",")[1])) 63 | .Select(p => new FilePathInfo(p)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Assertions/BinaryFormatAssertions.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using FluentAssertions; 21 | using FluentAssertions.Primitives; 22 | using Yarhl.IO; 23 | 24 | namespace SceneGate.Ekona.Tests.Assertions 25 | { 26 | public class BinaryFormatAssertions : 27 | ReferenceTypeAssertions 28 | { 29 | public BinaryFormatAssertions(BinaryFormat instance) 30 | : base(instance) 31 | { 32 | } 33 | 34 | protected override string Identifier => "binary"; 35 | 36 | [CustomAssertion] 37 | public AndConstraint MatchInfo(BinaryInfo info) 38 | { 39 | Subject.Stream.Should().MatchInfo(info); 40 | return new AndConstraint(this); 41 | } 42 | 43 | [CustomAssertion] 44 | public AndConstraint HaveSha256(string hash) 45 | { 46 | Subject.Stream.Should().HaveSha256(hash); 47 | return new AndConstraint(this); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Assertions/NodeAssertions.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using FluentAssertions; 21 | using FluentAssertions.Execution; 22 | using FluentAssertions.Primitives; 23 | using Yarhl.FileSystem; 24 | 25 | namespace SceneGate.Ekona.Tests.Assertions 26 | { 27 | public class NodeAssertions 28 | : ReferenceTypeAssertions 29 | { 30 | public NodeAssertions(Node instance) 31 | : base(instance) 32 | { 33 | } 34 | 35 | protected override string Identifier => "node"; 36 | 37 | [CustomAssertion] 38 | public AndConstraint MatchInfo(NodeContainerInfo info) 39 | { 40 | Subject.Name.Should().Be(info.Name); 41 | Subject.Format?.GetType().FullName.Should().Be(info.Format); 42 | 43 | if (info.Tags != null) { 44 | // YAML deserializer always gets the value as a string 45 | foreach (var entry in info.Tags) { 46 | Subject.Tags.Should().ContainKey(entry.Key); 47 | entry.Value.Should().Be(Subject.Tags[entry.Key].ToString()); 48 | } 49 | } 50 | 51 | if (info.Stream != null) { 52 | Subject.Stream.Should().MatchInfo(info.Stream); 53 | } 54 | 55 | int expectedCount = info.Children?.Count ?? 0; 56 | if (info.CheckChildren) { 57 | Subject.Children.Should().HaveCount(expectedCount); 58 | } 59 | 60 | for (int i = 0; i < expectedCount; i++) { 61 | NodeContainerInfo expectedNode = info.Children[i]; 62 | using (new AssertionScope(expectedNode.Name)) { 63 | Subject.Children.Should().Contain(n => n.Name == expectedNode.Name); 64 | Subject.Children[expectedNode.Name].Should().MatchInfo(expectedNode); 65 | } 66 | } 67 | 68 | return new AndConstraint(this); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Assertions/StreamAssertions.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | using System.IO; 22 | using System.Security.Cryptography; 23 | using FluentAssertions; 24 | using FluentAssertions.Execution; 25 | using FluentAssertions.Primitives; 26 | using Yarhl.IO; 27 | 28 | namespace SceneGate.Ekona.Tests.Assertions 29 | { 30 | public class StreamAssertions : 31 | ReferenceTypeAssertions 32 | { 33 | public StreamAssertions(Stream instance) 34 | : base(instance) 35 | { 36 | } 37 | 38 | protected override string Identifier => "stream"; 39 | 40 | [CustomAssertion] 41 | public AndConstraint MatchInfo(BinaryInfo info) 42 | { 43 | Subject.Should() 44 | .BeAssignableTo("We will compare its offset") 45 | .Which 46 | .Offset 47 | .Should().Be(info.Offset); 48 | Subject.Length.Should().Be(info.Length); 49 | 50 | if (!string.IsNullOrEmpty(info.Sha256)) { 51 | HaveSha256(info.Sha256); 52 | } 53 | 54 | return new AndConstraint(this); 55 | } 56 | 57 | [CustomAssertion] 58 | public AndConstraint HaveSha256(string hash) 59 | { 60 | Subject.Position = 0; 61 | using var sha256 = SHA256.Create(); 62 | sha256.ComputeHash(Subject); 63 | string actualHash = BitConverter.ToString(sha256.Hash) 64 | .Replace("-", string.Empty) 65 | .ToLowerInvariant(); 66 | 67 | Execute.Assertion 68 | .ForCondition(!string.IsNullOrEmpty(hash)) 69 | .FailWith("hash is empty") 70 | .Then 71 | .ForCondition(hash == actualHash) 72 | .FailWith("Expected {context} to have SHA-256 {0} but was {1}", hash, actualHash); 73 | 74 | return new AndConstraint(this); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Ekona.Tests/BinaryInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System.IO; 21 | using YamlDotNet.Serialization; 22 | using YamlDotNet.Serialization.NamingConventions; 23 | 24 | namespace SceneGate.Ekona.Tests 25 | { 26 | public class BinaryInfo 27 | { 28 | public long Offset { get; set; } 29 | 30 | public long Length { get; set; } 31 | 32 | public string Sha256 { get; set; } 33 | 34 | public static BinaryInfo FromYaml(string path) 35 | { 36 | string yaml = File.ReadAllText(path); 37 | return new DeserializerBuilder() 38 | .WithNamingConvention(UnderscoredNamingConvention.Instance) 39 | .Build() 40 | .Deserialize(yaml); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Containers/Rom/Binary2RomHeaderTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System.Collections.Generic; 21 | using System.IO; 22 | using System.Linq; 23 | using FluentAssertions; 24 | using NUnit.Framework; 25 | using SceneGate.Ekona.Containers.Rom; 26 | using SceneGate.Ekona.Security; 27 | using YamlDotNet.Serialization; 28 | using YamlDotNet.Serialization.NamingConventions; 29 | using Yarhl.FileFormat; 30 | using Yarhl.FileSystem; 31 | using Yarhl.IO; 32 | 33 | namespace SceneGate.Ekona.Tests.Containers.Rom 34 | { 35 | [TestFixture] 36 | public class Binary2RomHeaderTests 37 | { 38 | public static IEnumerable GetFiles() 39 | { 40 | string basePath = Path.Combine(TestDataBase.RootFromOutputPath, "Containers"); 41 | string listPath = Path.Combine(basePath, "header.txt"); 42 | return TestDataBase.ReadTestListFile(listPath) 43 | .Select(line => line.Split(',')) 44 | .Select(data => new TestCaseData( 45 | Path.Combine(basePath, data[0]), 46 | Path.Combine(basePath, data[1])) 47 | .SetName($"{{m}}({data[1]})")); 48 | } 49 | 50 | [TestCaseSource(nameof(GetFiles))] 51 | public void DeserializeRomHeaderMatchInfo(string infoPath, string headerPath) 52 | { 53 | TestDataBase.IgnoreIfFileDoesNotExist(infoPath); 54 | TestDataBase.IgnoreIfFileDoesNotExist(headerPath); 55 | 56 | string yaml = File.ReadAllText(infoPath); 57 | RomHeader expected = new DeserializerBuilder() 58 | .WithNamingConvention(UnderscoredNamingConvention.Instance) 59 | .Build() 60 | .Deserialize(yaml); 61 | 62 | using Node node = NodeFactory.FromFile(headerPath, FileOpenMode.Read); 63 | node.Invoking(n => n.TransformWith()).Should().NotThrow(); 64 | 65 | node.GetFormatAs().Should().BeEquivalentTo( 66 | expected, 67 | opts => opts 68 | .Excluding(p => p.CopyrightLogo) 69 | .Excluding((FluentAssertions.Equivalency.IMemberInfo info) => info.Type == typeof(NitroProgramCodeParameters)) 70 | .Excluding((FluentAssertions.Equivalency.IMemberInfo info) => info.Type == typeof(HashInfo))); 71 | } 72 | 73 | [TestCaseSource(nameof(GetFiles))] 74 | public void DeserializeRomHeaderWithoutKeysHasHashes(string infoPath, string headerPath) 75 | { 76 | TestDataBase.IgnoreIfFileDoesNotExist(infoPath); 77 | TestDataBase.IgnoreIfFileDoesNotExist(headerPath); 78 | 79 | using Node node = NodeFactory.FromFile(headerPath, FileOpenMode.Read); 80 | node.Invoking(n => n.TransformWith()).Should().NotThrow(); 81 | 82 | ProgramInfo programInfo = node.GetFormatAs().ProgramInfo; 83 | programInfo.ChecksumSecureArea.Status.Should().Be(HashStatus.NotValidated); 84 | programInfo.ChecksumLogo.Status.Should().Be(HashStatus.Valid); 85 | programInfo.ChecksumHeader.Status.Should().Be(HashStatus.Valid); 86 | 87 | bool isDsi = programInfo.UnitCode != DeviceUnitKind.DS; 88 | if (isDsi || programInfo.ProgramFeatures.HasFlag(DsiRomFeatures.NitroBannerSigned)) { 89 | programInfo.BannerMac.Should().NotBeNull(); 90 | programInfo.BannerMac.Status.Should().Be(HashStatus.NotValidated); 91 | } 92 | 93 | if (programInfo.ProgramFeatures.HasFlag(DsiRomFeatures.NitroProgramSigned)) { 94 | programInfo.NitroOverlaysMac.Should().NotBeNull(); 95 | programInfo.NitroOverlaysMac.Status.Should().Be(HashStatus.NotValidated); 96 | 97 | programInfo.NitroProgramMac.Should().NotBeNull(); 98 | programInfo.NitroProgramMac.Status.Should().Be(HashStatus.NotValidated); 99 | } 100 | 101 | if (isDsi || programInfo.ProgramFeatures.HasFlag(DsiRomFeatures.NitroProgramSigned)) { 102 | programInfo.Signature.Should().NotBeNull(); 103 | programInfo.Signature.Status.Should().Be(HashStatus.NotValidated); 104 | } 105 | 106 | if (isDsi) { 107 | programInfo.DsiInfo.Arm9SecureMac.Status.Should().Be(HashStatus.NotValidated); 108 | programInfo.DsiInfo.Arm7Mac.Status.Should().Be(HashStatus.NotValidated); 109 | programInfo.DsiInfo.DigestMain.Status.Should().Be(HashStatus.NotValidated); 110 | programInfo.DsiInfo.Arm9iMac.Status.Should().Be(HashStatus.NotValidated); 111 | programInfo.DsiInfo.Arm7iMac.Status.Should().Be(HashStatus.NotValidated); 112 | programInfo.DsiInfo.Arm9Mac.Status.Should().Be(HashStatus.NotValidated); 113 | } 114 | } 115 | 116 | [TestCaseSource(nameof(GetFiles))] 117 | public void TwoWaysIdenticalRomHeaderStream(string infoPath, string headerPath) 118 | { 119 | TestDataBase.IgnoreIfFileDoesNotExist(headerPath); 120 | 121 | using Node node = NodeFactory.FromFile(headerPath, FileOpenMode.Read); 122 | 123 | RomHeader header = node.GetFormatAs().ConvertWith(new Binary2RomHeader()); 124 | BinaryFormat generatedStream = header.ConvertWith(new RomHeader2Binary()); 125 | 126 | var originalStream = new DataStream(node.Stream!, 0, header.SectionInfo.HeaderSize); 127 | generatedStream.Stream.Length.Should().Be(originalStream.Length); 128 | generatedStream.Stream.Compare(originalStream).Should().BeTrue(); 129 | } 130 | 131 | [TestCaseSource(nameof(GetFiles))] 132 | public void ThreeWaysIdenticalRomHeaderObjects(string infoPath, string headerPath) 133 | { 134 | TestDataBase.IgnoreIfFileDoesNotExist(headerPath); 135 | 136 | using Node node = NodeFactory.FromFile(headerPath, FileOpenMode.Read); 137 | 138 | RomHeader originalHeader = node.GetFormatAs().ConvertWith(new Binary2RomHeader()); 139 | using BinaryFormat generatedStream = originalHeader.ConvertWith(new RomHeader2Binary()); 140 | RomHeader generatedHeader = generatedStream.ConvertWith(new Binary2RomHeader()); 141 | 142 | generatedHeader.Should().BeEquivalentTo( 143 | originalHeader, 144 | opts => opts.Excluding((FluentAssertions.Equivalency.IMemberInfo info) => info.Type == typeof(HashInfo))); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Containers/Rom/ProgramInfoTests.cs: -------------------------------------------------------------------------------- 1 | namespace SceneGate.Ekona.Tests.Containers.Rom; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | using SceneGate.Ekona.Containers.Rom; 9 | using SceneGate.Ekona.Security; 10 | 11 | [TestFixture] 12 | internal class ProgramInfoTests 13 | { 14 | [Test] 15 | public void HasValidHashesTrueIfNull() 16 | { 17 | var info = new ProgramInfo(); 18 | 19 | Assert.That(info.HasValidHashes(), Is.True); 20 | } 21 | 22 | [Test] 23 | public void HasValidHashesTrueIfGenerated() 24 | { 25 | var info = new ProgramInfo(); 26 | _ = CreateDsHashes(info, HashStatus.Generated); 27 | 28 | Assert.That(info.HasValidHashes(), Is.True); 29 | } 30 | 31 | [Test] 32 | public void HasValidHashesThrowsIfNotValidated() 33 | { 34 | var info = new ProgramInfo(); 35 | _ = CreateDsHashes(info, HashStatus.NotValidated); 36 | 37 | Assert.That(() => info.HasValidHashes(), Throws.InvalidOperationException); 38 | } 39 | 40 | [Test] 41 | public void HasValidHashesFalseIfAnyInvalid() 42 | { 43 | var info = new ProgramInfo(); 44 | HashInfo[] hashInfos = CreateDsHashes(info, HashStatus.Invalid); 45 | 46 | foreach (HashInfo hashInfo in hashInfos) { 47 | Assert.That(info.HasValidHashes(), Is.False); 48 | hashInfo.Status = HashStatus.Valid; 49 | } 50 | 51 | Assert.That(info.HasValidHashes(), Is.True); 52 | } 53 | 54 | [Test] 55 | public void HasValidHashesTrueIfAllValid() 56 | { 57 | var info = new ProgramInfo(); 58 | _ = CreateDsHashes(info, HashStatus.Valid); 59 | 60 | Assert.That(info.HasValidHashes(), Is.True); 61 | } 62 | 63 | [Test] 64 | public void HasValidHashesDsi() 65 | { 66 | var info = new ProgramInfo(); 67 | HashInfo[] hashInfos = CreateDsiHashes(info, HashStatus.Invalid); 68 | 69 | foreach (HashInfo hashInfo in hashInfos) { 70 | Assert.That(info.HasValidHashes(), Is.False); 71 | hashInfo.Status = HashStatus.Valid; 72 | } 73 | 74 | Assert.That(info.HasValidHashes(), Is.False); 75 | 76 | info.DsiInfo.DigestHashesStatus = HashStatus.Valid; 77 | Assert.That(info.HasValidHashes(), Is.True); 78 | } 79 | 80 | [Test] 81 | public void HasValidHashesDsExtended() 82 | { 83 | var info = new ProgramInfo(); 84 | HashInfo[] hashInfos = CreateDsExtendedHashes(info, HashStatus.Invalid); 85 | 86 | foreach (HashInfo hashInfo in hashInfos) { 87 | Assert.That(info.HasValidHashes(), Is.False); 88 | hashInfo.Status = HashStatus.Valid; 89 | } 90 | 91 | Assert.That(info.HasValidHashes(), Is.True); 92 | } 93 | 94 | [Test] 95 | public void HasSignatureForDsIsTrue() 96 | { 97 | var info = new ProgramInfo(); 98 | info.UnitCode = DeviceUnitKind.DS; 99 | 100 | Assert.That(info.HasValidSignature(), Is.True); 101 | 102 | info.Signature = CreateHashInfo(HashStatus.Invalid); 103 | Assert.That(info.HasValidSignature(), Is.True); 104 | } 105 | 106 | [Test] 107 | public void HasSignatureForDsi() 108 | { 109 | var info = new ProgramInfo(); 110 | info.UnitCode = DeviceUnitKind.DSiExclusive; 111 | 112 | // null signature 113 | Assert.That(info.HasValidSignature(), Is.True); 114 | 115 | info.Signature = CreateHashInfo(HashStatus.Generated); 116 | Assert.That(info.HasValidSignature(), Is.True); 117 | 118 | info.Signature.Status = HashStatus.Invalid; 119 | Assert.That(info.HasValidSignature(), Is.False); 120 | 121 | info.Signature.Status = HashStatus.Valid; 122 | Assert.That(info.HasValidSignature(), Is.True); 123 | 124 | info.Signature.Status = HashStatus.NotValidated; 125 | Assert.That(() => info.HasValidSignature(), Throws.InvalidOperationException); 126 | } 127 | 128 | private static HashInfo[] CreateDsHashes(ProgramInfo info, HashStatus status) 129 | { 130 | info.UnitCode = DeviceUnitKind.DS; 131 | 132 | #pragma warning disable S1121 // cool syntax for tests 133 | var hashInfos = new List { 134 | (info.ChecksumHeader = CreateHashInfo(status)), 135 | (info.ChecksumLogo = CreateHashInfo(status)), 136 | (info.ChecksumSecureArea = CreateHashInfo(status)), 137 | }; 138 | #pragma warning restore S1121 139 | 140 | return hashInfos.ToArray(); 141 | } 142 | 143 | private static HashInfo[] CreateDsExtendedHashes(ProgramInfo info, HashStatus status) 144 | { 145 | info.UnitCode = DeviceUnitKind.DS; 146 | 147 | info.ProgramFeatures = DsiRomFeatures.NitroProgramSigned | DsiRomFeatures.NitroBannerSigned; 148 | 149 | #pragma warning disable S1121 // cool syntax for tests 150 | var hashInfos = new List { 151 | (info.ChecksumHeader = CreateHashInfo(status)), 152 | (info.ChecksumLogo = CreateHashInfo(status)), 153 | (info.ChecksumSecureArea = CreateHashInfo(status)), 154 | 155 | (info.BannerMac = CreateHashInfo(status)), 156 | 157 | (info.NitroProgramMac = CreateHashInfo(status)), 158 | (info.NitroOverlaysMac = CreateHashInfo(status)), 159 | }; 160 | #pragma warning restore S1121 161 | 162 | return hashInfos.ToArray(); 163 | } 164 | 165 | private static HashInfo[] CreateDsiHashes(ProgramInfo info, HashStatus status) 166 | { 167 | info.UnitCode = DeviceUnitKind.DSiExclusive; 168 | info.DsiInfo ??= new DsiProgramInfo(); 169 | 170 | info.DsiInfo.DigestHashesStatus = status; 171 | 172 | #pragma warning disable S1121 // cool syntax for tests 173 | var hashInfos = new List { 174 | (info.ChecksumHeader = CreateHashInfo(status)), 175 | (info.ChecksumLogo = CreateHashInfo(status)), 176 | (info.ChecksumSecureArea = CreateHashInfo(status)), 177 | 178 | (info.BannerMac = CreateHashInfo(status)), 179 | 180 | (info.DsiInfo.Arm9SecureMac = CreateHashInfo(status)), 181 | (info.DsiInfo.Arm7Mac = CreateHashInfo(status)), 182 | (info.DsiInfo.DigestMain = CreateHashInfo(status)), 183 | (info.DsiInfo.Arm9iMac = CreateHashInfo(status)), 184 | (info.DsiInfo.Arm7iMac = CreateHashInfo(status)), 185 | (info.DsiInfo.Arm9Mac = CreateHashInfo(status)), 186 | }; 187 | #pragma warning restore S1121 188 | 189 | return hashInfos.ToArray(); 190 | } 191 | 192 | private static HashInfo CreateHashInfo(HashStatus status) 193 | { 194 | return new HashInfo("TestAlgo", new byte[] { 0x42 }) { 195 | Status = status, 196 | }; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Ekona.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | SceneGate.Ekona.Tests 5 | Tests for Ekona. 6 | 7 | net6.0;net8.0 8 | 9 | SceneGate.Ekona.Tests 10 | false 11 | disable 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/Ekona.Tests/NodeContainerInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System.Collections.Generic; 21 | using System.Collections.ObjectModel; 22 | using System.IO; 23 | using YamlDotNet.Serialization; 24 | using YamlDotNet.Serialization.NamingConventions; 25 | 26 | namespace SceneGate.Ekona.Tests 27 | { 28 | public class NodeContainerInfo 29 | { 30 | public string Name { get; set; } 31 | 32 | public string Format { get; set; } 33 | 34 | public BinaryInfo Stream { get; set; } 35 | 36 | public Dictionary Tags { get; set; } 37 | 38 | public bool CheckChildren { get; set; } 39 | 40 | public Collection Children { get; set; } 41 | 42 | public static NodeContainerInfo FromYaml(string path) 43 | { 44 | string yaml = File.ReadAllText(path); 45 | return new DeserializerBuilder() 46 | .WithNamingConvention(UnderscoredNamingConvention.Instance) 47 | .Build() 48 | .Deserialize(yaml); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Resources/.gitignore: -------------------------------------------------------------------------------- 1 | *.nds 2 | *.banner 3 | *.png 4 | *.yml 5 | *.gif 6 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Resources/Containers/banner.txt: -------------------------------------------------------------------------------- 1 | PSL_VPYJ2P_00.banner 2 | METALMAX3_BM9JQC_00.banner 3 | NINOKUNI_B2KJHF_00.banner 4 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Resources/Containers/header.txt: -------------------------------------------------------------------------------- 1 | PSL_VPYJ2P_00.header.yml,PSL_VPYJ2P_00.nds 2 | NINOKUNI_B2KJHF_00.header.yml,NINOKUNI_B2KJHF_00.nds 3 | METALMAX3_BM9JQC_00.header.yml,METALMAX3_BM9JQC_00.nds 4 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Resources/Containers/rom.txt: -------------------------------------------------------------------------------- 1 | PSL_VPYJ2P_00.yml,PSL_VPYJ2P_00.nds 2 | METALMAX3_BM9JQC_00.yml,METALMAX3_BM9JQC_00.nds 3 | NINOKUNI_B2KJHF_00.yml,NINOKUNI_B2KJHF_00.nds 4 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Security/NitroBlowfishTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | using FluentAssertions; 22 | using NUnit.Framework; 23 | using SceneGate.Ekona.Security; 24 | using Yarhl.IO; 25 | 26 | namespace SceneGate.Ekona.Tests.Security; 27 | 28 | [TestFixture] 29 | public class NitroBlowfishTest 30 | { 31 | [Test] 32 | public void InitializationGuards() 33 | { 34 | byte[] keysValid = new byte[NitroBlowfish.KeyLength]; 35 | byte[] keysInvalid = new byte[8]; 36 | 37 | var blowfish = new NitroBlowfish(); 38 | blowfish.Invoking(b => b.Initialize(null, 2, 8, keysValid)).Should().ThrowExactly(); 39 | blowfish.Invoking(b => b.Initialize(string.Empty, 2, 8, keysValid)).Should().ThrowExactly(); 40 | blowfish.Invoking(b => b.Initialize("AAA", 2, 8, keysValid)).Should().ThrowExactly(); 41 | blowfish.Invoking(b => b.Initialize("AAAAA", 2, 8, keysValid)).Should().ThrowExactly(); 42 | blowfish.Invoking(b => b.Initialize("AAAA", 0, 8, keysValid)).Should().ThrowExactly(); 43 | blowfish.Invoking(b => b.Initialize("AAAA", 4, 8, keysValid)).Should().ThrowExactly(); 44 | blowfish.Invoking(b => b.Initialize("AAAA", 2, 0, keysValid)).Should().ThrowExactly(); 45 | blowfish.Invoking(b => b.Initialize("AAAA", 2, 16, keysValid)).Should().ThrowExactly(); 46 | blowfish.Invoking(b => b.Initialize("AAAA", 2, 8, null)).Should().ThrowExactly(); 47 | blowfish.Invoking(b => b.Initialize("AAAA", 2, 8, keysInvalid)).Should().ThrowExactly(); 48 | } 49 | 50 | [Test] 51 | [TestCase(new uint[] { 0x01234567, 0x89ABCDEF }, "AAAA", 2, 8, new uint[] { 0xECD83DAE, 0xAB3EF361 })] 52 | public void Decrypt(uint[] data, string idCode, int level, int modulo, uint[] expected) 53 | { 54 | DsiKeyStore keys = TestDataBase.GetDsiKeyStore(); 55 | var blowfish = new NitroBlowfish(); 56 | blowfish.Initialize(idCode, level, modulo, keys.BlowfishDsKey); 57 | 58 | blowfish.Decrypt(ref data[0], ref data[1]); 59 | data.Should().BeEquivalentTo(expected); 60 | } 61 | 62 | [Test] 63 | [TestCase(new uint[] { 0xECD83DAE, 0xAB3EF361 }, "AAAA", 2, 8, new uint[] { 0x01234567, 0x89ABCDEF })] 64 | public void Encrypt(uint[] data, string idCode, int level, int modulo, uint[] expected) 65 | { 66 | DsiKeyStore keys = TestDataBase.GetDsiKeyStore(); 67 | var blowfish = new NitroBlowfish(); 68 | blowfish.Initialize(idCode, level, modulo, keys.BlowfishDsKey); 69 | 70 | blowfish.Encrypt(ref data[0], ref data[1]); 71 | data.Should().BeEquivalentTo(expected); 72 | } 73 | 74 | [Test] 75 | public void StreamEncDecryption() 76 | { 77 | using var stream = new DataStream(); 78 | var inputWriter = new DataWriter(stream); 79 | inputWriter.Write(0xECD83DAE); 80 | inputWriter.Write(0xAB3EF361); 81 | 82 | DsiKeyStore keys = TestDataBase.GetDsiKeyStore(); 83 | var blowfish = new NitroBlowfish(); 84 | blowfish.Initialize("AAAA", 2, 8, keys.BlowfishDsKey); 85 | 86 | blowfish.Encrypt(stream); 87 | 88 | stream.Position = 0; 89 | var outputReader = new DataReader(stream); 90 | outputReader.ReadUInt32().Should().Be(0x01234567); 91 | outputReader.ReadUInt32().Should().Be(0x89ABCDEF); 92 | 93 | blowfish.Decrypt(stream); 94 | 95 | stream.Position = 0; 96 | outputReader.ReadUInt32().Should().Be(0xECD83DAE); 97 | outputReader.ReadUInt32().Should().Be(0xAB3EF361); 98 | } 99 | 100 | [Test] 101 | public void ArrayEncDecryption() 102 | { 103 | using var inputStream = new DataStream(); 104 | var inputWriter = new DataWriter(inputStream); 105 | inputWriter.Write(0xECD83DAE); 106 | inputWriter.Write(0xAB3EF361); 107 | byte[] input = new byte[8]; 108 | inputStream.Position = 0; 109 | inputStream.Read(input); 110 | 111 | DsiKeyStore keys = TestDataBase.GetDsiKeyStore(); 112 | var blowfish = new NitroBlowfish(); 113 | blowfish.Initialize("AAAA", 2, 8, keys.BlowfishDsKey); 114 | 115 | byte[] output = blowfish.Encrypt(input); 116 | 117 | using var outputStream = DataStreamFactory.FromArray(output); 118 | var outputReader = new DataReader(outputStream); 119 | outputReader.ReadUInt32().Should().Be(0x01234567); 120 | outputReader.ReadUInt32().Should().Be(0x89ABCDEF); 121 | 122 | byte[] finalEncryption = blowfish.Decrypt(output); 123 | 124 | finalEncryption.Should().BeEquivalentTo(input); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Ekona.Tests/Security/NitroKey1EncryptionTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System.Collections.Generic; 21 | using System.IO; 22 | using System.Linq; 23 | using FluentAssertions; 24 | using NUnit.Framework; 25 | using SceneGate.Ekona.Containers.Rom; 26 | using SceneGate.Ekona.Security; 27 | using Yarhl.FileSystem; 28 | using Yarhl.IO; 29 | 30 | namespace SceneGate.Ekona.Tests.Security; 31 | 32 | [TestFixture] 33 | public class NitroKey1EncryptionTests 34 | { 35 | public static IEnumerable GetRoms() 36 | { 37 | string basePath = Path.Combine(TestDataBase.RootFromOutputPath, "Containers"); 38 | string listPath = Path.Combine(basePath, "rom.txt"); 39 | return TestDataBase.ReadTestListFile(listPath) 40 | .Select(line => line.Split(',')) 41 | .Select(data => new TestCaseData(Path.Combine(basePath, data[1])) 42 | .SetName($"{{m}}({data[1]})")); 43 | } 44 | 45 | [Test] 46 | public void GenerateDisabledSecureAreaMatch() 47 | { 48 | DsiKeyStore keys = TestDataBase.GetDsiKeyStore(); 49 | var encryption = new NitroKey1Encryption("AAAA", keys); 50 | 51 | byte[] token = encryption.GenerateEncryptedDisabledSecureAreaToken(); 52 | 53 | encryption.HasDisabledSecureArea(token).Should().BeTrue(); 54 | } 55 | 56 | [Test] 57 | [TestCase(new byte[] { 0x1D, 0x9E, 0xBA, 0xC7, 0xBB, 0x0E, 0x9E, 0x6A }, "B2KJ")] 58 | public void DecryptSecureAreaId(byte[] encrypted, string idCode) 59 | { 60 | DsiKeyStore keys = TestDataBase.GetDsiKeyStore(); 61 | 62 | var encryption = new NitroKey1Encryption(idCode, keys); 63 | encryption.HasValidSecureAreaId(encrypted).Should().BeTrue(); 64 | } 65 | 66 | [Test] 67 | [TestCase(new byte[] { 0x1D, 0x9E, 0xBA, 0xC7, 0xBB, 0x0E, 0x9E, 0x6A }, "B2KJ")] 68 | public void EncryptSecureAreaId(byte[] encrypted, string idCode) 69 | { 70 | DsiKeyStore keys = TestDataBase.GetDsiKeyStore(); 71 | var encryption = new NitroKey1Encryption(idCode, keys); 72 | 73 | byte[] actualEncrypted = encryption.GenerateEncryptedSecureAreaId(); 74 | actualEncrypted.Should().BeEquivalentTo(encrypted); 75 | } 76 | 77 | [TestCaseSource(nameof(GetRoms))] 78 | public void EncryptedDecryptArm9IsIdentical(string romPath) 79 | { 80 | TestDataBase.IgnoreIfFileDoesNotExist(romPath); 81 | DsiKeyStore keys = TestDataBase.GetDsiKeyStore(); 82 | 83 | using Node node = NodeFactory.FromFile(romPath, FileOpenMode.Read); 84 | node.TransformWith(); 85 | var nitroRom = node.GetFormatAs(); 86 | var originalArm9 = nitroRom.System.Children["arm9"].Stream!; 87 | 88 | var encryption = new NitroKey1Encryption(nitroRom.Information.GameCode, keys); 89 | using var encryptedArm9 = encryption.EncryptArm9(originalArm9); 90 | using var decryptedArm9 = encryption.DecryptArm9(encryptedArm9); 91 | 92 | decryptedArm9.Compare(originalArm9).Should().BeTrue(); 93 | } 94 | 95 | [TestCaseSource(nameof(GetRoms))] 96 | public void GameSecureAreaAreDecrypted(string romPath) 97 | { 98 | TestDataBase.IgnoreIfFileDoesNotExist(romPath); 99 | DsiKeyStore keys = TestDataBase.GetDsiKeyStore(); 100 | 101 | using Node node = NodeFactory.FromFile(romPath, FileOpenMode.Read); 102 | node.TransformWith(); 103 | var nitroRom = node.GetFormatAs(); 104 | var originalArm9 = nitroRom.System.Children["arm9"].Stream!; 105 | 106 | var encryption = new NitroKey1Encryption(nitroRom.Information.GameCode, keys); 107 | encryption.HasEncryptedArm9(originalArm9).Should().BeFalse(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Ekona.Tests/TestDataBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | using System.Collections.Generic; 22 | using System.IO; 23 | using System.Linq; 24 | using NUnit.Framework; 25 | using SceneGate.Ekona.Security; 26 | using YamlDotNet.Serialization; 27 | 28 | namespace SceneGate.Ekona.Tests 29 | { 30 | public static class TestDataBase 31 | { 32 | public static string RootFromOutputPath { 33 | get { 34 | string envVar = Environment.GetEnvironmentVariable("SCENEGATE_TEST_DIR"); 35 | if (!string.IsNullOrEmpty(envVar)) { 36 | return envVar; 37 | } 38 | 39 | string programDir = AppDomain.CurrentDomain.BaseDirectory; 40 | string path = Path.Combine(programDir, "Resources"); 41 | return Path.GetFullPath(path); 42 | } 43 | } 44 | 45 | public static DsiKeyStore GetDsiKeyStore() 46 | { 47 | string keysPath = Path.Combine(RootFromOutputPath, "dsi_keys.yml"); 48 | TestDataBase.IgnoreIfFileDoesNotExist(keysPath); 49 | 50 | string keysYaml = File.ReadAllText(keysPath); 51 | return new DeserializerBuilder() 52 | .Build() 53 | .Deserialize(keysYaml); 54 | } 55 | 56 | public static void IgnoreIfFileDoesNotExist(string file) 57 | { 58 | if (!File.Exists(file)) { 59 | string msg = $"[{TestContext.CurrentContext.Test.ClassName}] Missing resource file: {file}"; 60 | TestContext.Progress.WriteLine(msg); 61 | Assert.Ignore(msg); 62 | } 63 | } 64 | 65 | public static IEnumerable ReadTestListFile(string filePath) 66 | { 67 | if (!File.Exists(filePath)) { 68 | return Array.Empty(); 69 | } 70 | 71 | return File.ReadAllLines(filePath) 72 | .Select(line => line.Trim()) 73 | .Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith('#')); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Ekona.Tests/YarhlTestExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System.IO; 21 | using SceneGate.Ekona.Tests.Assertions; 22 | using Yarhl.FileSystem; 23 | using Yarhl.IO; 24 | 25 | namespace SceneGate.Ekona.Tests 26 | { 27 | public static class YarhlTestExtensions 28 | { 29 | public static StreamAssertions Should(this DataStream binary) 30 | { 31 | return new StreamAssertions(binary); 32 | } 33 | 34 | public static StreamAssertions Should(this Stream binary) 35 | { 36 | return new StreamAssertions(binary); 37 | } 38 | 39 | public static BinaryFormatAssertions Should(this BinaryFormat binary) 40 | { 41 | return new BinaryFormatAssertions(binary); 42 | } 43 | 44 | public static NodeAssertions Should(this Node node) 45 | { 46 | return new NodeAssertions(node); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Ekona.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34322.80 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ekona", "Ekona\Ekona.csproj", "{2E92C69F-6D59-4430-A017-E4A6E8B916E4}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ekona.Tests", "Ekona.Tests\Ekona.Tests.csproj", "{487D33E4-F410-49A5-832B-986DF19E1A91}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{105F9DFE-4DDF-436A-BF87-106E3676759D}" 11 | ProjectSection(SolutionItems) = preProject 12 | Directory.Build.props = Directory.Build.props 13 | Directory.Packages.props = Directory.Packages.props 14 | EndProjectSection 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ekona.PerformanceTests", "Ekona.PerformanceTests\Ekona.PerformanceTests.csproj", "{5808F29C-C802-4728-99A5-E1F299178002}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ekona.Examples", "Ekona.Examples\Ekona.Examples.csproj", "{E1BC44DB-CAA3-4C74-8A5A-7D536A53E09F}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {2E92C69F-6D59-4430-A017-E4A6E8B916E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {2E92C69F-6D59-4430-A017-E4A6E8B916E4}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {2E92C69F-6D59-4430-A017-E4A6E8B916E4}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {2E92C69F-6D59-4430-A017-E4A6E8B916E4}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {487D33E4-F410-49A5-832B-986DF19E1A91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {487D33E4-F410-49A5-832B-986DF19E1A91}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {487D33E4-F410-49A5-832B-986DF19E1A91}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {487D33E4-F410-49A5-832B-986DF19E1A91}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {5808F29C-C802-4728-99A5-E1F299178002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {5808F29C-C802-4728-99A5-E1F299178002}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {5808F29C-C802-4728-99A5-E1F299178002}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {5808F29C-C802-4728-99A5-E1F299178002}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {E1BC44DB-CAA3-4C74-8A5A-7D536A53E09F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {E1BC44DB-CAA3-4C74-8A5A-7D536A53E09F}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {E1BC44DB-CAA3-4C74-8A5A-7D536A53E09F}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {E1BC44DB-CAA3-4C74-8A5A-7D536A53E09F}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {76EAFF32-6370-4CEC-B045-997C9BD5B7ED} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /src/Ekona.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 3 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 4 | True 5 | True -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/AgeRating.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | namespace SceneGate.Ekona.Containers.Rom; 21 | 22 | /// 23 | /// Age rating for a region. 24 | /// 25 | public class AgeRating 26 | { 27 | /// 28 | /// Gets or sets a value indicating whether this rating applies or not. 29 | /// 30 | public bool Enabled { get; set; } 31 | 32 | /// 33 | /// Gets or sets a value indicating whether the program is prohibited in the region. 34 | /// 35 | public bool Prohibited { get; set; } 36 | 37 | /// 38 | /// Gets or sets the minimal age for the program. The value meaning may vary per region. 39 | /// 40 | public int Age { get; set; } 41 | } 42 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/ArmEncodeFieldFinder.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | using System.IO; 22 | using Yarhl.IO; 23 | 24 | namespace SceneGate.Ekona.Containers.Rom 25 | { 26 | /// 27 | /// Find the constant with the decoded size of the ARM. 28 | /// 29 | public static class ArmEncodeFieldFinder 30 | { 31 | private const int SecureAreaSize = 0x4000; 32 | 33 | // Number of bytes from the header to the first instruction in DecoderOps 34 | private const int DecoderShift = 0x0C; 35 | 36 | // First ARM instructions of the BLZ decoder 37 | private static readonly uint[] DecoderOps = new uint[] 38 | { 39 | 0xE9100006, 0xE0802002, 0xE0403C21, 0xE3C114FF, 40 | 0xE0401001, 0xE1A04002, 41 | }; 42 | 43 | /// 44 | /// Searchs the encoded size address. 45 | /// 46 | /// The ARM file to analyze. 47 | /// The information of the program. 48 | /// The encoded size address. 0 if not found. 49 | public static int SearchEncodedSizeAddress(IBinary arm, ProgramInfo info) 50 | { 51 | // Steps to find the ARM9 size address that we need to change 52 | // in order to fix the BLZ decoded error. 53 | // 1º Get ARM9 entry address. 54 | // 2º From that point and while we're in the secure zone, 55 | // search the decode_BLZ routine. 56 | // 3º Search previous BL (jump) instruction that call the decoder. 57 | // 4º Search instructions before it that loads R0 (parameter of decode_BLZ). 58 | DataReader reader = new DataReader(arm.Stream); 59 | 60 | // 1º 61 | uint entryAddress = info.Arm9EntryAddress - info.Arm9RamAddress; 62 | 63 | // 2º 64 | arm.Stream.Seek(entryAddress, SeekOrigin.Begin); 65 | uint decoderAddress = SearchDecoder(arm.Stream); 66 | if (decoderAddress == 0x00) { 67 | throw new FormatException("Invalid decoder address"); 68 | } 69 | 70 | // 3º & 4º 71 | arm.Stream.Seek(entryAddress, SeekOrigin.Begin); 72 | uint baseOffset = SearchBaseOffset(arm.Stream, decoderAddress); 73 | if (baseOffset == 0x00) { 74 | throw new FormatException("Invalid base offset"); 75 | } 76 | 77 | // Get relative address (not RAM address) 78 | arm.Stream.Seek(baseOffset, SeekOrigin.Begin); 79 | uint sizeAddress = reader.ReadUInt32() + 0x14; // Size is at 0x14 from that address 80 | sizeAddress -= info.Arm9RamAddress; 81 | 82 | return (int)sizeAddress; 83 | } 84 | 85 | private static uint SearchDecoder(DataStream stream) 86 | { 87 | DataReader reader = new DataReader(stream); 88 | long startPosition = stream.Position; 89 | 90 | uint decoderAddress = 0x00; 91 | while (stream.Position - startPosition < SecureAreaSize && decoderAddress == 0x00) 92 | { 93 | long loopPosition = stream.Position; 94 | 95 | // Compare instructions to see if it's the routing we want 96 | bool found = true; 97 | for (int i = 0; i < DecoderOps.Length && found; i++) { 98 | if (reader.ReadUInt32() != DecoderOps[i]) { 99 | found = false; 100 | } 101 | } 102 | 103 | if (found) { 104 | decoderAddress = (uint)loopPosition - DecoderShift; // Get start of routine 105 | } else { 106 | stream.Seek(loopPosition + 4, SeekOrigin.Begin); // Go to next instruction 107 | } 108 | } 109 | 110 | return decoderAddress; 111 | } 112 | 113 | private static uint SearchBaseOffset(DataStream stream, uint decoderAddress) 114 | { 115 | DataReader reader = new DataReader(stream); 116 | uint instr; 117 | 118 | // Search the instruction: BL DecoderAddress 119 | // Where DecoderAddress=(PC+8+nn*4) 120 | bool found = false; 121 | while (stream.Position < decoderAddress && !found) 122 | { 123 | instr = reader.ReadUInt32(); 124 | if ((instr & 0xFF000000) == 0xEB000000) { 125 | uint shift = instr & 0x00FFFFFF; 126 | shift = 4 + (shift * 4); 127 | 128 | // Check if that jump goes to the correct routine 129 | if (stream.Position + shift == decoderAddress) 130 | found = true; 131 | } 132 | } 133 | 134 | // Search for the Load instruction, btw LDR R1=[PC+ZZ]. 135 | // Usually two instruction before. 136 | stream.Seek(-0x0C, SeekOrigin.Current); 137 | uint baseOffset = 0x00; 138 | instr = reader.ReadUInt32(); 139 | if ((instr & 0xFFFF0000) == 0xE59F0000) { 140 | baseOffset = (uint)stream.Position + (instr & 0xFFF) + 4; 141 | } 142 | 143 | // If not found... Should we continue looking above instructions? 144 | // I run a test with > 500 games and at the moment it is always there 145 | return baseOffset; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/Banner.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | using SceneGate.Ekona.Security; 22 | using Yarhl.FileFormat; 23 | 24 | namespace SceneGate.Ekona.Containers.Rom 25 | { 26 | /// 27 | /// Banner of a program. 28 | /// 29 | public class Banner : IFormat 30 | { 31 | private Version version; 32 | 33 | /// 34 | /// Gets or sets the version of the banner model. 35 | /// 36 | /// 37 | /// Only the following version are valid: 38 | /// 39 | /// 0.1: Original 40 | /// 0.2: Support Chinese title 41 | /// 0.3: Support Chinese and Korean titles 42 | /// 1.3: Support Chinese and Korean titles and animated DSi icon. 43 | /// 44 | /// 45 | /// Invalid version number. 46 | public Version Version { 47 | get => version; 48 | set { 49 | if (value.Minor is < 1 or > 3) 50 | throw new ArgumentOutOfRangeException(nameof(value), value.Minor, "Minor must be [1,3]"); 51 | if (value.Major is < 0 or > 1) 52 | throw new ArgumentOutOfRangeException(nameof(value), value.Major, "Major must be [0, 1]"); 53 | if (value.Major == 1 && value.Minor != 3) 54 | throw new ArgumentOutOfRangeException(nameof(value), value.Minor, "Minor must be 3 for major 1"); 55 | 56 | version = value; 57 | } 58 | } 59 | 60 | /// 61 | /// Gets a value indicating whether the banner supports animated icons (version >= 1.3). 62 | /// 63 | public bool SupportAnimatedIcon => Version is { Major: > 1 } or { Major: 1, Minor: >= 3 }; 64 | 65 | /// 66 | /// Gets or sets the CRC-16 checksum for the banner binary data of version 0.1. 67 | /// 68 | /// This field may be null if the model was not deserialized from binary data. 69 | public HashInfo ChecksumBase { get; set; } 70 | 71 | /// 72 | /// Gets or sets the CRC-16 checksum for the banner binary data of version 0.2 73 | /// that includes Chinese title. 74 | /// 75 | /// This field may be null if the model was not deserialized from binary data. 76 | public HashInfo ChecksumChinese { get; set; } 77 | 78 | /// 79 | /// Gets or sets the CRC-16 checksum for the banner binary data of version 0.3 80 | /// that includes Chinese and Korean titles. 81 | /// 82 | /// This field may be null if the model was not deserialized from binary data. 83 | public HashInfo ChecksumKorean { get; set; } 84 | 85 | /// 86 | /// Gets or sets the CRC-16 checksum for the animated DSi icon. 87 | /// 88 | /// This field may be null if the model was not deserialized from binary data. 89 | public HashInfo ChecksumAnimatedIcon { get; set; } 90 | 91 | /// 92 | /// Gets or sets the Japenese title. 93 | /// 94 | public string JapaneseTitle { get; set; } 95 | 96 | /// 97 | /// Gets or sets the English title. 98 | /// 99 | public string EnglishTitle { get; set; } 100 | 101 | /// 102 | /// Gets or sets the French title. 103 | /// 104 | public string FrenchTitle { get; set; } 105 | 106 | /// 107 | /// Gets or sets the German title. 108 | /// 109 | public string GermanTitle { get; set; } 110 | 111 | /// 112 | /// Gets or sets the Italian title. 113 | /// 114 | public string ItalianTitle { get; set; } 115 | 116 | /// 117 | /// Gets or sets the Spanish title. 118 | /// 119 | public string SpanishTitle { get; set; } 120 | 121 | /// 122 | /// Gets or sets the Chinese title. 123 | /// 124 | public string ChineseTitle { get; set; } 125 | 126 | /// 127 | /// Gets or sets the Korean title. 128 | /// 129 | public string KoreanTitle { get; set; } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/DeviceUnitKind.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | namespace SceneGate.Ekona.Containers.Rom; 21 | 22 | /// 23 | /// Device unit compatible for the ROM. 24 | /// 25 | public enum DeviceUnitKind 26 | { 27 | /// 28 | /// DS only game. 29 | /// 30 | DS = 0, 31 | 32 | /// 33 | /// DSi game compatible on DS units too. 34 | /// 35 | DSiEnhanced = 2, 36 | 37 | /// 38 | /// DSi exclusive game. 39 | /// 40 | DSiExclusive = 3, 41 | } 42 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/DsiCryptoMode.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | 22 | namespace SceneGate.Ekona.Containers.Rom; 23 | 24 | /// 25 | /// Security mode for DSi ROMs. 26 | /// 27 | [Flags] 28 | public enum DsiCryptoMode 29 | { 30 | /// 31 | /// ROM contains a TWL-exclusive region (DSi games). 32 | /// 33 | TwlExclusiveRegion = 1 << 0, 34 | 35 | /// 36 | /// ROM is encrypted with modcrypt. 37 | /// 38 | Modcrypted = 1 << 1, 39 | 40 | /// 41 | /// ROM modcryption uses debug key. 42 | /// 43 | ModcryptKeyDebug = 1 << 2, 44 | 45 | /// 46 | /// Disable debug features. 47 | /// 48 | DisableDebug = 1 << 3, 49 | } 50 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/DsiRomFeatures.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | 22 | namespace SceneGate.Ekona.Containers.Rom; 23 | 24 | /// 25 | /// ROM features for DSi or new DS games. 26 | /// 27 | [Flags] 28 | public enum DsiRomFeatures 29 | { 30 | /// 31 | /// TSC touchscreen / sound controller for DSi. 32 | /// 33 | DsiTouchscreenSound = 1 << 0, 34 | 35 | /// 36 | /// Require to accept the EULA agreement. 37 | /// 38 | RequireEula = 1 << 1, 39 | 40 | /// 41 | /// Use in the launcher the icon from banner.sav instead of the ROM banner. 42 | /// 43 | SaveBannerIcon = 1 << 2, 44 | 45 | /// 46 | /// Show in the launcher the Wi-Fi connection icon. 47 | /// 48 | ShowWifiIcon = 1 << 3, 49 | 50 | /// 51 | /// Show in the launcher the DS Wireless icon. 52 | /// 53 | ShowWirelessIcon = 1 << 4, 54 | 55 | /// 56 | /// DS ROM contains an HMAC of the icon. 57 | /// 58 | NitroBannerSigned = 1 << 5, 59 | 60 | /// 61 | /// DS Program contains an HMAC and RSA signature of the header and programs. 62 | /// 63 | NitroProgramSigned = 1 << 6, 64 | 65 | /// 66 | /// ROM is a developer application. 67 | /// 68 | DeveloperApp = 1 << 7, 69 | } 70 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/GlobalMemoryBankSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | namespace SceneGate.Ekona.Containers.Rom; 21 | 22 | /// 23 | /// DSi extended memory global memory bank settings (MBK 1 to 5). 24 | /// 25 | public class GlobalMemoryBankSettings 26 | { 27 | /// 28 | /// Gets or sets the processor reserved for this memory. 29 | /// 30 | /// 31 | /// For MBK1 only ARM9 or ARM7. 32 | /// For MBK2-3: also DSP for code. 33 | /// For MBK4-5: also DSP for data. 34 | /// 35 | public MemoryBankProcessor Processor { get; set; } 36 | 37 | /// 38 | /// Gets or sets the offset slot in blocks. 39 | /// 40 | /// 41 | /// For MBK1: 64 KB units. 42 | /// For MBK2-5: 32 KB units. 43 | /// 44 | public byte OffsetSlot { get; set; } 45 | 46 | /// 47 | /// Gets or sets a value indicating whether this memory bank is enabled. 48 | /// 49 | public bool Enabled { get; set; } 50 | } 51 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/IconAnimation2AnimatedImage.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | using System.Drawing; 22 | using System.Linq; 23 | using Texim.Animations; 24 | using Texim.Images; 25 | using Texim.Palettes; 26 | using Texim.Pixels; 27 | using Yarhl.FileFormat; 28 | using Yarhl.FileSystem; 29 | 30 | namespace SceneGate.Ekona.Containers.Rom 31 | { 32 | /// 33 | /// Converter for ROM banner animated icons into an animated image. 34 | /// 35 | public class IconAnimation2AnimatedImage : IConverter 36 | { 37 | /// 38 | /// Convert the 'animated' node from the ROM banner into an animated image. 39 | /// 40 | /// The animated node of the ROM banner. 41 | /// A new animated image. 42 | /// The argument is null. 43 | public AnimatedFullImage Convert(NodeContainerFormat source) 44 | { 45 | if (source is null) 46 | throw new ArgumentNullException(nameof(source)); 47 | 48 | IndexedImage[] bitmaps = source.Root.Children 49 | .Where(n => n.Name.StartsWith("bitmap")) 50 | .Select(n => n.GetFormatAs()) 51 | .ToArray(); 52 | if (bitmaps.Length == 0) { 53 | throw new FormatException("Missing bitmaps"); 54 | } 55 | 56 | IPaletteCollection palette = source.Root.Children["palettes"]?.GetFormatAs() 57 | ?? throw new FormatException("Missing palettes or wrong format"); 58 | IconAnimationSequence animation = source.Root.Children["animation"]?.GetFormatAs() 59 | ?? throw new FormatException("Missing animation sequence or wrong format"); 60 | 61 | var animatedImage = new AnimatedFullImage(); 62 | animatedImage.Loops = 0; // infinite 63 | 64 | int width = bitmaps[0].Width; 65 | int height = bitmaps[0].Height; 66 | 67 | foreach (IconAnimationFrame frameInfo in animation.Frames) { 68 | var framePixels = ((IndexedPixel[])bitmaps[frameInfo.BitmapIndex].Pixels.Clone()).AsSpan(); 69 | if (frameInfo.FlipHorizontal) { 70 | framePixels.FlipHorizontal(new Size(width, height)); 71 | } 72 | 73 | if (frameInfo.FlipVertical) { 74 | framePixels.FlipVertical(new Size(width, height)); 75 | } 76 | 77 | var frameIndexed = new IndexedImage(width, height, framePixels.ToArray()); 78 | var fullImageConverter = new Indexed2FullImage(palette.Palettes[frameInfo.PaletteIndex]); 79 | FullImage frameImage = fullImageConverter.Convert(frameIndexed); 80 | 81 | var frame = new FullImageFrame { 82 | Image = frameImage, 83 | Duration = frameInfo.Duration, 84 | }; 85 | animatedImage.Frames.Add(frame); 86 | } 87 | 88 | return animatedImage; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/IconAnimationFrame.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | 22 | namespace SceneGate.Ekona.Containers.Rom; 23 | 24 | /// 25 | /// Frame definition for the ROM animated icon. 26 | /// 27 | public class IconAnimationFrame 28 | { 29 | private int bitmapIndex; 30 | private int paletteIndex; 31 | 32 | /// 33 | /// Gets or sets the duration of the frame in milliseconds. 34 | /// 35 | /// 36 | /// The resolution is 60 Hz. 37 | /// 38 | public int Duration { get; set; } 39 | 40 | /// 41 | /// Gets or sets the index of the bitmap for this frame. 42 | /// 43 | /// 44 | /// There are maximum 8 bitmaps. 45 | /// 46 | public int BitmapIndex { 47 | get => bitmapIndex; 48 | set { 49 | if (value < 0 || value >= Binary2Banner.NumAnimatedImages) { 50 | throw new ArgumentOutOfRangeException( 51 | nameof(value), 52 | value, 53 | "Animated icons can't have more than 8 bitmaps"); 54 | } 55 | 56 | bitmapIndex = value; 57 | } 58 | } 59 | 60 | /// 61 | /// Gets or sets the palette to use for the bitmap of this frame. 62 | /// 63 | /// 64 | /// There are maximum 8 palettes. 65 | /// 66 | public int PaletteIndex { 67 | get => paletteIndex; 68 | set { 69 | if (value < 0 || value >= Binary2Banner.NumAnimatedImages) { 70 | throw new ArgumentOutOfRangeException( 71 | nameof(value), 72 | value, 73 | "Animated icons can't have more than 8 palettes"); 74 | } 75 | 76 | paletteIndex = value; 77 | } 78 | } 79 | 80 | /// 81 | /// Gets or sets a value indicating whether the bitmap should flip horizontally. 82 | /// 83 | public bool FlipHorizontal { get; set; } 84 | 85 | /// 86 | /// Gets or sets a value indicating whether the bitmap should flip vertically. 87 | /// 88 | public bool FlipVertical { get; set; } 89 | } 90 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/IconAnimationSequence.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System.Collections.ObjectModel; 21 | using Yarhl.FileFormat; 22 | 23 | namespace SceneGate.Ekona.Containers.Rom 24 | { 25 | /// 26 | /// Sequence of definition of frames for the ROM animated icon. 27 | /// 28 | public class IconAnimationSequence : IFormat 29 | { 30 | /// 31 | /// Gets the collection of frames. 32 | /// 33 | public Collection Frames { get; init; } = new Collection(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/LocalMemoryBankSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | namespace SceneGate.Ekona.Containers.Rom; 21 | 22 | /// 23 | /// DSi extended memory local (to a processor) memory bank settings (MBK 6 to 8). 24 | /// 25 | public class LocalMemoryBankSettings 26 | { 27 | /// 28 | /// Gets or sets the start address slot. 29 | /// 30 | /// 31 | /// Final address is: 0x03000000 + value * (0x010000 for MBK6 or 0x8000 for 7-8). 32 | /// 33 | public int StartAddressSlot { get; set; } 34 | 35 | /// 36 | /// Gets or sets the image size kind. 37 | /// 38 | /// 39 | /// According to existing research: 40 | /// MBK6: 0 or 1 = 64 KB /slot0, 2 = 128 KB / slot0-2, 3 = 256 KB / slot0-3 41 | /// MBK7-8: 0 = 32 KB / slot0, 1 = 64 KB / slot0-1, 2 = 128 KB / slot0-3, 3 = 256 KB / slot0-7. 42 | /// 43 | public int ImageSize { get; set; } 44 | 45 | /// 46 | /// Gets or sets the end address slot. 47 | /// 48 | /// 49 | /// Final address is: 0x03000000 + value * (0x010000 for MBK6 or 0x8000 for 7-8) - 1. 50 | /// 51 | public int EndAddressSlot { get; set; } 52 | } 53 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/MemoryBankProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | namespace SceneGate.Ekona.Containers.Rom; 21 | 22 | /// 23 | /// Gets or sets the processor assigned to a memory bank. 24 | /// 25 | public enum MemoryBankProcessor 26 | { 27 | /// 28 | /// ARM9 processor. 29 | /// 30 | Arm9 = 0, 31 | 32 | /// 33 | /// ARM7 processor. 34 | /// 35 | Arm7 = 1, 36 | 37 | /// 38 | /// DSP for value 2 (code?). 39 | /// 40 | Dsp2 = 2, 41 | 42 | /// 43 | /// DSP for value 3 (data?). 44 | /// 45 | Dsp3 = 3, 46 | } 47 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/ModcryptTargetKind.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | namespace SceneGate.Ekona.Containers.Rom; 21 | 22 | /// 23 | /// Target area to modcrypt. 24 | /// 25 | public enum ModcryptTargetKind 26 | { 27 | /// 28 | /// No area have modcrypt. 29 | /// 30 | None, 31 | 32 | /// 33 | /// ARM9 program. 34 | /// 35 | Arm9, 36 | 37 | /// 38 | /// ARM7 program. 39 | /// 40 | Arm7, 41 | 42 | /// 43 | /// ARM9i (DSi) program. 44 | /// 45 | Arm9i, 46 | 47 | /// 48 | /// ARM9i (DSi) secure area program only. 49 | /// 50 | Arm9iSecureArea, 51 | 52 | /// 53 | /// ARM7i (DSi) program. 54 | /// 55 | Arm7i, 56 | 57 | /// 58 | /// Unknown area. 59 | /// 60 | Unknown, 61 | } 62 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/NitroProgramCodeParameters.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | 22 | namespace SceneGate.Ekona.Containers.Rom; 23 | 24 | /// 25 | /// Nitro and twilight parameters related to the code program (arm9). 26 | /// 27 | public class NitroProgramCodeParameters 28 | { 29 | /// 30 | /// Gets or sets the offsets to the ARM9 parameters table offset in DS programs. 31 | /// DSi programs have this value in the header. 32 | /// 33 | public uint ProgramParameterOffset { get; set; } 34 | 35 | /// 36 | /// Gets or sets the offset inside the decompressed ARM9 to the list of HMAC-SHA1 37 | /// hashes of each compressed overlay (only DS games). 38 | /// 39 | /// 40 | /// The key is inside the ARM9 as well (`HMacKeyDSiGames`). 41 | /// 42 | public uint NitroOverlayHMacOffset { get; set; } 43 | 44 | /// 45 | /// Gets or sets the ITCM first block info offset. 46 | /// 47 | /// 48 | /// A block info consists in two uint values: output RAM address and size. 49 | /// After moving the ITCM to the output, is clean so we can reuse the place for BSS. 50 | /// 51 | public uint ItcmBlockInfoOffset { get; set; } 52 | 53 | /// 54 | /// Gets or sets the end offset for the ITCM block info section. 55 | /// 56 | public uint ItcmBlockInfoEndOffset { get; set; } 57 | 58 | /// 59 | /// Gets or sets the ITCM input data offset. 60 | /// 61 | public uint ItcmInputDataOffset { get; set; } 62 | 63 | /// 64 | /// Gets or sets the BSS offset (non-initialized global/static variables area). 65 | /// 66 | /// 67 | /// Usually this value matches with 68 | /// because after moving the ITCM code, we can reuse that area. 69 | /// 70 | public uint BssOffset { get; set; } 71 | 72 | /// 73 | /// Gets or sets the end offset for BSS. 74 | /// 75 | public uint BssEndOffset { get; set; } 76 | 77 | /// 78 | /// Gets or sets the program (arm9) compressed length. 79 | /// If 0, then the program is not compressed. 80 | /// 81 | public uint CompressedLength { get; set; } 82 | 83 | /// 84 | /// Gets or sets the SDK version. 85 | /// 86 | public Version SdkVersion { get; set; } 87 | } 88 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/NitroRom.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using Yarhl.FileSystem; 21 | 22 | namespace SceneGate.Ekona.Containers.Rom 23 | { 24 | /// 25 | /// NDS cartridge file system. 26 | /// 27 | /// 28 | /// The container hierarchy is: 29 | /// 30 | /// Node pathDescription 31 | /// /systemROM information and program files. 32 | /// /system/infoProgram information. 33 | /// /system/copyright_logoCopyright logo. 34 | /// /system/banner/Program banner. 35 | /// /system/banner/infoProgram banner content. 36 | /// /system/banner/iconProgram icon. 37 | /// /system/banner/animatedAnimated program icon. 38 | /// /system/banner/animated/bitmapXBitmap X (0-7) for the animated icon. 39 | /// /system/banner/animated/palettesPalettes (0-7) for the animated icon. 40 | /// /system/banner/animated/animationAnimation icon information. 41 | /// /system/arm9Program executable for ARM9 CPU. 42 | /// /system/overlays9Overlay libraries for ARM9 CPU. 43 | /// /system/overlays9/overlay_XOverlay X for ARM9 CPU. 44 | /// /system/arm7Program executable for ARM7 CPU. 45 | /// /system/overlays7Overlay libraries for ARM7 CPU. 46 | /// /system/overlays7/overlay7_XOverlay X for ARM7 CPU. 47 | /// /dataProgram data files. 48 | /// 49 | /// 50 | public class NitroRom : NodeContainerFormat 51 | { 52 | /// 53 | /// Initializes a new instance of the class. 54 | /// 55 | public NitroRom() 56 | { 57 | Node system = NodeFactory.CreateContainer("system"); 58 | system.Add(new Node("info", new ProgramInfo())); 59 | system.Add(new Node("copyright_logo")); 60 | system.Add(NodeFactory.CreateContainer("banner")); 61 | system.Add(new Node("arm9")); 62 | system.Add(NodeFactory.CreateContainer("overlays9")); 63 | system.Add(new Node("arm7")); 64 | system.Add(NodeFactory.CreateContainer("overlays7")); 65 | Root.Add(system); 66 | 67 | Node data = NodeFactory.CreateContainer("data"); 68 | Root.Add(data); 69 | } 70 | 71 | /// 72 | /// Gets the Nitro constant in little endian: 2-10-6 (NiToRo in Japanese numbers) + 0xCODE. 73 | /// It's a marker for the program code to find constants. 74 | /// 75 | public static uint NitroCode => 0xDEC00621; 76 | 77 | /// 78 | /// Gets the container with the system files of the program. 79 | /// 80 | public Node System => Root.Children["system"]; 81 | 82 | /// 83 | /// Gets the container with the program data files. 84 | /// 85 | public Node Data => Root.Children["data"]; 86 | 87 | /// 88 | /// Gets the information of the program. 89 | /// 90 | public ProgramInfo Information => System?.Children["info"]?.GetFormatAs(); 91 | 92 | /// 93 | /// Gets the banner of the program. 94 | /// 95 | public Banner Banner => System?.Children["banner"]?.Children["info"]?.GetFormatAs(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/NitroRom2BinaryParams.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System.IO; 21 | using SceneGate.Ekona.Security; 22 | 23 | namespace SceneGate.Ekona.Containers.Rom; 24 | 25 | /// 26 | /// Additional parameters for the converter. 27 | /// 28 | public class NitroRom2BinaryParams 29 | { 30 | /// 31 | /// Gets or sets the output stream for the converter. If not set, the 32 | /// converter will create a new one in-memory. 33 | /// 34 | public Stream OutputStream { get; set; } 35 | 36 | /// 37 | /// Gets or sets the key store to re-generate the HMAC hashes. If not set, 38 | /// the hashes are not regenerated. 39 | /// 40 | public DsiKeyStore KeyStore { get; set; } 41 | 42 | /// 43 | /// Gets or sets a value indicating whether the program (arm9) is decompressed. 44 | /// 45 | public bool DecompressedProgram { get; set; } 46 | } 47 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/OverlayInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | namespace SceneGate.Ekona.Containers.Rom 21 | { 22 | /// 23 | /// Information about a program overlay. 24 | /// 25 | public class OverlayInfo 26 | { 27 | /// 28 | /// Gets or sets the ID of the overlay. 29 | /// 30 | public uint OverlayId { get; set; } 31 | 32 | /// 33 | /// Gets or sets the address where the overlay will be load in the RAM. 34 | /// 35 | public uint RamAddress { get; set; } 36 | 37 | /// 38 | /// Gets or sets the amount of bytes to load in RAM of the overlay. 39 | /// 40 | public uint RamSize { get; set; } 41 | 42 | /// 43 | /// Gets or sets the size of the BSS data region. 44 | /// 45 | public uint BssSize { get; set; } 46 | 47 | /// 48 | /// Gets or sets the static initialization start address. 49 | /// 50 | public uint StaticInitStart { get; set; } 51 | 52 | /// 53 | /// Gets or sets the static initialization end address. 54 | /// 55 | public uint StaticInitEnd { get; set; } 56 | 57 | /// 58 | /// Gets or sets the size of the overlay compressed or 0 if it is not compressed. 59 | /// 60 | public uint CompressedSize { get; set; } 61 | 62 | /// 63 | /// Gets or sets a value indicating whether the overlay is BLZ compressed. 64 | /// 65 | public bool IsCompressed { get; set; } 66 | 67 | /// 68 | /// Gets or sets a value indicating whether this overlay is digitally signed. 69 | /// 70 | /// 71 | /// The signature seems to be validated only when executing a program 72 | /// recived from other devices. 73 | /// 74 | public bool IsSigned { get; set; } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/ProgramRegions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | using System.Diagnostics.CodeAnalysis; 22 | 23 | namespace SceneGate.Ekona.Containers.Rom; 24 | 25 | /// 26 | /// Supported regions for the program. 27 | /// 28 | [Flags] 29 | [SuppressMessage("", "S4070", Justification = "RegionFree is a combination of every possible bit")] 30 | public enum ProgramRegions : uint 31 | { 32 | /// 33 | /// Typical Nintendo supported regions for DS. 34 | /// 35 | [SuppressMessage("", "S2346", Justification = "In this context makes more sense than None.")] 36 | NitroBase = 0, 37 | 38 | /// 39 | /// Japan region (DSi only). 40 | /// 41 | Japan = 1 << 0, 42 | 43 | /// 44 | /// USA region (DSi only). 45 | /// 46 | Usa = 1 << 1, 47 | 48 | /// 49 | /// Europe region (DSi only). 50 | /// 51 | Europe = 1 << 2, 52 | 53 | /// 54 | /// Australia region (DSi only). 55 | /// 56 | Australia = 1 << 3, 57 | 58 | /// 59 | /// China region. 60 | /// 61 | China = 1 << 4, 62 | 63 | /// 64 | /// Korea region. 65 | /// 66 | Korea = 1 << 5, 67 | 68 | /// 69 | /// Region free, support on all possible regions. 70 | /// 71 | RegionFree = 0xFFFFFFFF, 72 | } 73 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/ProgramStartJumpKind.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | namespace SceneGate.Ekona.Containers.Rom; 21 | 22 | /// 23 | /// The kind of program start jump at launch. 24 | /// 25 | public enum ProgramStartJumpKind 26 | { 27 | /// 28 | /// Normal jump copying header in the RAM. 29 | /// 30 | Normal = 0, 31 | 32 | /// 33 | /// Special / temporary launch used for the system menu. 34 | /// 35 | SystemMenu = 1, 36 | } 37 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/RomHeader.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2021 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using Yarhl.FileFormat; 21 | 22 | namespace SceneGate.Ekona.Containers.Rom 23 | { 24 | /// 25 | /// Header of the program in a ROM. 26 | /// 27 | public class RomHeader : IFormat 28 | { 29 | /// 30 | /// Gets or sets the information of the program. 31 | /// 32 | public ProgramInfo ProgramInfo { get; set; } = new ProgramInfo(); 33 | 34 | /// 35 | /// Gets or sets the information of the sections of the ROM. 36 | /// 37 | public RomSectionInfo SectionInfo { get; set; } = new RomSectionInfo(); 38 | 39 | /// 40 | /// Gets or sets the compressed copyright logo. 41 | /// 42 | /// 43 | /// The logo is compressed with Huffman with a header present in the BIOS. 44 | /// The logo must be the original or the device won't boot the game. 45 | /// 46 | public byte[] CopyrightLogo { get; set; } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/ScfgExtendedFeaturesArm7.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | using System.Diagnostics.CodeAnalysis; 22 | 23 | namespace SceneGate.Ekona.Containers.Rom; 24 | 25 | /// 26 | /// Define the control register (SCFG) extended features for DSi ARM7. 27 | /// 28 | [Flags] 29 | [SuppressMessage("", "S4070", Justification = "False positive")] 30 | public enum ScfgExtendedFeaturesArm7 : uint 31 | { 32 | /// 33 | /// No setting set. 34 | /// 35 | None = 0, 36 | 37 | /// 38 | /// Use the revised ARM7 DMA circuit instead of the nitro (DS) one. 39 | /// 40 | RevisedArm7DmaCircuit = 1 << 0, 41 | 42 | /// 43 | /// Use the revised sound DMA instead of the nitro (DS) one. 44 | /// 45 | RevisedSoundDma = 1 << 1, 46 | 47 | /// 48 | /// Use the revised sound instead of the nitro (DS) one. 49 | /// 50 | RevisedSound = 1 << 2, 51 | 52 | /// 53 | /// Use the extended sound DMA instead of the nitro (DS) one. 54 | /// 55 | ExtendedSoundDma = 1 << 10, 56 | 57 | /// 58 | /// Allow access to the SD/eMMC registers. 59 | /// 60 | AccessStorageRegisters = 1 << 18, 61 | 62 | /// 63 | /// Allow access to the SCFG/MBK registers. 64 | /// 65 | AccessScfgMbkRegisters = 1u << 31, 66 | } 67 | -------------------------------------------------------------------------------- /src/Ekona/Containers/Rom/TwilightAccessControl.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 SceneGate 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | using System.Diagnostics.CodeAnalysis; 22 | 23 | namespace SceneGate.Ekona.Containers.Rom; 24 | 25 | /// 26 | /// Access control of DSi (Twilight) software. 27 | /// 28 | [Flags] 29 | [SuppressMessage("", "S4070", Justification = "False positive")] 30 | public enum TwilightAccessControl : uint 31 | { 32 | /// 33 | /// No access control set. 34 | /// 35 | None = 0, 36 | 37 | /// 38 | /// Use "common key" -> 0x3080F000 = 0x03FFC600 + 0x00. 39 | /// 40 | CommonClientKey = 1 << 0, 41 | 42 | /// 43 | /// Use AES slot B -> 0x0380F010 = 0x03FFC400 + 0x180 and KEY1 unchanged. 44 | /// 45 | AesSlotB = 1 << 1, 46 | 47 | /// 48 | /// Use AES slot C -> 0x0380F020 = 0x03FFC400 + 0x190 and KEY2.Y = 0x03FFC400 + 0x1A0. 49 | /// 50 | AesSlotC = 1 << 2, 51 | 52 | /// 53 | /// Allow access to SD Card device (I). 54 | /// 55 | SdCard = 1 << 3, 56 | 57 | /// 58 | /// Allow access to NAND (device A-H and KEY3 intact). 59 | /// 60 | NandAccess = 1 << 4, 61 | 62 | /// 63 | /// Game card power on. 64 | /// 65 | GameCardPowerOn = 1 << 5, 66 | 67 | /// 68 | /// Software uses shared2 file from storage SD/eMMC. 69 | /// 70 | Shared2File = 1 << 6, 71 | 72 | /// 73 | /// Sign the camera photo JPEG files for launcher (AES slot B). 74 | /// 75 | SignJpegForLauncher = 1 << 7, 76 | 77 | /// 78 | /// Game card nitro mode. 79 | /// 80 | GameCardNitroMode = 1 << 8, 81 | 82 | /// 83 | /// SSL client certificates (AES slot A) -> KEY0 = 0x03FFC600 + 0x30. 84 | /// 85 | SslClientCert = 1 << 9, 86 | 87 | /// 88 | /// Sign the camera photo JPEG files for the user (AES slot B). 89 | /// 90 | SignJpegForUser = 1 << 10, 91 | 92 | /// 93 | /// Allow read access for photos. 94 | /// 95 | PhotoReadAccess = 1 << 11, 96 | 97 | /// 98 | /// Allow write access for photos. 99 | /// 100 | PhotoWriteAccess = 1 << 12, 101 | 102 | /// 103 | /// Allow read access to the SD card. 104 | /// 105 | SdCardReadAccess = 1 << 13, 106 | 107 | /// 108 | /// Allow write access to the SD card. 109 | /// 110 | SdCardWriteAccess = 1 << 14, 111 | 112 | /// 113 | /// Read access to cartridge save files. 114 | /// 115 | GameCardSaveReadAccess = 1 << 15, 116 | 117 | /// 118 | /// Write access to cartridge save files. 119 | /// 120 | GameCardSaveWriteAccess = 1 << 16, 121 | 122 | /// 123 | /// Debugger common client key -> 0x0380F000 = 0x03FFC600 + 0x10. 124 | /// 125 | DebuggerCommonClientKey = 1u << 31, 126 | } 127 | -------------------------------------------------------------------------------- /src/Ekona/Ekona.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | SceneGate.Ekona 5 | Library for NDS file formats. 6 | true 7 | 8 | net6.0;net8.0 9 | 10 | SceneGate.Ekona 11 | disable 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Ekona/Security/DsiKeyStore.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | namespace SceneGate.Ekona.Security; 21 | 22 | /// 23 | /// Store of DSi (and new DS games) keys. 24 | /// 25 | public class DsiKeyStore 26 | { 27 | /// 28 | /// Gets or sets the key for the Blowfish (KEY1) encryption of DS mode. 29 | /// 30 | /// 31 | /// The key can be found in the DS ARM7 BIOS from 0x30 to 0x1077. 32 | /// It starts with `99 D5 20 5F` and has 0x1048 bytes. 33 | /// 34 | public byte[] BlowfishDsKey { get; set; } 35 | 36 | /// 37 | /// Gets or sets the HMAC key for the DS whitelist phases 1 and 2. 38 | /// 39 | /// 40 | /// The key can be found in the DSi launcher application ARM9. 41 | /// For instance, at position 0270EC90h of the RAM. 42 | /// It starts with `61 BD DD 72` and has 0x40 bytes. 43 | /// 44 | public byte[] HMacKeyWhitelist12 { get; set; } 45 | 46 | /// 47 | /// Gets or sets the HMAC key for the DS whitelist phases 3 and 4. 48 | /// 49 | /// 50 | /// The key can be found in the DSi launcher application ARM9. 51 | /// For instance, at position 0270ECD0h of the RAM. 52 | /// It starts with `85 29 48 F3` and has 0x40 bytes. 53 | /// 54 | public byte[] HMacKeyWhitelist34 { get; set; } 55 | 56 | /// 57 | /// Gets or sets the HMAC key used in DSi games (like banner HMAC) but also 58 | /// in some DS games to verify the (compressed) overlays in download play load mode. 59 | /// 60 | /// 61 | /// The key can be found inside the ARM9 of most DS and DSi games and in the launcher. 62 | /// It has 0x40 bytes and starts with `21 06 C0 DE BA 98`. 63 | /// It seems to start with the "nitrocode" token and it would be the second one in the ARM9. 64 | /// 65 | public byte[] HMacKeyDSiGames { get; set; } 66 | 67 | /// 68 | /// Gets or sets the modulus of the RSA public key used to sign DSi retail games. 69 | /// 70 | /// 71 | /// The data can be found in the ARM9 BIOS of the DSi at position 0x8974. 72 | /// It starts with `95 6F 79 0D` and has 0x80 bytes. 73 | /// 74 | public byte[] PublicModulusRetailGames { get; set; } 75 | } 76 | -------------------------------------------------------------------------------- /src/Ekona/Security/HashInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | using System.Linq; 22 | 23 | namespace SceneGate.Ekona.Security; 24 | 25 | /// 26 | /// Information of a data hash. 27 | /// 28 | public class HashInfo 29 | { 30 | /// 31 | /// Initializes a new instance of the class. 32 | /// 33 | /// The name of the hashing algorithm. 34 | /// The hash data. 35 | public HashInfo(string algoName, byte[] hash) 36 | { 37 | AlgorithmName = algoName; 38 | Hash = hash; 39 | Status = HashStatus.NotValidated; 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the class. 44 | /// 45 | /// The name of the hashing algorithm. 46 | /// The hash data. 47 | /// Value indicating whether this signature is valid. 48 | public HashInfo(string algoName, byte[] hash, bool isValid) 49 | : this(algoName, hash) 50 | { 51 | Status = isValid ? HashStatus.Valid : HashStatus.Invalid; 52 | } 53 | 54 | /// 55 | /// Gets the hashing algorithm name. 56 | /// 57 | public string AlgorithmName { get; } 58 | 59 | /// 60 | /// Gets the hash. 61 | /// 62 | public byte[] Hash { get; private set; } 63 | 64 | /// 65 | /// Gets a value indicating whether the hash is null. 66 | /// 67 | public bool IsNull => Array.TrueForAll(Hash, x => x == 0); 68 | 69 | /// 70 | /// Gets or sets the status of the hash. 71 | /// 72 | public HashStatus Status { get; set; } 73 | 74 | /// 75 | /// Validate this hash against the provided one. 76 | /// 77 | /// The hash to compare. 78 | public void Validate(byte[] actualHash) => 79 | Status = Hash.SequenceEqual(actualHash) ? HashStatus.Valid : HashStatus.Invalid; 80 | 81 | /// 82 | /// Change the hash, like after computing it again. 83 | /// 84 | /// The new hash. 85 | public void ChangeHash(byte[] newHash) 86 | { 87 | Hash = newHash; 88 | Status = HashStatus.Generated; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Ekona/Security/HashStatus.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | namespace SceneGate.Ekona.Security; 21 | 22 | /// 23 | /// Validation status of a hash. 24 | /// 25 | public enum HashStatus 26 | { 27 | /// 28 | /// The hash has not been validated yet. 29 | /// 30 | NotValidated, 31 | 32 | /// 33 | /// The hash has been validated and is valid. 34 | /// 35 | Valid, 36 | 37 | /// 38 | /// The hash has been validated and is not valid. 39 | /// 40 | Invalid, 41 | 42 | /// 43 | /// The hash has been regenerated from the data, so it's valid. 44 | /// 45 | Generated, 46 | } 47 | -------------------------------------------------------------------------------- /src/Ekona/Security/IOExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System.Data.HashFunction; 21 | using System.Data.HashFunction.CRC; 22 | using System.Linq; 23 | using System.Security.Cryptography; 24 | using Yarhl.IO; 25 | 26 | namespace SceneGate.Ekona.Security; 27 | 28 | /// 29 | /// Internal method extensions. 30 | /// 31 | internal static class IOExtensions 32 | { 33 | /// 34 | /// Read a CRC-16 value and verify it matches the region of data. 35 | /// 36 | /// The data reader with the stream to verify. 37 | /// The information about the checksum verification. 38 | public static HashInfo ReadCrc16(this DataReader reader) 39 | { 40 | return new HashInfo("CRC16-MODBUS", reader.ReadBytes(2)); 41 | } 42 | 43 | /// 44 | /// Read a SHA-1 HMAC. 45 | /// 46 | /// The data reader with the stream to read the HMAC. 47 | /// The information about the HMAC verification. 48 | public static HashInfo ReadHmacSha1(this DataReader reader) 49 | { 50 | byte[] hmac = reader.ReadBytes(0x14); 51 | return new HashInfo("HMAC-SHA1", hmac); 52 | } 53 | 54 | /// 55 | /// Read a RSA-SHA1 signature. 56 | /// 57 | /// The data reader with the stream to read the signature. 58 | /// The information about the signature. 59 | public static HashInfo ReadSignatureSha1Rsa(this DataReader reader) 60 | { 61 | byte[] signature = reader.ReadBytes(128); 62 | return new HashInfo("RawRSA-SHA1", signature); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Ekona/Security/NitroCrcGenerator.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System.Data.HashFunction; 21 | using System.Data.HashFunction.CRC; 22 | using System.IO; 23 | using Yarhl.IO; 24 | 25 | namespace SceneGate.Ekona.Security; 26 | 27 | /// 28 | /// Generate CRC compatible with Nitro devices. 29 | /// 30 | public class NitroCrcGenerator 31 | { 32 | /// 33 | /// Generate a CRC16-MODBUS of the data. 34 | /// 35 | /// The data to read. 36 | /// The CRC of the data in little endian. 37 | public byte[] GenerateCrc16(Stream stream) => GenerateCrc16(stream, 0, stream.Length); 38 | 39 | /// 40 | /// Generate a CRC16-MODBUS of the data. 41 | /// 42 | /// The data to read. 43 | /// The offset to start reading data. 44 | /// The length of the data. 45 | /// The CRC of the data in little endian. 46 | public byte[] GenerateCrc16(Stream stream, long offset, long length) 47 | { 48 | using var segment = new DataStream(stream, offset, length); 49 | 50 | ICRC crc = CRCFactory.Instance.Create(CRCConfig.MODBUS); 51 | IHashValue hash = crc.ComputeHash(segment); 52 | return new byte[] { hash.Hash[0], hash.Hash[1] }; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Ekona/Security/TwilightSigner.cs: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2022 SceneGate 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | using System; 21 | using System.IO; 22 | using System.Linq; 23 | using System.Security.Cryptography; 24 | using Org.BouncyCastle.Crypto; 25 | using Org.BouncyCastle.Crypto.Parameters; 26 | using Org.BouncyCastle.Security; 27 | using Yarhl.IO; 28 | 29 | namespace SceneGate.Ekona.Security; 30 | 31 | /// 32 | /// Signer for DSi data. 33 | /// 34 | public class TwilightSigner 35 | { 36 | private const int SignedDataOffset = 0; 37 | private const int SignedDataLength = 0xE00; 38 | 39 | /// 40 | /// Initializes a new instance of the class. 41 | /// 42 | /// The modulus of the public key. 43 | public TwilightSigner(byte[] publicModulus) 44 | { 45 | // In DSi always constant: 65537 46 | Exponent = new byte[] { 0x01, 0x00, 0x01 }; 47 | 48 | // prepend 0 to avoid treating as negative number 49 | PublicModulus = new byte[] { 0 }.Concat(publicModulus).ToArray(); 50 | } 51 | 52 | /// 53 | /// Gets the exponent part of the key. 54 | /// 55 | public byte[] Exponent { get; } 56 | 57 | /// 58 | /// Gets the modulus part of the public key. 59 | /// 60 | public byte[] PublicModulus { get; } 61 | 62 | /// 63 | /// Verify the program signature. 64 | /// 65 | /// Signature to verify. 66 | /// Data to verify the signature against. 67 | /// The validity of the signature. 68 | public HashStatus VerifySignature(byte[] signature, Stream romStream) 69 | { 70 | var exponent = new Org.BouncyCastle.Math.BigInteger(Exponent); 71 | var modulus = new Org.BouncyCastle.Math.BigInteger(PublicModulus); 72 | AsymmetricKeyParameter pubKey = new RsaKeyParameters(false, modulus, exponent); 73 | 74 | // We can't use standard libraries or methods because the signature 75 | // is the raw hash (with PKCS #7 1.5 padding). It is NOT encoded with ASN.1 76 | // as specified in PKCS #1. 77 | // That's why we do manually comparison: 78 | // 1. Decrypt signature with RSA public key 79 | var rsaCipher = CipherUtilities.GetCipher("RSA/NONE/PKCS1Padding"); 80 | rsaCipher.Init(false, pubKey); 81 | byte[] expectedHash = rsaCipher.DoFinal(signature); 82 | 83 | // 2. Calculate actual SHA-1 hash 84 | using var segment = new DataStream(romStream, SignedDataOffset, SignedDataLength); 85 | using var sha1 = SHA1.Create(); 86 | byte[] actualHash = sha1.ComputeHash(segment); 87 | 88 | // 3. Must be equal expected and actual. 89 | return actualHash.SequenceEqual(expectedHash) ? HashStatus.Valid : HashStatus.Invalid; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Tests.runsettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | cobertura 10 | [.*Tests]* 11 | GeneratedCodeAttribute,ExcludeFromCodeCoverageAttribute 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | --------------------------------------------------------------------------------