[
  {
    "path": "LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org>\n"
  },
  {
    "path": "README.md",
    "content": "# splitNSP\n\nAs some have become aware, it's been found out that the official Nintendo SDK contains a PowerShell script for splitting NSP files into 4GiB chunks so that they can be installed from FAT32 filesystems. Seeing as the official script cannot be shared, I re-wrote it in Python3 (which makes it useable on more than just Windows) as well as added in an additional feature. \n\nTo run it you'll need Python3 installed. Once installed, call the script from Terminal or Command Prompt with the following:\n\n```python3 splitNSP.py filename.nsp```\n\nBy default this will make a copy of the NSP and split it up into parts. Once created, you'll need to open the folder's properties and check the Archive flag. This is easily done on Windows, I'm still working on a way to do it for macOS since file flags aren't saved when copying to FAT32. You can also set the archive flag on a folder directly on the Switch using a homebrew such as NX-Shell.\n\nYou can also activate quick mode with this command:\n\n```python3 splitNSP.py -q filename.nsp```\n\nThis will not make a copy of the NSP and instead will split the original. This is useful if you're running low on space as it only requires that you have 4GiB of temporary space to run it. It's also much faster. \n\nOnce the folder is made and the archive flag is set copy it to your SD card (sdmc:/tinfoil/nsp/ if using tinfoil) and install it like any other NSP. \n\nIf you have any issues feel free to submit an issue and I'll try my best to work it out. \n"
  },
  {
    "path": "splitNSP.py",
    "content": "#!/usr/bin/env python3\n# Author: AnalogMan\n# Modified Date: 2018-10-08\n# Purpose: Splits Nintendo Switch NSP files into parts for installation on FAT32\n\nimport os\nimport argparse\nimport shutil\nfrom datetime import datetime\nstartTime = datetime.now()\n\nsplitSize = 0xFFFF0000 # 4,294,901,760 bytes\nchunkSize = 0x8000 # 32,768 bytes\n\ndef splitQuick(filepath):\n    fileSize = os.path.getsize(filepath)\n    info = shutil.disk_usage(os.path.dirname(os.path.abspath(filepath)))\n    if info.free < splitSize:\n        print('Not enough temporary space. Needs 4GiB of free space\\n')\n        return\n    print('Calculating number of splits...\\n')\n    splitNum = int(fileSize/splitSize)\n    if splitNum == 0:\n        print('This NSP is under 4GiB and does not need to be split.\\n')\n        return\n\n    print('Splitting NSP into {0} parts...\\n'.format(splitNum + 1))\n    \n    # Create directory, delete if already exists\n    dir = filepath[:-4] + '_split.nsp'\n    if os.path.exists(dir):\n        shutil.rmtree(dir)\n    os.makedirs(dir)\n\n    # Move input file to directory and rename it to first part\n    filename = os.path.basename(filepath)\n    shutil.move(filepath, os.path.join(dir, '00'))\n    filepath = os.path.join(dir, '00')\n\n    # Calculate size of final part to copy first\n    finalSplitSize = fileSize - (splitSize * splitNum)\n\n    # Copy final part and trim from main file\n    with open(filepath, 'r+b') as nspFile:\n        nspFile.seek(finalSplitSize * -1, os.SEEK_END)\n        outFile = os.path.join(dir, '{:02}'.format(splitNum))\n        partSize = 0\n        print('Starting part {:02}'.format(splitNum))\n        with open(outFile, 'wb') as splitFile:\n            while partSize < finalSplitSize:\n                splitFile.write(nspFile.read(chunkSize))\n                partSize += chunkSize\n        nspFile.seek(finalSplitSize * -1, os.SEEK_END)\n        nspFile.truncate()\n        print('Part {:02} complete'.format(splitNum))\n\n    # Loop through additional parts and trim\n    with open(filepath, 'r+b') as nspFile:\n        for i in range(splitNum - 1):\n            nspFile.seek(splitSize * -1, os.SEEK_END)\n            outFile = os.path.join(dir, '{:02}'.format(splitNum - (i + 1)))\n            partSize = 0\n            print('Starting part {:02}'.format(splitNum - (i + 1)))\n            with open(outFile, 'wb') as splitFile:\n                 while partSize < splitSize:\n                    splitFile.write(nspFile.read(chunkSize))\n                    partSize += chunkSize\n            nspFile.seek(splitSize * -1, os.SEEK_END)\n            nspFile.truncate()\n            print('Part {:02} complete'.format(splitNum - (i + 1)))\n    \n    # Print assurance statement for user\n    print('Starting part 00\\nPart 00 complete')\n\n    print('\\nNSP successfully split!\\n')\n    \ndef splitCopy(filepath, output_dir=\"\"):\n    fileSize = os.path.getsize(filepath)\n    info = shutil.disk_usage(os.path.dirname(os.path.abspath(filepath)))\n    if info.free < fileSize*2:\n        print('Not enough free space to run. Will require twice the space as the NSP file\\n')\n        return\n    print('Calculating number of splits...\\n')\n    splitNum = int(fileSize/splitSize)\n    if splitNum == 0:\n        print('This NSP is under 4GiB and does not need to be split.\\n')\n        return\n    \n    print('Splitting NSP into {0} parts...\\n'.format(splitNum + 1))\n\n    # Create directory, delete if already exists\n    if output_dir == \"\":\n        dir = filepath[:-4] + '_split.nsp'\n    else:\n        if output_dir[-4:] != '.nsp':\n            output_dir+= \".nsp\"\n        dir = output_dir\n    if os.path.exists(dir):\n        shutil.rmtree(dir)\n    os.makedirs(dir)\n\n    remainingSize = fileSize\n\n    # Open source file and begin writing to output files stoping at splitSize\n    with open(filepath, 'rb') as nspFile:\n        for i in range(splitNum + 1):\n            partSize = 0\n            print('Starting part {:02}'.format(i))\n            outFile = os.path.join(dir, '{:02}'.format(i))\n            with open(outFile, 'wb') as splitFile: \n                if remainingSize > splitSize:\n                    while partSize < splitSize:\n                        splitFile.write(nspFile.read(chunkSize))\n                        partSize += chunkSize\n                    remainingSize -= splitSize\n                else:\n                    while partSize < remainingSize:\n                        splitFile.write(nspFile.read(chunkSize))\n                        partSize += chunkSize\n            print('Part {:02} complete'.format(i))\n    print('\\nNSP successfully split!\\n')\n\ndef main():\n    print('\\n========== NSP Splitter ==========\\n')\n\n    # Arg parser for program options\n    parser = argparse.ArgumentParser(description='Split NSP files into FAT32 compatible sizes')\n    parser.add_argument('filepath', help='Path to NSP file')\n    group = parser.add_mutually_exclusive_group()\n    group.add_argument('-q', '--quick', action='store_true', help='Splits file in-place without creating a copy. Only requires 4GiB free space to run')\n    group.add_argument('-o', '--output-dir', type=str, default=\"\",\n                        help=\"Set alternative output dir\")\n\n    # Check passed arguments\n    args = parser.parse_args()\n\n    filepath = args.filepath\n\n    # Check if required files exist\n    if os.path.isfile(filepath) == False:\n        print('NSP cannot be found\\n')\n        return 1\n    \n    # Split NSP file\n    if args.quick:\n        splitQuick(filepath)\n    else:\n        splitCopy(filepath, args.output_dir)\n\nif __name__ == \"__main__\":\n    main()\n"
  }
]