├── README ├── LICENSE ├── scanner.go └── extract_firmware.py /README: -------------------------------------------------------------------------------- 1 | extract_firmware 2 | ---------------- 3 | 4 | This tool is for older NVIDIA blob versions. These had netlist 5 | archives embedded in them with gzip headers, and the video firmware 6 | was raw data in the object file. 7 | 8 | The output of the tool is the video firmware, and optionally, the 9 | extracted netlists classified by GPU (to potentially use in place of 10 | the nouveau-made firmware). This classification is only done for very 11 | specific versions. 12 | 13 | scanner 14 | ------- 15 | 16 | This is an explorational tool (written in Go!) to search for 17 | compressed blobs inside of the blob object file. It uses relocation 18 | data to locate potentially interesting bits of the rodata to search 19 | through. The outputs are netlist archives, as well as "whole" 20 | files. These tend to be falcon programs, but some are just data files. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ilia Mirkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR 19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 20 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 21 | OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /scanner.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Ilia Mirkin. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the "Software"), 5 | // to deal in the Software without restriction, including without limitation 6 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | // and/or sell copies of the Software, and to permit persons to whom the 8 | // Software is furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | // THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR 17 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 18 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 19 | // OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | // To run directly from source: 22 | // $ go run scanner.go path/to/nv-kernel.o_binary output-dir 23 | // 24 | // To make a reusable binary: 25 | // $ go build scanner.go 26 | // $ ./scanner path/to/nv-kernel.o_binary output-dir 27 | // 28 | // Tested on 387.34, 390.48 and 410.57 blobs. Should work on a wider range. 29 | // 30 | // Premise is to parse the relocations table to look for offests into 31 | // rodata, where the firmware is stored. We assume that rodata is 32 | // reasonably well-packed, and try to process the data in between 33 | // relocations. 34 | // 35 | // The assumption is that the data is deflated (but without 36 | // headers). This applies both to the netlist archives, as well as the 37 | // video/pmu/etc firmware which is stored "raw". 38 | 39 | package main 40 | 41 | import "bytes" 42 | import "compress/flate" 43 | import "debug/elf" 44 | import "encoding/binary" 45 | import "fmt" 46 | import "io/ioutil" 47 | import "os" 48 | import "path" 49 | import "sort" 50 | 51 | func must(err error) { 52 | if err != nil { 53 | panic(err) 54 | } 55 | } 56 | 57 | // from https://nv-tegra.nvidia.com/gitweb/?p=linux-nvgpu.git;a=blob;f=drivers/gpu/nvgpu/gk20a/gr_ctx_gk20a.h;hb=refs/tags/tegra-l4t-r31.0.2#l73 58 | var names = map[int]string{ 59 | 0: "fecs_data", 60 | 1: "fecs_inst", 61 | 2: "gpccs_data", 62 | 3: "gpccs_inst", 63 | 4: "sw_bundle_init", 64 | 5: "sw_ctx", 65 | 6: "sw_nonctx", 66 | 7: "sw_method_init", 67 | 8: "ctxreg_sys", 68 | 9: "ctxreg_gpc", 69 | 10: "ctxreg_tpc", 70 | 11: "ctxreg_zcull_gpc", 71 | 12: "ctxreg_pm_sys", 72 | 13: "ctxreg_pm_gpc", 73 | 14: "ctxreg_pm_tpc", 74 | 15: "majorv", 75 | 16: "buffer_size", 76 | 17: "ctxsw_reg_base_index", 77 | 18: "netlist_num", 78 | 19: "ctxreg_ppc", 79 | 20: "ctxreg_pmppc", 80 | 21: "nvperf_ctxreg_sys", 81 | 22: "nvperf_fbp_ctxregs", 82 | 23: "nvperf_ctxreg_gpc", 83 | 24: "nvperf_fbp_router", 84 | 25: "nvperf_gpc_router", 85 | 26: "ctxreg_pmltc", 86 | 27: "ctxreg_pmfbpa", 87 | 28: "swveidbundleinit", 88 | 29: "nvperf_sys_router", 89 | 30: "nvperf_pma", 90 | 31: "ctxreg_pmrop", 91 | 32: "ctxreg_pmucgpc", 92 | 33: "ctxreg_etpc", 93 | 34: "sw_bundle64_init", 94 | 35: "nvperf_pmcau", 95 | } 96 | 97 | type Processor struct { 98 | Destdir string 99 | archiveCounter, wholeCounter int 100 | } 101 | type ArchiveHeader struct { 102 | Magic, Count int32 103 | } 104 | type ArchiveEntry struct { 105 | Id, Length, Offset int32 106 | } 107 | 108 | func (p *Processor) Process(data []byte) { 109 | 110 | // If the data starts with the "magic" zero value (and is 111 | // large enough and has few enough entries to make sense), 112 | // assume it's an archive, and try to parse it that way. 113 | var header ArchiveHeader 114 | dataReader := bytes.NewReader(data) 115 | err := binary.Read(dataReader, binary.LittleEndian, &header) 116 | if len(data) < 32768 || header.Magic != 0 || header.Count > 64 { 117 | // A lot of small seemingly compressed files that 118 | // don't appear to mean much. Since there is no 119 | // compression header, there's a lot of potential for 120 | // garbage. 121 | if len(data) < 128 { 122 | return 123 | } 124 | 125 | // Dump out the file and continue 126 | fname := path.Join(p.Destdir, 127 | fmt.Sprintf("whole_%03d", p.wholeCounter)) 128 | err = ioutil.WriteFile(fname, data, os.FileMode(0666)) 129 | must(err) 130 | 131 | p.wholeCounter++ 132 | return 133 | } 134 | 135 | // Parse all the entries. Bail if any of them don't make 136 | // sense, e.g. have offsets that are in the entry descriptions 137 | // section. 138 | entries := make([]ArchiveEntry, header.Count) 139 | minOffset := int32(8 + 12 * len(entries)) 140 | for i, _ := range entries { 141 | err = binary.Read(dataReader, binary.LittleEndian, &entries[i]) 142 | if err != nil || entries[i].Offset < minOffset { 143 | return 144 | } 145 | } 146 | if len(entries) == 0 { 147 | return 148 | } 149 | 150 | // Create a directory for the archive, and put each entry into 151 | // its own file. Use the known names when possible. 152 | archbase := path.Join(p.Destdir, 153 | fmt.Sprintf("archive_%02d", p.archiveCounter)) 154 | os.Mkdir(archbase, os.FileMode(0777)) 155 | for _, entry := range entries { 156 | name := names[int(entry.Id)] 157 | if name == "" { 158 | name = fmt.Sprintf("unk%d", entry.Id) 159 | } 160 | fname := path.Join(archbase, name) 161 | err = ioutil.WriteFile(fname, 162 | data[entry.Offset:entry.Offset+entry.Length], 163 | os.FileMode(0666)) 164 | must(err) 165 | } 166 | p.archiveCounter++ 167 | } 168 | 169 | func ParseRelocations(f *elf.File, relSection, section string) (offsets []int64) { 170 | relsS := f.Section(relSection) 171 | rels, err := relsS.Data() 172 | must(err) 173 | if len(rels) % 24 != 0 { 174 | panic(fmt.Errorf("Unexpected length for %s: %x\n", 175 | relSection, len(rels))) 176 | } 177 | 178 | symbols, err := f.Symbols() 179 | must(err) 180 | 181 | // Borrowed from the debug/elf relocation processing logic 182 | b := bytes.NewReader(rels) 183 | var rela elf.Rela64 184 | for b.Len() > 0 { 185 | err = binary.Read(b, f.ByteOrder, &rela) 186 | must(err) 187 | 188 | symNo := rela.Info >> 32 189 | sym := &symbols[symNo-1] 190 | if elf.SymType(sym.Info & 0xf) != elf.STT_SECTION || 191 | f.Sections[sym.Section].Name != section { 192 | // We're only looking for relocations into the 193 | // target section 194 | continue 195 | } 196 | 197 | offsets = append(offsets, rela.Addend) 198 | } 199 | return 200 | } 201 | 202 | func main() { 203 | kernel_f := os.Args[1] 204 | f, err := elf.Open(kernel_f) 205 | must(err) 206 | 207 | destdir := os.Args[2] 208 | 209 | // The data actually resides in rodata 210 | rodataS := f.Section(".rodata") 211 | rodata, err := rodataS.Data() 212 | must(err) 213 | 214 | // The relocations for rodata tell us where potentially 215 | // interesting data might start. 216 | // 217 | // TODO: Should we parse other sections for rodata relocations? 218 | offsets := ParseRelocations(f, ".rela.rodata", ".rodata") 219 | offsets = append(offsets, int64(len(rodata))) 220 | 221 | sort.Slice(offsets, func (a, b int) bool { 222 | return offsets[a] < offsets[b] 223 | }) 224 | 225 | // We assume these offsets are tightly packed in rodata. So 226 | // look at sequential entries in the sorted list of offsets. 227 | p := &Processor{Destdir: destdir} 228 | for i, off := range offsets { 229 | var prev int64 230 | if i > 0 { 231 | prev = offsets[i - 1] 232 | } 233 | // Check that there's enough data between sequential offsets 234 | if off - prev < 32 { 235 | continue 236 | } 237 | 238 | // Attempt to decompress using basic flate algorithm 239 | // (underlying deflate/gzip) 240 | rodataReader := bytes.NewReader(rodata[prev:off]) 241 | c := flate.NewReader(rodataReader) 242 | data, err := ioutil.ReadAll(c) 243 | if err != nil { 244 | continue 245 | } 246 | 247 | p.Process(data) 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /extract_firmware.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2013 Ilia Mirkin. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a 6 | # copy of this software and associated documentation files (the "Software"), 7 | # to deal in the Software without restriction, including without limitation 8 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | # and/or sell copies of the Software, and to permit persons to whom the 10 | # Software is furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | # THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR 19 | # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 20 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 21 | # OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | from __future__ import print_function 24 | 25 | import itertools 26 | import mmap 27 | import os 28 | import re 29 | import struct 30 | import sys 31 | import tempfile 32 | import zlib 33 | 34 | # The firmware changes fairly rarely. From a limited sample, when the 35 | # firmware does change, the starts of the firmware remain the 36 | # same. When changing the version though, one should double-check the 37 | # sizes, which can be different. 38 | # 39 | # This is the list of tested versions that produce the same binaries 40 | VERSIONS = ( 41 | "319.17", 42 | "319.23", 43 | "319.32", 44 | "325.08", 45 | "325.15", 46 | "340.32", 47 | "340.108", 48 | ) 49 | 50 | ARCHES = ("x86_64", "x86") 51 | 52 | def product(a, b): 53 | for x in a: 54 | for y in b: 55 | yield (x, y) 56 | 57 | cwd = os.getcwd() 58 | for (VERSION, ARCH) in product(VERSIONS, ARCHES): 59 | if os.path.exists("NVIDIA-Linux-%s-%s" % (ARCH, VERSION)): 60 | break 61 | else: 62 | print("""Please run this in a directory where NVIDIA-Linux-x86-%(version)s is a subdir. 63 | 64 | You can make this happen by running 65 | wget http://us.download.nvidia.com/XFree86/Linux-x86/%(version)s/NVIDIA-Linux-x86-%(version)s.run 66 | sh NVIDIA-Linux-x86-%(version)s.run --extract-only 67 | 68 | Note: You can use other versions/arches, see the source for what is acceptable. 69 | """ % {"version": VERSIONS[-1]}) 70 | sys.exit(1) 71 | 72 | kernel_f = open("NVIDIA-Linux-%s-%s/kernel/nv-kernel.o" % (ARCH, VERSION), "rb") 73 | kernel = mmap.mmap(kernel_f.fileno(), 0, access=mmap.ACCESS_READ) 74 | 75 | user_f = open("NVIDIA-Linux-%s-%s/libnvcuvid.so.%s" % (ARCH, VERSION, VERSION), "rb") 76 | user = mmap.mmap(user_f.fileno(), 0, access=mmap.ACCESS_READ) 77 | 78 | vp2_kernel_prefix = b"\xcd\xab\x55\xee\x44" 79 | vp2_user_prefix = b"\xce\xab\x55\xee\x20\x00\x00\xd0\x00\x00\x00\xd0" 80 | vp4_kernel_prefix = b"\xf1\x97\x00\x42\xcf\x99" 81 | vp3_user_prefix = b"\x64\x00\xf0\x20\x64\x00\xf1\x20\x64\x00\xf2\x20" 82 | vp3_vc1_prefix = b"\x43\x00\x00\x34" * 2 83 | 84 | # List of chip revisions since the fuc loader expects nvXX_fucXXX files 85 | VP2_CHIPS = ["nv84"] # there are more, but no need for more symlinks 86 | VP3_CHIPS = ["nv98", "nvaa", "nvac"] 87 | VP4_0_CHIPS = ["nva3", "nva5", "nva8", "nvaf"] # nvaf is 4.1, but same fw 88 | VP4_2_CHIPS = ["nvc0", "nvc1", "nvc3", "nvc4", "nvc8", "nvce", "nvcf"] 89 | VP5_CHIPS = ["nvd7", "nvd9", "nve4", "nve6", "nve7", "nvf0", "nvf1", "nv106", "nv108"] 90 | 91 | def links(chips, tail): 92 | return list("%s_%s" % (chip, tail) for chip in chips) 93 | 94 | def vp3_offset(): 95 | # Note: 340 uses higher offset, 325 uses lower. Guessing 330 as the cutoff. 96 | if float(VERSION) < 330: 97 | return 2287 98 | return 2286 99 | 100 | def vp5_offset(): 101 | # Note: 340 uses higher offset, 325 uses lower. Guessing 330 as the cutoff. 102 | if float(VERSION) < 330: 103 | return 0xb3 104 | return 0xb7 105 | 106 | def at_offset_is(data, offset, bs): 107 | return data[offset:offset+1] == bs 108 | 109 | BLOBS = { 110 | # VP2 kernel xuc 111 | "nv84_bsp": { 112 | "data": kernel, 113 | "start": vp2_kernel_prefix + b"\x46", 114 | "length": 0x16f3c, 115 | "links": links(VP2_CHIPS, "xuc103"), 116 | }, 117 | "nv84_vp": { 118 | "data": kernel, 119 | "start": vp2_kernel_prefix + b"\x7c", 120 | "length": 0x1ae6c, 121 | "links": links(VP2_CHIPS, "xuc00f"), 122 | }, 123 | 124 | # VP3 kernel fuc 125 | "nv98_bsp": { 126 | "data": kernel, 127 | "start": b"\xf1\x07\x00\x10\xf1\x03\x00\x00", 128 | "length": 0xac00, 129 | "pred": lambda data, i: at_offset_is(data, i+vp3_offset(), b'\x8e'), 130 | "links": links(VP3_CHIPS, "fuc084"), 131 | }, 132 | "nv98_vp": { 133 | "data": kernel, 134 | "start": b"\xf1\x07\x00\x10\xf1\x03\x00\x00", 135 | "length": 0xa500, 136 | "pred": lambda data, i: at_offset_is(data, i+vp3_offset(), b'\x95'), 137 | "links": links(VP3_CHIPS, "fuc085"), 138 | }, 139 | "nv98_ppp": { 140 | "data": kernel, 141 | "start": b"\xf1\x07\x00\x08\xf1\x03\x00\x00", 142 | "length": 0x3800, 143 | "pred": lambda data, i: at_offset_is(data, i+vp3_offset(), b'\x30'), 144 | "links": links(VP3_CHIPS, "fuc086"), 145 | }, 146 | 147 | # VP4.0 kernel fuc 148 | "nva3_bsp": { 149 | "data": kernel, 150 | "start": vp4_kernel_prefix, 151 | "length": 0x10200, 152 | "pred": lambda data, i: at_offset_is(data, i+8*11+1, b'\xcf'), 153 | "links": links(VP4_0_CHIPS, "fuc084"), 154 | }, 155 | "nva3_vp": { 156 | "data": kernel, 157 | "start": vp4_kernel_prefix, 158 | "length": 0xc600, 159 | "pred": lambda data, i: at_offset_is(data, i+8*11+1, b'\x9e'), 160 | "links": links(VP4_0_CHIPS, "fuc085"), 161 | }, 162 | "nva3_ppp": { 163 | "data": kernel, 164 | "start": vp4_kernel_prefix, 165 | "length": 0x3f00, 166 | "pred": lambda data, i: at_offset_is(data, i+8*11+1, b'\x36'), 167 | "links": links(VP4_0_CHIPS, "fuc086"), 168 | }, 169 | 170 | # VP4.2 kernel fuc 171 | "nvc0_bsp": { 172 | "data": kernel, 173 | "start": vp4_kernel_prefix, 174 | "length": 0x10d00, 175 | "pred": lambda data, i: at_offset_is(data, i+0x59, b'\xd8'), 176 | "links": links(VP4_2_CHIPS, "fuc084"), 177 | }, 178 | "nvc0_vp": { 179 | "data": kernel, 180 | "start": vp4_kernel_prefix, 181 | "length": 0xd300, 182 | "pred": lambda data, i: at_offset_is(data, i+0x59, b'\xa5'), 183 | "links": links(VP4_2_CHIPS, "fuc085"), 184 | }, 185 | "nvc0_ppp": { 186 | "data": kernel, 187 | "start": vp4_kernel_prefix, 188 | "length": 0x4100, 189 | "pred": lambda data, i: at_offset_is(data, i+0x59, b'\x38'), 190 | "links": links(VP4_2_CHIPS, "fuc086") + links(VP5_CHIPS, "fuc086"), 191 | }, 192 | 193 | # VP5 kernel fuc 194 | "nve0_bsp": { 195 | "data": kernel, 196 | "start": vp4_kernel_prefix, 197 | "length": 0x11c00, 198 | "pred": lambda data, i: at_offset_is(data, i+vp5_offset(), b'\x27'), 199 | "links": links(VP5_CHIPS, "fuc084"), 200 | }, 201 | "nve0_vp": { 202 | "data": kernel, 203 | "start": vp4_kernel_prefix, 204 | "length": 0xdd00, 205 | "pred": lambda data, i: at_offset_is(data, i+vp5_offset(), b'\x0a'), 206 | "links": links(VP5_CHIPS, "fuc085"), 207 | }, 208 | 209 | # VP2 user xuc 210 | "nv84_bsp-h264": { 211 | "data": user, 212 | "start": vp2_user_prefix + b"\x88", 213 | "length": 0xd9d0, 214 | }, 215 | "nv84_vp-h264-1": { 216 | "data": user, 217 | "start": vp2_user_prefix + b"\x3c", 218 | "length": 0x1f334, 219 | }, 220 | "nv84_vp-h264-2": { 221 | "data": user, 222 | "start": vp2_user_prefix + b"\x04", 223 | "length": 0x1bffc, 224 | }, 225 | "nv84_vp-mpeg12": { 226 | "data": user, 227 | "start": vp2_user_prefix + b"\x4c", 228 | "length": 0x22084, 229 | }, 230 | "nv84_vp-vc1-1": { 231 | "data": user, 232 | "start": vp2_user_prefix + b"\x7c", 233 | "length": 0x2cd24, 234 | }, 235 | "nv84_vp-vc1-2": { 236 | "data": user, 237 | "start": vp2_user_prefix + b"\xa4", 238 | "length": 0x1535c, 239 | }, 240 | "nv84_vp-vc1-3": { 241 | "data": user, 242 | "start": vp2_user_prefix + b"\x34", 243 | "length": 0x133bc, 244 | }, 245 | 246 | # VP3 user vuc 247 | "vuc-vp3-mpeg12-0": { 248 | "data": user, 249 | "start": vp3_user_prefix, 250 | "length": 0xb00, 251 | "pred": lambda data, i: at_offset_is(data, i + 11 * 8, b'\x4a') and at_offset_is(data, i + 228, b'\x43'), 252 | }, 253 | "vuc-vp3-h264-0": { 254 | "data": user, 255 | "start": vp3_user_prefix, 256 | "length": 0x1600, 257 | "pred": lambda data, i: at_offset_is(data, i + 11 * 8 + 1, b'\xff') and at_offset_is(data, i + 225, b'\x81'), 258 | }, 259 | "vuc-vp3-vc1-0": { 260 | "data": user, 261 | "start": vp3_vc1_prefix + vp3_user_prefix, 262 | "length": 0x1d00, 263 | "pred": lambda data, i: at_offset_is(data, i + 11 * 8 + 1, b'\xf4'), 264 | }, 265 | "vuc-vp3-vc1-1": { 266 | "data": user, 267 | "start": vp3_vc1_prefix + vp3_user_prefix, 268 | "length": 0x2100, 269 | "pred": lambda data, i: at_offset_is(data, i + 11 * 8 + 1, b'\x34'), 270 | }, 271 | "vuc-vp3-vc1-2": { 272 | "data": user, 273 | "start": vp3_vc1_prefix + vp3_user_prefix, 274 | "length": 0x2300, 275 | "pred": lambda data, i: at_offset_is(data, i + 11 * 8 + 1, b'\x98'), 276 | }, 277 | 278 | # VP4.x user vuc 279 | "vuc-vp4-mpeg12-0": { 280 | "data": user, 281 | "start": vp3_user_prefix, 282 | "length": 0xc00, 283 | "pred": lambda data, i: at_offset_is(data, i + 11 * 8, b'\x4a') and at_offset_is(data, i + 228, b'\x44'), 284 | "links": ["vuc-mpeg12-0"], 285 | }, 286 | "vuc-vp4-h264-0": { 287 | "data": user, 288 | "start": vp3_user_prefix, 289 | "length": 0x1900, 290 | "pred": lambda data, i: at_offset_is(data, i + 11 * 8 + 1, b'\xff') and at_offset_is(data, i + 225, b'\x8c'), 291 | "links": ["vuc-h264-0"], 292 | }, 293 | "vuc-vp4-mpeg4-0": { 294 | "data": user, 295 | "start": vp3_user_prefix, 296 | "length": 0x1d00, 297 | "pred": lambda data, i: at_offset_is(data, i + 61, b'\x30') and at_offset_is(data, i + 6923, b'\x00'), 298 | "links": ["vuc-mpeg4-0"], 299 | }, 300 | "vuc-vp4-mpeg4-1": { 301 | "data": user, 302 | "start": vp3_user_prefix, 303 | "length": 0x1d00, 304 | "pred": lambda data, i: at_offset_is(data, i + 61, b'\x30') and at_offset_is(data, i + 6923, b'\x20'), 305 | "links": ["vuc-mpeg4-1"], 306 | }, 307 | "vuc-vp4-vc1-0": { 308 | "data": user, 309 | "start": vp3_vc1_prefix + vp3_user_prefix, 310 | "length": 0x1d00, 311 | "pred": lambda data, i: at_offset_is(data, i + 11 * 8 + 1, b'\xb4'), 312 | "links": ["vuc-vc1-0"], 313 | }, 314 | "vuc-vp4-vc1-1": { 315 | "data": user, 316 | "start": vp3_vc1_prefix + vp3_user_prefix, 317 | "length": 0x2100, 318 | "pred": lambda data, i: at_offset_is(data, i + 11 * 8 + 1, b'\x08'), 319 | "links": ["vuc-vc1-1"], 320 | }, 321 | "vuc-vp4-vc1-2": { 322 | "data": user, 323 | "start": vp3_vc1_prefix + vp3_user_prefix, 324 | "length": 0x2100, 325 | "pred": lambda data, i: at_offset_is(data, i + 11 * 8 + 1, b'\x6c'), 326 | "links": ["vuc-vc1-2"], 327 | }, 328 | } 329 | 330 | # Build a regex on the start data to speed things along. 331 | start_re = b"|".join(set(re.escape(v["start"]) for v in BLOBS.values())) 332 | files = set(v["data"] for v in BLOBS.values()) 333 | 334 | done = set() 335 | 336 | for data in files: 337 | for match in re.finditer(start_re, data): 338 | for name, v in BLOBS.items(): 339 | if name in done or data != v["data"] or match.group(0) != v["start"]: 340 | continue 341 | 342 | i = match.start(0) 343 | pred = v.get("pred") 344 | if pred and not pred(data, i): 345 | continue 346 | 347 | length = v["length"] 348 | links = v.get("links", []) 349 | 350 | with open(os.path.join(cwd, name), "wb") as f: 351 | f.write(data[i:i+length]) 352 | 353 | done.add(name) 354 | for link in links: 355 | try: 356 | os.unlink(link) 357 | except: 358 | pass 359 | os.symlink(name, link) 360 | 361 | for name in set(BLOBS) - done: 362 | print("Firmware %s not found, ignoring." % name) 363 | 364 | ARCHIVE_FILES = { 365 | 0: "fuc409d", 366 | 1: "fuc409c", 367 | 2: "fuc41ad", 368 | 3: "fuc41ac", 369 | } 370 | 371 | ARCHIVE_ORDERS = { 372 | "325.15": ["nvc0", "nvc8", "nvc3", "nvc4", "nvce", "nvcf", "nvc1", 373 | "nvd7", "nvd9", "nve4", "nve7", "nve6", "nvf0", "nvf1", 374 | "nv108"], 375 | } 376 | 377 | # Extract the gzipped archives found inside the kernel driver 378 | def decompress(prefix, start, s): 379 | try: 380 | decomp = zlib.decompressobj(-zlib.MAX_WBITS) 381 | data = decomp.decompress(s[10:]) 382 | except Exception as e: 383 | print(prefix, repr(s[:16]), len(s)) 384 | print(e) 385 | return False 386 | 387 | magic, count = struct.unpack("