Skip to content

Instantly share code, notes, and snippets.

@tesnos6921
Last active January 5, 2019 16:58
Show Gist options
  • Save tesnos6921/66d17f694ef570ae8011a53e97ce437d to your computer and use it in GitHub Desktop.
Save tesnos6921/66d17f694ef570ae8011a53e97ce437d to your computer and use it in GitHub Desktop.
patchnso.py + sample patches for SMO 1.2.0

patchnso.py: a simple script to patch the .text sections of nso files and reassemble them

Python 2.7, requires lz4 module to be installed

Usage Instructions:

  1. Using hactool, extract the exefs of your game
  2. Within the exefsdir, there should be "main" and "main.npdm" and possibly a few others
  3. "main" is the game's main nso, you can load it into IDA and create your own patches or go out and find some
  4. When you have your patches put them all in a file in this format: (all values should be in hex) location:codepatch Example: 2E8:21008052
  5. Then run "python patchnso.py main patch-file-here output-file-here"
  6. Your patched file will appear and you can put it under /atmosphere/titles/title-id/exefs/ on your sd card, renamed back to "main", with the other files from your extracted exefs and you have now patched your game!

Sample Patches

With this script, I included some sample patches I made for Super Mario Odyssey Version 1.2.0, try them out (on the correct version) and have fun!

import binascii, struct, hashlib, sys
import lz4.block as block
if len(sys.argv) != 4:
print "patchnso.py: simple script to patch the .text sections of nso files and reassemble them"
print "by tesnos6921, thanks to switchbrew.org for docs and the atmosphere team for exefs patching so early"
print "Usage: patchnso.py nso-file patch-file out-file"
print "patch-file should contain lines formatted like so, with values in hex."
print "location:codepatch, Example: 2E8:21008052"
print "Empty lines or lines beginning with pound signs are ignored."
def padding(padlength):
pad = ""
for i in range(padlength):
pad += "\x00"
return pad
def patchtext(source, patchlist):
patchedsource = source
for patch in patchlist:
if "#" in patch or patch.strip() == "":
continue
patchlocation, patchcode = patch.split(":")
patchlocation = int(patchlocation, 16)
patchcode = binascii.unhexlify(patchcode.strip())
patchedsource = patchedsource[:patchlocation] + patchcode + patchedsource[patchlocation+len(patchcode):]
return patchedsource
main_f = open(sys.argv[1], "rb")
mainsource = main_f.read()
main_f.close()
header = mainsource[:0x100]
textloc = struct.unpack("<I", header[0x10:0x14])[0]
dectextsize = struct.unpack("<I", header[0x18:0x1C])[0]
rodataloc = struct.unpack("<I", header[0x20:0x24])[0]
dataloc = struct.unpack("<I", header[0x30:0x34])[0]
oldtextsize = rodataloc - textloc
rodatasize = dataloc - rodataloc
textsource = mainsource[textloc:textloc+oldtextsize]
textsource = block.decompress(textsource, uncompressed_size=dectextsize)
patch_f = open(sys.argv[2], "r")
patches = patch_f.readlines()
patch_f.close()
textsource = patchtext(textsource, patches)
texthash = hashlib.sha256(textsource).digest()
compressedtext = block.compress(textsource, mode='fast', acceleration=2)
compressedtext = compressedtext[0x4:]
compressedtextsize = len(compressedtext)
rodata = mainsource[rodataloc:rodataloc+rodatasize]
data = mainsource[dataloc:]
newrodataloc = textloc + compressedtextsize
header = header[:0x20] + struct.pack("<I", newrodataloc) + header[0x24:]
newdataloc = newrodataloc + rodatasize
header = header[:0x30] + struct.pack("<I", newdataloc) + header[0x34:]
header = header[:0x60] + struct.pack("<I", compressedtextsize) + header[0x64:]
header = header[:0xA0] + texthash + header[0xC0:]
newmain = header + padding(textloc - len(header)) + compressedtext + rodata + data
newmain_f = open(sys.argv[3], "wb")
newmain_f.write(newmain)
newmain_f.close()
TO BE UPDATED SOON
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment