├── .gitattributes ├── .gitignore ├── Content └── dir.txt ├── LICENSE ├── README.md ├── bin ├── oggdec.exe ├── python │ ├── _ctypes.pyd │ ├── _elementtree.pyd │ ├── _hashlib.pyd │ ├── _multiprocessing.pyd │ ├── _socket.pyd │ ├── _sqlite3.pyd │ ├── _ssl.pyd │ ├── bz2.pyd │ ├── lxml.etree.pyd │ ├── lxml.objectify.pyd │ ├── pyexpat.pyd │ ├── python27.dll │ ├── python27.zip │ ├── python_mcp.exe │ ├── select.pyd │ ├── sqlite3.dll │ ├── unicodedata.pyd │ └── w9xpopen.exe └── xnbdecomp.exe ├── fez_decomp.bat ├── fez_decomp.py ├── fez_music_unpack.bat ├── fez_music_unpack.py ├── fez_unpack.bat ├── fez_unpack.py ├── full_export.bat ├── full_export.sh ├── identify.bat ├── identify.py ├── ogg_decode.bat ├── read_xact.bat ├── read_xact.py ├── read_xnb.bat ├── read_xnb.py ├── read_xnb_dir.bat ├── read_xnb_dir.py ├── show_ao.bat ├── show_ao.py ├── xma_decode.bat ├── xna_native.bat ├── xna_native.py ├── xnb_decomp.bat ├── xnb_decomp.py ├── xnb_decomp_net.bat ├── xnb_parse ├── __init__.py ├── binstream.py ├── fez_content_manager.py ├── fez_decomp.py ├── fez_music_content_manager.py ├── fez_music_unpack.py ├── fez_unpack.py ├── file_formats │ ├── __init__.py │ ├── img_decode.py │ ├── png.py │ ├── wav.py │ └── xml_utils.py ├── identify.py ├── read_xact.py ├── read_xnb.py ├── read_xnb_dir.py ├── show_ao.py ├── trackball_camera.py ├── type_reader.py ├── type_reader_manager.py ├── type_readers │ ├── __init__.py │ ├── fez │ │ ├── __init__.py │ │ ├── fez_basic.py │ │ ├── fez_graphics.py │ │ ├── fez_level.py │ │ └── fez_music.py │ ├── mercury │ │ ├── __init__.py │ │ ├── basic.py │ │ ├── emitters.py │ │ ├── modifiers.py │ │ └── particle.py │ ├── xna_graphics.py │ ├── xna_math.py │ ├── xna_media.py │ ├── xna_primitive.py │ └── xna_system.py ├── type_spec.py ├── xact │ ├── __init__.py │ ├── xgs.py │ ├── xsb.py │ └── xwb.py ├── xna_content_manager.py ├── xna_native.py ├── xna_types │ ├── __init__.py │ ├── fez │ │ ├── __init__.py │ │ ├── fez_basic.py │ │ ├── fez_graphics.py │ │ ├── fez_level.py │ │ └── fez_music.py │ ├── mercury │ │ ├── __init__.py │ │ ├── basic.py │ │ ├── emitters.py │ │ ├── modifiers.py │ │ └── particle.py │ ├── xna_graphics.py │ ├── xna_math.py │ ├── xna_media.py │ ├── xna_primitive.py │ └── xna_system.py ├── xnb_decomp.py └── xnb_reader.py └── xwma_decode.bat /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # bat/shell files 5 | *.bat text eol=crlf 6 | *.cmd text eol=crlf 7 | *.sh text eol=lf 8 | 9 | # source 10 | *.py text 11 | 12 | # binaries 13 | *.dll binary 14 | *.exe binary 15 | *.pyd binary 16 | *.zip binary 17 | *.xnb binary 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.bak 3 | .idea/ 4 | *.pyc 5 | *.pyo 6 | __pycache__/ 7 | Content*/ 8 | export*/ 9 | out*/ 10 | static*/ 11 | bin/*.exe 12 | bin/*.dll 13 | -------------------------------------------------------------------------------- /Content/dir.txt: -------------------------------------------------------------------------------- 1 | Essentials.pak and Other.pak go here 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2014 Andrew McRae 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | xnb_parse 2 | ========= 3 | 4 | XNA xnb/xact file parser, and FEZ extractor/viewer 5 | 6 | License 7 | ------- 8 | 9 | Copyright (c) Andrew McRae. Distributed under an [MIT License](LICENSE). 10 | -------------------------------------------------------------------------------- /bin/oggdec.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/oggdec.exe -------------------------------------------------------------------------------- /bin/python/_ctypes.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/_ctypes.pyd -------------------------------------------------------------------------------- /bin/python/_elementtree.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/_elementtree.pyd -------------------------------------------------------------------------------- /bin/python/_hashlib.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/_hashlib.pyd -------------------------------------------------------------------------------- /bin/python/_multiprocessing.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/_multiprocessing.pyd -------------------------------------------------------------------------------- /bin/python/_socket.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/_socket.pyd -------------------------------------------------------------------------------- /bin/python/_sqlite3.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/_sqlite3.pyd -------------------------------------------------------------------------------- /bin/python/_ssl.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/_ssl.pyd -------------------------------------------------------------------------------- /bin/python/bz2.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/bz2.pyd -------------------------------------------------------------------------------- /bin/python/lxml.etree.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/lxml.etree.pyd -------------------------------------------------------------------------------- /bin/python/lxml.objectify.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/lxml.objectify.pyd -------------------------------------------------------------------------------- /bin/python/pyexpat.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/pyexpat.pyd -------------------------------------------------------------------------------- /bin/python/python27.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/python27.dll -------------------------------------------------------------------------------- /bin/python/python27.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/python27.zip -------------------------------------------------------------------------------- /bin/python/python_mcp.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/python_mcp.exe -------------------------------------------------------------------------------- /bin/python/select.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/select.pyd -------------------------------------------------------------------------------- /bin/python/sqlite3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/sqlite3.dll -------------------------------------------------------------------------------- /bin/python/unicodedata.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/unicodedata.pyd -------------------------------------------------------------------------------- /bin/python/w9xpopen.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/python/w9xpopen.exe -------------------------------------------------------------------------------- /bin/xnbdecomp.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fesh0r/xnb_parse/cc3aef8fff4be1320068a78001795e7e1c8bd548/bin/xnbdecomp.exe -------------------------------------------------------------------------------- /fez_decomp.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "%~dp0bin\python\python_mcp.exe" "%~dpn0.py" %* 3 | -------------------------------------------------------------------------------- /fez_decomp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Extract and decompress FEZ .pak files 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from xnb_parse.fez_decomp import main 9 | 10 | 11 | if __name__ == '__main__': 12 | main() 13 | -------------------------------------------------------------------------------- /fez_music_unpack.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "%~dp0bin\python\python_mcp.exe" "%~dpn0.py" %* 3 | -------------------------------------------------------------------------------- /fez_music_unpack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Extract oggs from FEZ PC Music.pak file 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from xnb_parse.fez_music_unpack import main 9 | 10 | 11 | if __name__ == '__main__': 12 | main() 13 | -------------------------------------------------------------------------------- /fez_unpack.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "%~dp0bin\python\python_mcp.exe" "%~dpn0.py" %* 3 | -------------------------------------------------------------------------------- /fez_unpack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Extract FEZ .pak files 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from xnb_parse.fez_unpack import main 9 | 10 | 11 | if __name__ == '__main__': 12 | main() 13 | -------------------------------------------------------------------------------- /full_export.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if exist Content\Essentials.pak if exist Content\Other.pak goto content 4 | echo *** Essentials.pak and Other.pak not found in Content 5 | goto music 6 | 7 | :content 8 | echo Checking for XNA runtime 9 | call xna_native.bat 2>NUL 10 | if errorlevel 1 goto no_xna 11 | 12 | echo Decompressing Essentials.pak, Other.pak and Updates.pak... 13 | call fez_decomp.bat Content out 14 | if errorlevel 1 goto error 15 | goto convert 16 | 17 | :no_xna 18 | echo Unpacking Essentials.pak, Other.pak and Updates.pak... 19 | call fez_unpack.bat Content out_c 20 | if errorlevel 1 goto error 21 | 22 | echo Decompressing XNBs... 23 | call xnb_decomp_net.bat out_c out 24 | if errorlevel 1 goto error 25 | 26 | :convert 27 | echo Converting XNBs... 28 | call read_xnb_dir.bat out export 29 | if errorlevel 1 goto error 30 | 31 | :music 32 | if exist Content\Music.pak goto music_pak 33 | if exist "Content\Music\XACT Music.xgs" if exist "Content\Music\Sound Bank.xsb" if exist "Content\Music\Sound Bank.xsb" goto music_xact 34 | echo *** Music.pak or Music folder not found in Content 35 | goto end 36 | 37 | :music_pak 38 | echo Extracting Music.pak... 39 | call fez_music_unpack.bat Content export\Music 40 | if errorlevel 1 goto error 41 | 42 | echo Decoding OGGs... 43 | call ogg_decode.bat export 44 | 45 | goto end 46 | 47 | :music_xact 48 | echo Converting XACT Music... 49 | call read_xact.bat "Content\Music\XACT Music.xgs" "Content\Music\Sound Bank.xsb" "Content\Music\Wave Bank.xwb" export\Music 50 | if errorlevel 1 goto error 51 | 52 | echo Decoding XMAs... 53 | call xma_decode.bat export 54 | 55 | echo Decoding xWMAs... 56 | call xwma_decode.bat export 57 | 58 | goto end 59 | 60 | :error 61 | echo *** BANG *** 62 | exit /b 1 63 | 64 | :end 65 | -------------------------------------------------------------------------------- /full_export.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! command -v python 2>&1 >/dev/null 4 | then 5 | echo "*** python required" 6 | exit 1 7 | fi 8 | 9 | if ! python xna_native.py 2>/dev/null 10 | then 11 | if ! command -v mono 2>&1 >/dev/null 12 | then 13 | echo "*** mono required" 14 | exit 1 15 | fi 16 | fi 17 | 18 | if [ -e "Content/Essentials.pak" -a -e "Content/Other.pak" ] 19 | then 20 | if python xna_native.py 2>/dev/null 21 | then 22 | echo "Decompressing Essentials.pak, Other.pak and Updates.pak..." 23 | python fez_decomp.py Content out || exit 1 24 | else 25 | echo "Unpacking Essentials.pak, Other.pak and Updates.pak..." 26 | python fez_unpack.py Content out_c || exit 1 27 | 28 | echo "Decompressing XNBs..." 29 | mono bin/xnbdecomp.exe out_c out || exit 1 30 | fi 31 | 32 | echo "Converting XNBs..." 33 | python read_xnb_dir.py out export || exit 1 34 | else 35 | echo "*** Essentials.pak, Other.pak and Updates.pak not found in Content" 36 | fi 37 | 38 | if [ -e "Content/Music.pak" ] 39 | then 40 | echo "Extracting Music.pak..." 41 | python fez_music_unpack.py Content export/music || exit 1 42 | elif [ -e "Content/Music/XACT Music.xgs" -a -e "Content/Music/Sound Bank.xsb" -a -e "Content/Music/Wave Bank.xwb" ] 43 | then 44 | echo "Converting XACT Music..." 45 | python read_xact.py "Content/Music/XACT Music.xgs" "Content/Music/Sound Bank.xsb" "Content/Music/Wave Bank.xwb" export/music || exit 1 46 | else 47 | echo "*** Music.pak or Music folder not found in Content" 48 | fi 49 | -------------------------------------------------------------------------------- /identify.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "%~dp0bin\python\python_mcp.exe" "%~dpn0.py" %* 3 | -------------------------------------------------------------------------------- /identify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Identify file data based on content 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from xnb_parse.identify import main 9 | 10 | 11 | if __name__ == '__main__': 12 | main() 13 | -------------------------------------------------------------------------------- /ogg_decode.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set BIN_DIR=%~dp0bin\ 4 | 5 | if exist %BIN_DIR%oggdec.exe goto ogg 6 | echo *** oggdec.exe required to decode ogg files *** 7 | exit /B 1 8 | 9 | :ogg 10 | for /R %1 %%F in (*.ogg) do %BIN_DIR%oggdec.exe "%%F" -w "%%~dpnF.wav" 11 | goto end 12 | 13 | :end 14 | -------------------------------------------------------------------------------- /read_xact.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "%~dp0bin\python\python_mcp.exe" "%~dpn0.py" %* 3 | -------------------------------------------------------------------------------- /read_xact.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Dump info from XACT files 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from xnb_parse.read_xact import main 9 | 10 | 11 | if __name__ == '__main__': 12 | main() 13 | -------------------------------------------------------------------------------- /read_xnb.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "%~dp0bin\python\python_mcp.exe" "%~dpn0.py" %* 3 | -------------------------------------------------------------------------------- /read_xnb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Dump info from XNB 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from xnb_parse.read_xnb import main 9 | 10 | 11 | if __name__ == '__main__': 12 | main() 13 | -------------------------------------------------------------------------------- /read_xnb_dir.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "%~dp0bin\python\python_mcp.exe" "%~dpn0.py" %* 3 | -------------------------------------------------------------------------------- /read_xnb_dir.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Dump info from directory of XNBs 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from xnb_parse.read_xnb_dir import main 9 | 10 | 11 | if __name__ == '__main__': 12 | main() 13 | -------------------------------------------------------------------------------- /show_ao.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "%~dp0bin\python\python_mcp.exe" "%~dpn0.py" %* 3 | -------------------------------------------------------------------------------- /show_ao.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Show ArtObject from XNB 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from xnb_parse.show_ao import main 9 | 10 | 11 | if __name__ == '__main__': 12 | main() 13 | -------------------------------------------------------------------------------- /xma_decode.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set BIN_DIR=%~dp0bin\ 4 | 5 | if exist %BIN_DIR%xma2encode.exe goto xma2 6 | if exist %BIN_DIR%xmaencode.exe goto xma 7 | echo *** xmaencode.exe or xma2encode.exe required to decode xma files *** 8 | exit /B 1 9 | 10 | :xma2 11 | for /R %1 %%F in (*.xma) do %BIN_DIR%xma2encode.exe "%%F" /DecodeToPCM "%%~dpnF.wav" /Verbose 12 | goto end 13 | 14 | :xma 15 | for /R %1 %%F in (*.xma) do %BIN_DIR%xmaencode.exe "%%F" /X "%%~dpnF.wav" /V 16 | goto end 17 | 18 | :end 19 | -------------------------------------------------------------------------------- /xna_native.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "%~dp0bin\python\python_mcp.exe" "%~dpn0.py" %* 3 | -------------------------------------------------------------------------------- /xna_native.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Check is XnaNative.dll is available 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from xnb_parse.xna_native import main 9 | 10 | 11 | if __name__ == '__main__': 12 | main() 13 | -------------------------------------------------------------------------------- /xnb_decomp.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | "%~dp0bin\python\python_mcp.exe" "%~dpn0.py" %* 3 | -------------------------------------------------------------------------------- /xnb_decomp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Decompress XNB files. 4 | Requires win32 5 | """ 6 | 7 | from __future__ import print_function 8 | 9 | from xnb_parse.xnb_decomp import main 10 | 11 | 12 | if __name__ == '__main__': 13 | main() 14 | -------------------------------------------------------------------------------- /xnb_decomp_net.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set BIN_DIR=%~dp0bin\ 4 | 5 | if exist %BIN_DIR%xnbdecomp.exe goto xnbdecomp 6 | echo *** xnbdecomp.exe required to decompress XNB files *** 7 | exit /B 1 8 | 9 | :xnbdecomp 10 | %BIN_DIR%xnbdecomp.exe %1 %2 11 | goto end 12 | 13 | :end 14 | -------------------------------------------------------------------------------- /xnb_parse/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MS XNB file parser 3 | """ 4 | 5 | from __future__ import print_function 6 | -------------------------------------------------------------------------------- /xnb_parse/binstream.py: -------------------------------------------------------------------------------- 1 | """ 2 | .NET BinaryStream reader 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import struct 8 | import sys 9 | from io import BytesIO, SEEK_END 10 | 11 | 12 | _TYPE_FMT = ['Q', 'q', 'I', 'i', 'H', 'h', 'B', 'b', 'f', 'd', '?'] 13 | 14 | 15 | class BinaryStream(BytesIO): 16 | def __init__(self, data=None, filename=None, big_endian=False): 17 | if filename is not None: 18 | with open(filename, 'rb') as file_handle: 19 | data = file_handle.read() 20 | BytesIO.__init__(self, data) 21 | self._types = {k: None for k in _TYPE_FMT} 22 | self.set_endian(big_endian) 23 | 24 | def set_endian(self, big_endian=False): 25 | self.big_endian = big_endian 26 | if self.big_endian: 27 | self._fmt_end = '>' 28 | else: 29 | self._fmt_end = '<' 30 | self._types = {k: struct.Struct(self._fmt_end + k) for k, v in self._types.items()} 31 | 32 | def peek(self, count): 33 | cur_pos = self.tell() 34 | value = self.read(count) 35 | self.seek(cur_pos) 36 | return value 37 | 38 | def write_file(self, filename): 39 | with open(filename, 'wb') as file_handle: 40 | file_handle.write(self.getvalue()) 41 | 42 | def length(self): 43 | cur_pos = self.tell() 44 | cur_len = self.seek(0, SEEK_END) 45 | self.seek(cur_pos) 46 | return cur_len 47 | 48 | def read_7bit_encoded_int(self): 49 | value = 0 50 | shift = 0 51 | while shift < 32: 52 | val = self._types['B'].unpack(self.read(1))[0] 53 | value |= (val & 0x7F) << shift 54 | if val & 128 == 0: 55 | return value 56 | shift += 7 57 | raise ValueError("Shift out of range") 58 | 59 | def write_7bit_encoded_int(self, value): 60 | temp = value 61 | bytes_written = 0 62 | while temp >= 128: 63 | self.write(self._types['B'].pack(temp & 0xff | 0x80)) 64 | bytes_written += 1 65 | temp >>= 7 66 | self.write(self._types['B'].pack(temp)) 67 | bytes_written += 1 68 | return bytes_written 69 | 70 | def read_char(self): 71 | raw_value = self._types['B'].unpack(self.read(1))[0] 72 | byte_count = 0 73 | while raw_value & (0x80 >> byte_count): 74 | byte_count += 1 75 | raw_value &= (1 << (8 - byte_count)) - 1 76 | while byte_count > 1: 77 | raw_value <<= 6 78 | raw_value |= self._types['B'].unpack(self.read(1))[0] & 0x3f 79 | byte_count -= 1 80 | if sys.version < '3': 81 | return unichr(raw_value) 82 | else: 83 | return chr(raw_value) 84 | 85 | def write_char(self, value): 86 | return self.write(value.encode('utf-8')) 87 | 88 | def read_string(self): 89 | size = self.read_7bit_encoded_int() 90 | raw_value = self.read(size) 91 | return raw_value.decode('utf-8') 92 | 93 | def write_string(self, value): 94 | raw_value = value.encode('utf-8') 95 | bytes_written = self.write_7bit_encoded_int(len(raw_value)) 96 | bytes_written += self.write(raw_value) 97 | return bytes_written 98 | 99 | def read_cstring(self, encoding='utf-8'): 100 | raw_value = bytearray() 101 | cur_byte = self.read(1) 102 | while cur_byte != b'\x00' and cur_byte != b'': 103 | raw_value += cur_byte 104 | cur_byte = self.read(1) 105 | return raw_value.decode(encoding) 106 | 107 | def write_cstring(self, value, encoding='utf-8'): 108 | raw_value = value.encode(encoding) 109 | bytes_written = self.write(raw_value) 110 | bytes_written += self.write_byte(0) 111 | return bytes_written 112 | 113 | def unpack(self, fmt): 114 | if fmt not in self._types: 115 | self._types[fmt] = struct.Struct(self._fmt_end + fmt) 116 | return self._types[fmt].unpack(self.read(self._types[fmt].size)) 117 | 118 | def pack(self, fmt, *values): 119 | if fmt not in self._types: 120 | self._types[fmt] = struct.Struct(self._fmt_end + fmt) 121 | return self.write(self._types[fmt].pack(*values)) 122 | 123 | def calc_size(self, fmt): 124 | if fmt not in self._types: 125 | self._types[fmt] = struct.Struct(self._fmt_end + fmt) 126 | return self._types[fmt].size 127 | 128 | def read_byte(self): 129 | return self._types['B'].unpack(self.read(1))[0] 130 | 131 | def write_byte(self, value): 132 | return self.write(self._types['B'].pack(value)) 133 | 134 | def read_sbyte(self): 135 | return self._types['b'].unpack(self.read(1))[0] 136 | 137 | def write_sbyte(self, value): 138 | return self.write(self._types['b'].pack(value)) 139 | 140 | def read_int16(self): 141 | return self._types['h'].unpack(self.read(2))[0] 142 | 143 | def write_int16(self, value): 144 | return self.write(self._types['h'].pack(value)) 145 | 146 | def read_uint16(self): 147 | return self._types['H'].unpack(self.read(2))[0] 148 | 149 | def write_uint16(self, value): 150 | return self.write(self._types['H'].pack(value)) 151 | 152 | def read_int32(self): 153 | return self._types['i'].unpack(self.read(4))[0] 154 | 155 | def write_int32(self, value): 156 | return self.write(self._types['i'].pack(value)) 157 | 158 | def read_uint32(self): 159 | return self._types['I'].unpack(self.read(4))[0] 160 | 161 | def write_uint32(self, value): 162 | return self.write(self._types['I'].pack(value)) 163 | 164 | def read_int64(self): 165 | return self._types['q'].unpack(self.read(8))[0] 166 | 167 | def write_int64(self, value): 168 | return self.write(self._types['q'].pack(value)) 169 | 170 | def read_uint64(self): 171 | return self._types['Q'].unpack(self.read(8))[0] 172 | 173 | def write_uint64(self, value): 174 | return self.write(self._types['Q'].pack(value)) 175 | 176 | def read_boolean(self): 177 | return self._types['?'].unpack(self.read(1))[0] 178 | 179 | def write_boolean(self, value): 180 | return self.write(self._types['?'].pack(value)) 181 | 182 | def read_single(self): 183 | return self._types['f'].unpack(self.read(4))[0] 184 | 185 | def write_single(self, value): 186 | return self.write(self._types['f'].pack(value)) 187 | 188 | def read_double(self): 189 | return self._types['d'].unpack(self.read(8))[0] 190 | 191 | def write_double(self, value): 192 | return self.write(self._types['d'].pack(value)) 193 | -------------------------------------------------------------------------------- /xnb_parse/fez_content_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fez ContentManager 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import os 8 | 9 | from xnb_parse.identify import identify_buffer 10 | from xnb_parse.xna_content_manager import ContentManager 11 | from xnb_parse.xnb_reader import XNBReader 12 | from xnb_parse.binstream import BinaryStream 13 | 14 | 15 | class FezContentManager(ContentManager): 16 | content_pak_files = ['Essentials.pak', 'Updates.pak', 'Other.pak'] 17 | 18 | def find_assets(self): 19 | for pak_file in self.content_pak_files: 20 | filename = os.path.join(self.root_dir, pak_file) 21 | if os.path.isfile(filename): 22 | stream = BinaryStream(filename=filename) 23 | capacity = stream.read_int32() 24 | for _ in range(capacity): 25 | asset_name = stream.read_string() 26 | asset_size = stream.read_int32() 27 | asset_data = stream.read(asset_size) 28 | asset_name = asset_name.replace('\\', '/') 29 | asset_name = asset_name.lower() 30 | yield asset_name, asset_data 31 | 32 | def xnb(self, asset_name, expected_type=None, parse=True): 33 | asset_name = asset_name.replace('\\', '/') 34 | asset_name = asset_name.lower() 35 | return XNBReader.load(data=self._asset_dict[asset_name], expected_type=expected_type, parse=parse) 36 | 37 | def save(self, asset_name, out_dir): 38 | asset_data = self._asset_dict[asset_name] 39 | extension = identify_buffer(asset_data) 40 | filename = os.path.join(out_dir, os.path.normpath(asset_name) + extension) 41 | dirname = os.path.dirname(filename) 42 | if not os.path.isdir(dirname): 43 | os.makedirs(dirname) 44 | with open(filename, 'wb') as out_file: 45 | out_file.write(asset_data) 46 | -------------------------------------------------------------------------------- /xnb_parse/fez_decomp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Extract and decompress FEZ .pak files 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import sys 8 | import os 9 | import time 10 | 11 | from xnb_parse.fez_content_manager import FezContentManager 12 | from xnb_parse.type_reader import ReaderError 13 | 14 | 15 | def unpack(content_dir, out_dir): 16 | content_manager = FezContentManager(content_dir) 17 | out_dir = os.path.normpath(out_dir) 18 | for asset_name in content_manager.assets: 19 | print(asset_name) 20 | try: 21 | xnb = content_manager.xnb(asset_name, parse=False) 22 | out_file = os.path.join(out_dir, os.path.normpath(asset_name)) 23 | xnb.save(filename=out_file) 24 | except ReaderError as ex: 25 | print("FAILED: '{}' {}: {}".format(asset_name, type(ex).__name__, ex), file=sys.stderr) 26 | 27 | 28 | def main(): 29 | if len(sys.argv) == 3: 30 | totaltime = time.time() 31 | unpack(os.path.normpath(sys.argv[1]), os.path.normpath(sys.argv[2])) 32 | print('> Done in {:.2f} seconds'.format(time.time() - totaltime)) 33 | else: 34 | print('fez_decomp.py Content out_dir', file=sys.stderr) 35 | -------------------------------------------------------------------------------- /xnb_parse/fez_music_content_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fez PC Music ContentManager 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.fez_content_manager import FezContentManager 8 | 9 | 10 | class FezMusicContentManager(FezContentManager): 11 | content_pak_files = ['Music.pak'] 12 | -------------------------------------------------------------------------------- /xnb_parse/fez_music_unpack.py: -------------------------------------------------------------------------------- 1 | """ 2 | Extract oggs from FEZ PC Music.pak file 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import sys 8 | import os 9 | import time 10 | 11 | from xnb_parse.fez_music_content_manager import FezMusicContentManager 12 | 13 | 14 | def unpack(content_dir, out_dir): 15 | content_manager = FezMusicContentManager(content_dir) 16 | out_dir = os.path.normpath(out_dir) 17 | for asset_name in content_manager.assets: 18 | print(asset_name) 19 | content_manager.save(asset_name, out_dir) 20 | 21 | 22 | def main(): 23 | if len(sys.argv) == 3: 24 | totaltime = time.time() 25 | unpack(os.path.normpath(sys.argv[1]), os.path.normpath(sys.argv[2])) 26 | print('> Done in {:.2f} seconds'.format(time.time() - totaltime)) 27 | else: 28 | print('fez_music_unpack.py Content out_dir', file=sys.stderr) 29 | -------------------------------------------------------------------------------- /xnb_parse/fez_unpack.py: -------------------------------------------------------------------------------- 1 | """ 2 | Extract FEZ .pak files 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import sys 8 | import os 9 | import time 10 | 11 | from xnb_parse.fez_content_manager import FezContentManager 12 | 13 | 14 | def unpack(content_dir, out_dir): 15 | content_manager = FezContentManager(content_dir) 16 | out_dir = os.path.normpath(out_dir) 17 | for asset_name in content_manager.assets: 18 | print(asset_name) 19 | content_manager.save(asset_name, out_dir) 20 | 21 | 22 | def main(): 23 | if len(sys.argv) == 3: 24 | totaltime = time.time() 25 | unpack(os.path.normpath(sys.argv[1]), os.path.normpath(sys.argv[2])) 26 | print('> Done in {:.2f} seconds'.format(time.time() - totaltime)) 27 | else: 28 | print('fez_unpack.py Content out_dir', file=sys.stderr) 29 | -------------------------------------------------------------------------------- /xnb_parse/file_formats/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | writers for various file formats 3 | """ 4 | 5 | from __future__ import print_function 6 | -------------------------------------------------------------------------------- /xnb_parse/file_formats/img_decode.py: -------------------------------------------------------------------------------- 1 | """ 2 | Decode DXT/other textures to RGBA 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from struct import Struct 8 | 9 | from xnb_parse.type_reader import ReaderError 10 | 11 | 12 | def decode_bgra(data, width, height, needs_swap, alpha='yes'): 13 | if needs_swap: 14 | conv = 'argb_rgba' 15 | else: 16 | conv = 'bgra_rgba' 17 | return decode32(data, width, height, conv, alpha=alpha) 18 | 19 | 20 | def decode_rgba(data, width, height, needs_swap, alpha='yes'): 21 | if needs_swap: 22 | conv = 'abgr_rgba' 23 | else: 24 | conv = 'rgba_rgba' 25 | return decode32(data, width, height, conv, alpha=alpha) 26 | 27 | 28 | def decode32(data, width, height, conv, alpha='yes'): 29 | if conv not in ('rgba_rgba', 'abgr_rgba', 'bgra_rgba', 'argb_rgba'): 30 | raise ReaderError("Unknown conversion: '{}'".format(conv)) 31 | if alpha not in ('yes', 'no', 'only'): 32 | raise ValueError("Invalid alpha parameter: '{}'".format(alpha)) 33 | stride = width * 4 34 | expected_len = stride * height 35 | if len(data) != expected_len: 36 | raise ReaderError("Invalid data size: {} != {}".format(len(data), expected_len)) 37 | full_row_ff = bytearray([0xff] * width) 38 | for pos in range(0, len(data), stride): 39 | row = bytearray(data[pos:pos + stride]) 40 | if conv == 'bgra_rgba': 41 | row[2::4], row[1::4], row[0::4], row[3::4] = row[0::4], row[1::4], row[2::4], row[3::4] 42 | elif conv == 'argb_rgba': 43 | row[3::4], row[0::4], row[1::4], row[2::4] = row[0::4], row[1::4], row[2::4], row[3::4] 44 | elif conv == 'abgr_rgba': 45 | row[3::4], row[2::4], row[1::4], row[0::4] = row[0::4], row[1::4], row[2::4], row[3::4] 46 | if alpha == 'no': 47 | row[3::4] = full_row_ff 48 | elif alpha == 'only': 49 | row[0::4] = full_row_ff 50 | row[1::4] = full_row_ff 51 | row[2::4] = full_row_ff 52 | yield row 53 | 54 | 55 | def decode_a(data, width, height, needs_swap, alpha='yes'): 56 | return decode8(data, width, height, 'a_xxxa') 57 | 58 | 59 | def decode8(data, width, height, conv): 60 | if conv not in ('a_xxxa',): 61 | raise ReaderError("Unknown conversion: '{}'".format(conv)) 62 | stride = width 63 | expected_len = stride * height 64 | if len(data) != expected_len: 65 | raise ReaderError("Invalid data size: {} != {}".format(len(data), expected_len)) 66 | for pos in range(0, len(data), stride): 67 | row = bytearray([0xff] * width * 4) 68 | row[3::4] = data[pos:pos + stride] 69 | yield row 70 | 71 | 72 | def decode_dxt1(data, width, height, needs_swap, alpha='yes'): 73 | return DxtDecoder(width, height, 'DXT1', data, needs_swap).decode(alpha) 74 | 75 | 76 | def decode_dxt3(data, width, height, needs_swap, alpha='yes'): 77 | return DxtDecoder(width, height, 'DXT3', data, needs_swap).decode(alpha) 78 | 79 | 80 | def decode_dxt5(data, width, height, needs_swap, alpha='yes'): 81 | return DxtDecoder(width, height, 'DXT5', data, needs_swap).decode(alpha) 82 | 83 | 84 | class DxtDecoder(object): 85 | _FORMATS = {'DXT1': 8, 'DXT3': 16, 'DXT5': 16} 86 | 87 | def __init__(self, width, height, surface_format, data, needs_swap=False): 88 | if surface_format not in self._FORMATS: 89 | raise ReaderError("Unknown DXT format: '{}'".format(surface_format)) 90 | if (width | height) & 3: 91 | raise ReaderError("Bad dimensions for DXT: {}x{}".format(width, height)) 92 | self.width = width 93 | self.height = height 94 | self.surface_format = surface_format 95 | self.data = data 96 | self.block_size = self._FORMATS[self.surface_format] 97 | stride = (self.width >> 2) * self.block_size 98 | expected_len = stride * (self.height >> 2) 99 | if len(self.data) != expected_len: 100 | raise ReaderError("Invalid data size for DXT: {} != {}".format(len(data), expected_len)) 101 | self.out_rows = [bytearray([0] * self.width * 4), bytearray([0] * self.width * 4), 102 | bytearray([0] * self.width * 4), bytearray([0] * self.width * 4)] 103 | if needs_swap: 104 | self.swap_struct = Struct('>HHHH') 105 | else: 106 | self.swap_struct = Struct('> 11 & 0x1f 145 | color0_r = c_r << 3 | c_r >> 2 146 | c_g = color0_raw >> 5 & 0x3f 147 | color0_g = c_g << 2 | c_g >> 4 148 | c_b = color0_raw & 0x1f 149 | color0_b = c_b << 3 | c_b >> 2 150 | colors.append([color0_r, color0_g, color0_b, 255]) 151 | c_r = color1_raw >> 11 & 0x1f 152 | color1_r = c_r << 3 | c_r >> 2 153 | c_g = color1_raw >> 5 & 0x3f 154 | color1_g = c_g << 2 | c_g >> 4 155 | c_b = color1_raw & 0x1f 156 | color1_b = c_b << 3 | c_b >> 2 157 | colors.append([color1_r, color1_g, color1_b, 255]) 158 | if color0_raw > color1_raw or not dxt1: 159 | c_r = (2 * color0_r + color1_r) // 3 160 | c_g = (2 * color0_g + color1_g) // 3 161 | c_b = (2 * color0_b + color1_b) // 3 162 | colors.append([c_r, c_g, c_b, 255]) 163 | c_r = (color0_r + 2 * color1_r) // 3 164 | c_g = (color0_g + 2 * color1_g) // 3 165 | c_b = (color0_b + 2 * color1_b) // 3 166 | colors.append([c_r, c_g, c_b, 255]) 167 | else: 168 | c_r = (color0_r + color1_r) // 2 169 | c_g = (color0_g + color1_g) // 2 170 | c_b = (color0_b + color1_b) // 2 171 | colors.append([c_r, c_g, c_b, 255]) 172 | colors.append([0, 0, 0, 0]) 173 | for b_y in range(4): 174 | for b_x in range(cur_x << 2, (cur_x + 4) << 2, 4): 175 | self.out_rows[b_y][b_x:b_x + 4] = colors[bits & 3] 176 | bits >>= 2 177 | 178 | def decode_explicit_alpha_block(self, offset, cur_x): 179 | bits0, bits1, bits2, bits3 = self.swap_struct.unpack_from(self.data, offset) 180 | bits = bits0 | bits1 << 16 | bits2 << 32 | bits3 << 48 181 | for b_y in range(4): 182 | for b_x in range(cur_x << 2, (cur_x + 4) << 2, 4): 183 | self.out_rows[b_y][b_x + 3] = self.explicit_alphas[bits & 0xf] 184 | bits >>= 4 185 | 186 | def decode_interpolated_alpha_block(self, offset, cur_x): 187 | alpha_raw, bits0, bits1, bits2 = self.swap_struct.unpack_from(self.data, offset) 188 | bits = bits0 | bits1 << 16 | bits2 << 32 189 | alphas = [] 190 | alpha0 = alpha_raw & 0xff 191 | alphas.append(alpha0) 192 | alpha1 = alpha_raw >> 8 193 | alphas.append(alpha1) 194 | if alpha0 > alpha1: 195 | alphas.append((6 * alpha0 + 1 * alpha1) // 7) 196 | alphas.append((5 * alpha0 + 2 * alpha1) // 7) 197 | alphas.append((4 * alpha0 + 3 * alpha1) // 7) 198 | alphas.append((3 * alpha0 + 4 * alpha1) // 7) 199 | alphas.append((2 * alpha0 + 5 * alpha1) // 7) 200 | alphas.append((1 * alpha0 + 6 * alpha1) // 7) 201 | else: 202 | alphas.append((4 * alpha0 + 1 * alpha1) // 5) 203 | alphas.append((3 * alpha0 + 2 * alpha1) // 5) 204 | alphas.append((2 * alpha0 + 3 * alpha1) // 5) 205 | alphas.append((1 * alpha0 + 4 * alpha1) // 5) 206 | alphas.append(0) 207 | alphas.append(255) 208 | for b_y in range(4): 209 | for b_x in range(cur_x << 2, (cur_x + 4) << 2, 4): 210 | self.out_rows[b_y][b_x + 3] = alphas[bits & 0x7] 211 | bits >>= 3 212 | -------------------------------------------------------------------------------- /xnb_parse/file_formats/png.py: -------------------------------------------------------------------------------- 1 | """ 2 | PNG encoder 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import struct 8 | import zlib 9 | 10 | from xnb_parse.binstream import BinaryStream 11 | 12 | 13 | class PyPngWriter(object): 14 | # The PNG signature. 15 | # http://www.w3.org/TR/PNG/#5PNG-file-signature 16 | _SIGNATURE = b'\x89PNG\x0d\x0a\x1a\x0a' 17 | 18 | def __init__(self, width=None, height=None): 19 | if width <= 0 or height <= 0: 20 | raise ValueError("width and height must be greater than zero") 21 | 22 | # http://www.w3.org/TR/PNG/#7Integers-and-byte-order 23 | if width > 2 ** 32 - 1 or height > 2 ** 32 - 1: 24 | raise ValueError("width and height cannot exceed 2**32-1") 25 | 26 | self.width = width 27 | self.height = height 28 | self.chunk_limit = 2 ** 20 29 | 30 | def write_bytearray(self, filename, rows): 31 | stream = BinaryStream(big_endian=True) 32 | # http://www.w3.org/TR/PNG/#5PNG-file-signature 33 | stream.write(PyPngWriter._SIGNATURE) 34 | 35 | # http://www.w3.org/TR/PNG/#11IHDR 36 | PyPngWriter._write_chunk(stream, b'IHDR', struct.pack('!II B B BBB', self.width, self.height, 8, 6, 0, 0, 0)) 37 | 38 | # http://www.w3.org/TR/PNG/#11IDAT 39 | compressor = zlib.compressobj() 40 | 41 | data = bytearray() 42 | for row in rows: 43 | data.append(0) 44 | data.extend(row) 45 | if len(data) > self.chunk_limit: 46 | compressed = compressor.compress(bytes(data)) 47 | if len(compressed): 48 | PyPngWriter._write_chunk(stream, b'IDAT', compressed) 49 | data = bytearray() 50 | if len(data): 51 | compressed = compressor.compress(bytes(data)) 52 | else: 53 | compressed = bytes() 54 | flushed = compressor.flush() 55 | if len(compressed) or len(flushed): 56 | PyPngWriter._write_chunk(stream, b'IDAT', compressed + flushed) 57 | 58 | # http://www.w3.org/TR/PNG/#11IEND 59 | PyPngWriter._write_chunk(stream, b'IEND') 60 | stream.write_file(filename) 61 | 62 | @staticmethod 63 | def _write_chunk(stream, tag, data=b''): 64 | # http://www.w3.org/TR/PNG/#5Chunk-layout 65 | stream.write_uint32(len(data)) 66 | stream.write(tag) 67 | stream.write(data) 68 | checksum = zlib.crc32(tag) 69 | checksum = zlib.crc32(data, checksum) 70 | checksum &= 2 ** 32 - 1 71 | stream.write_uint32(checksum) 72 | 73 | 74 | def write_png(filename, width, height, rows): 75 | full_filename = filename + '.png' 76 | out_png = PyPngWriter(width=width, height=height) 77 | out_png.write_bytearray(full_filename, rows) 78 | -------------------------------------------------------------------------------- /xnb_parse/file_formats/wav.py: -------------------------------------------------------------------------------- 1 | """ 2 | WAV file writer 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from uuid import UUID 8 | 9 | from xnb_parse.type_reader import ReaderError 10 | from xnb_parse.binstream import BinaryStream 11 | 12 | 13 | WAVE_FORMAT_PCM = 0x0001 14 | WAVE_FORMAT_ADPCM = 0x002 15 | WAVE_FORMAT_WMAUDIO2 = 0x161 16 | WAVE_FORMAT_WMAUDIO3 = 0x162 17 | WAVE_FORMAT_XMA2 = 0x166 18 | WAVE_FORMAT_EXTENSIBLE = 0xFFFE 19 | WAVE_FORMAT = { 20 | 0x0000: 'WAVE_FORMAT_UNKNOWN', 21 | 0x0001: 'WAVE_FORMAT_PCM', 22 | 0x0002: 'WAVE_FORMAT_ADPCM', 23 | 0x0161: 'WAVE_FORMAT_WMAUDIO2', 24 | 0x0162: 'WAVE_FORMAT_WMAUDIO3', 25 | 0x0165: 'WAVE_FORMAT_XMA', 26 | 0x0166: 'WAVE_FORMAT_XMA2', 27 | 0xFFFE: 'WAVE_FORMAT_EXTENSIBLE', 28 | 0xFFFF: 'WAVE_FORMAT_DEVELOPMENT', 29 | } 30 | 31 | 32 | class PyWavWriter(object): 33 | _waveformatex = 'H H I I H H' 34 | _waveformat_xma2 = 'H I I I I I I I B B H' 35 | _waveformat_extensible = 'H I 16s' 36 | 37 | def __init__(self, header, data, dpds=None, seek=None, needs_swap=False): 38 | self.header_raw = header 39 | self.data_raw = data 40 | self.dpds_raw = dpds 41 | self.seek_raw = seek 42 | self.needs_swap = needs_swap 43 | 44 | h_s = BinaryStream(data=self.header_raw, big_endian=needs_swap) 45 | waveformatex_size = h_s.calc_size(self._waveformatex) 46 | (self.h_format_tag, self.h_channels, self.h_samples_per_sec, self.h_avg_bytes_per_sec, self.h_block_align, 47 | self.h_bits_per_sample) = h_s.unpack(self._waveformatex) 48 | header_size = waveformatex_size 49 | 50 | # do we have a WAVEFORMATEX 51 | self.h_size = None 52 | if len(self.header_raw) >= waveformatex_size + 2: 53 | self.h_size = h_s.read_uint16() 54 | header_size += 2 55 | 56 | if self.h_format_tag == WAVE_FORMAT_XMA2: 57 | waveformat_xma2_size = h_s.calc_size(self._waveformat_xma2) 58 | if self.h_size != waveformat_xma2_size: 59 | raise ReaderError("Unknown cbSize for XMA2WAVEFORMATEX: {}".format(self.h_size)) 60 | (self.hx_num_streams, self.hx_channel_mask, self.hx_samples_encoded, self.hx_bytes_per_block, 61 | self.hx_play_begin, self.hx_play_length, self.hx_loop_begin, self.hx_loop_length, self.hx_loop_count, 62 | self.hx_encoder_version, self.hx_block_count) = h_s.unpack(self._waveformat_xma2) 63 | header_size += waveformat_xma2_size 64 | elif self.h_format_tag == WAVE_FORMAT_EXTENSIBLE: 65 | waveformat_extensible_size = h_s.calc_size(self._waveformat_extensible) 66 | if self.h_size < waveformat_extensible_size: 67 | raise ReaderError("Invalid cbSize for WAVEFORMATEXTENSIBLE: {}".format(self.h_size)) 68 | (self.he_valid_bits_per_sample, self.he_channel_mask, 69 | he_subformat_bytes) = h_s.unpack(self._waveformat_extensible) 70 | self.he_subformat = UUID(bytes_le=he_subformat_bytes) 71 | header_size += waveformat_extensible_size 72 | self.he_remainder = None 73 | if self.h_size > waveformat_extensible_size: 74 | self.he_remainder = h_s.read(self.h_size - waveformat_extensible_size) 75 | header_size += self.h_size - waveformat_extensible_size 76 | raise ReaderError("Extra bytes in WAVEFORMATEXTENSIBLE: {}".format(len(self.he_remainder))) 77 | self.h_remainder = h_s.read() 78 | if len(self.h_remainder): 79 | header_size += len(self.h_remainder) 80 | if header_size != len(self.header_raw): 81 | raise ReaderError("Header size mismatch: {} != {}".format(header_size, len(self.header_raw))) 82 | 83 | def dump(self): 84 | if self.needs_swap: 85 | out_str = 'RIFX ' 86 | else: 87 | out_str = 'RIFF ' 88 | out_str += 'h:{} d:{} {}\n'.format(len(self.header_raw), len(self.data_raw), 89 | WAVE_FORMAT.get(self.h_format_tag, 'UNKNOWN')) 90 | out_str += 'wFormatTag:{:#04x} nChannels:{} nSamplesPerSec:{} nAvgBytesPerSec:{}\n'.format( 91 | self.h_format_tag, self.h_channels, self.h_avg_bytes_per_sec, self.h_samples_per_sec) 92 | out_str += 'nBlockAlign:{} wBitsPerSample:{} cbSize:{}\n'.format( 93 | self.h_block_align, self.h_bits_per_sample, self.h_size) 94 | if self.h_format_tag == WAVE_FORMAT_XMA2: 95 | out_str += 'NumStreams:{} ChannelMask:{:#08x} SamplesEncoded:{} BytesPerBlock:{}\n'.format( 96 | self.hx_num_streams, self.hx_channel_mask, self.hx_samples_encoded, self.hx_play_begin) 97 | out_str += 'PlayBegin:{} PlayLength:{} LoopBegin:{} LoopLength:{}\n'.format( 98 | self.hx_play_begin, self.hx_play_length, self.hx_loop_begin, self.hx_loop_length) 99 | out_str += 'LoopCount:{} EncoderVersion:{} BlockCount:{}\n'.format( 100 | self.hx_loop_count, self.hx_encoder_version, self.hx_block_count) 101 | elif self.h_format_tag == WAVE_FORMAT_EXTENSIBLE: 102 | out_str += 'wValidBitsPerSample:{} dwChannelMask:{:#08x}\n'.format( 103 | self.he_valid_bits_per_sample, self.he_channel_mask) 104 | print(out_str) 105 | 106 | def write(self, filename): 107 | h_s = BinaryStream() 108 | h_s.pack(self._waveformatex, self.h_format_tag, self.h_channels, self.h_samples_per_sec, 109 | self.h_avg_bytes_per_sec, self.h_block_align, self.h_bits_per_sample) 110 | if self.h_size is not None: 111 | h_s.write_uint16(self.h_size) 112 | if self.h_format_tag == WAVE_FORMAT_XMA2: 113 | # hack so mono sounds end up center rather than left 114 | if self.h_channels == 1 and self.hx_channel_mask == 1: 115 | hx_channel_mask = 0 116 | else: 117 | hx_channel_mask = self.hx_channel_mask 118 | h_s.pack(self._waveformat_xma2, self.hx_num_streams, hx_channel_mask, self.hx_samples_encoded, 119 | self.hx_bytes_per_block, self.hx_play_begin, self.hx_play_length, self.hx_loop_begin, 120 | self.hx_loop_length, self.hx_loop_count, self.hx_encoder_version, self.hx_block_count) 121 | elif self.h_format_tag == WAVE_FORMAT_EXTENSIBLE: 122 | h_s.pack(self._waveformat_extensible, self.he_valid_bits_per_sample, self.he_channel_mask, 123 | self.he_subformat.bytes_le) 124 | if self.he_remainder: 125 | h_s.write(self.he_remainder) 126 | if self.h_remainder: 127 | h_s.write(self.h_remainder) 128 | header_raw = h_s.getvalue() 129 | if self.dpds_raw: 130 | dpds_size = len(self.dpds_raw) 131 | else: 132 | dpds_size = None 133 | if self.seek_raw: 134 | seek_size = len(self.seek_raw) 135 | else: 136 | seek_size = None 137 | o_s = BinaryStream() 138 | if self.h_format_tag == WAVE_FORMAT_WMAUDIO2 or self.h_format_tag == WAVE_FORMAT_WMAUDIO3: 139 | riff_type = b'XWMA' 140 | else: 141 | riff_type = b'WAVE' 142 | self.write_header(o_s, riff_type, len(header_raw), len(self.data_raw), dpds_size, seek_size) 143 | self.write_chunk(o_s, b'fmt ', header_raw) 144 | if self.dpds_raw: 145 | self.write_chunk(o_s, b'dpds', self.dpds_raw) 146 | if self.seek_raw: 147 | self.write_chunk(o_s, b'seek', self.seek_raw) 148 | self.write_chunk(o_s, b'data', self.data_raw) 149 | if self.h_format_tag == WAVE_FORMAT_XMA2: 150 | full_filename = filename + '.xma' 151 | elif self.h_format_tag == WAVE_FORMAT_WMAUDIO2 or self.h_format_tag == WAVE_FORMAT_WMAUDIO2: 152 | full_filename = filename + '.xwma' 153 | else: 154 | full_filename = filename + '.wav' 155 | o_s.write_file(full_filename) 156 | 157 | @staticmethod 158 | def write_header(o_s, riff_type, header_size, data_size, dpds_size=None, seek_size=None): 159 | full_size = 4 + 8 + header_size + 8 + data_size 160 | if dpds_size: 161 | full_size += 8 + dpds_size 162 | if seek_size: 163 | full_size += 8 + seek_size 164 | o_s.write(b'RIFF') 165 | o_s.write_uint32(full_size) 166 | o_s.write(riff_type) 167 | 168 | @staticmethod 169 | def write_chunk(o_s, name, data): 170 | o_s.write(name) 171 | o_s.write_uint32(len(data)) 172 | o_s.write(data) 173 | 174 | 175 | def write_wav(filename, header, data, needs_swap): 176 | PyWavWriter(header, data, needs_swap=needs_swap).write(filename) 177 | -------------------------------------------------------------------------------- /xnb_parse/file_formats/xml_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | XML utils 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | try: 8 | import lxml.etree as ET 9 | 10 | def output_xml(xml, filename): 11 | ET.ElementTree(xml).write(filename, encoding='utf-8', xml_declaration=True, pretty_print=True) 12 | except ImportError: 13 | import xml.etree.cElementTree as ET 14 | 15 | def output_xml(xml, filename): 16 | ET.ElementTree(xml).write(filename, encoding='utf-8') 17 | -------------------------------------------------------------------------------- /xnb_parse/identify.py: -------------------------------------------------------------------------------- 1 | """ 2 | Identify file data based on content 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import os 8 | import time 9 | import sys 10 | 11 | 12 | def identify_buffer(data): 13 | if data[:3] == b'XNB': 14 | return '.xnb' 15 | elif data[:4] == b'OggS': 16 | return '.ogg' 17 | elif data[:4] == b'XGSF' or data[:4] == b'FSGX': 18 | return '.xgs' 19 | elif data[:4] == b'SDBK' or data[:4] == b'KBDS': 20 | return '.xsb' 21 | elif data[:4] == b'WBND' or data[:4] == b'DNBW': 22 | return '.xwb' 23 | elif data[2:4] == b'\xff\xfe' or data[:2] == b'\xfe\xff': 24 | return '.fxo' 25 | return '.bin' 26 | 27 | 28 | def identify_file(filename): 29 | with open(filename, 'rb') as file_handle: 30 | data = file_handle.read() 31 | return identify_buffer(data) 32 | 33 | 34 | def main(): 35 | if len(sys.argv) == 2: 36 | totaltime = time.time() 37 | ext = identify_file(os.path.normpath(sys.argv[1])) 38 | print('{} {}'.format(sys.argv[1], ext)) 39 | print('> Done in {:.2f} seconds'.format(time.time() - totaltime)) 40 | else: 41 | print('identify.py filename', file=sys.stderr) 42 | -------------------------------------------------------------------------------- /xnb_parse/read_xact.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dump info from XACT files 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import sys 8 | import os 9 | import time 10 | 11 | from xnb_parse.xact.xgs import XGS 12 | from xnb_parse.xact.xsb import XSB 13 | from xnb_parse.xact.xwb import XWB 14 | 15 | 16 | def read_xact(in_xgs_file, in_xsb_file, in_xwb_file, out_dir=None): 17 | in_xgs_file = os.path.normpath(in_xgs_file) 18 | print(in_xgs_file) 19 | xgs = XGS(filename=in_xgs_file) 20 | in_xsb_file = os.path.normpath(in_xsb_file) 21 | print(in_xsb_file) 22 | xsb = XSB(filename=in_xsb_file, audio_engine=xgs) 23 | in_xwb_file = os.path.normpath(in_xwb_file) 24 | print(in_xwb_file) 25 | xwb = XWB(filename=in_xwb_file, audio_engine=xgs) 26 | if out_dir is not None: 27 | xgs.export(out_dir) 28 | xsb.export(out_dir) 29 | xwb.export(out_dir) 30 | 31 | 32 | def main(): 33 | if 3 < len(sys.argv) <= 5: 34 | totaltime = time.time() 35 | in_xgs_file = sys.argv[1] 36 | in_xsb_file = sys.argv[2] 37 | in_xwb_file = sys.argv[3] 38 | out_dir = None 39 | if len(sys.argv) > 4: 40 | out_dir = sys.argv[4] 41 | read_xact(in_xgs_file, in_xsb_file, in_xwb_file, out_dir) 42 | print('> Done in {:.2f} seconds'.format(time.time() - totaltime)) 43 | else: 44 | print('read_xact.py file.xgs file.xsb file.xwb [export_dir]', file=sys.stderr) 45 | -------------------------------------------------------------------------------- /xnb_parse/read_xnb.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dump info from XNB 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import sys 8 | import os 9 | import time 10 | 11 | from xnb_parse.xnb_reader import XNBReader 12 | 13 | 14 | def read_xnb(in_file): 15 | in_file = os.path.normpath(in_file) 16 | print(in_file) 17 | xnb = XNBReader.load(filename=in_file, parse=False) 18 | print(xnb.parse()) 19 | out_filebase = os.path.normpath(os.path.join('../export', in_file.replace('.xnb', ''))) 20 | xnb.export(out_filebase) 21 | 22 | 23 | def main(): 24 | if len(sys.argv) > 1: 25 | totaltime = time.time() 26 | for filename in sys.argv[1:]: 27 | read_xnb(filename) 28 | print('> Done in {:.2f} seconds'.format(time.time() - totaltime)) 29 | else: 30 | print('read_xnb.py file1.xnb ...', file=sys.stderr) 31 | -------------------------------------------------------------------------------- /xnb_parse/read_xnb_dir.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dump info from directory of XNBs 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import sys 8 | import time 9 | 10 | from xnb_parse.type_reader import ReaderError 11 | from xnb_parse.xna_content_manager import ContentManager 12 | 13 | 14 | def read_xnb_dir(content_dir, export_dir=None): 15 | content_manager = ContentManager(content_dir) 16 | for asset_name in content_manager.assets: 17 | print(asset_name) 18 | try: 19 | asset = content_manager.load(asset_name) 20 | if export_dir is not None: 21 | content_manager.export(asset, asset_name, export_dir) 22 | except (ReaderError, KeyError) as ex: 23 | print("FAILED: '{}' {}: {}".format(asset_name, type(ex).__name__, ex), file=sys.stderr) 24 | 25 | 26 | def main(): 27 | if 1 < len(sys.argv) <= 3: 28 | totaltime = time.time() 29 | content_dir = sys.argv[1] 30 | export_dir = None 31 | if len(sys.argv) > 2: 32 | export_dir = sys.argv[2] 33 | read_xnb_dir(content_dir, export_dir) 34 | print('> Done in {:.2f} seconds'.format(time.time() - totaltime)) 35 | else: 36 | print('read_xnb_dir.py content_dir [export_dir]', file=sys.stderr) 37 | -------------------------------------------------------------------------------- /xnb_parse/show_ao.py: -------------------------------------------------------------------------------- 1 | """ 2 | Display ArtObject 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import sys 8 | import pyglet 9 | from pyglet.gl import * 10 | 11 | from xnb_parse.fez_content_manager import FezContentManager 12 | from xnb_parse.trackball_camera import TrackballCamera, norm1, vec_args 13 | from xnb_parse.type_reader import ReaderError 14 | from xnb_parse.xna_content_manager import ContentManager 15 | from xnb_parse.xna_types.xna_math import Vector3 16 | 17 | 18 | NORMALS = [Vector3(-1.0, 0.0, 0.0), Vector3(0.0, -1.0, 0.0), Vector3(0.0, 0.0, -1.0), 19 | Vector3(1.0, 0.0, 0.0), Vector3(0.0, 1.0, 0.0), Vector3(0.0, 0.0, 1.0)] 20 | 21 | 22 | class AOWindow(pyglet.window.Window): 23 | wireframe = False 24 | lighting = True 25 | culling = False 26 | texturing = True 27 | 28 | def __init__(self, content_manager, asset_name, width=1000, height=750, config=None): 29 | pyglet.window.Window.__init__(self, width=width, height=height, resizable=True, config=config) 30 | self.gl_setup() 31 | self.art_object = AO(content_manager, asset_name) 32 | self.tbcam = TrackballCamera() 33 | self.fps_display = pyglet.clock.ClockDisplay(color=(0.5, 0.5, 0.5, 1.0)) 34 | 35 | @staticmethod 36 | def gl_setup(): 37 | glClearColor(0.3926, 0.5843, 0.9294, 1.0) 38 | glClearDepth(1.0) 39 | glColor3f(1.0, 1.0, 1.0) 40 | 41 | glDisable(GL_CULL_FACE) 42 | glFrontFace(GL_CW) 43 | 44 | glEnable(GL_DEPTH_TEST) 45 | 46 | glShadeModel(GL_SMOOTH) 47 | 48 | glPolygonOffset(1, 1) 49 | 50 | glDisable(GL_LIGHTING) 51 | 52 | glEnable(GL_LIGHT0) 53 | glLightfv(GL_LIGHT0, GL_POSITION, vec_args(0.5, 0.5, 10.0, 1.0)) 54 | glLightfv(GL_LIGHT0, GL_AMBIENT, vec_args(0.5, 0.5, 0.5, 1.0)) 55 | glLightfv(GL_LIGHT0, GL_DIFFUSE, vec_args(1.0, 1.0, 1.0, 1.0)) 56 | 57 | def on_resize(self, width, height): 58 | # Override the default on_resize handler to create a 3D projection 59 | glViewport(0, 0, width, height) 60 | glMatrixMode(GL_PROJECTION) 61 | glLoadIdentity() 62 | gluPerspective(45.0, float(self.width) / float(self.height), 0.1, 100.0) 63 | self.tbcam.update_modelview() 64 | glMatrixMode(GL_MODELVIEW) 65 | return pyglet.event.EVENT_HANDLED 66 | 67 | def on_draw(self): 68 | self.clear() 69 | 70 | if self.culling: 71 | glEnable(GL_CULL_FACE) 72 | if self.lighting: 73 | glEnable(GL_LIGHTING) 74 | if self.wireframe: 75 | glEnable(GL_POLYGON_OFFSET_FILL) 76 | glColor3f(1.0, 1.0, 1.0) 77 | self.art_object.draw(self.texturing) 78 | glDisable(GL_POLYGON_OFFSET_FILL) 79 | 80 | glPolygonMode(GL_FRONT_AND_BACK, GL_LINE) 81 | glColor3f(0.0, 0.0, 0.0) 82 | glDisable(GL_LIGHTING) 83 | self.art_object.draw(False) 84 | glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) 85 | else: 86 | glColor3f(1.0, 1.0, 1.0) 87 | self.art_object.draw(self.texturing) 88 | glDisable(GL_CULL_FACE) 89 | glDisable(GL_LIGHTING) 90 | 91 | def on_key_press(self, symbol, modifiers): 92 | if symbol == pyglet.window.key.W: 93 | self.wireframe = not self.wireframe 94 | elif symbol == pyglet.window.key.L: 95 | self.lighting = not self.lighting 96 | elif symbol == pyglet.window.key.C: 97 | self.culling = not self.culling 98 | elif symbol == pyglet.window.key.T: 99 | self.texturing = not self.texturing 100 | elif symbol == pyglet.window.key.ESCAPE: 101 | self.dispatch_event('on_close') 102 | 103 | def on_mouse_press(self, x, y, button, modifiers): 104 | if button == pyglet.window.mouse.LEFT: 105 | self.tbcam.mouse_roll( 106 | norm1(x, self.width), 107 | norm1(y, self.height), 108 | False) 109 | elif button == pyglet.window.mouse.RIGHT: 110 | self.tbcam.mouse_zoom( 111 | norm1(x, self.width), 112 | norm1(y, self.height), 113 | False) 114 | 115 | def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): 116 | if buttons & pyglet.window.mouse.LEFT: 117 | self.tbcam.mouse_roll( 118 | norm1(x, self.width), 119 | norm1(y, self.height)) 120 | elif buttons & pyglet.window.mouse.RIGHT: 121 | self.tbcam.mouse_zoom( 122 | norm1(x, self.width), 123 | norm1(y, self.height)) 124 | 125 | 126 | class AO(object): 127 | def __init__(self, content_manager, asset_name): 128 | asset_name = 'art objects/' + asset_name 129 | art_object = content_manager.load(asset_name, expected_type='FezEngine.Structure.ArtObject') 130 | 131 | try: 132 | cubemap_name = 'art objects/' + art_object.cubemap_path 133 | cubemap = content_manager.load(cubemap_name, expected_type='Microsoft.Xna.Framework.Graphics.Texture2D') 134 | except AttributeError: 135 | cubemap = art_object.cubemap 136 | self.texture = pyglet.image.ImageData(cubemap.width, cubemap.height, 'RGBA', cubemap.full_data()).get_texture() 137 | 138 | indices = art_object.geometry.indices 139 | vertices = [] 140 | normals = [] 141 | texture_coords = [] 142 | for cur_vertex in art_object.geometry.vertices: 143 | vertices.append(cur_vertex.position.x / art_object.size.x) 144 | vertices.append(cur_vertex.position.y / art_object.size.y) 145 | vertices.append(cur_vertex.position.z / art_object.size.z) 146 | cur_normal = NORMALS[cur_vertex.normal] 147 | normals.append(cur_normal.x) 148 | normals.append(cur_normal.y) 149 | normals.append(cur_normal.z) 150 | texture_coords.append(cur_vertex.texture_coord.x * self.texture.tex_coords[2 * 3 + 0]) 151 | texture_coords.append(cur_vertex.texture_coord.y * self.texture.tex_coords[2 * 3 + 1]) 152 | self.vli = pyglet.graphics.vertex_list_indexed(len(vertices) // 3, indices, 153 | ('v3f', vertices), 154 | ('n3f', normals), 155 | ('t2f', texture_coords)) 156 | 157 | glDisable(self.texture.target) 158 | glTexParameteri(self.texture.target, GL_TEXTURE_MIN_FILTER, GL_NEAREST) 159 | glTexParameteri(self.texture.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST) 160 | glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, vec_args(0.6, 0.6, 0.6, 1.0)) 161 | 162 | def draw(self, texturing=True): 163 | if texturing: 164 | glEnable(self.texture.target) 165 | glBindTexture(self.texture.target, self.texture.id) 166 | self.vli.draw(pyglet.gl.GL_TRIANGLES) 167 | glDisable(self.texture.target) 168 | 169 | 170 | def main(): 171 | if len(sys.argv) == 3: 172 | # try and get config with 4x AA enabled, failing back to no AA 173 | platform = pyglet.window.get_platform() 174 | display = platform.get_default_display() 175 | screen = display.get_default_screen() 176 | template = pyglet.gl.Config(sample_buffers=1, samples=4) 177 | try: 178 | config = screen.get_best_config(template) 179 | except pyglet.window.NoSuchConfigException: 180 | template = pyglet.gl.Config() 181 | config = screen.get_best_config(template) 182 | # try and use FezContentManager if it works, failing back to directory reader 183 | try: 184 | content_manager = FezContentManager(sys.argv[1]) 185 | except ReaderError: 186 | content_manager = ContentManager(sys.argv[1]) 187 | AOWindow(content_manager=content_manager, asset_name=sys.argv[2], config=config) 188 | pyglet.app.run() 189 | else: 190 | print('show_ao.py Content|out objectao', file=sys.stderr) 191 | -------------------------------------------------------------------------------- /xnb_parse/type_reader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | # avoid circular import 8 | VERSION_40 = 5 9 | 10 | 11 | class Error(Exception): 12 | pass 13 | 14 | 15 | class ReaderError(Error): 16 | pass 17 | 18 | 19 | class NotGenericError(ReaderError): 20 | pass 21 | 22 | 23 | class Plugin(object): 24 | """ 25 | marker class for plugins 26 | """ 27 | 28 | 29 | class TypeReaderPlugin(Plugin): 30 | """ 31 | type reader plugins 32 | """ 33 | 34 | 35 | class BaseTypeReader(object): 36 | target_type = None 37 | reader_name = None 38 | is_value_type = False 39 | is_generic_type = False 40 | is_enum_type = False 41 | file_platform = None 42 | file_version = None 43 | 44 | def __init__(self, stream=None, version=None): 45 | self.stream = stream 46 | self.version = version 47 | 48 | def __str__(self): 49 | return self.reader_name 50 | 51 | def read(self): 52 | raise ReaderError("Unimplemented type reader: '{}'".format(self.reader_name)) 53 | 54 | def init_reader(self, file_platform=None, file_version=None): 55 | self.file_platform = file_platform 56 | self.file_version = file_version 57 | 58 | 59 | class ValueTypeReader(BaseTypeReader): 60 | is_value_type = True 61 | 62 | 63 | class GenericTypeReader(BaseTypeReader): 64 | generic_target_type = None 65 | generic_reader_name = None 66 | is_generic_type = True 67 | generic_params = None 68 | readers = None 69 | 70 | def init_reader(self, file_platform=None, file_version=None): 71 | BaseTypeReader.init_reader(self, file_platform, file_version) 72 | if self.readers is None: 73 | self.readers = [] 74 | for arg in self.generic_params: 75 | reader = self.stream.get_type_reader_by_type(arg) 76 | reader.init_reader(file_platform, file_version) 77 | self.readers.append(reader) 78 | 79 | @classmethod 80 | def create_from_type(cls, type_spec): 81 | # really need to figure out how to do this more cleanly 82 | body = dict(cls.__dict__) 83 | body['target_type'] = cls.generic_target_type + type_spec.suffix 84 | body['reader_name'] = cls.generic_reader_name + type_spec.suffix 85 | body['generic_params'] = [arg.full_name for arg in type_spec.generic_params] 86 | class_ = type(cls.__name__, cls.__bases__, body) 87 | return class_ 88 | 89 | 90 | class GenericValueTypeReader(GenericTypeReader): 91 | is_value_type = True 92 | 93 | 94 | class EnumTypeReader(ValueTypeReader): 95 | is_enum_type = True 96 | enum_type = None 97 | enum_type4 = None 98 | 99 | def read(self): 100 | value = self.stream.read_int32() 101 | if self.file_version == VERSION_40 and self.enum_type4 is not None: 102 | enum_type = self.enum_type4 103 | else: 104 | enum_type = self.enum_type 105 | if callable(enum_type): 106 | return enum_type(value) 107 | else: 108 | return value 109 | 110 | 111 | def generic_reader_name(main_type, args=None): 112 | if args is None: 113 | args = [] 114 | try: 115 | full_args = [arg.target_type for arg in args] 116 | except AttributeError: 117 | full_args = args 118 | try: 119 | return main_type.generic_reader_name + '[' + ','.join(full_args) + ']' 120 | except AttributeError: 121 | raise NotGenericError("Not generic type: '{}'".format(main_type)) 122 | 123 | 124 | def generic_reader_type(main_type, args=None): 125 | if args is None: 126 | args = [] 127 | try: 128 | full_args = [arg.target_type for arg in args] 129 | except AttributeError: 130 | full_args = args 131 | try: 132 | return main_type.generic_target_type + '[' + ','.join(full_args) + ']' 133 | except AttributeError: 134 | raise NotGenericError("Not generic type: '{}'".format(main_type.complete_name)) 135 | -------------------------------------------------------------------------------- /xnb_parse/type_reader_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | load and manage type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_spec import TypeSpec 8 | from xnb_parse.type_reader import TypeReaderPlugin, ReaderError, GenericTypeReader, BaseTypeReader 9 | 10 | # pull in all typereaders 11 | import xnb_parse.type_readers 12 | 13 | 14 | class TypeReaderManager(object): 15 | def __init__(self): 16 | self.type_readers = {} 17 | self.type_readers_type = {} 18 | self.generic_type_readers = {} 19 | self.generic_type_readers_type = {} 20 | for class_ in TypeReaderPlugin.__subclasses__(): 21 | if issubclass(class_, GenericTypeReader): 22 | if class_.generic_reader_name in self.generic_type_readers: 23 | raise ReaderError("Duplicate generic type reader name: '{}'".format(class_.generic_reader_name)) 24 | self.generic_type_readers[class_.generic_reader_name] = class_ 25 | if class_.generic_target_type in self.generic_type_readers_type: 26 | raise ReaderError("Duplicate generic type reader type: '{}'".format(class_.generic_target_type)) 27 | self.generic_type_readers_type[class_.generic_target_type] = class_ 28 | elif issubclass(class_, BaseTypeReader): 29 | if class_.reader_name in self.type_readers: 30 | raise ReaderError("Duplicate type reader name: '{}'".format(class_.reader_name)) 31 | self.type_readers[class_.reader_name] = class_ 32 | if class_.target_type in self.type_readers_type: 33 | raise ReaderError("Duplicate type reader type: '{}'".format(class_.target_type)) 34 | self.type_readers_type[class_.target_type] = class_ 35 | else: 36 | raise ReaderError("Unknown base class for reader: '{!s}'".format(class_)) 37 | 38 | def get_type_reader(self, type_reader): 39 | try: 40 | name = type_reader.reader_name 41 | except AttributeError: 42 | name = type_reader 43 | 44 | type_spec = TypeSpec.parse(name) 45 | 46 | if type_spec.full_name in self.type_readers: 47 | return self.type_readers[type_spec.full_name] 48 | 49 | if type_spec.generic_params: 50 | if type_spec.name in self.generic_type_readers: 51 | generic_type_class = self.generic_type_readers[type_spec.name] 52 | generic_type_reader_class = generic_type_class.create_from_type(type_spec) 53 | if generic_type_reader_class.reader_name in self.type_readers: 54 | raise ReaderError("Duplicate type reader name from generic: '{}' '{}'".format( 55 | generic_type_reader_class.reader_name, generic_type_class.generic_reader_name)) 56 | self.type_readers[generic_type_reader_class.reader_name] = generic_type_reader_class 57 | if generic_type_reader_class.target_type in self.type_readers_type: 58 | raise ReaderError("Duplicate type reader type from generic: '{}' '{}'".format( 59 | generic_type_reader_class.target_type, generic_type_class.generic_target_type)) 60 | self.type_readers_type[generic_type_reader_class.target_type] = generic_type_reader_class 61 | return generic_type_reader_class 62 | 63 | raise ReaderError("Type reader not found: '{}'".format(type_spec.full_name)) 64 | 65 | def get_type_reader_by_type(self, type_reader): 66 | try: 67 | reader_type = type_reader.target_type 68 | except AttributeError: 69 | reader_type = type_reader 70 | 71 | type_spec = TypeSpec.parse(reader_type) 72 | 73 | if type_spec.full_name in self.type_readers_type: 74 | return self.type_readers_type[type_spec.full_name] 75 | 76 | if type_spec.generic_params: 77 | if type_spec.name in self.generic_type_readers_type: 78 | generic_type_class = self.generic_type_readers_type[type_spec.name] 79 | generic_type_reader_class = generic_type_class.create_from_type(type_spec) 80 | if generic_type_reader_class.reader_name in self.type_readers: 81 | raise ReaderError("Duplicate type reader name from generic: '{}' '{}'".format( 82 | generic_type_reader_class.reader_name, generic_type_class.generic_reader_name)) 83 | self.type_readers[generic_type_reader_class.reader_name] = generic_type_reader_class 84 | if generic_type_reader_class.target_type in self.type_readers_type: 85 | raise ReaderError("Duplicate type reader type from generic: '{}' '{}'".format( 86 | generic_type_reader_class.target_type, generic_type_class.generic_target_type)) 87 | self.type_readers_type[generic_type_reader_class.target_type] = generic_type_reader_class 88 | return generic_type_reader_class 89 | 90 | raise ReaderError("Type reader not found: '{}'".format(type_spec.full_name)) 91 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | all type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_readers import xna_graphics, xna_math, xna_media, xna_primitive, xna_system 8 | 9 | from xnb_parse.type_readers.fez import * 10 | from xnb_parse.type_readers.mercury import * 11 | 12 | 13 | __all__ = ['xna_graphics', 'xna_math', 'xna_media', 'xna_primitive', 'xna_system'] 14 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/fez/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | FEZ type readers! 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_readers.fez import fez_basic, fez_graphics, fez_level, fez_music 8 | 9 | 10 | __all__ = ['fez_basic', 'fez_graphics', 'fez_level', 'fez_music'] 11 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/fez/fez_basic.py: -------------------------------------------------------------------------------- 1 | """ 2 | FEZ basic type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_reader import TypeReaderPlugin, GenericTypeReader, EnumTypeReader 8 | from xnb_parse.xna_types.xna_system import XNASet 9 | from xnb_parse.xna_types.fez.fez_basic import (FaceOrientation, LevelNodeType, CollisionType, Viewpoint, NpcAction, 10 | ActorType, SurfaceType, LiquidType, PathEndBehavior, ComparisonOperator, 11 | CodeInput, VibrationMotor) 12 | 13 | 14 | class FaceOrientationReader(EnumTypeReader, TypeReaderPlugin): 15 | target_type = 'FezEngine.FaceOrientation' 16 | reader_name = 'FezEngine.FaceOrientationReader' 17 | enum_type = FaceOrientation 18 | 19 | 20 | class LevelNodeTypeReader(EnumTypeReader, TypeReaderPlugin): 21 | target_type = 'FezEngine.LevelNodeType' 22 | reader_name = 'FezEngine.LevelNodeTypeReader' 23 | enum_type = LevelNodeType 24 | 25 | 26 | class CollisionTypeReader(EnumTypeReader, TypeReaderPlugin): 27 | target_type = 'FezEngine.CollisionType' 28 | reader_name = 'FezEngine.Readers.CollisionTypeReader' 29 | enum_type = CollisionType 30 | 31 | 32 | class ViewpointReader(EnumTypeReader, TypeReaderPlugin): 33 | target_type = 'FezEngine.Viewpoint' 34 | reader_name = 'FezEngine.Readers.ViewpointReader' 35 | enum_type = Viewpoint 36 | 37 | 38 | class NpcActionReader(EnumTypeReader, TypeReaderPlugin): 39 | target_type = 'FezEngine.Structure.NpcAction' 40 | reader_name = 'FezEngine.Readers.NpcActionReader' 41 | enum_type = NpcAction 42 | 43 | 44 | class ActorTypeReader(EnumTypeReader, TypeReaderPlugin): 45 | target_type = 'FezEngine.Structure.ActorType' 46 | reader_name = 'FezEngine.Readers.ActorTypeReader' 47 | enum_type = ActorType 48 | 49 | 50 | class SurfaceTypeReader(EnumTypeReader, TypeReaderPlugin): 51 | target_type = 'FezEngine.Structure.SurfaceType' 52 | reader_name = 'FezEngine.Readers.SurfaceTypeReader' 53 | enum_type = SurfaceType 54 | 55 | 56 | class LiquidTypeReader(EnumTypeReader, TypeReaderPlugin): 57 | target_type = 'FezEngine.Structure.LiquidType' 58 | reader_name = 'FezEngine.Readers.LiquidTypeReader' 59 | enum_type = LiquidType 60 | 61 | 62 | class PathEndBehaviorReader(EnumTypeReader, TypeReaderPlugin): 63 | target_type = 'FezEngine.Structure.PathEndBehavior' 64 | reader_name = 'FezEngine.Readers.PathEndBehaviorReader' 65 | enum_type = PathEndBehavior 66 | 67 | 68 | class ComparisonOperatorReader(EnumTypeReader, TypeReaderPlugin): 69 | target_type = 'FezEngine.Structure.Scripting.ComparisonOperator' 70 | reader_name = 'FezEngine.Readers.ComparisonOperatorReader' 71 | enum_type = ComparisonOperator 72 | 73 | 74 | class CodeInputReader(EnumTypeReader, TypeReaderPlugin): 75 | target_type = 'FezEngine.Structure.Input.CodeInput' 76 | reader_name = 'FezEngine.Readers.CodeInputReader' 77 | enum_type = CodeInput 78 | 79 | 80 | class VibrationMotorReader(EnumTypeReader, TypeReaderPlugin): 81 | target_type = 'FezEngine.Structure.Input.VibrationMotor' 82 | reader_name = 'FezEngine.Readers.VibrationMotorReader' 83 | enum_type = VibrationMotor 84 | 85 | 86 | class SetReader(GenericTypeReader, TypeReaderPlugin): 87 | generic_target_type = 'Common.Set`1' 88 | generic_reader_name = 'FezEngine.SetReader`1' 89 | 90 | def read(self): 91 | return XNASet() 92 | 93 | 94 | class IEqualityComparerReader(GenericTypeReader, TypeReaderPlugin): 95 | generic_target_type = 'System.Collections.Generic.IEqualityComparer`1' 96 | generic_reader_name = 'FezEngine.IEqualityComparerReader`1' 97 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/fez/fez_graphics.py: -------------------------------------------------------------------------------- 1 | """ 2 | FEZ graphics type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_reader import (TypeReaderPlugin, BaseTypeReader, ValueTypeReader, GenericTypeReader, 8 | generic_reader_type) 9 | from xnb_parse.type_readers.xna_graphics import PrimitiveTypeReader, Texture2DReader 10 | from xnb_parse.type_readers.xna_math import MatrixReader, RectangleReader 11 | from xnb_parse.type_readers.xna_primitive import StringReader, UInt16Reader 12 | from xnb_parse.type_readers.xna_system import ListReader, ArrayReader, TimeSpanReader, ReflectiveReader 13 | from xnb_parse.type_readers.fez.fez_basic import NpcActionReader, ActorTypeReader, SetReader, FaceOrientationReader 14 | from xnb_parse.xna_types.xna_math import Vector3, Vector2 15 | from xnb_parse.xna_types.fez.fez_graphics import (AnimatedTexture, Frame, ArtObject, ShaderInstancedIndexedPrimitives, 16 | VertexPositionNormalTextureInstance, NpcMetadata, AnimatedTexturePC, 17 | FramePC, ArtObjectPC) 18 | 19 | # avoiding circular import 20 | PLATFORM_WINDOWS = b'w' 21 | 22 | 23 | class ArtObjectReader(BaseTypeReader, TypeReaderPlugin): 24 | target_type = 'FezEngine.Structure.ArtObject' 25 | reader_name = 'FezEngine.Readers.ArtObjectReader' 26 | 27 | def read(self): 28 | if self.file_platform == PLATFORM_WINDOWS: 29 | name = self.stream.read_string() 30 | cubemap = self.stream.read_object(Texture2DReader) 31 | size = self.stream.read_vector3() 32 | geometry = self.stream.read_object(ShaderInstancedIndexedPrimitivesReader, 33 | [VertexPositionNormalTextureInstanceReader, MatrixReader]) 34 | actor_type = self.stream.read_object(ActorTypeReader) 35 | no_silhouette = self.stream.read_boolean() 36 | return ArtObjectPC(name, cubemap, size, geometry, actor_type, no_silhouette) 37 | else: 38 | name = self.stream.read_string() 39 | cubemap_path = self.stream.read_string() 40 | size = self.stream.read_vector3() 41 | geometry = self.stream.read_object(ShaderInstancedIndexedPrimitivesReader, 42 | [VertexPositionNormalTextureInstanceReader, MatrixReader]) 43 | actor_type = self.stream.read_object(ActorTypeReader) 44 | no_silhouette = self.stream.read_boolean() 45 | laser_outlets = self.stream.read_object(ReflectiveReader, [generic_reader_type(SetReader, 46 | [FaceOrientationReader])]) 47 | return ArtObject(name, cubemap_path, size, geometry, actor_type, no_silhouette, laser_outlets) 48 | 49 | 50 | class ShaderInstancedIndexedPrimitivesReader(GenericTypeReader, TypeReaderPlugin): 51 | generic_target_type = 'FezEngine.Structure.Geometry.ShaderInstancedIndexedPrimitives`2' 52 | generic_reader_name = 'FezEngine.Readers.ShaderInstancedIndexedPrimitivesReader`2' 53 | 54 | def read(self): 55 | primitive_type = self.stream.read_object(PrimitiveTypeReader) 56 | vertices = self.stream.read_object(ArrayReader, [self.readers[0]]) 57 | indices = self.stream.read_object(ArrayReader, [UInt16Reader]) 58 | return ShaderInstancedIndexedPrimitives(primitive_type, vertices, indices) 59 | 60 | 61 | class VertexPositionNormalTextureInstanceReader(ValueTypeReader, TypeReaderPlugin): 62 | target_type = 'FezEngine.Structure.Geometry.VertexPositionNormalTextureInstance' 63 | reader_name = 'FezEngine.Readers.VertexPositionNormalTextureInstanceReader' 64 | 65 | def read(self): 66 | values = self.stream.unpack('3f B 2f') 67 | position = Vector3._make(values[0:3]) 68 | normal = values[3] 69 | texture_coord = Vector2._make(values[4:6]) 70 | return VertexPositionNormalTextureInstance(position, normal, texture_coord) 71 | 72 | 73 | class NpcMetadataReader(BaseTypeReader, TypeReaderPlugin): 74 | target_type = 'FezEngine.Structure.NpcMetadata' 75 | reader_name = 'FezEngine.Readers.NpcMetadataReader' 76 | 77 | def read(self): 78 | walk_speed = self.stream.read_single() 79 | avoids_gomez = self.stream.read_boolean() 80 | sound_path = self.stream.read_object(StringReader) 81 | sound_actions = self.stream.read_object(ListReader, [NpcActionReader]) 82 | return NpcMetadata(walk_speed, avoids_gomez, sound_path, sound_actions) 83 | 84 | 85 | class AnimatedTextureReader(BaseTypeReader, TypeReaderPlugin): 86 | target_type = 'FezEngine.Structure.AnimatedTexture' 87 | reader_name = 'FezEngine.Readers.AnimatedTextureReader' 88 | 89 | def read(self): 90 | if self.file_platform == PLATFORM_WINDOWS: 91 | width = self.stream.read_int32() 92 | height = self.stream.read_int32() 93 | actual_width = self.stream.read_int32() 94 | actual_height = self.stream.read_int32() 95 | elements = self.stream.read_uint32() 96 | data = self.stream.read(elements) 97 | frames = self.stream.read_object(ListReader, [FrameReader]) 98 | return AnimatedTexturePC(width, height, actual_width, actual_height, data, frames) 99 | else: 100 | width = self.stream.read_int32() 101 | height = self.stream.read_int32() 102 | actual_width = self.stream.read_int32() 103 | actual_height = self.stream.read_int32() 104 | frames = self.stream.read_object(ListReader, [FrameReader]) 105 | return AnimatedTexture(width, height, actual_width, actual_height, frames) 106 | 107 | 108 | class FrameReader(BaseTypeReader, TypeReaderPlugin): 109 | target_type = 'FezEngine.Content.FrameContent' 110 | reader_name = 'FezEngine.Readers.FrameReader' 111 | 112 | def read(self): 113 | if self.file_platform == PLATFORM_WINDOWS: 114 | duration = self.stream.read_object(TimeSpanReader) 115 | rectangle = self.stream.read_object(RectangleReader) 116 | return FramePC(duration, rectangle) 117 | else: 118 | duration = self.stream.read_object(TimeSpanReader) 119 | _ = self.stream.read_7bit_encoded_int() 120 | elements = self.stream.read_uint32() 121 | data = self.stream.read(elements * 4) 122 | return Frame(duration, data) 123 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/fez/fez_music.py: -------------------------------------------------------------------------------- 1 | """ 2 | FEZ music type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_reader import TypeReaderPlugin, BaseTypeReader, EnumTypeReader 8 | from xnb_parse.type_readers.xna_primitive import Int32Reader 9 | from xnb_parse.type_readers.xna_system import ListReader, ArrayReader 10 | from xnb_parse.xna_types.fez.fez_music import ShardNotes, AssembleChords, TrackedSong, Loop 11 | 12 | 13 | class TrackedSongReader(BaseTypeReader, TypeReaderPlugin): 14 | target_type = 'FezEngine.Structure.TrackedSong' 15 | reader_name = 'FezEngine.Readers.TrackedSongReader' 16 | 17 | def read(self): 18 | loops = self.stream.read_object(ListReader, [LoopReader]) 19 | name = self.stream.read_string() 20 | tempo = self.stream.read_int32() 21 | time_signature = self.stream.read_int32() 22 | notes = self.stream.read_object(ArrayReader, [ShardNotesReader]) 23 | assemble_chord = self.stream.read_object(AssembleChordsReader) 24 | random_ordering = self.stream.read_boolean() 25 | custom_ordering = self.stream.read_object(ArrayReader, [Int32Reader]) 26 | return TrackedSong(loops, name, tempo, time_signature, notes, assemble_chord, random_ordering, custom_ordering) 27 | 28 | 29 | class LoopReader(BaseTypeReader, TypeReaderPlugin): 30 | target_type = 'FezEngine.Structure.Loop' 31 | reader_name = 'FezEngine.Readers.LoopReader' 32 | 33 | def read(self): 34 | duration = self.stream.read_int32() 35 | loop_times_from = self.stream.read_int32() 36 | loop_times_to = self.stream.read_int32() 37 | name = self.stream.read_string() 38 | trigger_from = self.stream.read_int32() 39 | trigger_to = self.stream.read_int32() 40 | delay = self.stream.read_int32() 41 | night = self.stream.read_boolean() 42 | day = self.stream.read_boolean() 43 | dusk = self.stream.read_boolean() 44 | dawn = self.stream.read_boolean() 45 | fractional_time = self.stream.read_boolean() 46 | one_at_a_time = self.stream.read_boolean() 47 | cut_off_tail = self.stream.read_boolean() 48 | return Loop(duration, loop_times_from, loop_times_to, name, trigger_from, trigger_to, delay, night, day, dusk, 49 | dawn, fractional_time, one_at_a_time, cut_off_tail) 50 | 51 | 52 | class ShardNotesReader(EnumTypeReader, TypeReaderPlugin): 53 | target_type = 'FezEngine.Structure.ShardNotes' 54 | reader_name = 'FezEngine.Readers.ShardNotesReader' 55 | enum_type = ShardNotes 56 | 57 | 58 | class AssembleChordsReader(EnumTypeReader, TypeReaderPlugin): 59 | target_type = 'FezEngine.Structure.AssembleChords' 60 | reader_name = 'FezEngine.Readers.AssembleChordsReader' 61 | enum_type = AssembleChords 62 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/mercury/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | mercury particle engine type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_readers.mercury import basic, emitters, modifiers, particle 8 | 9 | 10 | __all__ = ['particle', 'basic', 'emitters', 'modifiers'] 11 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/mercury/basic.py: -------------------------------------------------------------------------------- 1 | """ 2 | mercury particle engine basic type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_reader import EnumTypeReader, TypeReaderPlugin, ValueTypeReader 8 | from xnb_parse.xna_types.mercury.basic import BlendMode, VariableFloat, VariableFloat3 9 | 10 | 11 | class VariableFloatReader(ValueTypeReader, TypeReaderPlugin): 12 | target_type = 'ProjectMercury.VariableFloat' 13 | reader_name = 'Microsoft.Xna.Framework.Content.ReflectiveReader`1[ProjectMercury.VariableFloat]' 14 | 15 | def read(self): 16 | value = self.stream.read_single() 17 | variation = self.stream.read_single() 18 | return VariableFloat(value, variation) 19 | 20 | 21 | class VariableFloat3Reader(ValueTypeReader, TypeReaderPlugin): 22 | target_type = 'ProjectMercury.VariableFloat3' 23 | reader_name = 'Microsoft.Xna.Framework.Content.ReflectiveReader`1[ProjectMercury.VariableFloat3]' 24 | 25 | def read(self): 26 | value = self.stream.read_vector3() 27 | variation = self.stream.read_vector3() 28 | return VariableFloat3(value, variation) 29 | 30 | 31 | class BlendModeReader(EnumTypeReader, TypeReaderPlugin): 32 | target_type = 'ProjectMercury.BlendMode' 33 | reader_name = 'ProjectMercury.BlendMode' 34 | enum_type = BlendMode 35 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/mercury/emitters.py: -------------------------------------------------------------------------------- 1 | """ 2 | mercury particle engine emitter type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_reader import BaseTypeReader, TypeReaderPlugin 8 | from xnb_parse.type_readers.mercury.basic import BlendModeReader, VariableFloat3Reader, VariableFloatReader 9 | from xnb_parse.type_readers.mercury.modifiers import ModifierCollectionReader 10 | from xnb_parse.type_readers.xna_primitive import StringReader 11 | from xnb_parse.xna_types.mercury.emitters import Emitter, EmitterCollection, CircleEmitter, ConeEmitter, LineEmitter 12 | from xnb_parse.xna_types.xna_system import XNAList 13 | 14 | 15 | class EmitterCollectionReader(BaseTypeReader, TypeReaderPlugin): 16 | target_type = 'ProjectMercury.Emitters.EmitterCollection' 17 | reader_name = 'Microsoft.Xna.Framework.Content.ReflectiveReader`1[ProjectMercury.Emitters.EmitterCollection]' 18 | 19 | def read(self): 20 | elements = self.stream.read_int32() 21 | return EmitterCollection(XNAList([self.stream.read_object(EmitterReader) for _ in range(elements)])) 22 | 23 | 24 | class EmitterReader(BaseTypeReader, TypeReaderPlugin): 25 | target_type = 'ProjectMercury.Emitters.Emitter' 26 | reader_name = 'Microsoft.Xna.Framework.Content.ReflectiveReader`1[ProjectMercury.Emitters.Emitter]' 27 | 28 | def read(self): 29 | name = self.stream.read_object(StringReader) 30 | budget = self.stream.read_int32() 31 | term = self.stream.read_single() 32 | release_quantity = self.stream.read_int32() 33 | enabled = self.stream.read_boolean() 34 | release_speed = self.stream.read_value_or_object(VariableFloatReader) 35 | release_colour = self.stream.read_value_or_object(VariableFloat3Reader) 36 | release_opacity = self.stream.read_value_or_object(VariableFloatReader) 37 | release_scale = self.stream.read_value_or_object(VariableFloatReader) 38 | release_rotation = self.stream.read_value_or_object(VariableFloatReader) 39 | release_impulse = self.stream.read_vector2() 40 | particle_texture_asset_name = self.stream.read_object(StringReader) 41 | modifiers = self.stream.read_object(ModifierCollectionReader) 42 | blend_mode = self.stream.read_value_or_object(BlendModeReader) 43 | trigger_offset = self.stream.read_vector2() 44 | minimum_trigger_period = self.stream.read_single() 45 | 46 | return Emitter(name, budget, term, release_quantity, enabled, release_speed, release_colour, release_opacity, 47 | release_scale, release_rotation, release_impulse, particle_texture_asset_name, modifiers, 48 | blend_mode, trigger_offset, minimum_trigger_period) 49 | 50 | 51 | class CircleEmitterReader(EmitterReader, TypeReaderPlugin): 52 | target_type = 'ProjectMercury.Emitters.CircleEmitter' 53 | reader_name = 'Microsoft.Xna.Framework.Content.ReflectiveReader`1[ProjectMercury.Emitters.CircleEmitter]' 54 | 55 | def read(self): 56 | emitter = EmitterReader.read(self) 57 | radius = self.stream.read_single() 58 | ring = self.stream.read_boolean() 59 | radiate = self.stream.read_boolean() 60 | return CircleEmitter.make(emitter, radius, ring, radiate) 61 | 62 | 63 | class ConeEmitterReader(EmitterReader, TypeReaderPlugin): 64 | target_type = 'ProjectMercury.Emitters.ConeEmitter' 65 | reader_name = 'Microsoft.Xna.Framework.Content.ReflectiveReader`1[ProjectMercury.Emitters.ConeEmitter]' 66 | 67 | def read(self): 68 | emitter = EmitterReader.read(self) 69 | direction = self.stream.read_single() 70 | cone_angle = self.stream.read_single() 71 | return ConeEmitter.make(emitter, direction, cone_angle) 72 | 73 | 74 | class LineEmitterReader(EmitterReader, TypeReaderPlugin): 75 | target_type = 'ProjectMercury.Emitters.LineEmitter' 76 | reader_name = 'Microsoft.Xna.Framework.Content.ReflectiveReader`1[ProjectMercury.Emitters.LineEmitter]' 77 | 78 | def read(self): 79 | emitter = EmitterReader.read(self) 80 | length = self.stream.read_single() 81 | angle = self.stream.read_single() 82 | rectilinear = self.stream.read_boolean() 83 | emit_both_ways = self.stream.read_boolean() 84 | return LineEmitter.make(emitter, length, angle, rectilinear, emit_both_ways) 85 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/mercury/particle.py: -------------------------------------------------------------------------------- 1 | """ 2 | mercury particle engine main type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_reader import TypeReaderPlugin 8 | from xnb_parse.type_readers.mercury.emitters import EmitterCollectionReader 9 | from xnb_parse.type_readers.xna_primitive import StringReader 10 | from xnb_parse.xna_types.mercury.particle import ParticleEffect 11 | 12 | 13 | class ParticleEffectReader(EmitterCollectionReader, TypeReaderPlugin): 14 | target_type = 'ProjectMercury.ParticleEffect' 15 | reader_name = 'Microsoft.Xna.Framework.Content.ReflectiveReader`1[ProjectMercury.ParticleEffect]' 16 | 17 | def read(self): 18 | emitters = EmitterCollectionReader.read(self).emitters 19 | name = self.stream.read_object(StringReader) 20 | author = self.stream.read_object(StringReader) 21 | description = self.stream.read_object(StringReader) 22 | return ParticleEffect(emitters, name, author, description) 23 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/xna_graphics.py: -------------------------------------------------------------------------------- 1 | """ 2 | graphics type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_reader import TypeReaderPlugin, BaseTypeReader, ReaderError, EnumTypeReader 8 | from xnb_parse.type_readers.xna_math import Vector3Reader, RectangleReader 9 | from xnb_parse.type_readers.xna_primitive import CharReader, StringReader, ObjectReader 10 | from xnb_parse.type_readers.xna_system import ListReader, DictionaryReader 11 | from xnb_parse.xna_types.xna_graphics import (Texture2D, Texture3D, TextureCube, CUBE_SIDES, IndexBuffer, Effect, 12 | get_surface_format, PrimitiveType, SpriteFont, BasicEffect, 13 | PrimitiveType4) 14 | 15 | 16 | class TextureReader(BaseTypeReader, TypeReaderPlugin): 17 | target_type = 'Microsoft.Xna.Framework.Graphics.Texture' 18 | reader_name = 'Microsoft.Xna.Framework.Content.TextureReader' 19 | 20 | def read(self): 21 | raise ReaderError("TextureReader should never be invoked directly") 22 | 23 | 24 | class Texture2DReader(BaseTypeReader, TypeReaderPlugin): 25 | target_type = 'Microsoft.Xna.Framework.Graphics.Texture2D' 26 | reader_name = 'Microsoft.Xna.Framework.Content.Texture2DReader' 27 | 28 | def read(self): 29 | surface_format_raw = self.stream.read_int32() 30 | surface_format = get_surface_format(self.stream.file_version, surface_format_raw) 31 | width = self.stream.read_int32() 32 | height = self.stream.read_int32() 33 | mip_count = self.stream.read_int32() 34 | mip_levels = [] 35 | for _ in range(mip_count): 36 | size = self.stream.read_int32() 37 | data = self.stream.read(size) 38 | mip_levels.append(data) 39 | return Texture2D(surface_format, width, height, mip_levels, self.stream.needs_swap) 40 | 41 | 42 | class Texture3DReader(BaseTypeReader, TypeReaderPlugin): 43 | target_type = 'Microsoft.Xna.Framework.Graphics.Texture3D' 44 | reader_name = 'Microsoft.Xna.Framework.Content.Texture3DReader' 45 | 46 | def read(self): 47 | surface_format_raw = self.stream.read_int32() 48 | surface_format = get_surface_format(self.stream.file_version, surface_format_raw) 49 | width = self.stream.read_int32() 50 | height = self.stream.read_int32() 51 | depth = self.stream.read_int32() 52 | mip_count = self.stream.read_int32() 53 | mip_levels = [] 54 | for _ in range(mip_count): 55 | size = self.stream.read_int32() 56 | data = self.stream.read(size) 57 | mip_levels.append(data) 58 | return Texture3D(surface_format, width, height, depth, mip_levels, self.stream.needs_swap) 59 | 60 | 61 | class TextureCubeReader(BaseTypeReader, TypeReaderPlugin): 62 | target_type = 'Microsoft.Xna.Framework.Graphics.TextureCube' 63 | reader_name = 'Microsoft.Xna.Framework.Content.TextureCubeReader' 64 | 65 | def read(self): 66 | surface_format_raw = self.stream.read_int32() 67 | surface_format = get_surface_format(self.stream.file_version, surface_format_raw) 68 | texture_size = self.stream.read_int32() 69 | mip_count = self.stream.read_int32() 70 | sides = {} 71 | for side in CUBE_SIDES: 72 | mip_levels = [] 73 | for _ in range(mip_count): 74 | size = self.stream.read_int32() 75 | data = self.stream.read(size) 76 | mip_levels.append(data) 77 | sides[side] = mip_levels 78 | return TextureCube(surface_format, texture_size, sides, self.stream.needs_swap) 79 | 80 | 81 | class IndexBufferReader(BaseTypeReader, TypeReaderPlugin): 82 | target_type = 'Microsoft.Xna.Framework.Graphics.IndexBuffer' 83 | reader_name = 'Microsoft.Xna.Framework.Content.IndexBufferReader' 84 | 85 | def read(self): 86 | index_16 = self.stream.read_boolean() 87 | size = self.stream.read_int32() 88 | data = self.stream.read(size) 89 | return IndexBuffer(index_16, data) 90 | 91 | 92 | class VertexBufferReader(BaseTypeReader, TypeReaderPlugin): 93 | target_type = 'Microsoft.Xna.Framework.Graphics.VertexBuffer' 94 | reader_name = 'Microsoft.Xna.Framework.Content.VertexBufferReader' 95 | 96 | def read(self): 97 | size = self.stream.read_int32() 98 | data = self.stream.read(size) 99 | return data 100 | 101 | 102 | class VertexDeclarationReader(BaseTypeReader, TypeReaderPlugin): 103 | target_type = 'Microsoft.Xna.Framework.Graphics.VertexDeclaration' 104 | reader_name = 'Microsoft.Xna.Framework.Content.VertexDeclarationReader' 105 | 106 | def read(self): 107 | element_count = self.stream.read_int32() 108 | elements = [] 109 | for _ in range(element_count): 110 | v_stream = self.stream.read_int16() 111 | v_offset = self.stream.read_int16() 112 | v_format = self.stream.read_byte() 113 | v_method = self.stream.read_byte() 114 | v_usage = self.stream.read_byte() 115 | v_usage_index = self.stream.read_byte() 116 | element = v_stream, v_offset, v_format, v_method, v_usage, v_usage_index 117 | elements.append(element) 118 | return elements 119 | 120 | 121 | class EffectReader(BaseTypeReader, TypeReaderPlugin): 122 | target_type = 'Microsoft.Xna.Framework.Graphics.Effect' 123 | reader_name = 'Microsoft.Xna.Framework.Content.EffectReader' 124 | 125 | def read(self): 126 | size = self.stream.read_int32() 127 | data = self.stream.read(size) 128 | return Effect(data) 129 | 130 | 131 | class EffectMaterialReader(BaseTypeReader, TypeReaderPlugin): 132 | target_type = 'Microsoft.Xna.Framework.Graphics.EffectMaterial' 133 | reader_name = 'Microsoft.Xna.Framework.Content.EffectMaterialReader' 134 | 135 | def read(self): 136 | effect = self.stream.read_external_reference(EffectReader) 137 | parameters = self.stream.read_object(DictionaryReader, [StringReader, ObjectReader]) 138 | return effect, parameters 139 | 140 | 141 | class BasicEffectReader(BaseTypeReader, TypeReaderPlugin): 142 | target_type = 'Microsoft.Xna.Framework.Graphics.BasicEffect' 143 | reader_name = 'Microsoft.Xna.Framework.Content.BasicEffectReader' 144 | 145 | def read(self): 146 | texture = self.stream.read_external_reference(TextureReader) 147 | colour_d = self.stream.read_vector3() 148 | colour_e = self.stream.read_vector3() 149 | colour_s = self.stream.read_vector3() 150 | spec = self.stream.read_single() 151 | alpha = self.stream.read_single() 152 | colour_v = self.stream.read_boolean() 153 | return BasicEffect(texture, colour_d, colour_e, colour_s, spec, alpha, colour_v) 154 | 155 | 156 | class SpriteFontReader(BaseTypeReader, TypeReaderPlugin): 157 | target_type = 'Microsoft.Xna.Framework.Graphics.SpriteFont' 158 | reader_name = 'Microsoft.Xna.Framework.Content.SpriteFontReader' 159 | 160 | def read(self): 161 | texture = self.stream.read_object(Texture2DReader) 162 | glyphs = self.stream.read_object(ListReader, [RectangleReader]) 163 | cropping = self.stream.read_object(ListReader, [RectangleReader]) 164 | char_map = self.stream.read_object(ListReader, [CharReader]) 165 | v_space = self.stream.read_int32() 166 | h_space = self.stream.read_single() 167 | kerning = self.stream.read_object(ListReader, [Vector3Reader]) 168 | default_char = None 169 | has_default_char = self.stream.read_boolean() 170 | if has_default_char: 171 | default_char = self.stream.read_char() 172 | return SpriteFont(texture, glyphs, cropping, char_map, v_space, h_space, kerning, default_char) 173 | 174 | 175 | class ModelReader(BaseTypeReader, TypeReaderPlugin): 176 | target_type = 'Microsoft.Xna.Framework.Graphics.Model' 177 | reader_name = 'Microsoft.Xna.Framework.Content.ModelReader' 178 | 179 | 180 | class PrimitiveTypeReader(EnumTypeReader, TypeReaderPlugin): 181 | target_type = 'Microsoft.Xna.Framework.Graphics.PrimitiveType' 182 | reader_name = 'Microsoft.Xna.Framework.Content.PrimitiveTypeReader' 183 | enum_type = PrimitiveType 184 | enum_type4 = PrimitiveType4 185 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/xna_math.py: -------------------------------------------------------------------------------- 1 | """ 2 | math type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_reader import TypeReaderPlugin, BaseTypeReader, ValueTypeReader 8 | from xnb_parse.xna_types.xna_math import (Rectangle, Point, Plane, BoundingBox, BoundingSphere, Ray, BoundingFrustum, 9 | Vector2, Vector3, Vector4, Matrix, Quaternion, Color) 10 | from xnb_parse.xna_types.xna_system import XNAList 11 | 12 | 13 | class Vector2Reader(ValueTypeReader, TypeReaderPlugin): 14 | target_type = 'Microsoft.Xna.Framework.Vector2' 15 | reader_name = 'Microsoft.Xna.Framework.Content.Vector2Reader' 16 | 17 | def read(self): 18 | return Vector2._make(self.stream.unpack('2f')) 19 | 20 | 21 | class Vector3Reader(ValueTypeReader, TypeReaderPlugin): 22 | target_type = 'Microsoft.Xna.Framework.Vector3' 23 | reader_name = 'Microsoft.Xna.Framework.Content.Vector3Reader' 24 | 25 | def read(self): 26 | return Vector3._make(self.stream.unpack('3f')) 27 | 28 | 29 | class Vector4Reader(ValueTypeReader, TypeReaderPlugin): 30 | target_type = 'Microsoft.Xna.Framework.Vector4' 31 | reader_name = 'Microsoft.Xna.Framework.Content.Vector4Reader' 32 | 33 | def read(self): 34 | return Vector4._make(self.stream.unpack('4f')) 35 | 36 | 37 | class MatrixReader(ValueTypeReader, TypeReaderPlugin): 38 | target_type = 'Microsoft.Xna.Framework.Matrix' 39 | reader_name = 'Microsoft.Xna.Framework.Content.MatrixReader' 40 | 41 | def read(self): 42 | return Matrix(XNAList(self.stream.unpack('16f'))) 43 | 44 | 45 | class QuaternionReader(ValueTypeReader, TypeReaderPlugin): 46 | target_type = 'Microsoft.Xna.Framework.Quaternion' 47 | reader_name = 'Microsoft.Xna.Framework.Content.QuaternionReader' 48 | 49 | def read(self): 50 | return Quaternion._make(self.stream.unpack('4f')) 51 | 52 | 53 | class ColorReader(ValueTypeReader, TypeReaderPlugin): 54 | target_type = 'Microsoft.Xna.Framework.Graphics.Color' 55 | reader_name = 'Microsoft.Xna.Framework.Content.ColorReader' 56 | 57 | def read(self): 58 | return Color._make(self.stream.unpack('4B')) 59 | 60 | 61 | class PlaneReader(ValueTypeReader, TypeReaderPlugin): 62 | target_type = 'Microsoft.Xna.Framework.Plane' 63 | reader_name = 'Microsoft.Xna.Framework.Content.PlaneReader' 64 | 65 | def read(self): 66 | values = self.stream.unpack('3f f') 67 | plane_normal = Vector3._make(values[0:3]) 68 | plane_d = values[3] 69 | return Plane(plane_normal, plane_d) 70 | 71 | 72 | class PointReader(ValueTypeReader, TypeReaderPlugin): 73 | target_type = 'Microsoft.Xna.Framework.Point' 74 | reader_name = 'Microsoft.Xna.Framework.Content.PointReader' 75 | 76 | def read(self): 77 | return Point._make(self.stream.unpack('2i')) 78 | 79 | 80 | class RectangleReader(ValueTypeReader, TypeReaderPlugin): 81 | target_type = 'Microsoft.Xna.Framework.Rectangle' 82 | reader_name = 'Microsoft.Xna.Framework.Content.RectangleReader' 83 | 84 | def read(self): 85 | return Rectangle._make(self.stream.unpack('4i')) 86 | 87 | 88 | class BoundingBoxReader(ValueTypeReader, TypeReaderPlugin): 89 | target_type = 'Microsoft.Xna.Framework.BoundingBox' 90 | reader_name = 'Microsoft.Xna.Framework.Content.BoundingBoxReader' 91 | 92 | def read(self): 93 | values = self.stream.unpack('3f 3f') 94 | v_min = Vector3._make(values[0:3]) 95 | v_max = Vector3._make(values[3:6]) 96 | return BoundingBox(v_min, v_max) 97 | 98 | 99 | class BoundingSphereReader(ValueTypeReader, TypeReaderPlugin): 100 | target_type = 'Microsoft.Xna.Framework.BoundingSphere' 101 | reader_name = 'Microsoft.Xna.Framework.Content.BoundingSphereReader' 102 | 103 | def read(self): 104 | values = self.stream.unpack('3f f') 105 | v_centre = Vector3._make(values[0:3]) 106 | v_radius = values[3] 107 | return BoundingSphere(v_centre, v_radius) 108 | 109 | 110 | class BoundingFrustumReader(BaseTypeReader, TypeReaderPlugin): 111 | target_type = 'Microsoft.Xna.Framework.BoundingFrustum' 112 | reader_name = 'Microsoft.Xna.Framework.Content.BoundingFrustumReader' 113 | 114 | def read(self): 115 | return BoundingFrustum._make(Matrix(XNAList(self.stream.unpack('16f')))) 116 | 117 | 118 | class RayReader(ValueTypeReader, TypeReaderPlugin): 119 | target_type = 'Microsoft.Xna.Framework.Ray' 120 | reader_name = 'Microsoft.Xna.Framework.Content.RayReader' 121 | 122 | def read(self): 123 | values = self.stream.unpack('3f 3f') 124 | v_pos = Vector3._make(values[0:3]) 125 | v_dir = Vector3._make(values[3:6]) 126 | return Ray(v_pos, v_dir) 127 | 128 | 129 | class CurveReader(BaseTypeReader, TypeReaderPlugin): 130 | target_type = 'Microsoft.Xna.Framework.Curve' 131 | reader_name = 'Microsoft.Xna.Framework.Content.CurveReader' 132 | 133 | def read(self): 134 | pre_loop, post_loop, key_count = self.stream.unpack('3i') 135 | keys = [] 136 | for _ in range(key_count): 137 | v_pos, v_value, v_tangent_in, v_tangent_out, v_cont = self.stream.unpack('ffffi') 138 | key = (v_pos, v_value, v_tangent_in, v_tangent_out, v_cont) 139 | keys.append(key) 140 | return pre_loop, post_loop, keys 141 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/xna_media.py: -------------------------------------------------------------------------------- 1 | """ 2 | media type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_reader import TypeReaderPlugin, BaseTypeReader 8 | from xnb_parse.type_readers.xna_primitive import StringReader, Int32Reader, SingleReader 9 | from xnb_parse.xna_types.xna_media import SoundEffect, Song, Video, VideoSoundtrackType 10 | 11 | 12 | class SoundEffectReader(BaseTypeReader, TypeReaderPlugin): 13 | target_type = 'Microsoft.Xna.Framework.Audio.SoundEffect' 14 | reader_name = 'Microsoft.Xna.Framework.Content.SoundEffectReader' 15 | 16 | def read(self): 17 | format_size = self.stream.read_int32() 18 | wave_format = self.stream.read(format_size) 19 | data_size = self.stream.read_int32() 20 | wave_data = self.stream.read(data_size) 21 | loop_start = self.stream.read_int32() 22 | loop_length = self.stream.read_int32() 23 | duration = self.stream.read_int32() 24 | return SoundEffect(wave_format, wave_data, loop_start, loop_length, duration, self.stream.needs_swap) 25 | 26 | 27 | class SongReader(BaseTypeReader, TypeReaderPlugin): 28 | target_type = 'Microsoft.Xna.Framework.Media.Song' 29 | reader_name = 'Microsoft.Xna.Framework.Content.SongReader' 30 | 31 | def read(self): 32 | filename = self.stream.read_string() 33 | duration = self.stream.read_int32() 34 | return Song(filename, duration) 35 | 36 | 37 | class VideoReader(BaseTypeReader, TypeReaderPlugin): 38 | target_type = 'Microsoft.Xna.Framework.Media.Video' 39 | reader_name = 'Microsoft.Xna.Framework.Content.VideoReader' 40 | 41 | def read(self): 42 | filename = self.stream.read_object(StringReader) 43 | duration = self.stream.read_object(Int32Reader) 44 | width = self.stream.read_object(Int32Reader) 45 | height = self.stream.read_object(Int32Reader) 46 | fps = self.stream.read_object(SingleReader) 47 | video_soundtrack_type = VideoSoundtrackType(self.stream.read_object(Int32Reader)) 48 | return Video(filename, duration, width, height, fps, video_soundtrack_type) 49 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/xna_primitive.py: -------------------------------------------------------------------------------- 1 | """ 2 | primitive type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_reader import TypeReaderPlugin, BaseTypeReader, ValueTypeReader, ReaderError 8 | 9 | 10 | class ByteReader(ValueTypeReader, TypeReaderPlugin): 11 | target_type = 'System.Byte' 12 | reader_name = 'Microsoft.Xna.Framework.Content.ByteReader' 13 | 14 | def read(self): 15 | return self.stream.read_byte() 16 | 17 | 18 | class SByteReader(ValueTypeReader, TypeReaderPlugin): 19 | target_type = 'System.SByte' 20 | reader_name = 'Microsoft.Xna.Framework.Content.SByteReader' 21 | 22 | def read(self): 23 | return self.stream.read_sbyte() 24 | 25 | 26 | class Int16Reader(ValueTypeReader, TypeReaderPlugin): 27 | target_type = 'System.Int16' 28 | reader_name = 'Microsoft.Xna.Framework.Content.Int16Reader' 29 | 30 | def read(self): 31 | return self.stream.read_int16() 32 | 33 | 34 | class UInt16Reader(ValueTypeReader, TypeReaderPlugin): 35 | target_type = 'System.UInt16' 36 | reader_name = 'Microsoft.Xna.Framework.Content.UInt16Reader' 37 | 38 | def read(self): 39 | return self.stream.read_uint16() 40 | 41 | 42 | class Int32Reader(ValueTypeReader, TypeReaderPlugin): 43 | target_type = 'System.Int32' 44 | reader_name = 'Microsoft.Xna.Framework.Content.Int32Reader' 45 | 46 | def read(self): 47 | return self.stream.read_int32() 48 | 49 | 50 | class UInt32Reader(ValueTypeReader, TypeReaderPlugin): 51 | target_type = 'System.UInt32' 52 | reader_name = 'Microsoft.Xna.Framework.Content.UInt32Reader' 53 | 54 | def read(self): 55 | return self.stream.read_uint32() 56 | 57 | 58 | class Int64Reader(ValueTypeReader, TypeReaderPlugin): 59 | target_type = 'System.Int64' 60 | reader_name = 'Microsoft.Xna.Framework.Content.Int64Reader' 61 | 62 | def read(self): 63 | return self.stream.read_int64() 64 | 65 | 66 | class UInt64Reader(ValueTypeReader, TypeReaderPlugin): 67 | target_type = 'System.UInt64' 68 | reader_name = 'Microsoft.Xna.Framework.Content.UInt64Reader' 69 | 70 | def read(self): 71 | return self.stream.read_uint64() 72 | 73 | 74 | class SingleReader(ValueTypeReader, TypeReaderPlugin): 75 | target_type = 'System.Single' 76 | reader_name = 'Microsoft.Xna.Framework.Content.SingleReader' 77 | 78 | def read(self): 79 | return self.stream.read_single() 80 | 81 | 82 | class DoubleReader(ValueTypeReader, TypeReaderPlugin): 83 | target_type = 'System.Double' 84 | reader_name = 'Microsoft.Xna.Framework.Content.DoubleReader' 85 | 86 | def read(self): 87 | return self.stream.read_double() 88 | 89 | 90 | class BooleanReader(ValueTypeReader, TypeReaderPlugin): 91 | target_type = 'System.Boolean' 92 | reader_name = 'Microsoft.Xna.Framework.Content.BooleanReader' 93 | 94 | def read(self): 95 | return self.stream.read_boolean() 96 | 97 | 98 | class CharReader(ValueTypeReader, TypeReaderPlugin): 99 | target_type = 'System.Char' 100 | reader_name = 'Microsoft.Xna.Framework.Content.CharReader' 101 | 102 | def read(self): 103 | return self.stream.read_char() 104 | 105 | 106 | class StringReader(BaseTypeReader, TypeReaderPlugin): 107 | target_type = 'System.String' 108 | reader_name = 'Microsoft.Xna.Framework.Content.StringReader' 109 | 110 | def read(self): 111 | return self.stream.read_string() 112 | 113 | 114 | class ObjectReader(BaseTypeReader, TypeReaderPlugin): 115 | target_type = 'System.Object' 116 | reader_name = 'Microsoft.Xna.Framework.Content.ObjectReader' 117 | 118 | def read(self): 119 | raise ReaderError('ObjectReader invoked for {}'.format(self.target_type)) 120 | -------------------------------------------------------------------------------- /xnb_parse/type_readers/xna_system.py: -------------------------------------------------------------------------------- 1 | """ 2 | system type readers 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.type_reader import (TypeReaderPlugin, ValueTypeReader, GenericTypeReader, GenericValueTypeReader, 8 | ReaderError) 9 | from xnb_parse.xna_types.xna_system import XNAList, XNADict 10 | 11 | 12 | class EnumReader(GenericValueTypeReader, TypeReaderPlugin): 13 | generic_target_type = 'System.Enum`1' 14 | generic_reader_name = 'Microsoft.Xna.Framework.Content.EnumReader`1' 15 | 16 | def init_reader(self, file_platform=None, file_version=None): 17 | GenericValueTypeReader.init_reader(self, file_platform, file_version) 18 | if not self.readers[0].is_enum_type: 19 | ReaderError("Not enum type reader: '{}'".format(self.readers[0])) 20 | 21 | def read(self): 22 | return self.readers[0].read() 23 | 24 | 25 | class NullableReader(GenericValueTypeReader, TypeReaderPlugin): 26 | generic_target_type = 'System.Nullable`1' 27 | generic_reader_name = 'Microsoft.Xna.Framework.Content.NullableReader`1' 28 | 29 | def read(self): 30 | has_value = self.stream.read_boolean() 31 | if has_value: 32 | return self.readers[0].read() 33 | else: 34 | return None 35 | 36 | 37 | class ArrayReader(GenericTypeReader, TypeReaderPlugin): 38 | generic_target_type = 'System.Array`1' 39 | generic_reader_name = 'Microsoft.Xna.Framework.Content.ArrayReader`1' 40 | 41 | def read(self): 42 | elements = self.stream.read_int32() 43 | if self.readers[0].is_value_type: 44 | return XNAList([self.readers[0].read() for _ in range(elements)]) 45 | else: 46 | return XNAList([self.stream.read_object(self.readers[0]) for _ in range(elements)]) 47 | 48 | 49 | class ListReader(GenericTypeReader, TypeReaderPlugin): 50 | generic_target_type = 'System.Collections.Generic.List`1' 51 | generic_reader_name = 'Microsoft.Xna.Framework.Content.ListReader`1' 52 | 53 | def read(self): 54 | elements = self.stream.read_int32() 55 | if self.readers[0].is_value_type: 56 | return XNAList([self.readers[0].read() for _ in range(elements)]) 57 | else: 58 | return XNAList([self.stream.read_object(self.readers[0]) for _ in range(elements)]) 59 | 60 | 61 | class DictionaryReader(GenericTypeReader, TypeReaderPlugin): 62 | generic_target_type = 'System.Collections.Generic.Dictionary`2' 63 | generic_reader_name = 'Microsoft.Xna.Framework.Content.DictionaryReader`2' 64 | 65 | def read(self): 66 | elements = self.stream.read_int32() 67 | if self.readers[0].is_value_type: 68 | if self.readers[1].is_value_type: 69 | return XNADict([(self.readers[0].read(), self.readers[1].read()) 70 | for _ in range(elements)]) 71 | else: 72 | return XNADict([(self.readers[0].read(), self.stream.read_object(self.readers[1])) 73 | for _ in range(elements)]) 74 | else: 75 | if self.readers[1].is_value_type: 76 | return XNADict([(self.stream.read_object(self.readers[0]), self.readers[1].read()) 77 | for _ in range(elements)]) 78 | else: 79 | return XNADict([(self.stream.read_object(self.readers[0]), self.stream.read_object(self.readers[1])) 80 | for _ in range(elements)]) 81 | 82 | 83 | class TimeSpanReader(ValueTypeReader, TypeReaderPlugin): 84 | target_type = 'System.TimeSpan' 85 | reader_name = 'Microsoft.Xna.Framework.Content.TimeSpanReader' 86 | 87 | def read(self): 88 | return self.stream.read_int64() 89 | 90 | 91 | class DateTimeReader(ValueTypeReader, TypeReaderPlugin): 92 | target_type = 'System.DateTime' 93 | reader_name = 'Microsoft.Xna.Framework.Content.DateTimeReader' 94 | 95 | def read(self): 96 | return self.stream.read_int64() 97 | 98 | 99 | class DecimalReader(ValueTypeReader, TypeReaderPlugin): 100 | target_type = 'System.Decimal' 101 | reader_name = 'Microsoft.Xna.Framework.Content.DecimalReader' 102 | 103 | def read(self): 104 | return self.stream.unpack('4i') 105 | 106 | 107 | class ExternalReferenceReader(ValueTypeReader, TypeReaderPlugin): 108 | target_type = 'ExternalReference' 109 | reader_name = 'Microsoft.Xna.Framework.Content.ExternalReferenceReader' 110 | 111 | def read(self): 112 | return self.stream.read_external_reference() 113 | 114 | 115 | class ReflectiveReader(GenericTypeReader, TypeReaderPlugin): 116 | generic_target_type = 'Reflective' 117 | generic_reader_name = 'Microsoft.Xna.Framework.Content.ReflectiveReader`1' 118 | 119 | def read(self): 120 | return self.readers[0].read() 121 | -------------------------------------------------------------------------------- /xnb_parse/type_spec.py: -------------------------------------------------------------------------------- 1 | """ 2 | .net type parser 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import re 8 | from collections import namedtuple 9 | 10 | 11 | class Error(Exception): 12 | pass 13 | 14 | 15 | class TypeSpecError(Error): 16 | pass 17 | 18 | 19 | def _skip_space(name, start_pos): 20 | cur_pos = start_pos 21 | while cur_pos < len(name) and name[cur_pos].isspace(): 22 | cur_pos += 1 23 | return cur_pos 24 | 25 | 26 | _ESCAPE_RE = re.compile(r'([,+&*\[\]\\])') 27 | _UNESCAPE_RE = re.compile(r'\\([,+&*\[\]\\])') 28 | 29 | 30 | def _name_escape(name): 31 | return _ESCAPE_RE.sub(r'\\\1', name) 32 | 33 | 34 | def _name_unescape(name): 35 | return _UNESCAPE_RE.sub(r'\1', name) 36 | 37 | 38 | ArraySpec = namedtuple('ArraySpec', ['dimensions', 'bound']) 39 | 40 | _CACHED_TYPES = {} 41 | 42 | 43 | class TypeSpec(object): 44 | def __init__(self, complete_name): 45 | self.complete_name = complete_name 46 | self.name = None 47 | self.assembly_name = None 48 | self.nested = None 49 | self.generic_params = None 50 | self.array_spec = None 51 | self.pointer_level = 0 52 | self.is_byref = False 53 | 54 | def __str__(self): 55 | return self.full_name 56 | 57 | @property 58 | def full_name(self): 59 | return self.name + self.suffix 60 | 61 | @property 62 | def suffix(self): 63 | name = '' 64 | if self.nested: 65 | name += '+' + '+'.join(self.nested) 66 | if self.generic_params: 67 | name += '[' + ','.join([part.full_name for part in self.generic_params]) + ']' 68 | if self.array_spec: 69 | for cur_index in self.array_spec: 70 | if cur_index.bound: 71 | part = '*' 72 | else: 73 | part = ',' * (cur_index.dimensions - 1) 74 | name += '[' + part + ']' 75 | if self.pointer_level: 76 | name += '*' * self.pointer_level 77 | if self.is_byref: 78 | name += '&' 79 | return name 80 | 81 | @property 82 | def is_generic(self): 83 | return self.generic_params is not None 84 | 85 | @property 86 | def is_array(self): 87 | return self.array_spec is not None 88 | 89 | @staticmethod 90 | def parse(type_name): 91 | if not type_name: 92 | raise TypeSpecError("type_name empty") 93 | if type_name in _CACHED_TYPES: 94 | return _CACHED_TYPES[type_name] 95 | res, pos = TypeSpec._parse(type_name) 96 | if pos < len(type_name): 97 | raise TypeSpecError("Could not parse the whole type name: {} < {}".format(pos, len(type_name))) 98 | _CACHED_TYPES[type_name] = res 99 | return res 100 | 101 | def add_name(self, type_name): 102 | if self.name is None: 103 | self.name = type_name 104 | else: 105 | if self.nested is None: 106 | self.nested = [] 107 | self.nested.append(type_name) 108 | 109 | def add_array(self, dimensions, bound): 110 | if self.array_spec is None: 111 | self.array_spec = [] 112 | self.array_spec.append(ArraySpec(dimensions, bound)) 113 | 114 | @staticmethod 115 | def _parse(name, pos=0, is_recurse=False, allow_aqn=False): 116 | in_modifiers = False 117 | data = TypeSpec(name) 118 | pos = _skip_space(name, pos) 119 | name_start = pos 120 | while pos < len(name): 121 | if name[pos] == '\\': 122 | # skip escaped char 123 | pos += 1 124 | if pos >= len(name): 125 | raise TypeSpecError("Fell off end of name after backslash") 126 | elif name[pos] == '+': 127 | data.add_name(name[name_start:pos]) 128 | name_start = pos + 1 129 | elif name[pos] == ',' or name[pos] == ']': 130 | data.add_name(name[name_start:pos]) 131 | name_start = pos + 1 132 | in_modifiers = True 133 | if is_recurse and not allow_aqn: 134 | return data, pos 135 | elif name[pos] == '&' or name[pos] == '*' or name[pos] == '[': 136 | if name[pos] != '[' and is_recurse: 137 | raise TypeSpecError("Generic argument can't be byref or pointer type") 138 | data.add_name(name[name_start:pos]) 139 | name_start = pos + 1 140 | in_modifiers = True 141 | if in_modifiers: 142 | break 143 | pos += 1 144 | if name_start < pos: 145 | data.add_name(name[name_start:pos]) 146 | if in_modifiers: 147 | while pos < len(name): 148 | if name[pos] == '&': 149 | if data.is_byref: 150 | raise TypeSpecError("Can't have a byref of a byref") 151 | data.is_byref = True 152 | elif name[pos] == '*': 153 | if data.is_byref: 154 | raise TypeSpecError("Can't have a pointer to a byref type") 155 | data.pointer_level += 1 156 | elif name[pos] == ',': 157 | if is_recurse: 158 | end = pos 159 | while end < len(name) and name[end] != ']': 160 | end += 1 161 | if end >= len(name): 162 | raise TypeSpecError("Unmatched ']' while parsing generic argument assembly name") 163 | data.assembly_name = name[pos + 1:end].strip() 164 | pos = end + 1 165 | return data, pos 166 | data.assembly_name = name[pos + 1:].strip() 167 | pos = len(name) 168 | break 169 | elif name[pos] == '[': 170 | if data.is_byref: 171 | raise TypeSpecError("Byref qualifier must be the last one of a type") 172 | pos += 1 173 | if pos >= len(name): 174 | raise TypeSpecError("Invalid array/generic spec") 175 | pos = _skip_space(name, pos) 176 | if name[pos] != ',' and name[pos] != '*' and name[pos] != ']': 177 | # generic args 178 | if data.is_array: 179 | raise TypeSpecError("generic args after array spec") 180 | args = [] 181 | while pos < len(name): 182 | pos = _skip_space(name, pos) 183 | aqn = name[pos] == '[' 184 | if aqn: 185 | pos += 1 186 | new_type, pos = TypeSpec._parse(name, pos, True, aqn) 187 | args.append(new_type) 188 | if pos >= len(name): 189 | raise TypeSpecError("Invalid generic arguments spec") 190 | if name[pos] == ']': 191 | break 192 | if name[pos] == ',': 193 | pos += 1 194 | else: 195 | raise TypeSpecError("Invalid generic arguments separator: '{}'".format(name[pos])) 196 | if pos >= len(name) or name[pos] != ']': 197 | raise TypeSpecError("Error parsing generic params spec") 198 | data.generic_params = args 199 | else: 200 | # array spec 201 | dimensions = 1 202 | bound = False 203 | while pos < len(name) and name[pos] != ']': 204 | if name[pos] == '*': 205 | if bound: 206 | raise TypeSpecError("Array spec cannot have 2 bound dimensions") 207 | bound = True 208 | elif name[pos] != ',': 209 | raise TypeSpecError("Invalid character in array spec: '{}'".format(name[pos])) 210 | else: 211 | dimensions += 1 212 | pos += 1 213 | pos = _skip_space(name, pos) 214 | if name[pos] != ']': 215 | raise TypeSpecError("Error parsing array spec") 216 | if dimensions > 1 and bound: 217 | raise TypeSpecError("Invalid array spec, multi-dimensional array cannot be bound") 218 | data.add_array(dimensions, bound) 219 | elif name[pos] == ']': 220 | if is_recurse: 221 | pos += 1 222 | return data, pos 223 | raise TypeSpecError("Unmatched ]") 224 | else: 225 | raise TypeSpecError("Bad type def, can't handle '{}' at {}".format(name[pos], pos)) 226 | pos += 1 227 | if pos > len(name): 228 | raise TypeSpecError("Fell off end of name") 229 | return data, pos 230 | -------------------------------------------------------------------------------- /xnb_parse/xact/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | XACT file readers 3 | """ 4 | 5 | from __future__ import print_function 6 | -------------------------------------------------------------------------------- /xnb_parse/xact/xgs.py: -------------------------------------------------------------------------------- 1 | """ 2 | parse XGS files 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import os 8 | 9 | from xnb_parse.binstream import BinaryStream 10 | 11 | 12 | XGS_L_SIGNATURE = b'XGSF' 13 | XGS_B_SIGNATURE = b'FSGX' 14 | 15 | 16 | class XGS(object): 17 | def __init__(self, data=None, filename=None): 18 | # open in little endian initially 19 | stream = BinaryStream(data=data, filename=filename) 20 | del data 21 | 22 | # check sig to find actual endianess 23 | h_sig = stream.peek(len(XGS_L_SIGNATURE)) 24 | if h_sig == XGS_L_SIGNATURE: 25 | big_endian = False 26 | elif h_sig == XGS_B_SIGNATURE: 27 | big_endian = True 28 | else: 29 | raise ValueError("bad sig: {!r}".format(h_sig)) 30 | 31 | # switch stream to correct endianess 32 | stream.set_endian(big_endian) 33 | 34 | # TODO: actually parse something 35 | 36 | def export(self, out_dir): 37 | if not os.path.isdir(out_dir): 38 | os.makedirs(out_dir) 39 | # TODO: actually export something 40 | -------------------------------------------------------------------------------- /xnb_parse/xact/xsb.py: -------------------------------------------------------------------------------- 1 | """ 2 | parse XSB files 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import os 8 | from collections import OrderedDict 9 | 10 | from xnb_parse.type_reader import ReaderError 11 | from xnb_parse.xact.xwb import filetime_to_datetime 12 | from xnb_parse.binstream import BinaryStream 13 | 14 | 15 | SB_L_SIGNATURE = b'SDBK' 16 | SB_B_SIGNATURE = b'KBDS' 17 | SB_CUE_FLAGS_COMPLEX = 0x01 18 | SB_CUE_FLAGS_TRANSITION = 0x02 19 | SB_CUE_FLAGS_SOUND = 0x04 20 | SB_CUE_FLAGS_MASK = 0x07 21 | SB_SOUND_FLAGS_COMPLEX = 0x01 22 | SB_SOUND_FLAGS_RPC_SOUND = 0x02 23 | SB_SOUND_FLAGS_RPC_TRACK = 0x04 24 | SB_SOUND_FLAGS_RPC_EFFECT = 0x08 25 | SB_SOUND_FLAGS_DSP = 0x10 26 | SB_SOUND_FLAGS_MASK = 0x1F 27 | SB_EVENT_TYPE = 0x0000001F 28 | SB_EVENT_TIMESTAMP = 0x001FFFE0 29 | SB_EVENT_MASK = 0x001FFFFF 30 | 31 | 32 | def fix_offset(offset): 33 | if offset <= 0: 34 | offset = None 35 | return offset 36 | 37 | 38 | _SB_HEADER = '4s H H H II B HHHHBH I iiiiiiiiii 64s' 39 | _SB_LIMIT = 'B H H B' 40 | _SB_SOUND = 'B H B h B H' 41 | _SB_CLIP = 'B i HH' 42 | _SB_EVENT = 'I H B' 43 | 44 | 45 | class XSB(object): 46 | def __init__(self, data=None, filename=None, audio_engine=None): 47 | self.audio_engine = audio_engine 48 | 49 | # open in little endian initially 50 | stream = BinaryStream(data=data, filename=filename) 51 | del data 52 | 53 | # check sig to find actual endianess 54 | h_sig = stream.peek(len(SB_L_SIGNATURE)) 55 | if h_sig == SB_L_SIGNATURE: 56 | big_endian = False 57 | elif h_sig == SB_B_SIGNATURE: 58 | big_endian = True 59 | else: 60 | raise ValueError("bad sig: {!r}".format(h_sig)) 61 | 62 | # switch stream to correct endianess 63 | stream.set_endian(big_endian) 64 | (h_sig, self.version, self.header_version, self.crc, buildtime_raw_low, buildtime_raw_high, 65 | self.platform, h_simple_cue_count, h_complex_cue_count, h_unknown_count, h_cue_name_hash_count, 66 | h_wave_bank_count, h_sound_count, h_cue_names_length, simple_cue_offset_raw, complex_cue_offset_raw, 67 | cue_name_offset_raw, unknown_offset_raw, variation_offset_raw, transition_offset_raw, wave_bank_offset_raw, 68 | cue_name_hash_offset_raw, cue_name_table_offset_raw, sound_offset_raw, h_name_raw) = stream.unpack(_SB_HEADER) 69 | h_simple_cue_offset = fix_offset(simple_cue_offset_raw) 70 | h_complex_cue_offset = fix_offset(complex_cue_offset_raw) 71 | h_cue_name_offset = fix_offset(cue_name_offset_raw) 72 | h_unknown_offset = fix_offset(unknown_offset_raw) 73 | h_variation_offset = fix_offset(variation_offset_raw) 74 | h_transition_offset = fix_offset(transition_offset_raw) 75 | h_wave_bank_offset = fix_offset(wave_bank_offset_raw) 76 | h_cue_name_hash_offset = fix_offset(cue_name_hash_offset_raw) 77 | h_cue_name_table_offset = fix_offset(cue_name_table_offset_raw) 78 | h_sound_offset = fix_offset(sound_offset_raw) 79 | self.name = h_name_raw.rstrip(b'\x00').decode('iso8859-1') 80 | del h_name_raw 81 | self.buildtime = filetime_to_datetime(buildtime_raw_low, buildtime_raw_high) 82 | 83 | self.wave_banks = [] 84 | if h_wave_bank_count and h_wave_bank_offset: 85 | stream.seek(h_wave_bank_offset) 86 | self.wave_banks = [stream.read(64).rstrip(b'\x00').decode('iso8859-1') for _ in range(h_wave_bank_count)] 87 | else: 88 | raise ReaderError("No wave banks found in sound bank") 89 | 90 | cue_name_hash = [] 91 | if h_cue_name_hash_count and h_cue_name_hash_offset: 92 | stream.seek(h_cue_name_hash_offset) 93 | cue_name_hash = [stream.read_int16() for _ in range(h_cue_name_hash_count)] 94 | cue_name_hash_entry = [] 95 | if h_cue_names_length and h_cue_name_table_offset: 96 | stream.seek(h_cue_name_table_offset) 97 | cue_name_hash_entry = [(stream.read_int32(), stream.read_int16()) 98 | for _ in range(h_simple_cue_count + h_complex_cue_count)] 99 | cue_names = [] 100 | for (name_offset, _) in cue_name_hash_entry: 101 | stream.seek(name_offset) 102 | cue_names.append(stream.read_cstring()) 103 | 104 | self.cues = [] 105 | self.cues_name = OrderedDict() 106 | if h_simple_cue_count and h_simple_cue_offset: 107 | stream.seek(h_simple_cue_offset) 108 | for i in range(h_simple_cue_count): 109 | cue_name = None 110 | if cue_names: 111 | cue_name = cue_names[i] 112 | cue = Cue(cue_name, stream) 113 | self.cues.append(cue) 114 | if cue_name: 115 | self.cues_name[cue_name] = cue 116 | if h_complex_cue_count and h_complex_cue_offset: 117 | stream.seek(h_complex_cue_offset) 118 | for i in range(h_complex_cue_count): 119 | cue_name = None 120 | if cue_names: 121 | cue_name = cue_names[h_simple_cue_count + i] 122 | cue = Cue(cue_name, stream, is_complex=True) 123 | self.cues.append(cue) 124 | if cue_name: 125 | self.cues_name[cue_name] = cue 126 | 127 | def export(self, out_dir): 128 | if self.name: 129 | out_dir = os.path.join(out_dir, self.name) 130 | if not os.path.isdir(out_dir): 131 | os.makedirs(out_dir) 132 | 133 | 134 | class Cue(object): 135 | def __init__(self, name, stream, is_complex=False): 136 | self.name = name 137 | self.flags = stream.read_byte() 138 | if self.flags & ~SB_CUE_FLAGS_MASK: 139 | raise ReaderError("Unknown flags in SB_CUE") 140 | sound_offset = None 141 | unknown_offset = None 142 | variation_offset = None 143 | transition_offset = None 144 | if self.is_complex: 145 | if not is_complex: 146 | raise ReaderError("SB_CUE_FLAGS_COMPLEX not set for complex cue") 147 | if self.has_sound: 148 | sound_offset = fix_offset(stream.read_int32()) 149 | else: 150 | variation_offset = fix_offset(stream.read_int32()) 151 | if self.has_transition: 152 | transition_offset = fix_offset(stream.read_int32()) 153 | else: 154 | unknown_offset = fix_offset(stream.read_int32()) 155 | if unknown_offset: 156 | raise ReaderError("unknown_offset set in complex cue") 157 | (self.limit, self.fade_in, self.fade_out, 158 | limit_fade_raw) = stream.unpack(_SB_LIMIT) 159 | self.fade_type = limit_fade_raw & 0x03 160 | self.limit_type = limit_fade_raw >> 3 161 | else: 162 | if is_complex: 163 | raise ReaderError("SB_CUE_FLAGS_COMPLEX is set for simple cue") 164 | if self.has_sound: 165 | sound_offset = fix_offset(stream.read_int32()) 166 | else: 167 | unknown_offset = fix_offset(stream.read_int32()) 168 | raise ReaderError("SB_CUE_FLAGS_SOUND not set for simple cue") 169 | next_cue_offset = stream.tell() 170 | if sound_offset: 171 | stream.seek(sound_offset) 172 | self.sound = Sound(stream) 173 | stream.seek(next_cue_offset) 174 | 175 | @property 176 | def is_complex(self): 177 | return bool(self.flags & SB_CUE_FLAGS_COMPLEX) 178 | 179 | @property 180 | def has_sound(self): 181 | return bool(self.flags & SB_CUE_FLAGS_SOUND) 182 | 183 | @property 184 | def has_transition(self): 185 | return bool(self.flags & SB_CUE_FLAGS_TRANSITION) 186 | 187 | 188 | class Sound(object): 189 | def __init__(self, stream): 190 | start_pos = stream.tell() 191 | (self.flags, self.category, self.volume, self.pitch, self.priority, entry_len) = stream.unpack(_SB_SOUND) 192 | if self.flags & ~SB_SOUND_FLAGS_MASK: 193 | raise ReaderError("Unknown flags in SB_SOUND") 194 | clip_count = 0 195 | if self.is_complex: 196 | clip_count = stream.read_byte() 197 | else: 198 | self.track = stream.read_uint16() 199 | self.wavebank = stream.read_byte() 200 | if self.has_rpc_sound or self.has_rpc_track or self.has_rpc_effect: 201 | rpc_pos = stream.tell() 202 | rpc_extra = stream.read_uint16() 203 | # TODO: parse RPC data 204 | stream.seek(rpc_pos + rpc_extra) 205 | if self.has_dsp: 206 | dsp_pos = stream.tell() 207 | dsp_extra = stream.read_uint16() 208 | # TODO: parse DSP data 209 | stream.seek(dsp_pos + dsp_extra) 210 | self.clips = [] 211 | if self.is_complex: 212 | self.clips = [Clip(stream) for _ in range(clip_count)] 213 | if stream.tell() > entry_len + start_pos: 214 | raise ReaderError("SB_SOUND length mismatch") 215 | 216 | @property 217 | def is_complex(self): 218 | return bool(self.flags & SB_CUE_FLAGS_COMPLEX) 219 | 220 | @property 221 | def has_rpc_sound(self): 222 | return bool(self.flags & SB_SOUND_FLAGS_RPC_SOUND) 223 | 224 | @property 225 | def has_rpc_track(self): 226 | return bool(self.flags & SB_SOUND_FLAGS_RPC_TRACK) 227 | 228 | @property 229 | def has_rpc_effect(self): 230 | return bool(self.flags & SB_SOUND_FLAGS_RPC_EFFECT) 231 | 232 | @property 233 | def has_dsp(self): 234 | return bool(self.flags & SB_SOUND_FLAGS_DSP) 235 | 236 | 237 | class Clip(object): 238 | def __init__(self, stream): 239 | (self.volume, clip_offset_raw, self.filter_flags, self.filter_freq) = stream.unpack(_SB_CLIP) 240 | clip_offset = fix_offset(clip_offset_raw) 241 | next_clip_offset = stream.tell() 242 | self.events = [] 243 | if clip_offset: 244 | stream.seek(clip_offset) 245 | event_count = stream.read_byte() 246 | self.events = [ClipEvent(stream) for _ in range(event_count)] 247 | stream.seek(next_clip_offset) 248 | 249 | 250 | class Event(object): 251 | has_sound = False 252 | 253 | 254 | class Event1(Event): 255 | has_sound = True 256 | 257 | def __init__(self, stream): 258 | pass 259 | 260 | 261 | _EVENTS = { 262 | 1: Event1, 263 | } 264 | 265 | 266 | class ClipEvent(object): 267 | def __init__(self, stream): 268 | (self.flags, self.random_offset, unknown) = stream.unpack(_SB_EVENT) 269 | # if self.flags & ~SB_EVENT_MASK: 270 | # raise ReaderError("Unknown flags in SB_EVENT") 271 | self.event = _EVENTS[self.event_type](stream) 272 | # if unknown: 273 | # raise ReaderError("Unknown field set in SB_EVENT") 274 | 275 | @property 276 | def event_type(self): 277 | return self.flags & SB_EVENT_TYPE 278 | 279 | @property 280 | def timestamp(self): 281 | return (self.flags & SB_EVENT_TIMESTAMP) >> 5 282 | -------------------------------------------------------------------------------- /xnb_parse/xna_content_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | XNA ContentManager 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import fnmatch 8 | import os 9 | from collections import OrderedDict 10 | 11 | from xnb_parse.type_reader import ReaderError 12 | from xnb_parse.xnb_reader import XNBReader 13 | from xnb_parse.file_formats.xml_utils import output_xml 14 | 15 | 16 | class ContentManager(object): 17 | content_extension = '.xnb' 18 | 19 | def __init__(self, root_dir): 20 | root_dir = os.path.normpath(root_dir) 21 | if not os.path.isdir(root_dir): 22 | raise ReaderError("Content root directory not found: '%s'" % root_dir) 23 | self.root_dir = root_dir 24 | self._asset_dict = OrderedDict() 25 | for k, v in self.find_assets(): 26 | if k not in self._asset_dict: 27 | self._asset_dict[k] = v 28 | self.assets = self._asset_dict.keys() 29 | 30 | def xnb(self, asset_name, expected_type=None, parse=True): 31 | asset_name = asset_name.replace('\\', '/') 32 | asset_name = asset_name.lower() 33 | asset_filename = os.path.join(self.root_dir, self._asset_dict[asset_name]) 34 | return XNBReader.load(filename=asset_filename, expected_type=expected_type, parse=parse) 35 | 36 | def load(self, asset_name, expected_type=None): 37 | return self.xnb(asset_name, expected_type).content 38 | 39 | def find_assets(self): 40 | for path, _, filelist in os.walk(self.root_dir, followlinks=True): 41 | sub_dir = os.path.relpath(path, self.root_dir) 42 | for asset_filename in fnmatch.filter(filelist, '*' + self.content_extension): 43 | if sub_dir != '.': 44 | asset_filename = os.path.join(sub_dir, asset_filename) 45 | asset = asset_filename[:-len(self.content_extension)] 46 | asset = asset.replace(os.sep, '/') 47 | asset = asset.lower() 48 | yield asset, asset_filename 49 | 50 | def filter(self, search='*'): 51 | return fnmatch.filter(self.assets, search) 52 | 53 | @staticmethod 54 | def export(asset, asset_name, export_dir, export_file=True, export_xml=True): 55 | filename = os.path.join(export_dir, os.path.normpath(asset_name)) 56 | dirname = os.path.dirname(filename) 57 | if not os.path.isdir(dirname): 58 | os.makedirs(dirname) 59 | if export_file and hasattr(asset, 'export'): 60 | asset.export(filename) 61 | if export_xml and hasattr(asset, 'xml'): 62 | output_xml(asset.xml(), filename + '.xml') 63 | -------------------------------------------------------------------------------- /xnb_parse/xna_native.py: -------------------------------------------------------------------------------- 1 | """ 2 | wrapper for native XNA functions 3 | Requires win32 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | from contextlib import contextmanager 9 | 10 | import os 11 | import platform 12 | import sys 13 | import ctypes 14 | 15 | 16 | _XNA_VERSIONS = ['v4.0', 'v3.1', 'v3.0'] 17 | _DLL_NAME = 'XnaNative.dll' 18 | 19 | _native_dir = None 20 | 21 | 22 | def _find_native(): 23 | global _native_dir 24 | if _native_dir: 25 | return _native_dir 26 | 27 | if not sys.platform == 'win32' or not platform.architecture()[0] == '32bit': 28 | raise IOError("win32 required for decompression") 29 | try: 30 | import winreg 31 | except ImportError: 32 | import _winreg as winreg 33 | 34 | native_path = None 35 | for ver in _XNA_VERSIONS: 36 | try: 37 | key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\XNA\\Framework\\' + ver) 38 | lib_path, _ = winreg.QueryValueEx(key, 'NativeLibraryPath') 39 | if lib_path: 40 | lib_path = os.path.join(os.path.normpath(str(lib_path)), _DLL_NAME) 41 | if os.path.isfile(lib_path): 42 | native_path = lib_path 43 | break 44 | except WindowsError: 45 | pass 46 | if native_path is None: 47 | # TODO: must be a better way of doing this 48 | lib_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '../bin')) 49 | lib_path = os.path.join(os.path.normpath(lib_path), _DLL_NAME) 50 | if os.path.isfile(lib_path): 51 | native_path = lib_path 52 | if native_path is None: 53 | raise IOError("{} not found".format(_DLL_NAME)) 54 | _native_dir = native_path 55 | return native_path 56 | 57 | 58 | @contextmanager 59 | def decomp_context(dll): 60 | ctx = dll.CreateDecompressionContext() 61 | if ctx is None: 62 | raise IOError("CreateDecompressionContext failed") 63 | yield ctx 64 | dll.DestroyDecompressionContext(ctx) 65 | 66 | 67 | def decompress(in_buf, out_size): 68 | dll = ctypes.CDLL(_find_native()) 69 | 70 | with decomp_context(dll) as ctx: 71 | in_size = len(in_buf) 72 | compressed_position = 0 73 | compressed_todo = in_size 74 | decompressed_position = 0 75 | decompressed_todo = out_size 76 | 77 | s_in_buf = ctypes.create_string_buffer(in_buf, in_size) 78 | s_out_buf = ctypes.create_string_buffer(out_size) 79 | 80 | while decompressed_todo > 0 and compressed_todo > 0: 81 | compressed_size = in_size - compressed_position 82 | decompressed_size = out_size - decompressed_position 83 | 84 | s_compressed_size = ctypes.c_uint(compressed_size) 85 | s_decompressed_size = ctypes.c_uint(decompressed_size) 86 | err = dll.Decompress(ctx, ctypes.byref(s_out_buf, decompressed_position), ctypes.byref(s_decompressed_size), 87 | ctypes.byref(s_in_buf, compressed_position), ctypes.byref(s_compressed_size)) 88 | r_compressed_size = int(s_compressed_size.value) 89 | r_decompressed_size = int(s_decompressed_size.value) 90 | 91 | if err: 92 | raise IOError("Decompress failed: {}".format(err)) 93 | if r_compressed_size == 0 and r_decompressed_size == 0: 94 | raise IOError("Decompress failed") 95 | 96 | compressed_position += r_compressed_size 97 | decompressed_position += r_decompressed_size 98 | compressed_todo -= r_compressed_size 99 | decompressed_todo -= r_decompressed_size 100 | return s_out_buf.raw 101 | 102 | 103 | def main(): 104 | _find_native() 105 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | xnb types 3 | """ 4 | 5 | from __future__ import print_function 6 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/fez/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | FEZ types 3 | """ 4 | 5 | from __future__ import print_function 6 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/fez/fez_basic.py: -------------------------------------------------------------------------------- 1 | """ 2 | FEZ basic types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.xna_types.xna_primitive import Enum 8 | 9 | 10 | class FaceOrientation(Enum): 11 | __slots__ = () 12 | enum_values = dict(enumerate(['Left', 'Down', 'Back', 'Right', 'Top', 'Front'])) 13 | 14 | 15 | class LevelNodeType(Enum): 16 | __slots__ = () 17 | enum_values = dict(enumerate(['Node', 'Hub', 'Lesser'])) 18 | xml_tag = 'NodeType' 19 | 20 | 21 | class CollisionType(Enum): 22 | __slots__ = () 23 | enum_values = dict(enumerate(['AllSides', 'TopOnly', 'None', 'Immaterial', 'TopNoStraightLedge'])) 24 | 25 | 26 | class Viewpoint(Enum): 27 | __slots__ = () 28 | enum_values = dict(enumerate(['None', 'Front', 'Right', 'Back', 'Left', 'Up', 'Down', 'Perspective'])) 29 | 30 | 31 | class NpcAction(Enum): 32 | __slots__ = () 33 | enum_values = dict(enumerate(['None', 'Idle', 'Idle2', 'Idle3', 'Walk', 'Turn', 'Talk', 'Burrow', 'Hide', 'ComeOut', 34 | 'TakeOff', 'Fly', 'Land'])) 35 | 36 | 37 | class ActorType(Enum): 38 | __slots__ = () 39 | enum_values = dict(enumerate(['None', 'Ladder', 'Bouncer', 'Sign', 'GoldenCube', 'PickUp', 'Bomb', 'Destructible', 40 | 'DestructiblePermanent', 'Vase', 'Door', 'Heart', 'Watcher', 'Crystal', 'BlackHole', 41 | 'Vine', 'BigBomb', 'TntBlock', 'TntPickup', 'MotorBlock', 'Hurt', 'Checkpoint', 42 | 'TreasureChest', 'CubeShard', 'BigHeart', 'SkeletonKey', 'ExploSwitch', 'PushSwitch', 43 | 'EightBitDoor', 'PushSwitchSticky', 'PushSwitchPermanent', 'SuckBlock', 'WarpGate', 44 | 'OneBitDoor', 'SpinBlock', 'PivotHandle', 'FourBitDoor', 'LightningPlatform', 45 | 'LightningGhost', 'Tombstone', 'SplitUpCube', 'UnlockedDoor', 'Hole', 'Couch', 46 | 'Valve', 'Rumbler', 'Waterfall', 'Trickle', 'Drips', 'Geyser', 'ConnectiveRail', 47 | 'BoltHandle', 'BoltNutBottom', 'BoltNutTop', 'CodeMachine', 'NumberCube', 48 | 'LetterCube', 'TriSkull', 'Tome', 'SecretCube', 'LesserGate', 'Crumbler', 49 | 'LaserEmitter', 'LaserBender', 'LaserReceiver', 'RebuildingHexahedron', 'TreasureMap', 50 | 'Timeswitch', 'TimeswitchMovingPart', 'Mail', 'Mailbox', 'Bookcase', 'TwoBitDoor', 51 | 'SixteenBitDoor', 'ThirtyTwoBitDoor', 'SixtyFourBitDoor', 'Owl', 'Bell', 52 | 'RotatingGroup', 'BigWaterfall', 'Telescope', 'SinkPickup', 'QrCode', 'FpsPost', 53 | 'PieceOfHeart', 'SecretPassage', 'Piston'])) 54 | 55 | 56 | class SurfaceType(Enum): 57 | __slots__ = () 58 | enum_values = dict(enumerate(['Grass', 'Metal', 'Stone', 'Wood'])) 59 | 60 | 61 | class LiquidType(Enum): 62 | __slots__ = () 63 | enum_values = dict(enumerate(['None', 'Water', 'Blood', 'Lava', 'Sewer', 'Purple', 'Green'])) 64 | 65 | 66 | class PathEndBehavior(Enum): 67 | __slots__ = () 68 | enum_values = dict(enumerate(['Bounce', 'Loop', 'Stop'])) 69 | 70 | 71 | class ComparisonOperator(Enum): 72 | __slots__ = () 73 | enum_values = {-1: 'None', 0: 'Equal', 1: 'Greater', 2: 'GreaterEqual', 3: 'Less', 4: 'LessEqual', 5: 'NotEqual'} 74 | 75 | 76 | class CodeInput(Enum): 77 | __slots__ = () 78 | enum_values = {0: 'None', 1: 'Up', 2: 'Down', 4: 'Left', 8: 'Right', 16: 'SpinLeft', 32: 'SpinRight', 64: 'Jump'} 79 | 80 | 81 | class VibrationMotor(Enum): 82 | __slots__ = () 83 | enum_values = dict(enumerate(['None', 'LeftLow', 'RightHigh'])) 84 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/fez/fez_graphics.py: -------------------------------------------------------------------------------- 1 | """ 2 | FEZ graphics types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.file_formats.xml_utils import ET 8 | from xnb_parse.xna_types.xna_graphics import (Texture2D, FORMAT_COLOR, get_surface_format, VERSION_31, VERSION_40, 9 | FORMAT4_COLOR) 10 | 11 | 12 | class ArtObject(object): 13 | def __init__(self, name, cubemap_path, size, geometry, actor_type, no_silhouette, laser_outlets): 14 | self.name = name 15 | self.cubemap_path = cubemap_path 16 | self.size = size 17 | self.geometry = geometry 18 | self.actor_type = actor_type 19 | self.no_silhouette = no_silhouette 20 | self.laser_outlets = laser_outlets 21 | 22 | def __str__(self): 23 | return "ArtObject '{}' t:'{}' s:{} g:{}".format(self.name, self.cubemap_path, self.size, 24 | len(self.geometry.vertices)) 25 | 26 | def xml(self, parent=None): 27 | if parent is None: 28 | root = ET.Element('ArtObject') 29 | else: 30 | root = ET.SubElement(parent, 'ArtObject') 31 | root.set('name', self.name) 32 | root.set('cubemapPath', self.cubemap_path) 33 | root.set('noSilhouette', str(self.no_silhouette)) 34 | self.size.xml(ET.SubElement(root, 'Size')) 35 | if self.actor_type is not None: 36 | root.set('actorType', str(self.actor_type)) 37 | if self.geometry is not None: 38 | self.geometry.xml(root) 39 | if self.laser_outlets is not None: 40 | self.laser_outlets.xml(root, 'LaserOutlets') 41 | return root 42 | 43 | 44 | class ArtObjectPC(object): 45 | def __init__(self, name, cubemap, size, geometry, actor_type, no_silhouette): 46 | self.name = name 47 | self.cubemap = cubemap 48 | self.size = size 49 | self.geometry = geometry 50 | self.actor_type = actor_type 51 | self.no_silhouette = no_silhouette 52 | 53 | def __str__(self): 54 | return "ArtObjectPC '{}' s:{} g:{}".format(self.name, self.size, len(self.geometry.vertices)) 55 | 56 | def xml(self, parent=None): 57 | if parent is None: 58 | root = ET.Element('ArtObject') 59 | else: 60 | root = ET.SubElement(parent, 'ArtObject') 61 | root.set('name', self.name) 62 | root.set('noSilhouette', str(self.no_silhouette)) 63 | self.size.xml(ET.SubElement(root, 'Size')) 64 | if self.actor_type is not None: 65 | root.set('actorType', str(self.actor_type)) 66 | if self.geometry is not None: 67 | self.geometry.xml(root) 68 | return root 69 | 70 | def export(self, filename): 71 | self.cubemap.export(filename) 72 | 73 | 74 | class ShaderInstancedIndexedPrimitives(object): 75 | __slots__ = ('primitive_type', 'vertices', 'indices') 76 | 77 | def __init__(self, primitive_type, vertices, indices): 78 | self.primitive_type = primitive_type 79 | self.vertices = vertices 80 | self.indices = indices 81 | 82 | def __str__(self): 83 | return "ShaderInstancedIndexedPrimitives t:{} v:{} i:{}".format(self.primitive_type, len(self.vertices), 84 | len(self.indices)) 85 | 86 | def xml(self, parent): 87 | root = ET.SubElement(parent, 'ShaderInstancedIndexedPrimitives') 88 | if self.primitive_type is not None: 89 | root.set('type', str(self.primitive_type)) 90 | if self.vertices is not None: 91 | self.vertices.xml(root, 'Vertices') 92 | if self.indices is not None: 93 | self.indices.xml(root, 'Indices', 'Index') 94 | return root 95 | 96 | 97 | class VertexPositionNormalTextureInstance(object): 98 | __slots__ = ('position', 'normal', 'texture_coord') 99 | 100 | def __init__(self, position, normal, texture_coord): 101 | self.position = position 102 | self.normal = normal 103 | self.texture_coord = texture_coord 104 | 105 | def __str__(self): 106 | return "VertexPositionNormalTextureInstance p:{} n:{} c:{}".format(self.position, self.normal, 107 | self.texture_coord) 108 | 109 | def xml(self, parent): 110 | root = ET.SubElement(parent, 'VertexPositionNormalTextureInstance') 111 | self.position.xml(ET.SubElement(root, 'Position')) 112 | normal_tag = ET.SubElement(root, 'Normal') 113 | normal_tag.text = str(self.normal) 114 | self.texture_coord.xml(ET.SubElement(root, 'TextureCoord')) 115 | return root 116 | 117 | 118 | class NpcMetadata(object): 119 | def __init__(self, walk_speed, avoids_gomez, sound_path, sound_actions): 120 | self.walk_speed = walk_speed 121 | self.avoids_gomez = avoids_gomez 122 | self.sound_path = sound_path 123 | self.sound_actions = sound_actions 124 | 125 | def __str__(self): 126 | return "NpcMetadata s:{} a:{}".format(self.sound_path, len(self.sound_actions)) 127 | 128 | def xml(self, parent=None): 129 | if parent is None: 130 | root = ET.Element('NpcMetadata') 131 | else: 132 | root = ET.SubElement(parent, 'NpcMetadata') 133 | root.set('avoidsGomez', str(self.avoids_gomez)) 134 | root.set('walkSpeed', str(self.walk_speed)) 135 | if self.sound_path is not None: 136 | root.set('soundPath', self.sound_path) 137 | if self.sound_actions is not None: 138 | self.sound_actions.xml(root, 'SoundActions') 139 | return root 140 | 141 | 142 | class AnimatedTexture(object): 143 | surface_format = get_surface_format(VERSION_31, FORMAT_COLOR) 144 | 145 | def __init__(self, width, height, actual_width, actual_height, frames): 146 | self.width = width 147 | self.height = height 148 | self.actual_width = actual_width 149 | self.actual_height = actual_height 150 | self.frames = frames 151 | 152 | def __str__(self): 153 | return "AnimatedTexture d:{}x{} a:{}x{} f:{}".format(self.width, self.height, self.actual_width, 154 | self.actual_height, len(self.frames)) 155 | 156 | def xml(self, parent=None): 157 | if parent is None: 158 | root = ET.Element('AnimatedTexture') 159 | else: 160 | root = ET.SubElement(parent, 'AnimatedTexture') 161 | root.set('width', str(self.width)) 162 | root.set('height', str(self.height)) 163 | root.set('actualWidth', str(self.actual_width)) 164 | root.set('actualHeight', str(self.actual_height)) 165 | if self.frames is not None: 166 | self.frames.xml(root, 'Frames') 167 | return root 168 | 169 | def export(self, filename): 170 | if self.frames is not None: 171 | self.export_single(filename) 172 | 173 | def export_each(self, filename): 174 | for i, cur_frame in enumerate(self.frames): 175 | texture = Texture2D(self.surface_format, self.width, self.height, [cur_frame.data]) 176 | cur_filename = "{}_ani\\{}".format(filename, i) 177 | texture.export(cur_filename) 178 | 179 | def export_single(self, filename): 180 | texture_data = bytearray() 181 | for cur_frame in self.frames: 182 | texture_data.extend(cur_frame.data) 183 | texture = Texture2D(self.surface_format, self.width, self.height * len(self.frames), [texture_data]) 184 | texture.export(filename + '.ani') 185 | 186 | 187 | class AnimatedTexturePC(object): 188 | surface_format = get_surface_format(VERSION_40, FORMAT4_COLOR) 189 | 190 | def __init__(self, width, height, actual_width, actual_height, data, frames): 191 | self.width = width 192 | self.height = height 193 | self.actual_width = actual_width 194 | self.actual_height = actual_height 195 | self.data = data 196 | self.frames = frames 197 | 198 | def __str__(self): 199 | return "AnimatedTexturePC d:{}x{} a:{}x{} f:{}".format(self.width, self.height, self.actual_width, 200 | self.actual_height, len(self.frames)) 201 | 202 | def xml(self, parent=None): 203 | if parent is None: 204 | root = ET.Element('AnimatedTexturePC') 205 | else: 206 | root = ET.SubElement(parent, 'AnimatedTexturePC') 207 | root.set('width', str(self.width)) 208 | root.set('height', str(self.height)) 209 | root.set('actualWidth', str(self.actual_width)) 210 | root.set('actualHeight', str(self.actual_height)) 211 | if self.frames is not None: 212 | self.frames.xml(root, 'Frames') 213 | return root 214 | 215 | def export(self, filename): 216 | if self.data is not None: 217 | texture = Texture2D(self.surface_format, self.width, self.height, [self.data]) 218 | texture.export(filename + '.ani') 219 | 220 | 221 | class Frame(object): 222 | def __init__(self, duration, data): 223 | self.duration = duration 224 | self.data = data 225 | 226 | def __str__(self): 227 | return "Frame d:{} s:{}".format(self.duration, len(self.data)) 228 | 229 | def xml(self, parent): 230 | root = ET.SubElement(parent, 'Frame') 231 | if self.duration is not None: 232 | root.set('duration', str(self.duration)) 233 | return root 234 | 235 | 236 | class FramePC(object): 237 | def __init__(self, duration, rectangle): 238 | self.duration = duration 239 | self.rectangle = rectangle 240 | 241 | def __str__(self): 242 | return "FramePC d:{} r:{}".format(self.duration, self.rectangle) 243 | 244 | def xml(self, parent): 245 | root = ET.SubElement(parent, 'FramePC') 246 | if self.duration is not None: 247 | root.set('duration', str(self.duration)) 248 | if self.rectangle is not None: 249 | self.rectangle.xml(root) 250 | return root 251 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/fez/fez_music.py: -------------------------------------------------------------------------------- 1 | """ 2 | FEZ music types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.file_formats.xml_utils import ET 8 | from xnb_parse.xna_types.xna_primitive import Enum 9 | 10 | 11 | class ShardNotes(Enum): 12 | __slots__ = () 13 | enum_values = dict(enumerate(['C2', 'Csharp2', 'D2', 'Dsharp2', 'E2', 'F2', 'Fsharp2', 'G2', 'Gsharp2', 'A2', 14 | 'Asharp2', 'B2', 'C3', 'Csharp3', 'D3', 'Dsharp3', 'E3', 'F3', 'Fsharp3', 'G3', 15 | 'Gsharp3', 'A3', 'Asharp3', 'B3', 'C4'])) 16 | xml_tag = 'Note' 17 | 18 | 19 | class AssembleChords(Enum): 20 | __slots__ = () 21 | enum_values = dict(enumerate(['C_maj', 'Csharp_maj', 'D_maj', 'Dsharp_maj', 'E_maj', 'F_maj', 'Fsharp_maj', 'G_maj', 22 | 'Gsharp_maj', 'A_maj', 'Asharp_maj', 'B_maj'])) 23 | xml_tag = 'Chord' 24 | 25 | 26 | class TrackedSong(object): 27 | def __init__(self, loops, name, tempo, time_signature, notes, assemble_chord, random_ordering, custom_ordering): 28 | self.loops = loops 29 | self.name = name 30 | self.tempo = tempo 31 | self.time_signature = time_signature 32 | self.notes = notes 33 | self.assemble_chord = assemble_chord 34 | self.random_ordering = random_ordering 35 | self.custom_ordering = custom_ordering 36 | 37 | def __str__(self): 38 | return "TrackedSong '{}'".format(self.name) 39 | 40 | def xml(self, parent=None): 41 | if parent is None: 42 | root = ET.Element('TrackedSong') 43 | else: 44 | root = ET.SubElement(parent, 'TrackedSong') 45 | root.set('name', self.name) 46 | root.set('tempo', str(self.tempo)) 47 | root.set('timeSignature', str(self.time_signature)) 48 | if self.assemble_chord is not None: 49 | self.assemble_chord.xml(root) 50 | if self.notes is not None: 51 | self.notes.xml(root, 'Notes') 52 | if self.loops is not None: 53 | self.loops.xml(root, 'Loops') 54 | if self.random_ordering is not None: 55 | root.set('randomOrdering', str(self.random_ordering)) 56 | if self.custom_ordering is not None: 57 | self.custom_ordering.xml(root, 'CustomOrdering', 'Order') 58 | return root 59 | 60 | 61 | class Loop(object): 62 | def __init__(self, duration, loop_times_from, loop_times_to, name, trigger_from, trigger_to, delay, night, day, 63 | dusk, dawn, fractional_time, one_at_a_time, cut_off_tail): 64 | self.duration = duration 65 | self.loop_times_from = loop_times_from 66 | self.loop_times_to = loop_times_to 67 | self.name = name 68 | self.trigger_from = trigger_from 69 | self.trigger_to = trigger_to 70 | self.delay = delay 71 | self.night = night 72 | self.day = day 73 | self.dusk = dusk 74 | self.dawn = dawn 75 | self.fractional_time = fractional_time 76 | self.one_at_a_time = one_at_a_time 77 | self.cut_off_tail = cut_off_tail 78 | 79 | def __str__(self): 80 | return "Loop '{}' d:{}".format(self.name, self.duration) 81 | 82 | def xml(self, parent): 83 | root = ET.SubElement(parent, 'Loop') 84 | root.set('name', self.name) 85 | root.set('duration', str(self.duration)) 86 | root.set('loopTimesFrom', str(self.loop_times_from)) 87 | root.set('loopTimesTo', str(self.loop_times_to)) 88 | root.set('triggerFrom', str(self.trigger_from)) 89 | root.set('triggerTo', str(self.trigger_to)) 90 | root.set('delay', str(self.delay)) 91 | root.set('fractionalTime', str(self.fractional_time)) 92 | root.set('oneAtATime', str(self.one_at_a_time)) 93 | root.set('cutOffTail', str(self.cut_off_tail)) 94 | root.set('night', str(self.night)) 95 | root.set('day', str(self.day)) 96 | root.set('dusk', str(self.dusk)) 97 | root.set('dawn', str(self.dawn)) 98 | return root 99 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/mercury/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | mercury particle engine types 3 | """ 4 | 5 | from __future__ import print_function 6 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/mercury/basic.py: -------------------------------------------------------------------------------- 1 | """ 2 | mercury particle engine basic types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from collections import namedtuple 8 | 9 | from xnb_parse.xna_types.xna_primitive import Enum 10 | 11 | 12 | _VariableFloat = namedtuple('VariableFlat', ['value', 'variation']) 13 | 14 | 15 | class VariableFloat(_VariableFloat): 16 | __slots__ = () 17 | 18 | 19 | _VariableFloat3 = namedtuple('VariableFloat3', ['value', 'variation']) 20 | 21 | 22 | class VariableFloat3(_VariableFloat3): 23 | __slots__ = () 24 | 25 | 26 | class BlendMode(Enum): 27 | __slots__ = () 28 | enum_values = dict(enumerate(['Add', 'Alpha', 'Subtract', 'Multiply', 'None'])) 29 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/mercury/emitters.py: -------------------------------------------------------------------------------- 1 | """ 2 | mercury particle engine emitter types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | 8 | class EmitterCollection(object): 9 | def __init__(self, emitters): 10 | self.emitters = emitters 11 | 12 | def __repr__(self): 13 | return '{}'.format(self.emitters) 14 | 15 | 16 | class Emitter(object): 17 | def __init__(self, name, budget, term, release_quantity, enabled, release_speed, release_colour, release_opacity, 18 | release_scale, release_rotation, release_impulse, particle_texture_asset_name, modifiers, blend_mode, 19 | trigger_offset, minimum_trigger_period): 20 | self.name = name 21 | self.budget = budget 22 | self.term = term 23 | self.release_quantity = release_quantity 24 | self.enabled = enabled 25 | self.release_speed = release_speed 26 | self.release_colour = release_colour 27 | self.release_opacity = release_opacity 28 | self.release_scale = release_scale 29 | self.release_rotation = release_rotation 30 | self.release_impulse = release_impulse 31 | self.particle_texture_asset_name = particle_texture_asset_name 32 | self.modifiers = modifiers 33 | self.blend_mode = blend_mode 34 | self.trigger_offset = trigger_offset 35 | self.minimum_trigger_period = minimum_trigger_period 36 | 37 | def __repr__(self): 38 | return "Emitter(name={!r}, budget={}, term={}, release_quantity={}, enabled={}, release_speed={}, " \ 39 | "release_colour={}, release_opacity={}, release_scale={}, release_rotation={}, release_impulse={}, " \ 40 | "particle_texture_asset_name={!r}, modifiers={}, blend_mode={!r}, trigger_offset={}, " \ 41 | "minimum_trigger_period={})".format( 42 | self.name, self.budget, self.term, self.release_quantity, self.enabled, self.release_speed, 43 | self.release_colour, self.release_opacity, self.release_scale, self.release_rotation, 44 | self.release_impulse, self.particle_texture_asset_name, self.modifiers, self.blend_mode, 45 | self.trigger_offset, self.minimum_trigger_period) 46 | 47 | 48 | class CircleEmitter(Emitter): 49 | def __init__(self, name, budget, term, release_quantity, enabled, release_speed, release_colour, release_opacity, 50 | release_scale, release_rotation, release_impulse, particle_texture_asset_name, modifiers, blend_mode, 51 | trigger_offset, minimum_trigger_period, radius, ring, radiate): 52 | Emitter.__init__( 53 | self, name, budget, term, release_quantity, enabled, release_speed, release_colour, release_opacity, 54 | release_scale, release_rotation, release_impulse, particle_texture_asset_name, modifiers, blend_mode, 55 | trigger_offset, minimum_trigger_period) 56 | self.radius = radius 57 | self.ring = ring 58 | self.radiate = radiate 59 | 60 | def __repr__(self): 61 | return "CircleEmitter(name={!r}, budget={}, term={}, release_quantity={}, enabled={}, release_speed={}, " \ 62 | "release_colour={}, release_opacity={}, release_scale={}, release_rotation={}, release_impulse={}, " \ 63 | "particle_texture_asset_name={!r}, modifiers={}, blend_mode={!r}, trigger_offset={}, " \ 64 | "minimum_trigger_period={}, radius={}, ring={}, radiate={})".format( 65 | self.name, self.budget, self.term, self.release_quantity, self.enabled, self.release_speed, 66 | self.release_colour, self.release_opacity, self.release_scale, self.release_rotation, 67 | self.release_impulse, self.particle_texture_asset_name, self.modifiers, self.blend_mode, 68 | self.trigger_offset, self.minimum_trigger_period, self.radius, self.ring, self.radiate) 69 | 70 | @staticmethod 71 | def make(emitter, radius, ring, radiate): 72 | return CircleEmitter( 73 | emitter.name, emitter.budget, emitter.term, emitter.release_quantity, emitter.enabled, 74 | emitter.release_speed, emitter.release_colour, emitter.release_opacity, emitter.release_scale, 75 | emitter.release_rotation, emitter.release_impulse, emitter.particle_texture_asset_name, emitter.modifiers, 76 | emitter.blend_mode, emitter.trigger_offset, emitter.minimum_trigger_period, radius, ring, radiate) 77 | 78 | 79 | class ConeEmitter(Emitter): 80 | def __init__(self, name, budget, term, release_quantity, enabled, release_speed, release_colour, release_opacity, 81 | release_scale, release_rotation, release_impulse, particle_texture_asset_name, modifiers, blend_mode, 82 | trigger_offset, minimum_trigger_period, direction, cone_angle): 83 | Emitter.__init__( 84 | self, name, budget, term, release_quantity, enabled, release_speed, release_colour, release_opacity, 85 | release_scale, release_rotation, release_impulse, particle_texture_asset_name, modifiers, blend_mode, 86 | trigger_offset, minimum_trigger_period) 87 | self.direction = direction 88 | self.cone_angle = cone_angle 89 | 90 | def __repr__(self): 91 | return "ConeEmitter(name={!r}, budget={}, term={}, release_quantity={}, enabled={}, release_speed={}, " \ 92 | "release_colour={}, release_opacity={}, release_scale={}, release_rotation={}, release_impulse={}, " \ 93 | "particle_texture_asset_name={!r}, modifiers={}, blend_mode={!r}, trigger_offset={}, " \ 94 | "minimum_trigger_period={}, direction={}, cone_angle={})".format( 95 | self.name, self.budget, self.term, self.release_quantity, self.enabled, self.release_speed, 96 | self.release_colour, self.release_opacity, self.release_scale, self.release_rotation, 97 | self.release_impulse, self.particle_texture_asset_name, self.modifiers, self.blend_mode, 98 | self.trigger_offset, self.minimum_trigger_period, self.direction, self.cone_angle) 99 | 100 | @staticmethod 101 | def make(emitter, direction, cone_angle): 102 | return ConeEmitter( 103 | emitter.name, emitter.budget, emitter.term, emitter.release_quantity, emitter.enabled, 104 | emitter.release_speed, emitter.release_colour, emitter.release_opacity, emitter.release_scale, 105 | emitter.release_rotation, emitter.release_impulse, emitter.particle_texture_asset_name, emitter.modifiers, 106 | emitter.blend_mode, emitter.trigger_offset, emitter.minimum_trigger_period, direction, cone_angle) 107 | 108 | 109 | class LineEmitter(Emitter): 110 | def __init__(self, name, budget, term, release_quantity, enabled, release_speed, release_colour, release_opacity, 111 | release_scale, release_rotation, release_impulse, particle_texture_asset_name, modifiers, blend_mode, 112 | trigger_offset, minimum_trigger_period, length, angle, rectilinear, emit_both_ways): 113 | Emitter.__init__( 114 | self, name, budget, term, release_quantity, enabled, release_speed, release_colour, release_opacity, 115 | release_scale, release_rotation, release_impulse, particle_texture_asset_name, modifiers, blend_mode, 116 | trigger_offset, minimum_trigger_period) 117 | self.length = length 118 | self.angle = angle 119 | self.rectilinear = rectilinear 120 | self.emit_both_ways = emit_both_ways 121 | 122 | def __repr__(self): 123 | return "LineEmitter(name={!r}, budget={}, term={}, release_quantity={}, enabled={}, release_speed={}, " \ 124 | "release_colour={}, release_opacity={}, release_scale={}, release_rotation={}, release_impulse={}, " \ 125 | "particle_texture_asset_name={!r}, modifiers={}, blend_mode={!r}, trigger_offset={}, " \ 126 | "minimum_trigger_period={}, length={}, angle={}, rectilinear={}, emit_both_ways={})".format( 127 | self.name, self.budget, self.term, self.release_quantity, self.enabled, self.release_speed, 128 | self.release_colour, self.release_opacity, self.release_scale, self.release_rotation, 129 | self.release_impulse, self.particle_texture_asset_name, self.modifiers, self.blend_mode, 130 | self.trigger_offset, self.minimum_trigger_period, self.length, self.angle, self.rectilinear, 131 | self.emit_both_ways) 132 | 133 | @staticmethod 134 | def make(emitter, length, angle, rectilinear, emit_both_ways): 135 | return LineEmitter( 136 | emitter.name, emitter.budget, emitter.term, emitter.release_quantity, emitter.enabled, 137 | emitter.release_speed, emitter.release_colour, emitter.release_opacity, emitter.release_scale, 138 | emitter.release_rotation, emitter.release_impulse, emitter.particle_texture_asset_name, emitter.modifiers, 139 | emitter.blend_mode, emitter.trigger_offset, emitter.minimum_trigger_period, length, angle, rectilinear, 140 | emit_both_ways) 141 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/mercury/modifiers.py: -------------------------------------------------------------------------------- 1 | """ 2 | mercury particle engine modifier types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | 8 | class ModifierCollection(object): 9 | def __init__(self, modifiers): 10 | self.modifiers = modifiers 11 | 12 | def __repr__(self): 13 | return '{}'.format(self.modifiers) 14 | 15 | 16 | class Modifier(object): 17 | pass 18 | 19 | 20 | class OpacityModifier(Modifier): 21 | def __init__(self, initial, ultimate): 22 | self.initial = initial 23 | self.ultimate = ultimate 24 | 25 | def __repr__(self): 26 | return 'OpacityModifier(initial={}, ultimate={})'.format(self.initial, self.ultimate) 27 | 28 | 29 | class ScaleModifier(Modifier): 30 | def __init__(self, initial, ultimate): 31 | self.initial = initial 32 | self.ultimate = ultimate 33 | 34 | def __repr__(self): 35 | return 'ScaleModifier(initial={}, ultimate={})'.format(self.initial, self.ultimate) 36 | 37 | 38 | class RotationRateModifier(Modifier): 39 | def __init__(self, initial_rate, final_rate): 40 | self.initial_rate = initial_rate 41 | self.final_rate = final_rate 42 | 43 | def __repr__(self): 44 | return 'RotationRateModifier(initial_rate={}, final_rate={})'.format(self.initial_rate, self.final_rate) 45 | 46 | 47 | class ColourModifier(Modifier): 48 | def __init__(self, initial_colour, ultimate_colour): 49 | self.initial_colour = initial_colour 50 | self.ultimate_colour = ultimate_colour 51 | 52 | def __repr__(self): 53 | return 'ColourModifier(initial_colour={}, ultimate_colour={})'.format(self.initial_colour, self.ultimate_colour) 54 | 55 | 56 | class LinearGravityModifier(Modifier): 57 | def __init__(self, gravity): 58 | self.gravity = gravity 59 | 60 | def __repr__(self): 61 | return 'LinearGravityModifier(gravity={})'.format(self.gravity) 62 | 63 | 64 | class ColourInterpolatorModifier(Modifier): 65 | def __init__(self, initial_colour, middle_colour, middle_position, final_colour): 66 | self.initial_colour = initial_colour 67 | self.middle_colour = middle_colour 68 | self.middle_position = middle_position 69 | self.final_colour = final_colour 70 | 71 | def __repr__(self): 72 | return 'ColourInterpolatorModifier(' \ 73 | 'initial_colour={}, middle_colour={}, middle_position={}, final_colour={})'.format( 74 | self.initial_colour, self.middle_colour, self.middle_position, self.final_colour) 75 | 76 | 77 | class ColourMergeModifier(Modifier): 78 | def __init__(self, merge_colour): 79 | self.merge_colour = merge_colour 80 | 81 | def __repr__(self): 82 | return 'ColourMergeModifier(merge_colour={})'.format(self.merge_colour) 83 | 84 | 85 | class DampingModifier(Modifier): 86 | def __init__(self, damping_coefficient): 87 | self.damping_coefficient = damping_coefficient 88 | 89 | def __repr__(self): 90 | return 'DampingModifier(damping_coefficient={})'.format(self.damping_coefficient) 91 | 92 | 93 | class HueShiftModifier(Modifier): 94 | def __init__(self, hue_shift): 95 | self.hue_shift = hue_shift 96 | 97 | def __repr__(self): 98 | return 'HueShiftModifier(hue_shift={})'.format(self.hue_shift) 99 | 100 | 101 | class OpacityInterpolatorModifier(Modifier): 102 | def __init__(self, initial_opacity, middle_opacity, middle_position, final_opacity): 103 | self.initial_opacity = initial_opacity 104 | self.middle_opacity = middle_opacity 105 | self.middle_position = middle_position 106 | self.final_opacity = final_opacity 107 | 108 | def __repr__(self): 109 | return 'OpacityInterpolatorModifier(' \ 110 | 'initial_opacity={}, middle_opacity={}, middle_position={}, final_opacity={})'.format( 111 | self.initial_opacity, self.middle_opacity, self.middle_position, self.final_opacity) 112 | 113 | 114 | class OpacityOscillator(Modifier): 115 | def __init__(self, frequency, minimum_opacity, maximum_opacity): 116 | self.frequency = frequency 117 | self.minimum_opacity = minimum_opacity 118 | self.maximum_opacity = maximum_opacity 119 | 120 | def __repr__(self): 121 | return 'OpacityOscillator(frequency={}, minimum_opacity={}, maximum_opacity={})'.format( 122 | self.frequency, self.minimum_opacity, self.maximum_opacity) 123 | 124 | 125 | class RotationModifier(Modifier): 126 | def __init__(self, rotation_rate): 127 | self.rotation_rate = rotation_rate 128 | 129 | def __repr__(self): 130 | return 'RotationModifier(rotation_rate={})'.format(self.rotation_rate) 131 | 132 | 133 | class TrajectoryRotationModifier(Modifier): 134 | def __init__(self, rotation_offset): 135 | self.rotation_offset = rotation_offset 136 | 137 | def __repr__(self): 138 | return 'TrajectoryRotationModifier(rotation_offset={})'.format(self.rotation_offset) 139 | 140 | 141 | class RadialForceModifier(Modifier): 142 | def __init__(self, radius, position, force, strength): 143 | self.radius = radius 144 | self.position = position 145 | self.force = force 146 | self.strength = strength 147 | 148 | def __repr__(self): 149 | return 'RadialForceModifier(radius={}, position={}, force={}, strength={})'.format( 150 | self.radius, self.position, self.force, self.strength) 151 | 152 | 153 | class RadialGravityModifier(Modifier): 154 | def __init__(self, radius, position): 155 | self.radius = radius 156 | self.position = position 157 | 158 | def __repr__(self): 159 | return 'RadialGravityModifier(radius={}, position={})'.format(self.radius, self.position) 160 | 161 | 162 | class RectangleConstraintDeflector(Modifier): 163 | def __init__(self, width, height, position): 164 | self.width = width 165 | self.height = height 166 | self.position = position 167 | 168 | def __repr__(self): 169 | return 'RectangleConstraintDeflector(width={}, height={}, position={})'.format( 170 | self.width, self.height, self.position) 171 | 172 | 173 | class RectangleForceModifier(Modifier): 174 | def __init__(self, width, height, position, force, strength): 175 | self.width = width 176 | self.height = height 177 | self.position = position 178 | self.force = force 179 | self.strength = strength 180 | 181 | def __repr__(self): 182 | return 'RectangleForceModifier(width={}, height={}, position={}, force={}, strength={})'.format( 183 | self.width, self.height, self.position, self.force, self.strength) 184 | 185 | 186 | class ScaleInterpolatorModifier(Modifier): 187 | def __init__(self, initial_scale, middle_scale, middle_position, final_scale): 188 | self.initial_scale = initial_scale 189 | self.middle_scale = middle_scale 190 | self.middle_position = middle_position 191 | self.final_scale = final_scale 192 | 193 | def __repr__(self): 194 | return 'ScaleInterpolatorModifier(' \ 195 | 'initial_scale={}, middle_scale={}, middle_position={}, final_scale={})'.format( 196 | self.initial_scale, self.middle_scale, self.middle_position, self.final_scale) 197 | 198 | 199 | class ScaleMergeModifier(Modifier): 200 | def __init__(self, merge_scale): 201 | self.merge_scale = merge_scale 202 | 203 | def __repr__(self): 204 | return 'ScaleMergeModifier(merge_scale={])'.format(self.merge_scale) 205 | 206 | 207 | class ScaleOscillator(Modifier): 208 | def __init__(self, frequency, minimum_scale, maximum_scale): 209 | self.frequency = frequency 210 | self.minimum_scale = minimum_scale 211 | self.maximum_scale = maximum_scale 212 | 213 | def __repr__(self): 214 | return 'ScaleOscillator(frequency={}, minimum_scale={}, maximum_scale={})'.format( 215 | self.frequency, self.minimum_scale, self.maximum_scale) 216 | 217 | 218 | class SineForceModifier(Modifier): 219 | def __init__(self, rotation, frequency, amplitude): 220 | self.rotation = rotation 221 | self.frequency = frequency 222 | self.amplitude = amplitude 223 | 224 | def __repr__(self): 225 | return 'SineForceModifier(rotation={}, frequency={}, amplitude={})'.format( 226 | self.rotation, self.frequency, self.amplitude) 227 | 228 | 229 | class VelocityClampModifier(Modifier): 230 | def __init__(self, maximum_velocity): 231 | self.maximum_velocity = maximum_velocity 232 | 233 | def __repr__(self): 234 | return 'VelocityClampModifier(maximum_velocity={})'.format(self.maximum_velocity) 235 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/mercury/particle.py: -------------------------------------------------------------------------------- 1 | """ 2 | mercury particle engine main types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.xna_types.mercury.emitters import EmitterCollection 8 | 9 | 10 | class ParticleEffect(EmitterCollection): 11 | def __init__(self, emitters, name, author, description): 12 | EmitterCollection.__init__(self, emitters) 13 | self.name = name 14 | self.author = author 15 | self.description = description 16 | 17 | def __repr__(self): 18 | return 'ParticleEffect(name={!r}, emitters={}, author={!r}, description={!r})'.format( 19 | self.name, self.emitters, self.author, self.description) 20 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/xna_graphics.py: -------------------------------------------------------------------------------- 1 | """ 2 | graphics types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import os 8 | 9 | from xnb_parse.type_reader import ReaderError 10 | from xnb_parse.xna_types.xna_primitive import Enum 11 | from xnb_parse.file_formats.png import write_png 12 | from xnb_parse.file_formats.xml_utils import ET 13 | from xnb_parse.file_formats.img_decode import decode_bgra, decode_rgba, decode_a, decode_dxt1, decode_dxt3, decode_dxt5 14 | 15 | 16 | VERSION_31 = 4 17 | VERSION_40 = 5 18 | CUBE_SIDES = ['+x', '-x', '+y', '-y', '+z', '-z'] 19 | FORMAT_COLOR = 1 20 | SURFACE_FORMAT = { 21 | 1: ('Color', decode_bgra), 22 | 2: ('Bgr32', None), 23 | 3: ('Bgra1010102', None), 24 | 4: ('Rgba32', decode_rgba), 25 | 5: ('Rgb32', None), 26 | 6: ('Rgba1010102', None), 27 | 7: ('Rg32', None), 28 | 8: ('Rgba64', None), 29 | 9: ('Bgr565', None), 30 | 10: ('Bgra5551', None), 31 | 11: ('Bgr555', None), 32 | 12: ('Bgra4444', None), 33 | 13: ('Bgr444', None), 34 | 14: ('Bgra2338', None), 35 | 15: ('Alpha8', decode_a), 36 | 16: ('Bgr233', None), 37 | 17: ('Bgr24', None), 38 | 18: ('NormalizedByte2', None), 39 | 19: ('NormalizedByte4', None), 40 | 20: ('NormalizedShort2', None), 41 | 21: ('NormalizedShort4', None), 42 | 22: ('Single', None), 43 | 23: ('Vector2', None), 44 | 24: ('Vector4', None), 45 | 25: ('HalfSingle', None), 46 | 26: ('HalfVector2', None), 47 | 27: ('HalfVector4', None), 48 | 28: ('Dxt1', decode_dxt1), 49 | 29: ('Dxt2', None), 50 | 30: ('Dxt3', decode_dxt3), 51 | 31: ('Dxt4', None), 52 | 32: ('Dxt5', decode_dxt5), 53 | 33: ('Luminance8', None), 54 | 34: ('Luminance16', None), 55 | 35: ('LuminanceAlpha8', None), 56 | 36: ('LuminanceAlpha16', None), 57 | 37: ('Palette8', None), 58 | 38: ('PaletteAlpha16', None), 59 | 39: ('NormalizedLuminance16', None), 60 | 40: ('NormalizedLuminance32', None), 61 | 41: ('NormalizedAlpha1010102', None), 62 | 42: ('NormalizedByte2Computed', None), 63 | 43: ('VideoYuYv', None), 64 | 44: ('VideoUyVy', None), 65 | 45: ('VideoGrGb', None), 66 | 46: ('VideoRgBg', None), 67 | 47: ('Multi2Bgra32', None), 68 | 48: ('Depth24Stencil8', None), 69 | 49: ('Depth24Stencil8Single', None), 70 | 50: ('Depth24Stencil4', None), 71 | 51: ('Depth24', None), 72 | 52: ('Depth32', None), 73 | 54: ('Depth16', None), 74 | 56: ('Depth15Stencil1', None), 75 | } 76 | FORMAT4_COLOR = 0 77 | SURFACE_FORMAT4 = { 78 | 0: ('Color', decode_rgba), 79 | 1: ('Bgr565', None), 80 | 2: ('Bgra5551', None), 81 | 3: ('Bgra4444', None), 82 | 4: ('Dxt1', decode_dxt1), 83 | 5: ('Dxt3', decode_dxt3), 84 | 6: ('Dxt5', decode_dxt5), 85 | 7: ('NormalizedByte2', None), 86 | 8: ('NormalizedByte4', None), 87 | 9: ('Rgba1010102', None), 88 | 10: ('Rg32', None), 89 | 11: ('Rgba64', None), 90 | 12: ('Alpha8', decode_a), 91 | 13: ('Single', None), 92 | 14: ('Vector2', None), 93 | 15: ('Vector4', None), 94 | 16: ('HalfSingle', None), 95 | 17: ('HalfVector2', None), 96 | 18: ('HalfVector4', None), 97 | 19: ('HdrBlendable', None), 98 | } 99 | 100 | 101 | class SurfaceFormat(Enum): 102 | __slots__ = () 103 | enum_values = {k: v[0] for k, v in SURFACE_FORMAT.items()} 104 | 105 | @property 106 | def reader(self): 107 | return SURFACE_FORMAT[self.value][1] 108 | 109 | 110 | class SurfaceFormat4(Enum): 111 | __slots__ = () 112 | enum_values = {k: v[0] for k, v in SURFACE_FORMAT4.items()} 113 | 114 | @property 115 | def reader(self): 116 | return SURFACE_FORMAT4[self.value][1] 117 | 118 | 119 | def get_surface_format(xna_version, surface_format): 120 | try: 121 | if xna_version >= VERSION_40: 122 | return SurfaceFormat4(surface_format) 123 | else: 124 | return SurfaceFormat(surface_format) 125 | except KeyError: 126 | raise ReaderError("Invalid surface format for V{}: {}".format(xna_version, surface_format)) 127 | 128 | 129 | class Texture2D(object): 130 | def __init__(self, surface_format, width, height, mip_levels, needs_swap=False): 131 | self.surface_format = surface_format 132 | self.width = width 133 | self.height = height 134 | self.mip_levels = mip_levels 135 | self.needs_swap = needs_swap 136 | 137 | def __str__(self): 138 | return "Texture2D f:{} d:{}x{} m:{} s:{}".format(self.surface_format, self.width, self.height, 139 | len(self.mip_levels), len(self.mip_levels[0])) 140 | 141 | def export(self, filename): 142 | if not self.surface_format.reader: 143 | raise ReaderError("No decoder found: '{}'".format(self.surface_format)) 144 | dirname = os.path.dirname(filename) 145 | if not os.path.isdir(dirname): 146 | os.makedirs(dirname) 147 | 148 | # hack for ArtObject/TrileSet alpha channel 149 | alpha = 'yes' 150 | if 'art objects' in filename or 'trile sets' in filename: 151 | alpha = 'no' 152 | rows = self.surface_format.reader(self.mip_levels[0], self.width, self.height, self.needs_swap, 153 | alpha='only') 154 | write_png(filename + '_alpha', self.width, self.height, rows) 155 | rows = self.surface_format.reader(self.mip_levels[0], self.width, self.height, self.needs_swap, alpha=alpha) 156 | write_png(filename, self.width, self.height, rows) 157 | 158 | def full_data(self, alpha='yes'): 159 | if not self.surface_format.reader: 160 | raise ReaderError("No decoder found: '{}'".format(self.surface_format)) 161 | 162 | rows = self.surface_format.reader(self.mip_levels[0], self.width, self.height, self.needs_swap, alpha=alpha) 163 | data = bytearray() 164 | for row in rows: 165 | data.extend(row) 166 | return bytes(data) 167 | 168 | 169 | class Texture3D(object): 170 | def __init__(self, surface_format, width, height, depth, mip_levels, needs_swap=False): 171 | self.surface_format = surface_format 172 | self.width = width 173 | self.height = height 174 | self.depth = depth 175 | self.mip_levels = mip_levels 176 | self.needs_swap = needs_swap 177 | 178 | def __str__(self): 179 | return "Texture3D f:{} d:{}x{}x{} m:{} s:{}".format(self.surface_format, self.width, self.height, self.depth, 180 | len(self.mip_levels), len(self.mip_levels[0])) 181 | 182 | 183 | class TextureCube(object): 184 | def __init__(self, surface_format, texture_size, sides, needs_swap=False): 185 | self.surface_format = surface_format 186 | self.texture_size = texture_size 187 | self.sides = sides 188 | self.needs_swap = needs_swap 189 | 190 | def __str__(self): 191 | return "TextureCube f:{} d:{} m:{} s:{}".format(self.surface_format, self.texture_size, len(self.sides['+x']), 192 | len(self.sides['+x'][0])) 193 | 194 | 195 | class IndexBuffer(object): 196 | def __init__(self, index_16, index_data): 197 | self.index_16 = index_16 198 | self.index_data = index_data 199 | 200 | def __str__(self): 201 | return "IndexBuffer t:{} s:{}".format(16 if self.index_16 else 32, len(self.index_data)) 202 | 203 | 204 | class Effect(object): 205 | def __init__(self, effect_data): 206 | self.effect_data = effect_data 207 | 208 | def __str__(self): 209 | return "Effect s:{}".format(len(self.effect_data)) 210 | 211 | def export(self, filename): 212 | out_dir = os.path.dirname(filename) 213 | if not os.path.isdir(out_dir): 214 | os.makedirs(out_dir) 215 | with open(filename + '.fxo', 'wb') as out_handle: 216 | out_handle.write(self.effect_data) 217 | 218 | 219 | class BasicEffect(object): 220 | def __init__(self, texture, colour_d, colour_e, colour_s, spec, alpha, colour_v): 221 | self.texture = texture 222 | self.colour_d = colour_d 223 | self.colour_e = colour_e 224 | self.colour_s = colour_s 225 | self.spec = spec 226 | self.alpha = alpha 227 | self.colour_v = colour_v 228 | 229 | def __str__(self): 230 | return "BasicEffectReader '{}'".format(self.texture) 231 | 232 | def xml(self, parent): 233 | if parent is None: 234 | root = ET.Element('BasicEffect') 235 | else: 236 | root = ET.SubElement(parent, 'BasicEffect') 237 | root.set('spec', str(self.spec)) 238 | root.set('alpha', str(self.alpha)) 239 | root.set('colorV', str(self.colour_v)) 240 | self.texture.xml(ET.SubElement(root, 'Texture')) 241 | self.colour_d.xml(ET.SubElement(root, 'ColorD')) 242 | self.colour_e.xml(ET.SubElement(root, 'ColorE')) 243 | self.colour_s.xml(ET.SubElement(root, 'ColorS')) 244 | return root 245 | 246 | 247 | class SpriteFont(object): 248 | def __init__(self, texture, glyphs, cropping, char_map, v_space, h_space, kerning, default_char): 249 | self.texture = texture 250 | self.glyphs = glyphs 251 | self.cropping = cropping 252 | self.char_map = char_map 253 | self.v_space = v_space 254 | self.h_space = h_space 255 | self.kerning = kerning 256 | self.default_char = default_char 257 | 258 | def __str__(self): 259 | return 'SpriteFont c:{} f:{} d:{}x{}'.format(len(self.glyphs), self.texture.surface_format, self.texture.width, 260 | self.texture.height) 261 | 262 | def xml(self, parent=None): 263 | if parent is None: 264 | root = ET.Element('SpriteFont') 265 | else: 266 | root = ET.SubElement(parent, 'SpriteFont') 267 | root.set('width', str(self.texture.width)) 268 | root.set('height', str(self.texture.height)) 269 | root.set('hSpace', str(self.h_space)) 270 | root.set('vSpace', str(self.v_space)) 271 | if self.default_char is not None: 272 | root.set('defaultChar', self.default_char) 273 | if self.glyphs is not None: 274 | self.glyphs.xml(root, 'Glyphs') 275 | if self.cropping is not None: 276 | self.cropping.xml(root, 'Cropping') 277 | if self.kerning is not None: 278 | self.kerning.xml(root, 'Kerning') 279 | if self.char_map is not None: 280 | self.char_map.xml(root, 'CharMap', 'Char', 'c') 281 | return root 282 | 283 | def export(self, filename): 284 | if self.texture is not None: 285 | self.texture.export(filename) 286 | 287 | 288 | class PrimitiveType(Enum): 289 | __slots__ = () 290 | enum_values = {1: 'PointList', 2: 'LineList', 3: 'LineStrip', 4: 'TriangleList', 5: 'TriangleStrip', 291 | 6: 'TriangleFan'} 292 | 293 | 294 | class PrimitiveType4(Enum): 295 | __slots__ = () 296 | enum_values = {0: 'TriangleList', 1: 'TriangleStrip', 2: 'LineList', 3: 'LineStrip'} 297 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/xna_math.py: -------------------------------------------------------------------------------- 1 | """ 2 | math types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from collections import namedtuple 8 | 9 | from xnb_parse.file_formats.xml_utils import ET 10 | 11 | 12 | _Color = namedtuple('Color', ['r', 'g', 'b', 'a']) 13 | 14 | 15 | class Color(_Color): 16 | __slots__ = () 17 | 18 | def to_packed(self): 19 | return self.r | self.g << 8 | self.b << 16 | self.a << 24 20 | 21 | @staticmethod 22 | def from_packed(data): 23 | v_r = data & 0xff 24 | v_g = data >> 8 & 0xff 25 | v_b = data >> 16 & 0xff 26 | v_a = data >> 24 & 0xff 27 | return Color(v_r, v_g, v_b, v_a) 28 | 29 | @staticmethod 30 | def from_string(data): 31 | clean_data = data 32 | if clean_data.startswith('#'): 33 | clean_data = clean_data[1:] 34 | if len(clean_data) == 6: 35 | clean_data = 'ff' + clean_data 36 | if len(clean_data) != 8: 37 | raise ValueError("Invalid color: '{}'".format(data)) 38 | v_a = int(clean_data[0:2], 16) 39 | v_r = int(clean_data[2:4], 16) 40 | v_g = int(clean_data[4:6], 16) 41 | v_b = int(clean_data[6:8], 16) 42 | return Color(v_r, v_g, v_b, v_a) 43 | 44 | def attrib(self): 45 | return "#{:02X}{:02X}{:02X}{:02X}".format(self.a, self.r, self.g, self.b) 46 | 47 | def xml(self, parent): 48 | root = ET.SubElement(parent, 'Color') 49 | root.set('c', self.attrib()) 50 | return root 51 | 52 | 53 | _Rectangle = namedtuple('Rectangle', ['x', 'y', 'w', 'h']) 54 | 55 | 56 | class Rectangle(_Rectangle): 57 | __slots__ = () 58 | 59 | def xml(self, parent): 60 | root = ET.SubElement(parent, 'Rectangle') 61 | root.set('x', str(self.x)) 62 | root.set('y', str(self.y)) 63 | root.set('w', str(self.w)) 64 | root.set('h', str(self.h)) 65 | return root 66 | 67 | 68 | _Quarternion = namedtuple('Quarternion', ['x', 'y', 'z', 'w']) 69 | 70 | 71 | class Quaternion(_Quarternion): 72 | __slots__ = () 73 | 74 | def xml(self, parent): 75 | root = ET.SubElement(parent, 'Quaternion') 76 | root.set('x', str(self.x)) 77 | root.set('y', str(self.y)) 78 | root.set('z', str(self.z)) 79 | root.set('w', str(self.w)) 80 | return root 81 | 82 | 83 | _Vector2 = namedtuple('Vector2', ['x', 'y']) 84 | 85 | 86 | class Vector2(_Vector2): 87 | __slots__ = () 88 | 89 | def xml(self, parent): 90 | root = ET.SubElement(parent, 'Vector2') 91 | root.set('x', str(self.x)) 92 | root.set('y', str(self.y)) 93 | return root 94 | 95 | 96 | _Vector3 = namedtuple('Vector3', ['x', 'y', 'z']) 97 | 98 | 99 | class Vector3(_Vector3): 100 | __slots__ = () 101 | 102 | def xml(self, parent): 103 | root = ET.SubElement(parent, 'Vector3') 104 | root.set('x', str(self.x)) 105 | root.set('y', str(self.y)) 106 | root.set('z', str(self.z)) 107 | return root 108 | 109 | 110 | _Vector4 = namedtuple('Vector4', ['x', 'y', 'z', 'w']) 111 | 112 | 113 | class Vector4(_Vector4): 114 | __slots__ = () 115 | 116 | def xml(self, parent): 117 | root = ET.SubElement(parent, 'Vector4') 118 | root.set('x', str(self.x)) 119 | root.set('y', str(self.y)) 120 | root.set('z', str(self.z)) 121 | root.set('w', str(self.w)) 122 | return root 123 | 124 | 125 | _Point = namedtuple('Point', ['x', 'y']) 126 | 127 | 128 | class Point(_Point): 129 | __slots__ = () 130 | 131 | def xml(self, parent): 132 | root = ET.SubElement(parent, 'Point') 133 | root.set('x', str(self.x)) 134 | root.set('y', str(self.y)) 135 | return root 136 | 137 | 138 | _Plane = namedtuple('Plane', ['normal', 'd']) 139 | 140 | 141 | class Plane(_Plane): 142 | __slots__ = () 143 | 144 | def xml(self, parent): 145 | root = ET.SubElement(parent, 'Plane') 146 | root.set('d', str(self.d)) 147 | self.normal.xml(root) 148 | return root 149 | 150 | 151 | _BoundingBox = namedtuple('BoundingBox', ['min', 'max']) 152 | 153 | 154 | class BoundingBox(_BoundingBox): 155 | __slots__ = () 156 | 157 | def xml(self, parent): 158 | root = ET.SubElement(parent, 'BoundingBox') 159 | self.min.xml(root) 160 | self.max.xml(root) 161 | return root 162 | 163 | 164 | _BoundingSphere = namedtuple('BoundingSphere', ['center', 'radius']) 165 | 166 | 167 | class BoundingSphere(_BoundingSphere): 168 | __slots__ = () 169 | 170 | def xml(self, parent): 171 | root = ET.SubElement(parent, 'BoundingSphere') 172 | root.set('radius', str(self.radius)) 173 | self.center.xml(root) 174 | return root 175 | 176 | 177 | _Ray = namedtuple('Ray', ['pos', 'dir']) 178 | 179 | 180 | class Ray(_Ray): 181 | __slots__ = () 182 | 183 | def xml(self, parent): 184 | root = ET.SubElement(parent, 'Ray') 185 | self.pos.xml(root) 186 | self.dir.xml(root) 187 | return root 188 | 189 | 190 | class Matrix(object): 191 | __slots__ = ('value',) 192 | 193 | def __init__(self, value): 194 | if len(value) != 16: 195 | raise ValueError("Invalid Matrix") 196 | self.value = value 197 | 198 | def __repr__(self): 199 | return 'Matrix(' + ','.join([str(v) for v in self.value]) + ')' 200 | 201 | def xml(self, parent): 202 | root = self.value.xml(parent, 'Matrix', 'Cell') 203 | return root 204 | 205 | 206 | _BoundingFrustum = namedtuple('BoundingFrustum', ['v']) 207 | 208 | 209 | class BoundingFrustum(_BoundingFrustum): 210 | __slots__ = () 211 | 212 | def xml(self, parent): 213 | root = ET.SubElement(parent, 'BoundingFrustum') 214 | self.v.xml(root) 215 | return root 216 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/xna_media.py: -------------------------------------------------------------------------------- 1 | """ 2 | media types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.file_formats.wav import write_wav 8 | from xnb_parse.file_formats.xml_utils import ET 9 | from xnb_parse.xna_types.xna_primitive import Enum 10 | 11 | 12 | class SoundEffect(object): 13 | def __init__(self, sound_format, sound_data, loop_start, loop_length, duration, needs_swap=False): 14 | self.sound_format = sound_format 15 | self.sound_data = sound_data 16 | self.loop_start = loop_start 17 | self.loop_length = loop_length 18 | self.duration = duration 19 | self.needs_swap = needs_swap 20 | 21 | def __str__(self): 22 | return "SoundEffect fs:{} ds:{} d:{}ms ls:{} ll:{}".format(len(self.sound_format), len(self.sound_data), 23 | self.duration, self.loop_start, self.loop_length) 24 | 25 | def export(self, filename): 26 | write_wav(filename, self.sound_format, self.sound_data, self.needs_swap) 27 | 28 | def xml(self, parent=None): 29 | if parent is None: 30 | root = ET.Element('SoundEffect') 31 | else: 32 | root = ET.SubElement(parent, 'SoundEffect') 33 | root.set('loopStart', str(self.loop_start)) 34 | root.set('loopLength', str(self.loop_length)) 35 | root.set('duration', str(self.duration)) 36 | return root 37 | 38 | 39 | class Song(object): 40 | def __init__(self, filename, duration): 41 | self.filename = filename 42 | self.duration = duration 43 | 44 | def __str__(self): 45 | return "Song f:'{}' d:{}ms".format(self.filename, self.duration) 46 | 47 | def xml(self, parent=None): 48 | if parent is None: 49 | root = ET.Element('Song') 50 | else: 51 | root = ET.SubElement(parent, 'Song') 52 | root.set('filename', self.filename) 53 | root.set('duration', str(self.duration)) 54 | return root 55 | 56 | 57 | class Video(object): 58 | def __init__(self, filename, duration, width, height, fps, video_soundtrack_type): 59 | self.filename = filename 60 | self.duration = duration 61 | self.width = width 62 | self.height = height 63 | self.fps = fps 64 | self.video_soundtrack_type = video_soundtrack_type 65 | 66 | def __str__(self): 67 | return "Video f:'{}' d:{}ms s:{}x{} f:{:.2f} t:{}".format(self.filename, self.duration, self.width, self.height, 68 | self.fps, self.video_soundtrack_type) 69 | 70 | def xml(self, parent=None): 71 | if parent is None: 72 | root = ET.Element('Video') 73 | else: 74 | root = ET.SubElement(parent, 'Video') 75 | if self.filename is not None: 76 | root.set('filename', self.filename) 77 | if self.duration is not None: 78 | root.set('duration', str(self.duration)) 79 | if self.width is not None: 80 | root.set('width', str(self.width)) 81 | if self.height is not None: 82 | root.set('height', str(self.height)) 83 | if self.fps is not None: 84 | root.set('fps', str(self.fps)) 85 | if self.video_soundtrack_type is not None: 86 | root.set('soundtrackType', str(self.video_soundtrack_type)) 87 | return root 88 | 89 | 90 | class VideoSoundtrackType(Enum): 91 | __slots__ = () 92 | enum_values = dict(enumerate(['Music', 'Dialog', 'MusicAndDialog'])) 93 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/xna_primitive.py: -------------------------------------------------------------------------------- 1 | """ 2 | primitive types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | from xnb_parse.file_formats.xml_utils import ET 8 | 9 | 10 | class Enum(object): 11 | __slots__ = ('_value', '_name') 12 | enum_values = None 13 | xml_tag = None 14 | 15 | def __init__(self, value): 16 | self._value = value 17 | self._name = None 18 | if value is not None: 19 | self._name = self.enum_values[value] 20 | 21 | @property 22 | def value(self): 23 | return self._value 24 | 25 | @property 26 | def name(self): 27 | return self._name 28 | 29 | def __str__(self): 30 | return self._name 31 | 32 | def __repr__(self): 33 | return '{}({})'.format(self.__class__.__name__, self._value) 34 | 35 | def xml(self, parent): 36 | xml_tag = self.xml_tag if self.xml_tag else self.__class__.__name__ 37 | root = ET.SubElement(parent, xml_tag) 38 | root.text = self._name 39 | return root 40 | -------------------------------------------------------------------------------- /xnb_parse/xna_types/xna_system.py: -------------------------------------------------------------------------------- 1 | """ 2 | system types 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import sys 8 | from collections import OrderedDict 9 | 10 | from xnb_parse.file_formats.xml_utils import ET 11 | from xnb_parse.xna_types.xna_primitive import Enum 12 | 13 | 14 | class XNAList(list): 15 | __slots__ = () 16 | 17 | def xml(self, parent=None, xml_tag='List', xml_entry='Entry', attrib=None): 18 | if sys.version < '3': 19 | conv = unicode 20 | else: 21 | conv = str 22 | if parent is None: 23 | root = ET.Element(xml_tag) 24 | else: 25 | root = ET.SubElement(parent, xml_tag) 26 | for cur_value in self: 27 | if hasattr(cur_value, 'xml'): 28 | cur_value.xml(root) 29 | elif attrib is not None: 30 | cur_tag = ET.SubElement(root, xml_entry) 31 | cur_tag.set(attrib, conv(cur_value)) 32 | else: 33 | cur_tag = ET.SubElement(root, xml_entry) 34 | cur_tag.text = conv(cur_value) 35 | return root 36 | 37 | 38 | class XNADict(OrderedDict): 39 | __slots__ = () 40 | 41 | def xml(self, parent=None, xml_tag='Dict', xml_entry='Entry', attrib=None): 42 | if sys.version < '3': 43 | conv = unicode 44 | else: 45 | conv = str 46 | if parent is None: 47 | root = ET.Element(xml_tag) 48 | else: 49 | root = ET.SubElement(parent, xml_tag) 50 | for cur_key, cur_value in self.items(): 51 | cur_tag = ET.SubElement(root, xml_entry) 52 | if hasattr(cur_key, 'xml') and not isinstance(cur_key, Enum): 53 | cur_key.xml(cur_tag) 54 | else: 55 | cur_tag.set('key', conv(cur_key)) 56 | if hasattr(cur_value, 'xml'): 57 | cur_value.xml(cur_tag) 58 | elif attrib is not None: 59 | cur_tag.set(attrib, conv(cur_value)) 60 | else: 61 | cur_tag.text = conv(cur_value) 62 | return root 63 | 64 | 65 | class XNASet(XNAList): 66 | __slots__ = () 67 | 68 | def xml(self, parent=None, xml_tag='Set', xml_entry='Entry', attrib=None): 69 | root = XNAList.xml(self, parent=parent, xml_tag=xml_tag, xml_entry=xml_entry, attrib=attrib) 70 | return root 71 | 72 | 73 | class ExternalReference(object): 74 | def __init__(self, filename, expected_type): 75 | self.filename = filename 76 | self.expected_type = expected_type 77 | 78 | def __str__(self): 79 | return "ExternalReference '{}'".format(self.filename) 80 | 81 | def xml(self, parent): 82 | root = ET.SubElement(parent, 'ExternalReference') 83 | root.set('filename', self.filename) 84 | if self.expected_type is not None: 85 | root.set('expectedType', self.expected_type.target_type) 86 | return root 87 | -------------------------------------------------------------------------------- /xnb_parse/xnb_decomp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Decompress XNB files. 3 | Requires win32 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | import sys 9 | import time 10 | import os 11 | 12 | from xnb_parse.xna_content_manager import ContentManager 13 | 14 | 15 | def read_xnb(in_dir, out_dir): 16 | content_manager = ContentManager(in_dir) 17 | out_dir = os.path.normpath(out_dir) 18 | for asset_name in content_manager.assets: 19 | print(asset_name) 20 | xnb = content_manager.xnb(asset_name, parse=False) 21 | out_file = os.path.join(out_dir, os.path.normpath(asset_name)) 22 | xnb.save(filename=out_file) 23 | 24 | 25 | def main(): 26 | if len(sys.argv) == 3: 27 | totaltime = time.time() 28 | read_xnb(os.path.normpath(sys.argv[1]), os.path.normpath(sys.argv[2])) 29 | print('> Done in {:.2f} seconds'.format(time.time() - totaltime)) 30 | else: 31 | print('xnb_decomp.py in_dir out_dir', file=sys.stderr) 32 | -------------------------------------------------------------------------------- /xnb_parse/xnb_reader.py: -------------------------------------------------------------------------------- 1 | """ 2 | XNB parser 3 | """ 4 | 5 | from __future__ import print_function 6 | 7 | import os 8 | 9 | import sys 10 | 11 | from xnb_parse.binstream import BinaryStream 12 | from xnb_parse.type_reader_manager import TypeReaderManager 13 | from xnb_parse.xna_native import decompress 14 | from xnb_parse.type_reader import ReaderError, generic_reader_type 15 | from xnb_parse.type_readers.xna_system import EnumReader 16 | from xnb_parse.xna_types.xna_math import Color, Vector2, Vector3, Vector4, Quaternion, Matrix 17 | from xnb_parse.xna_types.xna_system import XNAList, ExternalReference 18 | from xnb_parse.file_formats.xml_utils import output_xml 19 | 20 | 21 | XNB_EXTENSION = '.xnb' 22 | XNB_SIGNATURE = b'XNB' 23 | PLATFORM_WINDOWS = b'w' 24 | PLATFORM_XBOX = b'x' 25 | PLATFORM_MOBILE = b'm' 26 | PROFILE_REACH = 0 27 | PROFILE_HIDEF = 1 28 | VERSION_30 = 3 29 | VERSION_31 = 4 30 | VERSION_40 = 5 31 | XNB_PLATFORMS = {PLATFORM_WINDOWS: 'W', PLATFORM_XBOX: 'X', PLATFORM_MOBILE: 'M'} 32 | XNB_VERSIONS = {VERSION_30: '30', VERSION_31: '31', VERSION_40: '40'} 33 | XNB_PROFILES = {PROFILE_REACH: 'r', PROFILE_HIDEF: 'h'} 34 | 35 | _PROFILE_MASK = 0x7f 36 | _COMPRESS_MASK = 0x80 37 | _XNB_HEADER = '3s c B B I' 38 | 39 | 40 | class XNBReader(BinaryStream): 41 | _type_reader_manager = None 42 | 43 | def __init__(self, data, file_platform=PLATFORM_WINDOWS, file_version=VERSION_40, graphics_profile=PROFILE_REACH, 44 | compressed=False, parse=True, expected_type=None): 45 | BinaryStream.__init__(self, data=data) 46 | del data 47 | if XNBReader._type_reader_manager is None: 48 | XNBReader._type_reader_manager = TypeReaderManager() 49 | self.type_reader_manager = XNBReader._type_reader_manager 50 | self.file_platform = file_platform 51 | self.file_version = file_version 52 | self.graphics_profile = graphics_profile 53 | self.compressed = compressed 54 | self.needs_swap = self.file_platform == PLATFORM_XBOX 55 | self.type_readers = [] 56 | self.shared_objects = [] 57 | self.content = None 58 | if parse: 59 | self.parse(expected_type=expected_type) 60 | 61 | def __str__(self): 62 | return 'XNB {}{}{} s:{}'.format(XNB_PLATFORMS[self.file_platform], XNB_VERSIONS[self.file_version], 63 | XNB_PROFILES[self.graphics_profile], self.length()) 64 | 65 | def parse(self, expected_type=None, verbose=False): 66 | if self.content is not None: 67 | return self.content 68 | 69 | reader_count = self.read_7bit_encoded_int() 70 | for _ in range(reader_count): 71 | reader_name = self.read_string() 72 | reader_version = self.read_int32() 73 | reader = self.get_type_reader(reader_name, reader_version) 74 | self.type_readers.append(reader) 75 | 76 | if verbose: 77 | print("Type: {!s}".format(self.type_readers[0])) 78 | 79 | for reader in self.type_readers: 80 | reader.init_reader(self.file_platform, self.file_version) 81 | 82 | shared_count = self.read_7bit_encoded_int() 83 | 84 | if shared_count: 85 | raise ReaderError("Shared resources present") 86 | 87 | self.content = self.read_object(expected_type=expected_type) 88 | if verbose: 89 | print("Asset: {!s}".format(self.content)) 90 | 91 | for i in range(shared_count): 92 | obj = self.read_object() 93 | self.shared_objects.append(obj) 94 | if verbose: 95 | print("Shared resource {}: {!s}".format(i, obj)) 96 | 97 | remaining = self.read() 98 | if len(remaining): 99 | print("remaining bytes: {}".format(len(remaining)), file=sys.stderr) 100 | return self.content 101 | 102 | def get_type_reader(self, type_reader, version=None): 103 | reader_type_class = self.type_reader_manager.get_type_reader(type_reader) 104 | return reader_type_class(self, version) 105 | 106 | def get_type_reader_by_type(self, type_reader, version=None): 107 | reader_type_class = self.type_reader_manager.get_type_reader_by_type(type_reader) 108 | return reader_type_class(self, version) 109 | 110 | @classmethod 111 | def load(cls, data=None, filename=None, parse=True, expected_type=None): 112 | if filename is not None: 113 | filename = os.path.normpath(filename) 114 | stream = BinaryStream(data=data, filename=filename) 115 | del data 116 | (sig, platform, version, attribs, size) = stream.unpack(_XNB_HEADER) 117 | if sig != XNB_SIGNATURE: 118 | raise ReaderError("bad sig: '{!r}'".format(sig)) 119 | if platform not in XNB_PLATFORMS: 120 | raise ReaderError("bad platform: '{!r}'".format(platform)) 121 | if version not in XNB_VERSIONS: 122 | raise ReaderError("bad version: {}".format(version)) 123 | stream_length = stream.length() 124 | if stream_length != size: 125 | raise ReaderError("bad size: {} != {}".format(stream_length, size)) 126 | compressed = False 127 | profile = 0 128 | if version >= VERSION_40: 129 | profile = attribs & _PROFILE_MASK 130 | if profile not in XNB_PROFILES: 131 | raise ReaderError("bad profile: {}".format(profile)) 132 | if version >= VERSION_30: 133 | compressed = bool(attribs & _COMPRESS_MASK) 134 | size -= stream.calc_size(_XNB_HEADER) 135 | if compressed: 136 | uncomp = stream.read_int32() 137 | size -= 4 138 | content_comp = stream.read(size) 139 | content = decompress(content_comp, uncomp) 140 | else: 141 | content = stream.read(size) 142 | return cls(content, platform, version, profile, compressed, parse=parse, expected_type=expected_type) 143 | 144 | def save(self, filename=None, compress=False): 145 | if self.file_platform not in XNB_PLATFORMS: 146 | raise ReaderError("bad platform: '{!r}'".format(self.file_platform)) 147 | if self.file_version not in XNB_VERSIONS: 148 | raise ReaderError("bad version: {}".format(self.file_version)) 149 | attribs = 0 150 | if self.file_version >= VERSION_40: 151 | if self.graphics_profile not in XNB_PROFILES: 152 | raise ReaderError("bad profile: {}".format(self.graphics_profile)) 153 | attribs |= self.graphics_profile & _PROFILE_MASK 154 | do_compress = False 155 | if self.file_version >= VERSION_30: 156 | if compress: 157 | do_compress = True 158 | attribs |= _COMPRESS_MASK 159 | stream = BinaryStream() 160 | if do_compress: 161 | raise ReaderError("Recompression not supported") 162 | else: 163 | data = self.getvalue() 164 | size = len(data) + stream.calc_size(_XNB_HEADER) 165 | stream.pack(_XNB_HEADER, XNB_SIGNATURE, self.file_platform, self.file_version, attribs, size) 166 | stream.write(data) 167 | if filename is not None: 168 | filename = os.path.normpath(filename) 169 | dirname = os.path.dirname(filename) 170 | if not os.path.isdir(dirname): 171 | os.makedirs(dirname) 172 | if not filename.endswith(XNB_EXTENSION): 173 | filename += XNB_EXTENSION 174 | stream.write_file(filename) 175 | else: 176 | return stream.getvalue() 177 | 178 | def read_object(self, expected_type_reader=None, type_params=None, expected_type=None): 179 | type_id = self.read_7bit_encoded_int() 180 | if type_id == 0: 181 | # null object 182 | return None 183 | try: 184 | type_reader = self.type_readers[type_id - 1] 185 | except IndexError: 186 | raise ReaderError("type id out of range: {} > {}".format(type_id, len(self.type_readers))) 187 | if expected_type_reader is not None: 188 | try: 189 | if expected_type_reader.is_generic_type and expected_type_reader.target_type is None: 190 | expected_type = generic_reader_type(expected_type_reader, type_params) 191 | elif expected_type_reader.is_enum_type: 192 | expected_type = generic_reader_type(EnumReader, [expected_type_reader.target_type]) 193 | else: 194 | expected_type = expected_type_reader.target_type 195 | except AttributeError: 196 | raise ReaderError("bad expected_type_reader: '{}'".format(expected_type_reader)) 197 | if expected_type is not None: 198 | if expected_type != 'System.Object': 199 | if type_reader.target_type != expected_type: 200 | # check parent type readers 201 | for cls in type_reader.__class__.__mro__: 202 | if hasattr(cls, 'target_type'): 203 | if cls.target_type == expected_type: 204 | break 205 | else: 206 | raise ReaderError("Unexpected type: '{}' != '{}'".format(type_reader.target_type, 207 | expected_type)) 208 | return type_reader.read() 209 | 210 | def read_value_or_object(self, expected_type): 211 | if expected_type.is_value_type: 212 | type_reader = self.get_type_reader(expected_type) 213 | return type_reader.read() 214 | else: 215 | return self.read_object(expected_type=expected_type) 216 | 217 | def read_type_id(self): 218 | type_id = self.read_7bit_encoded_int() 219 | if type_id == 0: 220 | # null object 221 | return None 222 | try: 223 | return self.type_readers[type_id - 1] 224 | except IndexError: 225 | raise ReaderError("type id out of range: {} > {}".format(type_id, len(self.type_readers))) 226 | 227 | def export(self, filename, export_file=True, export_xml=True): 228 | if not hasattr(self, 'content'): 229 | raise ReaderError("XNB content deleted") 230 | if self.content is None: 231 | self.parse() 232 | filename = os.path.normpath(filename) 233 | dirname = os.path.dirname(filename) 234 | if not os.path.isdir(dirname): 235 | os.makedirs(dirname) 236 | if export_file and hasattr(self.content, 'export'): 237 | self.content.export(filename) 238 | if export_xml and hasattr(self.content, 'xml'): 239 | output_xml(self.content.xml(), filename + '.xml') 240 | 241 | def read_color(self): 242 | return Color._make(self.unpack('4B')) 243 | 244 | def read_external_reference(self, expected_type=None): 245 | filename = self.read_string() 246 | return ExternalReference(filename, expected_type) 247 | 248 | def read_matrix(self): 249 | return Matrix(XNAList(self.unpack('16f'))) 250 | 251 | def read_quaternion(self): 252 | return Quaternion._make(self.unpack('4f')) 253 | 254 | def read_vector2(self): 255 | return Vector2._make(self.unpack('2f')) 256 | 257 | def read_vector3(self): 258 | return Vector3._make(self.unpack('3f')) 259 | 260 | def read_vector4(self): 261 | return Vector4._make(self.unpack('4f')) 262 | -------------------------------------------------------------------------------- /xwma_decode.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | set BIN_DIR=%~dp0bin\ 4 | 5 | if exist %BIN_DIR%xwmaencode.exe goto xwma 6 | echo *** xwmaencode.exe required to decode xwma files *** 7 | exit /B 1 8 | 9 | :xwma 10 | for /R %1 %%F in (*.xwma) do %BIN_DIR%xwmaencode.exe "%%F" "%%~dpnF.wav" 11 | goto end 12 | 13 | :end 14 | --------------------------------------------------------------------------------