Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added auto footer scan for GTX 10XX, GTX 16XX and GTX 400 - 900 Series | Proxmox Example #11

Open
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

Marvo2011
Copy link

Hello,

I added some lines to find the vBIOS footer for GTX 16XX and older cards.
I test it with a GTX 650 Ti and GTX 1650, please check the work for GTX 1050...

  • Simple Proxmox Example

@blackening
Copy link

blackening commented Jan 5, 2020

Your PR mixes tabs and spaces, will never run (edit: On python3).

@blackening
Copy link

blackening commented Jan 5, 2020

Here is a fixed version which uses the same footer regex strings as the above.

That being said, i have not tested it with any other graphics cards other than a 1660.

#!/usr/bin/env python

from __future__ import print_function

import sys
import binascii
import argparse
import re


# raw_input doesn't exist in Python 3
try:
    raw_input
except NameError:
    raw_input = input


PROMPT_TEXT = "I agree to be careful"

WARNING_TEXT = """
USE THIS SOFTWARE AT YOUR OWN DISCRETION. THIS SOFTWARE HAS *NOT* BEEN
EXTENSIVELY TESTED AND MAY NOT WORK WITH YOUR GRAPHICS CARD.

If you want to save the created vBIOS file, type the following phrase
EXACTLY as it is written below:

%s
""" % PROMPT_TEXT


class CheckException(Exception):
    pass


class VBIOSROM(object):
    def __init__(self, f):
        """
        Load a VBIOS and convert it into a hex-ascii format
        for easier editing
        """
        content = f.read()
        self.offsets = {
            "header": None,
            "footer": None
        }
        self.content = binascii.hexlify(content)

    def detect_offsets(self, disable_footer=False):
        """
        Search the ROM for known sections of data and raise an AssertionError
        if any of the checks fails
        """
        # Search for the header that starts the file
        # Examples of this header:   
        #
        # U.y.K7400.L.w.VIDEO
        # U.x.K7400.L.w.VIDEO
        #
        HEADER_REGEX = (
            b'55aa(([a-z]|[0-9]){2})(eb)(([a-z]|[0-9]){20})(564944454f)'
        )
        result = re.compile(HEADER_REGEX).search(self.content)

        if not result or len(result.groups()) != 6:
            raise CheckException("Couldn't find the ROM header!")

        self.offsets["header"] = result.start(0)

        FOOTER_DETECTORS = [
            (b'564e(([a-z]|[0-9]){476})(4e504453)(([a-z]|[0-9]){56})(4e504445)', 'GTX 16XX'),
            (b'564e(([a-z]|[0-9]){348})(4e504453)(([a-z]|[0-9]){56})(4e504445)', 'GTX 10XX'),
            (b'564e(([a-z]|[0-9]){124})(4e504453)(([a-z]|[0-9]){56})(4e504445)', 'GTX XXX (400 - 900 Series)')
        ]

        if not disable_footer:
            # Search for the footer, which are shortly followed by
            # 'NPDS' and 'NPDE' strings. 'NPDS' and 'NPDE' markers are separated by
            # 28 ASCII characters
            for FOOTER_REGEX, SERIES in FOOTER_DETECTORS: 
                result = re.compile(FOOTER_REGEX).search(self.content)
                if result and len(result.groups()) == 6:
                    self.offsets["footer"] = result.start(0)
                    print("ROM footer for " + SERIES + " found!")
                    return
                    
            raise CheckException("Couldn't find the ROM footer!")

    def run_sanity_tests(self, ignore_check=False):
        """
        Run a few sanity tests on the ROM to be a little more sure we are
        working with a valid ROM
        """
        try:
            # There should be one 'NPDS' marker and three 'NPDE' markers
            # before the footer we've already found
            #
            # The 'NPDS' marker should be followed by two 'NPDE' markers
            npds_count = self.content.count(
                b"4e504453", self.offsets["header"], self.offsets["footer"])
            if npds_count != 1:
                raise CheckException(
                    "Expected only one 'NPDS' marker between header and "
                    "footer, found %d" % npds_count)

            npde_count = self.content.count(
                b"4e504445", self.offsets["header"], self.offsets["footer"])
            if npde_count != 3:
                raise CheckException(
                    "Expected only three 'NPDE' markers between header and "
                    "footer, found %d, potential vBIOS without UEFI support!" % npde_count)

            npde_after_npds_count = self.content.count(
                b"4e504445", self.content.find(b"4e504453"),
                self.offsets["footer"])

            if npde_after_npds_count != 2:
                raise CheckException(
                    "Expected two 'NPDE' markers after the 'NPDS' marker")
        except CheckException as e:
            if ignore_check:
                print("Encountered error during sanity check: %s" % str(e))
                print("Ignoring...")
                return
            else:
                raise

        print("No problems found.")

    def get_spliced_rom(self, disable_footer=False):
        """
        Convert the internal hex-ascii representation of the ROM
        into binary data for saving
        """
        start = self.offsets["header"]
        if not disable_footer:
            end = self.offsets["footer"]
            spliced = self.content[start:end]
        else:
            spliced = self.content[start:]

        return binascii.unhexlify(spliced)


def main():
    parser = argparse.ArgumentParser(
        description=(
            "Convert a full NVIDIA vBIOS ROM into a form compatible "
            "for PCI passthrough."
        )
    )
    parser.add_argument(
        "-i", type=str, required=True,
        help="The full ROM to read")
    parser.add_argument(
        "-o", type=str, required=True,
        help="Path for saving the newly generated ROM")
    parser.add_argument(
        "--ignore-sanity-check", default=False, action="store_true",
        help="Don't halt the script if any of the sanity checks fails"
    )
    parser.add_argument(
        "--disable-footer-strip", default=False, action="store_true",
        help="Don't strip the footer from the vBIOS (Allows you to convert older gen GPUs)"
    )
    parser.add_argument(
        "--skip-the-very-important-warning",
        default=False, action="store_true",
        help=(
            "Skip the very important warning and save the ROM without asking "
            "for any input."
        )
    )

    args = parser.parse_args()

    print("Opening the ROM file...")

    with open(args.i, "rb") as f:
        rom = VBIOSROM(f)

    print("Scanning for ROM offsets...")
    rom.detect_offsets(args.disable_footer_strip)
    print("Offsets found!")

    if not args.disable_footer_strip:
        print("Running sanity checks...")
        rom.run_sanity_tests(args.ignore_sanity_check)

    spliced_rom = rom.get_spliced_rom(args.disable_footer_strip)

    if not args.skip_the_very_important_warning:
        print(WARNING_TEXT)
        print("Type here: ", end="")
        answer = raw_input()

        if answer != PROMPT_TEXT:
            print("Wrong answer, halting...")
            sys.exit(1)

    print("Writing the edited ROM...")

    with open(args.o, "wb") as f:
        f.write(spliced_rom)

    print("Done!")


if __name__ == "__main__":
    main()

@Marvo2011
Copy link
Author

Thank you, in python2 the code has worked. Soon I will try your code with some vBIOS from all card generations. When all is working I will merge it into the pull request.

@arcnmx
Copy link
Contributor

arcnmx commented Jan 29, 2020

blackening's version above works and looks good to me! The 16XX pattern also seems to work for RTX 20XX cards as well.

@Marvo2011
Copy link
Author

Marvo2011 commented Feb 13, 2020

@arcnmx Nice, I updated the code. I will try to input some RTX 20XX vbios files when it's working I add it to the description.

@Marvo2011 Marvo2011 mentioned this pull request Feb 13, 2020
@xux1217
Copy link

xux1217 commented Apr 13, 2020

so we just extract the data between "55aa 76eb"(include) and "564e 0000 0000 0000 0000 0000 0000 0000"(exclude) from gpu-z vbios?
I found in 2xxx series, the gpu-z vbios has double data in one rom, and we should extract just one?

woudt and others added 2 commits May 8, 2021 12:06
Tested with Quadro P400/P620
Add support for Quadro PXXX
@RecreationalGarbage
Copy link

I believe the script provided by blackening will not work the the GTX 900 series, I just did a 980Ti the other day and that ROM had 188 characters for the footer regex, not the 124 that the 5, 6, and 7XX series uses.

This is what I use for 900 series cards:
"564e(([a-z]|[0-9]){188})(4e504453)(([a-z]|[0-9]){56})(4e504445)"

Its also part of this dumb little GUI I made:
https://github.com/RecreationalGarbage/ROMPatcherGUI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants