From 27c684a04457098666befbd9d02dfdb68d2a4d23 Mon Sep 17 00:00:00 2001 From: Avery Black Date: Wed, 11 Aug 2021 19:21:11 -0700 Subject: [PATCH] =?UTF-8?q?Rust=20=F0=9F=A6=80=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * First rust code * Main menu * Deleted TS version, Finished first Rust version * Add CI * Fix CI * Upload artifacts --- .github/workflows/ci.yml | 45 +++ .gitignore | 6 +- Cargo.lock | 180 +++++++++++ Cargo.toml | 12 + README.md | 4 +- index.js | 652 --------------------------------------- index.ts | 642 -------------------------------------- package-lock.json | 57 ---- package.json | 19 -- src/main.rs | 438 ++++++++++++++++++++++++++ src/nvidia.rs | 276 +++++++++++++++++ src/util.rs | 100 ++++++ tsconfig.json | 69 ----- 13 files changed, 1057 insertions(+), 1443 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 Cargo.lock create mode 100644 Cargo.toml delete mode 100755 index.js delete mode 100755 index.ts delete mode 100755 package-lock.json delete mode 100755 package.json create mode 100644 src/main.rs create mode 100644 src/nvidia.rs create mode 100644 src/util.rs delete mode 100755 tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..515f1b9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: Build and Release + +on: + push: + pull_request: + workflow_dispatch: + release: + types: [published] + +jobs: + release: + name: Build and Release + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + artifact_name: nvcap_calculator + asset_name: nvcap_calculator_linux_amd64 + - os: macos-latest + artifact_name: nvcap_calculator + asset_name: nvcap_calculator_macos_amd64 + - os: windows-latest + artifact_name: nvcap_calculator.exe + asset_name: nvcap_calculator_windows_amd64 + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Build Project + run: cargo build --release --locked + - name: Upload binary to release + if: github.event_name == 'release' + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: target/release/${{ matrix.artifact_name }} + asset_name: ${{ matrix.asset_name }} + tag: ${{ github.ref }} + - name: Upload binary artifacts + uses: actions/upload-artifact@v2 + with: + name: ${{ matrix.asset_name }} + path: target/release/${{ matrix.artifact_name }} + + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 553cdbf..1e9ede7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ **/*.rom **/.DS_STORE -node_modules/ \ No newline at end of file +node_modules/ + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..97cb9bc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,180 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cc" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + +[[package]] +name = "ctrlc" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "377c9b002a72a0b2c1a18c62e2f3864bdfea4a015e3683a96e24aa45dd6c02d1" +dependencies = [ + "nix", + "winapi", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nix" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nvcap_calculator" +version = "2.0.0" +dependencies = [ + "chrono", + "colored", + "ctrlc", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..71e8766 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nvcap_calculator" +version = "2.0.0" +authors = ["Avery Black "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +colored = "2" +chrono = "0.4" +ctrlc = "3.2.0" \ No newline at end of file diff --git a/README.md b/README.md index e4c698d..a1fe32c 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,7 @@ This is a simple script which reads an Nvidia GPU VBIOS and calculates an NVCAP This is compatible with GTX 4xx series GPUs and older, and works with both laptop and desktop GPUs. -To use this program, you need node.js. To run, you'll want to clone this project, run `npm install`, then `npm run run` OR `node index.js`. Building is not required, but this can be built using typescript. `tsc` will automatically generate `index.js`, but `npm run build` will compile and run in one command if preferred. - -Once running, give it a VBIOS file, and then select `3` once it dumps you to the main menu again. From there, you need to assign each Display (at the top) to a head. +Once running, give it a VBIOS file, and then select `2` once it dumps you to the main menu again. From there, you need to assign each Display (at the top) to a head. When parsing the VBIOS, the script automatically merges DCB entries with the same type and bus index and presents them as a single Display. If the bus index is the same, but are differing types, this will be presented as a single DVI Display. diff --git a/index.js b/index.js deleted file mode 100755 index 7ef694e..0000000 --- a/index.js +++ /dev/null @@ -1,652 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -var chalk_1 = __importDefault(require("chalk")); -var fs_1 = require("fs"); -// Based off of https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/nouveau/nouveau_bios.c -var romFile = "9600MGT.rom"; -function prompt(question) { - return __awaiter(this, void 0, void 0, function () { - var stdin, stdout; - return __generator(this, function (_a) { - stdin = process.stdin; - stdout = process.stdout; - stdin.resume(); - stdout.write(question.concat(": ")); - return [2 /*return*/, new Promise(function (res, rej) { - stdin.once('data', function (data) { - stdin.pause(); - res(data.toString()); - }); - })]; - }); - }); -} -var ConnectorType; -(function (ConnectorType) { - ConnectorType[ConnectorType["CRT"] = 0] = "CRT"; - ConnectorType[ConnectorType["TV"] = 1] = "TV"; - ConnectorType[ConnectorType["TMDS"] = 2] = "TMDS"; - ConnectorType[ConnectorType["LVDS"] = 3] = "LVDS"; - ConnectorType[ConnectorType["Reserved"] = 4] = "Reserved"; - ConnectorType[ConnectorType["SDI"] = 5] = "SDI"; - ConnectorType[ConnectorType["DisplayPort"] = 6] = "DisplayPort"; - ConnectorType[ConnectorType["EOL"] = 7] = "EOL"; - ConnectorType[ConnectorType["SkipEntry"] = 8] = "SkipEntry"; -})(ConnectorType || (ConnectorType = {})); -var DisplayType; -(function (DisplayType) { - DisplayType[DisplayType["LVDS"] = 0] = "LVDS"; - DisplayType[DisplayType["TV"] = 1] = "TV"; - DisplayType[DisplayType["Analog"] = 2] = "Analog"; - DisplayType[DisplayType["Digital"] = 3] = "Digital"; - DisplayType[DisplayType["DVI"] = 4] = "DVI"; -})(DisplayType || (DisplayType = {})); -function parseSignature(version, rom, offset) { - if (version >= 0x42) { - console.log("Unknown version"); - return false; - } - else if (version >= 0x30) { - if (rom.readUInt32LE(offset + 6) != 0x4edcbdcb) { - console.error("Corrupt VBIOS"); - return false; - } - } - else { - console.error("To old version - GPU incompatible with macOS"); - return false; - } - return true; -} -var nvcap = { - version: 5, - isMobile: false, - composite: false, - tvDCBMask: 0, - head0DCBMask: 0, - head1DCBMask: 0, - head2DCBMask: 0, - head3DCBMask: 0, - scriptBasedPowerAndBacklight: false, - /* - 07: Clover's default - 0A: Desktop-class GPU (Chameleon default) - 0B: Laptop-class GPU - 0E: 300 series+ MacBook Air/Low end - 0F: 300 series+ MacBook Pro/iMac/High End - */ - fieldF: 0x0F, -}; -// DCB entries -var parsedEntries = []; -// Displays (display can represent multiple DCB entries) -var filteredEntries = []; -var headTV = []; -var head0 = []; -var head1 = []; -function readRom() { - parsedEntries = []; - filteredEntries = []; - headTV = []; - head0 = []; - head1 = []; - if (!fs_1.existsSync(romFile)) { - console.error("Rom file not found"); - return false; - } - var rom = fs_1.readFileSync(romFile); - console.log("Read ROM file " + romFile + ", which is " + rom.byteLength + " bytes long"); - var dcbHeader = rom.readUInt16LE(0x36); - var version = rom.readUInt8(dcbHeader); - if (version == 0) { - console.error("Version is zero"); - return false; - } - var versionMajor = version >> 4; - var versionMinor = version & 0xf; - // DCB 3.0 and 4.0 are very similar, just treat them the same - var size = rom.readUInt8(dcbHeader + 1); - console.log("DCB Header is at 0x" + dcbHeader.toString(16) + " with length 0x" + size.toString(16)); - console.log("DCB Version " + versionMajor + "." + versionMinor); - if (!parseSignature(version, rom, dcbHeader)) { - console.error("Invalid DCB Signature"); - return false; - } - var dcbEntries = rom.readUInt8(dcbHeader + 2); - var dcbEntrySize = rom.readUInt8(dcbHeader + 3); - console.log(dcbEntries + " DCB entries of size " + dcbEntrySize.toString(16) + " bytes\n"); - for (var i = 0; i < dcbEntries; i++) { - var offset = dcbHeader + size + (dcbEntrySize * i); - var conn = rom.readUInt32LE(offset); - var dcbHead = { - type: conn & 0xf, - edidPort: (conn >> 4) & 0xf, - headBitmask: (conn >> 8) & 0xf, - con: (conn >> 12) & 0xf, - bus: (conn >> 16) & 0xf, - loc: (conn >> 20) & 0x3, - bdr: !!((conn >> 22) & 0x1), - bbdr: !!((conn >> 23) & 0x1), - outputResources: (conn >> 24) & 0xf, - virtual: !!((conn >> 28) & 0x1), - reserved: (conn >> 29) & 0x7, - entry: i, - merged: false - }; - // Skip entry - if (dcbHead.type == 0xf) - continue; - if (dcbHead.type == ConnectorType.LVDS) - nvcap.isMobile = true; - // EOL (End of Line) - start parsing entries - if (dcbHead.type == 0xE) - break; - parsedEntries.push(dcbHead); - } - console.log(); - console.log("Found " + parsedEntries.length + " populated DCB Entries"); - // Merge displays with the same connector ID - parsedEntries.forEach(function (entry, index, parsedEntries) { - if (entry.merged) - return; - /* - * https://nvidia.github.io/open-gpu-doc/DCB/DCB-4.x-Specification.html#_dcb_device_entries - * Use Bus id, not connector index, to merge devices together - */ - var similarDCBs = parsedEntries.filter(function (value, filterIndex) { return (entry.bus == value.bus && filterIndex != index); }); - if (similarDCBs.length != 1) { - var type = DisplayType.Digital; - switch (entry.type) { - case ConnectorType.LVDS: - type = DisplayType.LVDS; - break; - case ConnectorType.CRT: - type = DisplayType.Analog; - break; - case ConnectorType.TV: - type = DisplayType.TV; - break; - } - filteredEntries.push({ - type: type, - dcbEntries: [index], - headBitmask: entry.headBitmask - }); - } - else { - var mergingEntry = similarDCBs[0]; - mergingEntry.merged = true; - var type = DisplayType.DVI; - if (entry.type == mergingEntry.type) { - type = DisplayType.Digital; - switch (entry.type) { - case ConnectorType.LVDS: - type = DisplayType.LVDS; - break; - case ConnectorType.CRT: - type = DisplayType.Analog; - break; - case ConnectorType.TV: - type = DisplayType.TV; - break; - } - } - filteredEntries.push({ - type: type, - dcbEntries: [index, mergingEntry.entry], - headBitmask: entry.headBitmask & mergingEntry.headBitmask - }); - } - }); - var lvdsExists = false; - // Help the user by doing some preliminary placement of displays. - // Important thing is that TV actually goes on TV and that LVDS gets it's own head! - filteredEntries.forEach(function (display, index) { - if (display.type == DisplayType.TV) { - headTV.push(index); - return; - } - // If there is an LVDS display, shove everything else on the other head - if (lvdsExists) { - if (display.headBitmask & 0x2) { - head1.push(index); - } - } - // Assign LVDS to it's own head - if (display.type == DisplayType.LVDS) { - if (display.headBitmask & 0x1) { - head0.push(index); - lvdsExists = true; - } - } - }); -} -function header() { - console.log(chalk_1.default.green("+" + new Array(26).fill("-").join("") + "+")); - console.log(chalk_1.default.green("|") + chalk_1.default.cyan(" NVCAP Calculator ") + chalk_1.default.green("|")); - console.log(chalk_1.default.green("+" + new Array(26).fill("-").join("") + "+")); - console.log(); //new line -} -function dumpDCBEntries() { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - parsedEntries.forEach(function (dcbHead, i) { - console.log(chalk_1.default.blueBright("DCB Entry " + i.toString(16))); - var output = ""; - output += chalk_1.default.green("Type: ") + ConnectorType[dcbHead.type] + " "; - output += chalk_1.default.green("EdidPort: ") + dcbHead.edidPort + " "; - output += chalk_1.default.green("Head: ") + dcbHead.headBitmask + " "; - output += chalk_1.default.green("Connector: ") + dcbHead.con + " "; - output += chalk_1.default.green("Bus: ") + dcbHead.bus + " "; - output += chalk_1.default.green("Loc: ") + dcbHead.loc + " "; - output += "\n"; - output += chalk_1.default.green("BDR: ") + dcbHead.bdr + " "; - output += chalk_1.default.green("BBDR: ") + dcbHead.bbdr + " "; - output += chalk_1.default.green("Resources: ") + dcbHead.outputResources + " "; - output += chalk_1.default.green("Virtual: ") + dcbHead.virtual + " "; - console.log(output); - }); - return [4 /*yield*/, prompt("\nPress enter to continue")]; - case 1: - _a.sent(); - return [2 /*return*/]; - } - }); - }); -} -function chooseROM() { - return __awaiter(this, void 0, void 0, function () { - var res; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (!true) return [3 /*break*/, 5]; - console.clear(); - header(); - console.log("Enter in the location of your VBIOS\n"); - console.log(chalk_1.default.cyan("Windows Tip: ") + " Shift + Right click your VBIOS and click \"Copy Path\""); - console.log(chalk_1.default.cyan("Linooox/macOS: ") + " Drag and drop your VBIOS into this prompt\n"); - return [4 /*yield*/, prompt("New ROM Location (q to go to the menu)")]; - case 1: - res = _a.sent(); - res = res.replace(/[\n\r"]/g, "").trim(); - console.log("Parsed Path: " + res); - if (res == "q") - return [2 /*return*/]; - if (!fs_1.existsSync(res)) return [3 /*break*/, 2]; - romFile = res; - return [3 /*break*/, 5]; - case 2: return [4 /*yield*/, prompt("Unable to find ROM! Press enter to continue")]; - case 3: - _a.sent(); - _a.label = 4; - case 4: return [3 /*break*/, 0]; - case 5: - readRom(); - return [4 /*yield*/, prompt("\nPress enter to continue")]; - case 6: - _a.sent(); - return [2 /*return*/]; - } - }); - }); -} -function listDisplaysAndNvcap() { - console.log("Displays"); - filteredEntries.forEach(function (display, index) { - var output = chalk_1.default.blueBright("(" + (index + 1) + ")"); - output += chalk_1.default.green(" Type: "); - output += DisplayType[display.type]; - output += chalk_1.default.green("\tSupported Heads: "); - // Max of 4 heads - for (var i = 0; i < 4; i++) { - if (display.headBitmask & (1 << i)) { - output += i + 1 + ","; - } - } - // trim last comma - output = output.substring(0, output.length - 1); - if (display.type == DisplayType.TV) { - output += ",TV"; - } - console.log(output); - }); - console.log(); - console.log("Heads"); - var headTVOut = chalk_1.default.blueBright("(TV) ") + "["; - var head0Out = chalk_1.default.blueBright("(1) ") + "["; - var head1Out = chalk_1.default.blueBright("(2) ") + "["; - headTV.forEach(function (dcbEntry) { return headTVOut += (dcbEntry + 1) + ","; }); - head0.forEach(function (dcbEntry) { return head0Out += (dcbEntry + 1) + ","; }); - head1.forEach(function (dcbEntry) { return head1Out += (dcbEntry + 1) + ","; }); - // trim last comma - if (headTV.length > 0) - headTVOut = headTVOut.substring(0, headTVOut.length - 1); - if (head0.length > 0) - head0Out = head0Out.substring(0, head0Out.length - 1); - if (head1.length > 0) - head1Out = head1Out.substring(0, head1Out.length - 1); - headTVOut += "]"; - head0Out += "]"; - head1Out += "]"; - console.log(headTVOut); - console.log(head0Out); - console.log(head1Out); - console.log(); - console.log("NVCAP:"); - console.log(chalk_1.default.blueBright("(n1)") + chalk_1.default.green(" Version: ") + nvcap.version); - console.log(chalk_1.default.blueBright("(n2)") + chalk_1.default.green(" Composite: ") + nvcap.composite); - console.log(chalk_1.default.blueBright("(n3)") + chalk_1.default.green(" Script Based Power/Backlight: ") + nvcap.scriptBasedPowerAndBacklight); - console.log(chalk_1.default.blueBright("(n4)") + chalk_1.default.green(" Field F: ") + "0x" + nvcap.fieldF.toString(16)); - console.log(); - console.log("To add/remove a display to/from a head, type (ex: \"1 1\")"); - console.log("To change an NVCAP value, do n (ex: \"n2 true\")"); - console.log("To calculate the NVCAP value, type c/create"); -} -function drawNVCap() { - return __awaiter(this, void 0, void 0, function () { - var result, splitArr, arr, display, head, command, _a, newValue, bool; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - if (!true) return [3 /*break*/, 22]; - console.clear(); - header(); - listDisplaysAndNvcap(); - return [4 /*yield*/, prompt("To return to the previous menu, use q/quit")]; - case 1: - result = _b.sent(); - console.log(); - if (result.length == 0) - return [3 /*break*/, 0]; - if (result.toLowerCase().startsWith("q")) - return [2 /*return*/]; - if (!result.toLowerCase().startsWith("c")) return [3 /*break*/, 3]; - console.log(); - createNVCap(); - return [4 /*yield*/, prompt("Press enter to continue (or q/quit to exit)")]; - case 2: - if ((_b.sent()).toLowerCase().startsWith("q")) { - showGoodbye(); - process.exit(0); - } - return [3 /*break*/, 0]; - case 3: - splitArr = result.split(" "); - if (!(splitArr.length == 2 && parseInt(splitArr[0]) && - (parseInt(splitArr[1]) || splitArr[1].toLowerCase().startsWith("tv")))) return [3 /*break*/, 12]; - arr = null; - ; - display = parseInt(splitArr[0]); - if (!(display < 1 || display > filteredEntries.length)) return [3 /*break*/, 5]; - return [4 /*yield*/, prompt("Unknown display - press enter to continue")]; - case 4: - _b.sent(); - return [3 /*break*/, 0]; - case 5: - display--; - if (!splitArr[1].toLowerCase().startsWith("tv")) return [3 /*break*/, 8]; - if (!(filteredEntries[display].type != DisplayType.TV)) return [3 /*break*/, 7]; - return [4 /*yield*/, prompt("Only a display of type TV can be put in the TV Mask - Pres enter to continue")]; - case 6: - _b.sent(); - return [3 /*break*/, 0]; - case 7: - arr = headTV; - return [3 /*break*/, 9]; - case 8: - head = parseInt(splitArr[1]); - if (head == 1) { - arr = head0; - } - else if (head == 2) { - arr = head1; - } - _b.label = 9; - case 9: - if (!(arr == null)) return [3 /*break*/, 11]; - return [4 /*yield*/, prompt("Unknown head - press enter to continue")]; - case 10: - _b.sent(); - return [3 /*break*/, 0]; - case 11: - if (arr.includes(display)) { - arr.splice(arr.indexOf(display), 1); - } - else { - arr.push(display); - } - return [3 /*break*/, 0]; - case 12: - splitArr[0] = splitArr[0].replace("n", ""); - if (!parseInt(splitArr[0])) return [3 /*break*/, 20]; - command = parseInt(splitArr[0]) - 1; - _a = command; - switch (_a) { - case 0: return [3 /*break*/, 13]; - case 3: return [3 /*break*/, 13]; - case 1: return [3 /*break*/, 16]; - case 2: return [3 /*break*/, 16]; - } - return [3 /*break*/, 20]; - case 13: - newValue = parseInt(splitArr[1]); - if (!(newValue < 0 || newValue > 0xf)) return [3 /*break*/, 15]; - return [4 /*yield*/, prompt("New value is out of bounds! Must be between 0 and 0xf - press enter to continue")]; - case 14: - _b.sent(); - return [3 /*break*/, 0]; - case 15: - if (command == 0) - nvcap.version = newValue; - if (command == 3) - nvcap.fieldF = newValue; - return [3 /*break*/, 20]; - case 16: - if (splitArr.length < 2) { - if (command == 1) { - nvcap.composite = !nvcap.composite; - } - else { - nvcap.scriptBasedPowerAndBacklight = !nvcap.scriptBasedPowerAndBacklight; - } - return [3 /*break*/, 0]; - } - splitArr[1] = splitArr[1].replace("\n", "").toLowerCase(); - bool = false; - if (!splitArr[1].startsWith("t")) return [3 /*break*/, 17]; - bool = true; - return [3 /*break*/, 19]; - case 17: - if (!!splitArr[1].startsWith("f")) return [3 /*break*/, 19]; - return [4 /*yield*/, prompt("Unrecognized new value! Must be true or false - press enter to continue")]; - case 18: - _b.sent(); - return [3 /*break*/, 0]; - case 19: - if (command == 1) { - nvcap.composite = bool; - } - else { - nvcap.scriptBasedPowerAndBacklight = bool; - } - return [3 /*break*/, 20]; - case 20: return [4 /*yield*/, prompt("Unknown command - press enter to continue")]; - case 21: - _b.sent(); - return [3 /*break*/, 0]; - case 22: return [2 /*return*/]; - } - }); - }); -} -function createHeadMask(displays) { - var mask = 0; - displays.forEach(function (displayIndex) { - var dcbEntries = filteredEntries[displayIndex].dcbEntries; - dcbEntries.forEach(function (dcbIndex) { - mask |= (1 << dcbIndex); - }); - }); - return mask; -} -function createNVCap() { - nvcap.tvDCBMask = createHeadMask(headTV); - nvcap.head0DCBMask = createHeadMask(head0); - nvcap.head1DCBMask = createHeadMask(head1); - console.log("TV Mask: 0x" + nvcap.tvDCBMask.toString(16)); - console.log("Head 1 Mask: 0x" + nvcap.head0DCBMask.toString(16)); - console.log("Head 2 Mask: 0x" + nvcap.head1DCBMask.toString(16)); - var buffer = Buffer.alloc(20); - buffer.writeInt8(nvcap.version, 0); - buffer.writeInt8(nvcap.isMobile ? 1 : 0, 1); - buffer.writeInt8(nvcap.composite ? 1 : 0, 2); - // Unknown field - backlight related? - buffer.writeInt8(0, 3); - buffer.writeInt16LE(nvcap.tvDCBMask, 4); - buffer.writeInt16LE(nvcap.head0DCBMask, 6); - buffer.writeInt16LE(nvcap.head1DCBMask, 8); - buffer.writeInt16LE(nvcap.head2DCBMask, 10); - buffer.writeInt16LE(nvcap.head3DCBMask, 12); - buffer.writeInt8(nvcap.scriptBasedPowerAndBacklight ? 1 : 0, 14); - buffer.writeInt8(nvcap.fieldF, 15); - // Unknwon field - 10bit/EDID_Manufacturer_Reserved_timings support? - buffer.writeInt8(0, 16); - buffer.writeInt8(0, 17); - buffer.writeInt8(0, 18); - buffer.writeInt8(0, 19); - var output = "NVCAP: "; - // Pad to always be 8 digits long - for (var i = 0; i < 5; i++) { - var number = buffer.readInt32BE(i * 4); - output += ("00000000" + number.toString(16)).slice(-8); - output += " "; - } - console.log(output); -} -function showGoodbye() { - console.clear(); - header(); - console.log("By 1Revenger1\n"); - console.log("Thanks for using this program - if you have any issues,"); - console.log("visit github.com/1Revenger1/NVCAPCalculator"); - console.log("For more projects, visit github.com/1Revenger1\n"); - var hour = new Date().getHours(); - if (hour > 3 && hour < 12) { - console.log("Have a nice morning!\n"); - } - else if (hour >= 12 && hour < 17) { - console.log("Have a nice afternoon!\n"); - } - else if (hour >= 17 && hour < 21) { - console.log("Have a nice evening!\n"); - } - else { - console.log("Have a nice night!\n"); - } -} -function main() { - return __awaiter(this, void 0, void 0, function () { - var romExists, output, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (!!fs_1.existsSync(romFile)) return [3 /*break*/, 2]; - return [4 /*yield*/, chooseROM()]; - case 1: - _a.sent(); - _a.label = 2; - case 2: - readRom(); - _a.label = 3; - case 3: - if (!true) return [3 /*break*/, 11]; - romExists = fs_1.existsSync(romFile); - console.clear(); - header(); - output = chalk_1.default.cyan("(1) ") + "Choose VBIOS/ROM file\n"; - if (romExists) { - output += chalk_1.default.cyan("(2) ") + "Show DCB Entries\n"; - output += chalk_1.default.cyan("(3) ") + "Calculate NVCAP\n"; - } - output += "\n"; - if (romExists) { - output += "Current ROM file: " + chalk_1.default.green(romFile); - } - else { - output += "Current ROM file (not found): " + chalk_1.default.red(romFile); - } - console.log(output); - return [4 /*yield*/, prompt("Type in the number to select your option, or \"q\"/\"quit\" to quit")]; - case 4: - result = _a.sent(); - if (result.toLowerCase().startsWith("q")) - return [3 /*break*/, 11]; - if (!result.toLowerCase().startsWith("1")) return [3 /*break*/, 6]; - return [4 /*yield*/, chooseROM()]; - case 5: - _a.sent(); - _a.label = 6; - case 6: - if (!romExists) return [3 /*break*/, 10]; - if (!result.toLowerCase().startsWith("2")) return [3 /*break*/, 8]; - return [4 /*yield*/, dumpDCBEntries()]; - case 7: - _a.sent(); - _a.label = 8; - case 8: - if (!result.toLowerCase().startsWith("3")) return [3 /*break*/, 10]; - return [4 /*yield*/, drawNVCap()]; - case 9: - _a.sent(); - _a.label = 10; - case 10: return [3 /*break*/, 3]; - case 11: - showGoodbye(); - return [2 /*return*/]; - } - }); - }); -} -main(); diff --git a/index.ts b/index.ts deleted file mode 100755 index c4a6d3b..0000000 --- a/index.ts +++ /dev/null @@ -1,642 +0,0 @@ -import chalk from "chalk"; -import {existsSync, readFileSync} from "fs"; - -// Based off of https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/nouveau/nouveau_bios.c -let romFile = "9600MGT.rom"; - -// Write question string, and then wait for a user response (ie when they press enter) -async function prompt (question : string) : Promise { - let stdin = process.stdin; - let stdout = process.stdout; - - stdin.resume(); - stdout.write(question.concat(": ")); - - return new Promise ((res, rej) => { - stdin.once('data', data => { - stdin.pause(); - res(data.toString()); - }); - }); -} - -interface NVCAP { - version: number; - isMobile: boolean; - composite: boolean, - tvDCBMask: number, - head0DCBMask: number, - head1DCBMask: number, - // Only for GK107 and older (which this script doesn't support) - head2DCBMask: number, - head3DCBMask: number, - scriptBasedPowerAndBacklight: boolean, - /* - 07: Clover's default - 0A: Desktop-class GPU (Chameleon default) - 0B: Laptop-class GPU - 0E: 300 series+ MacBook Air/Low end - 0F: 300 series+ MacBook Pro/iMac/High End - */ - fieldF: number -} - -// DCB connector type -enum ConnectorType { - CRT = 0, - TV, - TMDS, - LVDS, - Reserved, - SDI, - DisplayPort, - EOL, - SkipEntry -} - -interface DCBEntry { - type: ConnectorType, - edidPort: number, - headBitmask: number, - con: number, - bus: number, - loc: number, - bdr: boolean, - bbdr: boolean, - outputResources: number, - virtual: boolean, - reserved: number, - entry: number, - merged: boolean, -} - -// Parsed display type -enum DisplayType { - LVDS = 0, - TV, - Analog, - Digital, - DVI -} - -// A display is the parsed version of a DCB entry -// A display can represent 1 or more DCB entries (ie DVI is generally 2 entries) -interface Display { - type: DisplayType, - dcbEntries: number[], - headBitmask: number -} - -function parseSignature (version: number, rom: Buffer, offset: number) : boolean { - if (version >= 0x42) { - console.log("Unknown version"); - return false; - } else if (version >= 0x30) { - if (rom.readUInt32LE(offset + 6) != 0x4edcbdcb) { - console.error("Corrupt VBIOS"); - return false; - } - } else { - console.error("To old version - GPU incompatible with macOS"); - return false; - } - - return true; -} - -let nvcap: NVCAP = { - version: 5, - isMobile: false, - composite: false, - tvDCBMask: 0, - head0DCBMask: 0, - head1DCBMask: 0, - head2DCBMask: 0, - head3DCBMask: 0, - scriptBasedPowerAndBacklight: false, - /* - 07: Clover's default - 0A: Desktop-class GPU (Chameleon default) - 0B: Laptop-class GPU - 0E: 300 series+ MacBook Air/Low end - 0F: 300 series+ MacBook Pro/iMac/High End - */ - fieldF: 0x0F, -}; - -// DCB entries -let parsedEntries: DCBEntry[] = []; -// Displays (display can represent multiple DCB entries) -let filteredEntries: Display[] = []; -let headTV: number[] = []; -let head0: number[] = []; -let head1: number[] = []; - - -function readRom() { - parsedEntries = []; - filteredEntries = []; - headTV = []; - head0 = []; - head1 = []; - - if (!existsSync(romFile)) { - console.error("Rom file not found"); - return false; - } - - let rom: Buffer = readFileSync(romFile); - console.log(`Read ROM file ${romFile}, which is ${rom.byteLength} bytes long`); - - const dcbHeader = rom.readUInt16LE(0x36); - const version = rom.readUInt8(dcbHeader); - if (version == 0) { - console.error("Version is zero"); - return false; - } - - const versionMajor = version >> 4; - const versionMinor = version & 0xf; - - // DCB 3.0 and 4.0 are very similar, just treat them the same - const size = rom.readUInt8(dcbHeader + 1); - console.log(`DCB Header is at 0x${dcbHeader.toString(16)} with length 0x${size.toString(16)}`); - console.log(`DCB Version ${versionMajor}.${versionMinor}`); - - if (!parseSignature(version, rom, dcbHeader)) { - console.error("Invalid DCB Signature"); - return false; - } - - const dcbEntries = rom.readUInt8(dcbHeader + 2); - const dcbEntrySize = rom.readUInt8(dcbHeader + 3); - - console.log(`${dcbEntries} DCB entries of size ${dcbEntrySize.toString(16)} bytes\n`); - - for (let i = 0; i < dcbEntries; i++) { - let offset = dcbHeader + size + (dcbEntrySize * i); - let conn = rom.readUInt32LE(offset); - - const dcbHead: DCBEntry = { - type: conn & 0xf, - edidPort: (conn >> 4) & 0xf, - headBitmask: (conn >> 8) & 0xf, - con: (conn >> 12) & 0xf, - bus: (conn >> 16) & 0xf, - loc: (conn >> 20) & 0x3, - bdr: !!((conn >> 22) & 0x1), - bbdr: !!((conn >> 23) & 0x1), - outputResources: (conn >> 24) & 0xf, - virtual: !!((conn >> 28) & 0x1), - reserved: (conn >> 29) & 0x7, - entry: i, - merged: false - } - - // Skip entry - if (dcbHead.type == 0xf) - continue; - - if (dcbHead.type == ConnectorType.LVDS) - nvcap.isMobile = true; - - // EOL (End of Line) - stop parsing entries - if (dcbHead.type == 0xE) - break; - - parsedEntries.push(dcbHead); - } - - console.log(); - console.log(`Found ${parsedEntries.length} populated DCB Entries`); - - // Merge displays with the same connector ID - - parsedEntries.forEach((entry: DCBEntry, index, parsedEntries: DCBEntry[]) => { - if (entry.merged) - return; - - /* - * https://nvidia.github.io/open-gpu-doc/DCB/DCB-4.x-Specification.html#_dcb_device_entries - * Use Bus id, not connector index, to merge devices together - */ - let similarDCBs = parsedEntries.filter((value: DCBEntry, filterIndex: number) => (entry.bus == value.bus && filterIndex != index)); - if (similarDCBs.length != 1) { - let type = DisplayType.Digital; - switch (entry.type) { - case ConnectorType.LVDS: - type = DisplayType.LVDS; - break; - case ConnectorType.CRT: - type = DisplayType.Analog; - break; - case ConnectorType.TV: - type = DisplayType.TV; - break; - } - - filteredEntries.push({ - type: type, - dcbEntries: [index], - headBitmask: entry.headBitmask - }); - } else { - let mergingEntry = similarDCBs[0]; - mergingEntry.merged = true; - - let type = DisplayType.DVI; - if (entry.type == mergingEntry.type) { - type = DisplayType.Digital; - switch (entry.type) { - case ConnectorType.LVDS: - type = DisplayType.LVDS; - break; - case ConnectorType.CRT: - type = DisplayType.Analog; - break; - case ConnectorType.TV: - type = DisplayType.TV; - break; - } - } - - filteredEntries.push({ - type: type, - dcbEntries: [index, mergingEntry.entry], - headBitmask: entry.headBitmask & mergingEntry.headBitmask - }) - } - }); - - let lvdsExists = false; - - // Help the user by doing some preliminary placement of displays. - // Important thing is that TV goes to the TV head and that LVDS gets it's own head! - filteredEntries.forEach((display: Display, index: number) => { - if (display.type == DisplayType.TV) { - headTV.push(index); - return; - } - - // If there is an LVDS display, put any other displays - if (lvdsExists) { - if (display.headBitmask & 0x2) { - head1.push(index); - } - } - - // Assign LVDS to it's own head - if (display.type == DisplayType.LVDS) { - if (display.headBitmask & 0x1) { - head0.push(index); - lvdsExists = true; - } - } - }); -} - -function header() { - console.log(chalk.green(`+${new Array(26).fill("-").join("")}+`)); - console.log(chalk.green("|") + chalk.cyan(" NVCAP Calculator ") + chalk.green("|")); - console.log(chalk.green(`+${new Array(26).fill("-").join("")}+`)); - console.log(); //new line -} - -async function dumpDCBEntries() { - parsedEntries.forEach((dcbHead: DCBEntry, i: number) => { - console.log(chalk.blueBright(`DCB Entry ${i.toString(16)}`)); - let output = ""; - output += chalk.green("Type: ") + ConnectorType[dcbHead.type] + " "; - output += chalk.green("EdidPort: ") + dcbHead.edidPort + " "; - output += chalk.green("Head: ") + dcbHead.headBitmask + " "; - output += chalk.green("Connector: ") + dcbHead.con + " "; - output += chalk.green("Bus: ") + dcbHead.bus + " "; - output += chalk.green("Loc: ") + dcbHead.loc + " "; - - output += "\n"; - - output += chalk.green("BDR: ") + dcbHead.bdr + " "; - output += chalk.green("BBDR: ") + dcbHead.bbdr + " "; - output += chalk.green("Resources: ") + dcbHead.outputResources + " "; - output += chalk.green("Virtual: ") + dcbHead.virtual + " "; - - console.log(output); - }); - - await prompt("\nPress enter to continue"); -} - -async function chooseROM() { - while (true) { - console.clear(); - header(); - - console.log("Enter in the location of your VBIOS\n"); - console.log(chalk.cyan("Windows Tip: ") + " Shift + Right click your VBIOS and click \"Copy Path\""); - console.log(chalk.cyan("Linooox/macOS: ") + " Drag and drop your VBIOS into this prompt\n"); - - let res = await prompt("New ROM Location (q to go to the menu)"); - res = res.replace(/[\n\r"]/g, "").trim(); - console.log(`Parsed Path: ${res}`); - if (res == "q") return; - if (existsSync(res)) { - romFile = res; - break; - } else { - await prompt("Unable to find ROM! Press enter to continue"); - } - } - readRom(); - await prompt("\nPress enter to continue"); -} - -function listDisplaysAndNvcap() { - console.log("Displays"); - filteredEntries.forEach((display: Display, index: number) => { - let output = chalk.blueBright(`(${index + 1})`); - output += chalk.green(" Type: "); - output += DisplayType[display.type]; - output += chalk.green("\tSupported Heads: "); - - // Max of 4 heads - for (let i = 0; i < 4; i++) { - if (display.headBitmask & (1 << i)) { - output += `${i + 1},` - } - } - - // trim last comma - output = output.substring(0, output.length - 1); - - if (display.type == DisplayType.TV) { - output += ",TV"; - } - - console.log(output); - }); - - console.log(); - - console.log("Heads"); - let headTVOut = chalk.blueBright("(TV) ") + "[" - let head0Out = chalk.blueBright("(1) ") + "[" - let head1Out = chalk.blueBright("(2) ") + "[" - - headTV.forEach((dcbEntry: number) => headTVOut += (dcbEntry + 1) + ","); - head0.forEach((dcbEntry: number) => head0Out += (dcbEntry + 1) + ","); - head1.forEach((dcbEntry: number) => head1Out += (dcbEntry + 1) + ","); - - // trim last comma - if (headTV.length > 0) headTVOut = headTVOut.substring(0, headTVOut.length - 1); - if (head0.length > 0) head0Out = head0Out.substring(0, head0Out.length - 1); - if (head1.length > 0) head1Out = head1Out.substring(0, head1Out.length - 1); - - headTVOut += "]"; - head0Out += "]"; - head1Out += "]"; - console.log(headTVOut); - console.log(head0Out); - console.log(head1Out); - - - console.log(); - console.log("NVCAP:"); - console.log(chalk.blueBright("(n1)") + chalk.green(" Version: ") + nvcap.version); - console.log(chalk.blueBright("(n2)") + chalk.green(" Composite: ") + nvcap.composite); - console.log(chalk.blueBright("(n3)") + chalk.green(" Script Based Power/Backlight: ") + nvcap.scriptBasedPowerAndBacklight); - console.log(chalk.blueBright("(n4)") + chalk.green(" Field F: ") + "0x" + nvcap.fieldF.toString(16)); - - console.log(); - console.log("To add/remove a display to/from a head, type (ex: \"1 1\")"); - console.log("To change an NVCAP value, do n (ex: \"n2 true\")"); - console.log("To calculate the NVCAP value, type c/create"); -} - -async function drawNVCap() { - while (true) { - console.clear(); - header(); - listDisplaysAndNvcap(); - let result = await prompt("To return to the previous menu, use q/quit"); - console.log(); - - if (result.length == 0) continue; - // quit - if (result.toLowerCase().startsWith("q")) return; - // Create NVCAP - if (result.toLowerCase().startsWith("c")) { - console.log(); - createNVCap(); - if((await prompt("Press enter to continue (or q/quit to exit)")).toLowerCase().startsWith("q")) { - showGoodbye(); - process.exit(0); - } - continue; - } - - // Handle commands like "2 1" where display 2 gets put on head 1 - // If the first letter is "n", then that is ignored here - let splitArr = result.split(" "); - if (splitArr.length == 2 && parseInt(splitArr[0]) && - (parseInt(splitArr[1]) || splitArr[1].toLowerCase().startsWith("tv"))) { - - let arr: number[] | null = null;; - - let display = parseInt(splitArr[0]); - if (display < 1 || display > filteredEntries.length) { - await prompt("Unknown display - press enter to continue"); - continue; - } - display--; - - if (splitArr[1].toLowerCase().startsWith("tv")) { - if (filteredEntries[display].type != DisplayType.TV) { - await prompt("Only a display of type TV can be put in the TV Mask - Pres enter to continue"); - continue; - } - arr = headTV; - } else { - let head = parseInt(splitArr[1]); - if (head == 1) { - arr = head0; - } else if (head == 2) { - arr = head1; - } - } - - if (arr == null) { - await prompt("Unknown head - press enter to continue"); - continue; - } - - if (arr.includes(display)) { - arr.splice(arr.indexOf(display), 1); - } else { - arr.push(display); - } - continue; - } - - // Handle other options with "n" in front - splitArr[0] = splitArr[0].replace("n", ""); - if (parseInt(splitArr[0])) { - let command = parseInt(splitArr[0]) - 1; - switch (command) { - case 0: - case 3: - let newValue = parseInt(splitArr[1]); - if (newValue < 0 || newValue > 0xf) { - await prompt("New value is out of bounds! Must be between 0 and 0xf - press enter to continue"); - continue; - } - - if (command == 0) nvcap.version = newValue; - if (command == 3) nvcap.fieldF = newValue; - break; - case 1: - case 2: - if (splitArr.length < 2) { - if (command == 1) { - nvcap.composite = !nvcap.composite; - } else { - nvcap.scriptBasedPowerAndBacklight = !nvcap.scriptBasedPowerAndBacklight; - } - continue; - } - splitArr[1] = splitArr[1].replace("\n", "").toLowerCase(); - let bool = false; - if (splitArr[1].startsWith("t")) { - bool = true; - } else if (!splitArr[1].startsWith("f")) { - await prompt ("Unrecognized new value! Must be true or false - press enter to continue"); - continue; - } - - if (command == 1) { - nvcap.composite = bool; - } else { - nvcap.scriptBasedPowerAndBacklight = bool; - } - break; - } - } - - await prompt("Unknown command - press enter to continue"); - } -} - -function createHeadMask(displays: number[]) { - let mask = 0; - displays.forEach((displayIndex: number) => { - let dcbEntries: number[] = filteredEntries[displayIndex]!.dcbEntries; - dcbEntries.forEach((dcbIndex: number) => { - mask |= (1 << dcbIndex); - }); - }); - - return mask; -} - -function createNVCap() { - nvcap.tvDCBMask = createHeadMask(headTV); - nvcap.head0DCBMask = createHeadMask(head0); - nvcap.head1DCBMask = createHeadMask(head1); - console.log(`TV Mask: 0x${nvcap.tvDCBMask.toString(16)}`); - console.log(`Head 1 Mask: 0x${nvcap.head0DCBMask.toString(16)}`); - console.log(`Head 2 Mask: 0x${nvcap.head1DCBMask.toString(16)}`); - - let buffer = Buffer.alloc(20); - buffer.writeInt8(nvcap.version, 0); - buffer.writeInt8(nvcap.isMobile ? 1 : 0, 1); - buffer.writeInt8(nvcap.composite ? 1 : 0, 2); - // Unknown field - backlight related? - buffer.writeInt8(0, 3); - - buffer.writeInt16LE(nvcap.tvDCBMask, 4); - buffer.writeInt16LE(nvcap.head0DCBMask, 6); - buffer.writeInt16LE(nvcap.head1DCBMask, 8); - buffer.writeInt16LE(nvcap.head2DCBMask, 10); - buffer.writeInt16LE(nvcap.head3DCBMask, 12); - - buffer.writeInt8(nvcap.scriptBasedPowerAndBacklight ? 1 : 0, 14); - buffer.writeInt8(nvcap.fieldF, 15); - - // Unknwon field - 10bit/EDID_Manufacturer_Reserved_timings support? - buffer.writeInt8(0, 16); - buffer.writeInt8(0, 17); - buffer.writeInt8(0, 18); - buffer.writeInt8(0, 19); - - let output = "NVCAP: "; - // Pad to always be 8 digits long - for (let i = 0; i < 5; i++) { - let number = buffer.readInt32BE(i * 4); - output += ("00000000" + number.toString(16)).slice(-8); - output += " "; - - } - console.log(output); -} - -function showGoodbye() { - console.clear(); - header(); - - console.log("By 1Revenger1\n"); - console.log("Thanks for using this program - if you have any issues,"); - console.log("visit github.com/1Revenger1/NVCAPCalculator"); - console.log("For more projects, visit github.com/1Revenger1\n"); - - let hour = new Date().getHours(); - if (hour > 3 && hour < 12) { - console.log("Have a nice morning!\n"); - } else if (hour >= 12 && hour < 17) { - console.log("Have a nice afternoon!\n"); - } else if (hour >= 17 && hour < 21) { - console.log("Have a nice evening!\n"); - } else { - console.log("Have a nice night!\n"); - } -} - -async function main() { - // Ask user for path if rom does not exist - if (!existsSync(romFile)) { - await chooseROM(); - } - - readRom(); - - while (true) { - let romExists = existsSync(romFile); - console.clear(); - header(); - - - let output = chalk.cyan("(1) ") + "Choose VBIOS/ROM file\n"; - if (romExists) { - output += chalk.cyan("(2) ") + "Show DCB Entries\n"; - output += chalk.cyan("(3) ") + "Calculate NVCAP\n"; - } - output += "\n"; - - if (romExists) { - output += "Current ROM file: " + chalk.green(romFile); - } else { - output += "Current ROM file (not found): " + chalk.red(romFile); - } - console.log(output); - - let result = await prompt("Type in the number to select your option, or \"q\"/\"quit\" to quit"); - if (result.toLowerCase().startsWith("q")) break; - if (result.toLowerCase().startsWith("1")) await chooseROM(); - if (romExists) { - if (result.toLowerCase().startsWith("2")) await dumpDCBEntries(); - if (result.toLowerCase().startsWith("3")) await drawNVCap(); - } - } - - showGoodbye(); -} - -main(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100755 index 09748c7..0000000 --- a/package-lock.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "name": "dbccalculator", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@types/node": { - "version": "14.11.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.4.tgz", - "integrity": "sha512-KmoLCUeW2cWKkEOQ0gQcECuqOc0g7B7zcmRPQNMT4ntNm0luKv3BTLcqIyWpTxkhLDzLTdMus11j/6DROaZdPw==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100755 index 735741b..0000000 --- a/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "dbccalculator", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "run": "node index.js", - "build": "tsc && npm run run", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "chalk": "^4.1.0" - }, - "devDependencies": { - "@types/node": "^14.11.4" - } -} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ab6d2fe --- /dev/null +++ b/src/main.rs @@ -0,0 +1,438 @@ +mod nvidia; +mod util; +use colored::*; +use std::process::exit; +use std::{io}; +use std::io::prelude::*; + +use crate::nvidia::{NVCAP_VERSION_MODERN}; + +fn main() { + let dcb_entries_opt: Vec; + let display_entries_opt: Vec; + let filename; + + ctrlc::set_handler(move || { + util::clear_console(); + util::goodbye(); + exit(0); + }).expect("Error setting Ctrl-c handler"); + + #[cfg(target_os = "windows")] + control::set_virtual_terminal(true).unwrap(); + + let res = choose_rom(); + match res { + Ok(tuple) => { + filename = tuple.0; + dcb_entries_opt = tuple.1; + display_entries_opt = tuple.2; + }, + Err(_) => { + util::goodbye(); + return; + } + } + + loop { + let mut input = String::new(); + let mut opt: u32 = 0; + util::header(); + + println!("{} Show DCB Entries", "(1)".cyan()); + println!("{} Calculate NVCAP", "(2)".cyan()); + println!(""); + println!("Current ROM file: {}", filename.green()); + + util::prompt("Type in the number to select your option, or \"q\"/\"quit\" to quit: ", &mut input); + input = input.trim().to_owned(); + match input.parse::() { + Ok(val) => { opt = val; } + Err(_) => {} + } + + if opt == 1 { + dump_dcb_entries(&dcb_entries_opt); + } else if opt == 2 { + draw_nvcap(&display_entries_opt); + } else if input.starts_with("q") { + break; + } + } + + util::goodbye(); +} + +fn choose_rom () -> Result<(String, Vec, Vec), util::NVErrors> { + loop { + let mut filename = String::new(); + util::header(); + + println!("Enter in the location of your VBIOS (or q/quit to exit to menu)\n"); + println!("{} Shift + Right click your VBIOS and click \"Copy Path\"", "Windows Tip: ".cyan()); + println!("{} Drag and drop your VBIOS into this prompt", "Linooox/macOS: ".cyan()); + print!("Location of VBIOS: "); + + io::stdout().flush().unwrap(); + + let result = io::stdin().read_line(&mut filename); + match result { + Err(_) => { + continue; + } + Ok(_) => { + // Remove newline at end and quotes as those mess up finding the ROM + filename = filename.trim_end().replace("\"", "").to_owned(); + } + } + + println!("{}", filename); + + if filename.to_lowercase().starts_with("q") { + return Err(util::NVErrors::FileNotFound); + } + + let read_result = nvidia::read_rom(&filename); + match read_result { + Err(_) => { + util::press_any_key(); + continue; + } + Ok(res) => { + util::press_any_key(); + return Ok((filename, res.0, res.1)); + } + } + } +} + +fn dump_dcb_entries(dcb_entries: &Vec) { + util::header(); + + for i in 0..dcb_entries.len() { + let dcb_entry = &dcb_entries[i]; + println!("{} {:#x}", "DCB Entry".bright_blue(), i); + println!("{} {} ({:#x}) {} {} {} {} {} {} {} {} {} {}", + "Type:".green(), nvidia::dcb_type_to_string(dcb_entry.entry_type), dcb_entry.entry_type, + "EdidPort:".green(), dcb_entry.edid_port, + "Head:".green(), dcb_entry.head_bitmask, + "Connector:".green(), dcb_entry.con, + "Bus:".green(), dcb_entry.bus, + "Loc:".green(), dcb_entry.loc, + ); + + println!("{} {} {} {} {} {} {} {}", + "BDR:".green(), dcb_entry.bdr, + "BBDR:".green(), dcb_entry.bbdr, + "Resources:".green(), dcb_entry.output_resources, + "Virtual:".green(), dcb_entry.entry_is_virtual, + ); + } + + util::press_any_key(); +} + +fn draw_nvcap(displays: &Vec) { + // Store indexes to displays + let mut head_tv: Vec = vec![]; + let mut head_0: Vec = vec![]; + let mut head_1: Vec = vec![]; + + filter_displays(displays, &mut head_tv, &mut head_0, &mut head_1); + + let mut nvcap = nvidia::NVCAP { + version: NVCAP_VERSION_MODERN, + is_mobile: util::is_mobile(displays), + is_composite: util::has_tv(displays), + unknown_1: 0, + dcb_tv_mask: 0, + dcb_0_mask: 0, + dcb_1_mask: 0, + dcb_2_mask: 0, + dcb_3_mask: 0, + script_based_power_and_backlight: false, + field_f: 0x0f, + edid_bitness: 0, + unknown_2: [0, 0, 0] + }; + + loop { + util::clear_console(); + util::header(); + + list_displays(displays, &head_tv, &head_0, &head_1, false); + list_options(&nvcap); + + let mut input = String::new(); + let mut opt: u32 = 0; + + util::prompt("Select one of the above options (1-5, q, or c): ", &mut input); + input = input.trim().to_lowercase().to_owned(); + match input.parse::() { + Ok(val) => { opt = val; } + Err(_) => {} + } + + match opt { + 1 => { choose_heads(displays, &mut head_tv, &mut head_0, &mut head_1) } + 2 => { nvcap.is_mobile = !nvcap.is_mobile } + 3 => { choose_version(&mut nvcap); } + 4 => { nvcap.is_composite = !nvcap.is_composite } + 5 => { nvcap.script_based_power_and_backlight = !nvcap.script_based_power_and_backlight } + 6 => { choose_f(&mut nvcap); } + _ => { /* Do nothing */ } + } + + if input.eq("c") { + nvidia::create_nvcap_value(&mut nvcap, displays, &head_tv, &head_0, &head_1); + } else if input.eq("q") { + break; + } + } +} + +// Automatically assign displays to heads when they should obviously be there +fn filter_displays(displays: &Vec, head_tv: &mut Vec, head_0: &mut Vec, head_1: &mut Vec) { + + // If mobile, then the internal display should be on one head with everything else on another head + let is_mobile = util::is_mobile(displays); + + for i in 0..displays.len() { + let display = &displays[i]; + // Composite/TV can exist on it's own head + if display.disp_type == util::DisplayType::TV { + head_tv.push(i); + continue; + } + + if !is_mobile { + continue; + } + + if display.disp_type == util::DisplayType::LVDS && + display.head_bitmask & nvidia::HEAD_0_BITMASK != 0 { + head_0.push(i); + } else if display.head_bitmask & nvidia::HEAD_1_BITMASK != 0 { + head_1.push(i); + } + } +} + +fn list_displays(displays: &Vec, head_tv: &Vec, head_0: &Vec, head_1: &Vec, color: bool) { + + let mut head_tv_out = format!("{} - [", "TV"); + let mut head_1_out = format!("{} - [", "1"); + let mut head_2_out = format!("{} - [", "2"); + + if color { + head_tv_out = format!("{} - [", "TV".green()); + head_1_out = format!("{} - [", "1".green()); + head_2_out = format!("{} - [", "2".green()); + } + + println!("Displays:"); + for i in 0..displays.len() { + let display = &displays[i]; + let mut heads = "".to_owned(); + + for i in 0..4 { + if display.head_bitmask & (1 << i) != 0 { + heads += &format!("{}, ", i + 1); + } + } + + heads = heads[0..(heads.len() - 2)].to_owned(); + + if display.disp_type == util::DisplayType::TV { + heads += ", TV"; + } + + + let idx_str = &format!("({})", i + 1); + if color { + print!("{} ", idx_str.bright_blue()); + } else { + print!("{} ", idx_str); + } + + println!("{} {:?} {} {}", + "Type:", + display.disp_type, + "\tSupported Heads:", + heads + ); + } + + println!(); + + head_tv.iter().for_each(|disp| head_tv_out += &format!("{},", disp + 1)); + head_0.iter().for_each(|disp| head_1_out += &format!("{},", disp + 1)); + head_1.iter().for_each(|disp| head_2_out += &format!("{},", disp + 1)); + + if head_tv.len() > 0 { head_tv_out = head_tv_out[0..(head_tv_out.len() - 1)].to_owned(); } + if head_0.len() > 0 { head_1_out = head_1_out[0..(head_1_out.len() - 1)].to_owned(); } + if head_1.len() > 0 { head_2_out = head_2_out[0..(head_2_out.len() - 1)].to_owned(); } + + head_tv_out += "]"; + head_1_out += "]"; + head_2_out += "]"; + + println!("NVCAP Heads:"); + if util::has_tv(displays) { + println!("{}", head_tv_out); + } + println!("{}", head_1_out); + println!("{}", head_2_out); + println!(); +} + +fn list_options(nvcap: &nvidia::NVCAP) { + println!("{} Add/remove displays from head", "(1)".bright_blue()); + println!("{} Mobile: {}", "(2)".bright_blue(), nvcap.is_mobile); + println!("{} Version: {}", "(3)".bright_blue(), nvcap.version); + println!("{} Composite: {}", "(4)".bright_blue(), nvcap.is_composite); + println!("{} Script Based Power/Backlight: {}", "(5)".bright_blue(), + nvcap.script_based_power_and_backlight); + println!("{} Field F: {:#x}", "(6)".bright_blue(), nvcap.field_f); + println!(); + println!("{} Return to previous menu", "(q)".bright_blue()); + println!("{} Print out current NVCAP value", "(c)".bright_blue()); +} + +fn toggle_display(head: &mut Vec, disp_idx: usize) { + match head.iter().position(|&x| x == disp_idx) { + Some(idx) => { head.remove(idx); } + None => { head.push(disp_idx); } + } +} + +fn choose_heads(displays: &Vec, head_tv: &mut Vec, + head_0: &mut Vec, head_1: &mut Vec) { + let has_tv = util::has_tv(displays); + + loop { + let mut input = String::new(); + let mut disp_idx: usize; + let mut head: usize = 0; + + util::header(); + list_displays(displays, head_tv, head_0, head_1, true); + + println!("Select a display and head to add/remove it from the chosen head"); + println!("The input should look like \"{} {}\"", "".bright_blue(), "".green()); + println!("For example, \"1 2\" for display 1, head 2"); + println!(); + println!("{} Return to previous menu", "(q)".bright_blue()); + + util::prompt("Display/Head: ", &mut input); + input = input.trim().to_lowercase(); + + if input.eq("q") { + break; + } + + let args: Vec<&str> = input.split(" ").collect(); + + if args.len() != 2 { + continue; + } + + // Parse display + match args[0].parse::() { + Ok(opt) => { disp_idx = opt; } + Err(_) => { continue; } + } + + if disp_idx > displays.len() { + // Out of index display + continue; + } + + // Add/remove display from head + match args[1].parse::() { + Ok(opt) => { head = opt; } + Err(_) => { /* Could be TV, handled below */ } + } + + // Vec is 0 based idx, we start at 1 when displaying though + disp_idx -= 1; + let disp = &displays[disp_idx]; + + match head { + 1 => { + if disp.head_bitmask & nvidia::HEAD_0_BITMASK == 0 { + continue; + } + + toggle_display(head_0, disp_idx); } + 2 => { + if disp.head_bitmask & nvidia::HEAD_1_BITMASK == 0 { + continue; + } + + toggle_display(head_1, disp_idx); } + _ => { + // Maybe TV + if !has_tv || !args[1].eq("tv") { + continue; + } + + toggle_display(head_tv, disp_idx); + } + } + } +} + +fn choose_f(nvcap: &mut nvidia::NVCAP) { + loop { + let mut input = String::new(); + util::header(); + + println!("Select f (unknown) field"); + println!("0x0F for 300 series and newer GPUs"); + println!("0x07 for older GPUs"); + println!(); + println!("{} Return to previous menu", "(q)".bright_blue()); + + util::prompt("New Value: ", &mut input); + input = input.trim().to_lowercase(); + + if input.eq("q") { + break; + } + + let no_prefix = input.trim_start_matches("0x"); + + match u8::from_str_radix(no_prefix, 16) { + Ok(val) => { nvcap.field_f = val; break; } + Err(_) => { continue; } + } + } +} + +fn choose_version(nvcap: &mut nvidia::NVCAP) { + loop { + let mut input = String::new(); + util::header(); + + println!("Select version"); + println!("5 for 8000 series cards or newer (Most new cards)"); + println!("4 for 6000/7000 series cards"); + println!(); + println!("{} Return to previous menu", "(q)".bright_blue()); + + util::prompt("New Value: ", &mut input); + input = input.trim().to_lowercase(); + + if input.eq("q") { + break; + } + + let no_prefix = input.trim_start_matches("0x"); + + match u8::from_str_radix(no_prefix, 16) { + Ok(val) => { nvcap.version = val; break; } + Err(_) => { continue; } + } + } +} \ No newline at end of file diff --git a/src/nvidia.rs b/src/nvidia.rs new file mode 100644 index 0000000..30e87d5 --- /dev/null +++ b/src/nvidia.rs @@ -0,0 +1,276 @@ + +use colored::*; +use std::{fs, mem}; +use crate::util::{self, NVErrors}; + +// DCB numbers +const DCB_SIGNATURE: u32 = 0x4edcbdcb; +const DCB_MAX_VERSION: u8 = 0x42; +const DCB_MIN_VERSION: u8 = 0x30; + +const DCB_HEADER_ADDR: usize = 0x36; + +// DCB offsets +const DCB_SIZE_OFFSET: usize = 0x1; +const DCB_ENTRY_COUNT_OFFSET: usize = 0x2; +const DCB_ENTRY_SIZE_OFFSET: usize = 0x3; +const DCB_SIGNATURE_OFFSET: usize = 0x6; + +// DCB connector types +const DCB_CONN_CRT: u32 = 0; // VGA +const DCB_CONN_TV: u32 = 1; // Composite +const DCB_CONN_TMDS: u32 = 2; // DVI, HDMI, etc +const DCB_CONN_LVDS: u32 = 3; // Laptop Disp +const DCB_CONN_SDI: u32 = 5; // SDI +const DCB_CONN_DP: u32 = 6; // DisplayPort + +pub struct DcbEntry { + pub entry_type: u32, + pub edid_port: u32, + pub head_bitmask: u32, + pub con: u32, + pub bus: u32, + pub loc: u32, + pub bdr: u32, + pub bbdr: u32, + pub output_resources: u32, + pub entry_is_virtual: bool, + pub reserved: u32, + pub entry: u8, +} + +pub fn dcb_type_to_string(dcb_type: u32) -> &'static str { + match dcb_type { + DCB_CONN_CRT => "CRT", + DCB_CONN_TV => "TV", + DCB_CONN_TMDS => "TMDS", + DCB_CONN_LVDS => "LVDS", + DCB_CONN_SDI => "SDI", + DCB_CONN_DP => "DisplayPort", + _ => "Unknown", + } +} + + +// https://github.com/acidanthera/WhateverGreen/blob/master/Manual/NVCAP.bt#L9 +// Starting from 8000 series +pub const NVCAP_VERSION_MODERN: u8 = 5; +pub const HEAD_0_BITMASK: u32 = 0x1; +pub const HEAD_1_BITMASK: u32 = 0x2; + +#[repr(C)] +pub struct NVCAP { + pub version: u8, + pub is_mobile: bool, + pub is_composite: bool, + pub unknown_1: u8, + + pub dcb_tv_mask: u16, + pub dcb_0_mask: u16, + pub dcb_1_mask: u16, + // Only for GK107 and newer (which this script doesn't support) + pub dcb_2_mask: u16, + pub dcb_3_mask: u16, + + pub script_based_power_and_backlight: bool, + /* + 07: Clover's default + 0A: Desktop-class GPU (Chameleon default) + 0B: Laptop-class GPU + 0E: 300 series+ MacBook Air/Low end + 0F: 300 series+ MacBook Pro/iMac/High End + */ + pub field_f: u8, + // Unknown field - 10 bit/EDID Manufacturer reserved timings support? + pub edid_bitness: u8, + pub unknown_2: [u8; 3], +} + +pub fn parse_signature(version: u8, rom: &Vec, offset: usize) -> bool { + if version >= DCB_MAX_VERSION { + println!("Unknown version"); + return false; + } + + if version < DCB_MIN_VERSION { + println!("To old version - GPU incompatible with macOS"); + return false; + } + + if util::read_uint_32_le(rom, offset + DCB_SIGNATURE_OFFSET) != DCB_SIGNATURE { + println!("Corrupt VBIOS"); + return false; + } + + true +} + +fn parse_dcb_entries(rom: &Vec, offset: usize, dcb_size: usize, parsed_entries: &mut Vec) { + let dcb_entries: u8 = rom[offset + DCB_ENTRY_COUNT_OFFSET]; + let dcb_entry_size: u8 = rom[offset + DCB_ENTRY_SIZE_OFFSET]; + + println!("{} DCB entries of size {:#x} bytes\n", dcb_entries, dcb_entry_size); + + for number in 0..dcb_entries { + let entry_offset = offset + dcb_size + (dcb_entry_size * number) as usize; + let conn: u32 = util::read_uint_32_le(rom, entry_offset); + + let dcb_head = DcbEntry { + entry_type: conn & 0xf, + edid_port: (conn >> 4) & 0xf, + head_bitmask: (conn >> 8) & 0xf, + con: (conn >> 12) & 0xf, + bus: (conn >> 16) & 0xf, + loc: (conn >> 20) & 0x3, + bdr: (conn >> 22) & 0x1, + bbdr: (conn >> 23) & 0x1, + output_resources: (conn >> 24) & 0xf, + entry_is_virtual: ((conn >> 28) & 0x1) == 1, + reserved: (conn >> 28) & 0x7, + entry: number, + }; + + // Skip entry + if dcb_head.entry_type == 0xf { + continue; + } + + // EOL - stop parsing entries + if dcb_head.entry_type == 0xe { + break; + } + + parsed_entries.push(dcb_head); + } + + println!("Found {} populated DCB Entries", parsed_entries.len()); +} + +fn get_display_type(entry: &DcbEntry) -> util::DisplayType { + match entry.entry_type { + DCB_CONN_LVDS => util::DisplayType::LVDS, + DCB_CONN_CRT => util::DisplayType::Analog, + DCB_CONN_TV => util::DisplayType::TV, + _ => util::DisplayType::Digital, + } +} + +// Merge DVI entries together and condense into info we just need for the user +fn merge_dcb_entries(parsed_dcb_entries: &mut Vec, filtered_pub_entries: &mut Vec) { + let mut merged_entries: Vec = Vec::new(); + for dcb_entry in parsed_dcb_entries.iter() { + if merged_entries.contains(&dcb_entry.entry) { + continue; + } + + // https://nvidia.github.io/open-gpu-doc/DCB/DCB-4.x-Specification.html#_dcb_device_entries + // Use Bus id, not connector index, to merge devices together + let dcb_entry_2 = parsed_dcb_entries.iter().find( + |x| (x.bus == dcb_entry.bus) && (x.entry != dcb_entry.entry)); + + match dcb_entry_2 { + Some(x) => { + merged_entries.push(x.entry); + + let mut disp_type = util::DisplayType::DVI; + if dcb_entry.entry_type == x.entry_type { + disp_type = get_display_type(x) + } + + let display = util::Display { + disp_type: disp_type, + dcb_entries: vec![dcb_entry.entry, x.entry], + head_bitmask: dcb_entry.head_bitmask & x.head_bitmask, + }; + + filtered_pub_entries.push(display); + } + None => { + let display = util::Display { + disp_type: get_display_type(dcb_entry), + dcb_entries: vec![dcb_entry.entry], + head_bitmask: dcb_entry.head_bitmask, + }; + + filtered_pub_entries.push(display); + } + } + } +} + +// Convert indexes of displays to DCB head mask +fn create_head_mask(display_indexes: &Vec, displays: &Vec) -> u16 { + let mut mask: u16 = 0; + for &idx in display_indexes { + let dcb_entries = &displays[idx].dcb_entries; + for dcb_entry in dcb_entries { + mask |= 1 << dcb_entry; + } + } + + mask +} + +pub fn read_rom(filename: &str) -> Result<(Vec, Vec), NVErrors> { + let rom: Vec; + let mut parsed_dcb_entries: Vec = Vec::new(); + let mut filtered_disp_entries: Vec = Vec::new(); + + + let rom_res = fs::read(filename); + match rom_res { + Ok(bytes) => { + println!("Read ROM file {}, which is {} bytes long", filename, bytes.len()); + rom = bytes; + } + Err(e) => { + println!("{}", "Rom file not found!".red()); + println!("{}", e); + return Err(NVErrors::FileNotFound); + } + } + + // DCB 3.0 and 4.0 are very similar, just treat them the same + let dcb_header_offset: usize = util::read_uint_16_le(&rom, DCB_HEADER_ADDR) as usize; + let dcb_version: u8 = rom[dcb_header_offset]; + let ver_maj: u8 = dcb_version >> 4; + let ver_min: u8 = dcb_version & 0xf; + let dcb_size: usize = rom[dcb_header_offset + DCB_SIZE_OFFSET] as usize; + + println!("DCB header is at {:#x} with length {:#x}", dcb_header_offset, dcb_size); + println!("DCB Version {}.{}", ver_maj, ver_min); + + if !parse_signature(dcb_version, &rom, dcb_header_offset) { + println!("{}", "Invalid DCB Signature".red()); + return Err(NVErrors::Corrupted); + } + + parse_dcb_entries(&rom, dcb_header_offset, dcb_size, &mut parsed_dcb_entries); + merge_dcb_entries(&mut parsed_dcb_entries, &mut filtered_disp_entries); + Ok((parsed_dcb_entries, filtered_disp_entries)) +} + +pub fn create_nvcap_value (nvcap: &mut NVCAP, displays: &Vec, + head_tv: &Vec, head_0: &Vec, head_1: &Vec) { + nvcap.dcb_tv_mask = create_head_mask(head_tv, displays); + nvcap.dcb_0_mask = create_head_mask(head_0, displays); + nvcap.dcb_1_mask = create_head_mask(head_1, displays); + + util::header(); + + println!("TV mask: {:#x}", nvcap.dcb_tv_mask); + println!("Head 0 mask: {:#x}", nvcap.dcb_0_mask); + println!("Head 1 mask: {:#x}", nvcap.dcb_1_mask); + + println!("{}: ", "NVCAP".cyan()); + + let view = nvcap as *const _ as *const u32; + let max_bound = mem::size_of::() / 4; + for i in 0..max_bound as isize { + let val: u32 = unsafe { *view.offset(i) }; + print!("{:08x} ", val.to_be()); + } + + println!("\n"); + util::press_any_key(); +} \ No newline at end of file diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..863d6d3 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,100 @@ +use std::{io}; +use colored::*; +use std::io::prelude::*; +use chrono::{Local, Timelike}; + +#[derive(Debug)] +#[derive(PartialEq)] +pub enum DisplayType { + LVDS, + TV, + Analog, + Digital, + DVI, +} + +pub enum NVErrors { + FileNotFound, + Corrupted, +} + +pub struct Display { + pub disp_type: DisplayType, + pub dcb_entries: Vec, + pub head_bitmask: u32, +} + +pub fn read_uint_16_le(rom: &Vec, offset: usize) -> u16 { + return ((rom[offset] as u16)) + ((rom[offset + 1] as u16) << 8); +} + +pub fn read_uint_32_le(rom: &Vec, offset: usize) -> u32 { + return (rom[offset] as u32) + + ((rom[offset + 1] as u32) << 8) + + ((rom[offset + 2] as u32) << 16) + + ((rom[offset + 3] as u32) << 24); +} + +pub fn press_any_key() { + let mut buf = String::new(); + prompt("Press the enter key to continue...", &mut buf); +} + +pub fn prompt(prompt: &str, input: &mut String) { + let mut stdout = io::stdout(); + let stdin = io::stdin(); + + write!(stdout, "{}", prompt).unwrap(); + stdout.flush().unwrap(); + stdin.read_line(input).unwrap(); +} + +pub fn clear_console() { + print!("{esc}c", esc = 27 as char); +} + +pub fn goodbye() { + clear_console(); + header(); + + println!("By 1Revenger1\n"); + println!("Thanks for using this program - if you have any issues,"); + println!("visit github.com/1Revenger1/NVCAPCalculator"); + println!("For more projects, visit github.com/1Revenger1\n"); + + let hour = Local::now().time().hour(); + match hour { + 3..=11 => println!("Have a nice morning!"), + 12..=17 => println!("Have a nice afternoon!"), + 18..=21 => println!("Have a nice evening!"), + _ => println!("Have a nice night!"), + }; +} + +pub fn header() { + clear_console(); + println!("{}", "+--------------------------+".green()); + println!("{}{}{}", "|".green(), " NVCAP Calculator ".cyan(), "|".green()); + println!("{}", "+--------------------------+".green()); + println!(""); +} + +pub fn is_mobile(displays: &Vec) -> bool { + for disp in displays { + if disp.disp_type == DisplayType::LVDS { + return true; + } + } + + false +} + +pub fn has_tv(displays: &Vec) -> bool { + for disp in displays { + if disp.disp_type == DisplayType::TV { + return true; + } + } + + false +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100755 index 11e725c..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - - /* Basic Options */ - // "incremental": true, /* Enable incremental compilation */ - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - // "lib": [], /* Specify library files to be included in the compilation. */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - - /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - - /* Advanced Options */ - "skipLibCheck": true, /* Skip type checking of declaration files. */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } -}