Skip to content

Instantly share code, notes, and snippets.

@AltimorTASDK
Created August 26, 2022 13:17
Show Gist options
  • Save AltimorTASDK/0b5aabfc0666ef33bf13e49ccd85e00e to your computer and use it in GitHub Desktop.
Save AltimorTASDK/0b5aabfc0666ef33bf13e49ccd85e00e to your computer and use it in GitHub Desktop.
import io
import os
import os.path
import struct
import sys
class NcdFile():
def __init__(self, name, compressed, offset, size):
self.name = name
self.compressed = compressed
self.offset = offset
self.size = size
FILES = [
NcdFile("LOGO.BMP", compressed=True, offset=0x01333C32, size=0x000946),
NcdFile("PSEL.BMP", compressed=True, offset=0x01334578, size=0x0079E2),
NcdFile("TITLE.BMP", compressed=True, offset=0x0133BF5A, size=0x0026B6),
NcdFile("TOKYO.BMP", compressed=True, offset=0x0133E610, size=0x00CEF0),
NcdFile("SE.NCD", compressed=True, offset=0x0134B500, size=0x023752),
NcdFile("MANUALBG.BMP", compressed=True, offset=0x0136EC52, size=0x00B336),
NcdFile("SIGN.BMP", compressed=True, offset=0x01379F88, size=0x004A7A),
NcdFile("GRAD.BMP", compressed=True, offset=0x0137EA02, size=0x003034),
NcdFile("AE.FRM", compressed=True, offset=0x026B5648, size=0x002234),
NcdFile("AE.SP", compressed=True, offset=0x026B787C, size=0x012479),
NcdFile("AE.PAL", compressed=True, offset=0x026C9CF5, size=0x00011B),
NcdFile("AE.VSE", compressed=True, offset=0x026C9E10, size=0x0032C4),
NcdFile("AE.NCD", compressed=True, offset=0x026CD0D4, size=0x00D4C5),
NcdFile("AS.FRM", compressed=True, offset=0x03A0E1AB, size=0x003F99),
NcdFile("AS.SP", compressed=True, offset=0x03A12144, size=0x012BE2),
NcdFile("AS.PAL", compressed=True, offset=0x03A24D26, size=0x000128),
NcdFile("AS.VSE", compressed=True, offset=0x03A24E4E, size=0x0033E6),
NcdFile("AS.NCD", compressed=True, offset=0x03A28234, size=0x0175D8),
NcdFile("CH.FRM", compressed=True, offset=0x04D7341E, size=0x003DA1),
NcdFile("CH.SP", compressed=True, offset=0x04D771BF, size=0x0138FD),
NcdFile("CH.PAL", compressed=True, offset=0x04D8AABC, size=0x000129),
NcdFile("CH.VSE", compressed=True, offset=0x04D8ABE5, size=0x0036B2),
NcdFile("CH.NCD", compressed=True, offset=0x04D8E297, size=0x017C70),
NcdFile("FU.FRM", compressed=True, offset=0x060D9B19, size=0x003CF0),
NcdFile("FU.SP", compressed=True, offset=0x060DD809, size=0x00FD54),
NcdFile("FU.PAL", compressed=True, offset=0x060ED55D, size=0x000103),
NcdFile("FU.VSE", compressed=True, offset=0x060ED660, size=0x0035DA),
NcdFile("FU.NCD", compressed=True, offset=0x060F0C3A, size=0x013172),
NcdFile("HA.FRM", compressed=True, offset=0x074379BE, size=0x002B45),
NcdFile("HA.SP", compressed=True, offset=0x0743A503, size=0x0122A2),
NcdFile("HA.PAL", compressed=True, offset=0x0744C7A5, size=0x0000FE),
NcdFile("HA.VSE", compressed=True, offset=0x0744C8A3, size=0x002F58),
NcdFile("HA.NCD", compressed=True, offset=0x0744F7FB, size=0x0188C3),
NcdFile("HI.FRM", compressed=True, offset=0x0879BCD0, size=0x004677),
NcdFile("HI.SP", compressed=True, offset=0x087A0347, size=0x014895),
NcdFile("HI.PAL", compressed=True, offset=0x087B4BDC, size=0x0000EB),
NcdFile("HI.VSE", compressed=True, offset=0x087B4CC7, size=0x0033AC),
NcdFile("HI.NCD", compressed=True, offset=0x087B8073, size=0x00AAB7),
NcdFile("HO.FRM", compressed=True, offset=0x09AF673C, size=0x004321),
NcdFile("HO.SP", compressed=True, offset=0x09AFAA5D, size=0x0126FB),
NcdFile("HO.PAL", compressed=True, offset=0x09B0D158, size=0x0000E8),
NcdFile("HO.VSE", compressed=True, offset=0x09B0D240, size=0x003895),
NcdFile("HO.NCD", compressed=True, offset=0x09B10AD5, size=0x011CF4),
NcdFile("KA.FRM", compressed=True, offset=0x0AE563DB, size=0x001E7D),
NcdFile("KA.SP", compressed=True, offset=0x0AE58258, size=0x00AF8D),
NcdFile("KA.PAL", compressed=True, offset=0x0AE631E5, size=0x000118),
NcdFile("KA.VSE", compressed=True, offset=0x0AE632FD, size=0x002A70),
NcdFile("KA.NCD", compressed=True, offset=0x0AE65D6D, size=0x013D79),
NcdFile("RE.FRM", compressed=True, offset=0x0C1AD6F8, size=0x00432C),
NcdFile("RE.SP", compressed=True, offset=0x0C1B1A24, size=0x0117DC),
NcdFile("RE.PAL", compressed=True, offset=0x0C1C3200, size=0x000144),
NcdFile("RE.VSE", compressed=True, offset=0x0C1C3344, size=0x0039A9),
NcdFile("RE.NCD", compressed=True, offset=0x0C1C6CED, size=0x00E806),
NcdFile("RR.FRM", compressed=True, offset=0x0D509105, size=0x003FF4),
NcdFile("RR.SP", compressed=True, offset=0x0D50D0F9, size=0x012848),
NcdFile("RR.PAL", compressed=True, offset=0x0D51F941, size=0x00011C),
NcdFile("RR.VSE", compressed=True, offset=0x0D51FA5D, size=0x0030A6),
NcdFile("RR.NCD", compressed=True, offset=0x0D522B03, size=0x0182EC),
NcdFile("RY.FRM", compressed=True, offset=0x0E86EA01, size=0x002F88),
NcdFile("RY.SP", compressed=True, offset=0x0E871989, size=0x012A82),
NcdFile("RY.PAL", compressed=True, offset=0x0E88440B, size=0x0000AF),
NcdFile("RY.VSE", compressed=True, offset=0x0E8844BA, size=0x002E6D),
NcdFile("RY.NCD", compressed=True, offset=0x0E887327, size=0x019A27),
NcdFile("UM.FRM", compressed=True, offset=0x0FBD4960, size=0x003470),
NcdFile("UM.SP", compressed=True, offset=0x0FBD7DD0, size=0x010061),
NcdFile("UM.PAL", compressed=True, offset=0x0FBE7E31, size=0x0000F1),
NcdFile("UM.VSE", compressed=True, offset=0x0FBE7F22, size=0x002DB8),
NcdFile("UM.NCD", compressed=True, offset=0x0FBEACDA, size=0x00AE7F),
NcdFile("US.FRM", compressed=True, offset=0x10F2976B, size=0x00386F),
NcdFile("US.SP", compressed=True, offset=0x10F2CFDA, size=0x012573),
NcdFile("US.PAL", compressed=True, offset=0x10F3F54D, size=0x0000F8),
NcdFile("US.VSE", compressed=True, offset=0x10F3F645, size=0x0031A8),
NcdFile("US.NCD", compressed=True, offset=0x10F427ED, size=0x014906),
NcdFile("MA.FRM", compressed=True, offset=0x1228AD05, size=0x005027),
NcdFile("MA.SP", compressed=True, offset=0x1228FD2C, size=0x01ACD2),
NcdFile("MA.PAL", compressed=True, offset=0x122AA9FE, size=0x0000E7),
NcdFile("MA.VSE", compressed=True, offset=0x122AAAE5, size=0x00412F),
NcdFile("MA.NCD", compressed=False, offset=0x122AEC14, size=0x017CC8)
]
def decompress(data: io.BufferedIOBase, out: io.BufferedIOBase):
def read():
result = data.read(1)
if len(result) == 0:
raise EOFError
return result[0]
def write(value):
out.write(bytes([value]))
nonlocal history_index
history[history_index] = value
history_index = (history_index + 1) % HISTORY_SIZE
HISTORY_SIZE = 0x1000
history = [0] * HISTORY_SIZE
history_index = 0xFEE
try:
while True:
mask = read()
for bit in range(8):
if mask & (1 << bit) != 0:
write(read())
else:
b1, b2 = (read(), read())
copy_size = (b2 & 0xF) + 3
copy_start = b1 | ((b2 & 0xF0) << 4)
for offset in range(0, copy_size):
write(history[(copy_start + offset) % HISTORY_SIZE])
except EOFError:
return
def extract_sound_ncd(ncd: io.BufferedIOBase, out_directory):
_, _, flags, count = struct.unpack("IIII", ncd.read(0x10))
if (flags & 1) == 0:
raise Exception("Sound NCD must have flag bit 0")
os.makedirs(out_directory, exist_ok=True)
for index in range(count):
ncd.seek(0x20 + index * 0x10)
offset, _, _, size = struct.unpack("IIII", ncd.read(0x10))
ncd.seek(offset)
data = ncd.read(size)
with open(os.path.join(out_directory, f"{index}.wav"), "wb") as out:
out.write(data)
def main():
if len(sys.argv) < 3:
print("Usage: extract_ncd.py <LIGHTS2.NCD> <out directory>",
file=sys.stderr)
sys.exit(1)
ncd_path, out_directory = sys.argv[1:3]
with open(ncd_path, "rb") as ncd:
os.makedirs(out_directory, exist_ok=True)
for file in FILES:
ncd.seek(file.offset)
data = ncd.read(file.size)
if file.compressed:
buffer = io.BytesIO()
decompress(io.BytesIO(data), buffer)
decompressed = buffer.getvalue()
else:
decompressed = data
out_path = os.path.join(out_directory, file.name)
with open(out_path, "wb") as out:
out.write(decompressed)
if file.name.endswith(".NCD"):
extract_sound_ncd(io.BytesIO(decompressed), out_path[:-4])
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment