//
//  main.c
//  NVRAMdump
//
//  Created by Rodion Shingarev on 27.12.20.
//  Copyright © 2020 Rodion Shingarev. All rights reserved.
//  based on the original NVRAM utility
//  Copyright (c) 2000-2016 Apple Inc. All rights reserved.
//  https://opensource.apple.com/source/system_cmds/system_cmds-854.40.2/nvram.tproj/nvram.c
//

#include <stdio.h>

#ifdef __APPLE__

#include <stdlib.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOKitKeys.h>
#include <CoreFoundation/CoreFoundation.h>
#include <err.h>
#include <mach/mach_error.h>
#include <string.h>

static io_registry_entry_t gOptionsRef;

// GetOFVariable(name, nameRef, valueRef)
//
//   Get the named firmware variable.
//   Return it and its symbol in valueRef and nameRef.
//
static kern_return_t GetOFVariable(char *name, CFStringRef *nameRef,
                                   CFTypeRef *valueRef)
{
    *nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name,
                                         kCFStringEncodingUTF8);
    if (*nameRef == 0) {
        errx(1, "Error creating CFString for key %s", name);
    }

    *valueRef = IORegistryEntryCreateCFProperty(gOptionsRef, *nameRef, 0, 0);
    if (*valueRef == 0) return kIOReturnNotFound;

    if (CFGetTypeID(*valueRef) == CFStringGetTypeID()) {
        CFTypeRef oldValue = *valueRef;
        *valueRef = CFStringCreateExternalRepresentation(kCFAllocatorDefault, *valueRef, kCFStringEncodingUTF8, 0);
        CFRelease(oldValue);
        if (*valueRef == 0) return kIOReturnNotFound;
    }

    return KERN_SUCCESS;
}

static CFMutableDictionaryRef dict4;

static void ConvertStringValues (const void* key, const void* value, void* context) {
    if (CFGetTypeID(value) == CFStringGetTypeID()) {
        CFDataRef dataValue = CFStringCreateExternalRepresentation(kCFAllocatorDefault, value, kCFStringEncodingUTF8, 0);
        if (dataValue != NULL) {
            CFDictionaryAddValue(dict4, key, dataValue);
        }
    } else {
        CFDictionaryAddValue(dict4, key, value);
    }
}


CFDictionaryRef CreateMyDictionary(void) {
    char *guid;

    // root
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                                                            &kCFTypeDictionaryKeyCallBacks,
                                                            &kCFTypeDictionaryValueCallBacks);
    //version
    int version = 1;
    CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &version);
    CFDictionarySetValue(dict, CFSTR("Version"), num);
    CFRelease(num);
    //Add
    CFMutableDictionaryRef dict0 = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
                                                             &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(dict, CFSTR("Add"), dict0);
    CFRelease(dict0);

    //   APPLE_BOOT_VARIABLE_GUID
    //   Print all of the firmware variables.
    CFMutableDictionaryRef dict1;
    kern_return_t          result;
    result = IORegistryEntryCreateCFProperties(gOptionsRef, &dict1, 0, 0);
    if (result != KERN_SUCCESS) {
        errx(1, "Error getting the firmware variables: %s", mach_error_string(result));
    }

    // To work round limitations of OpenCore XML parser (i.e. XML entities not
    // automatically processed), force all string values to save as data.
    dict4 = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                                      &kCFTypeDictionaryKeyCallBacks,
                                      &kCFTypeDictionaryValueCallBacks);
    CFDictionaryApplyFunction(dict1, ConvertStringValues, NULL);

    CFDictionarySetValue(dict0, CFSTR("7C436110-AB2A-4BBB-A880-FE41995C9F82"), dict4);
    CFRelease(dict1);
    CFRelease(dict4);

    CFMutableDictionaryRef dict2 = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
                                                             &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(dict0, CFSTR("8BE4DF61-93CA-11D2-AA0D-00E098032B8C"), dict2);

    //   EFI_GLOBAL_VARIABLE_GUID
    //   Print the given firmware variable.
    CFStringRef   nameRef;
    CFTypeRef     valueRef;

    const char *key[32];
    char name[64];
    CFStringRef   var;
    int i = 0;
    int n = 7; // num of keys in this GUID

    key[0] = "Boot0080";
    key[1] = "BootOrder";
    key[2] = "BootNext";
    key[3] = "Boot0081";
    key[4] = "Boot0082";
    key[5] = "Boot0083";
    key[6] = "BootCurrent";
    guid = "8BE4DF61-93CA-11D2-AA0D-00E098032B8C";

    for ( i = 0; i < n; i++ ) {
        snprintf(name, sizeof(name), "%s:%s", guid, key[i]);
        var = CFStringCreateWithCString(NULL, key[i], kCFStringEncodingUTF8);
        result = GetOFVariable(name, &nameRef, &valueRef);
        if (result == KERN_SUCCESS) {
            CFDictionaryAddValue (dict2, var, valueRef);
            CFRelease(valueRef);
        }
        CFRelease(nameRef);
    }
    CFRelease(dict2);

    //  APPLE_WIRELESS_NETWORK_VARIABLE_GUID
    CFMutableDictionaryRef dict3 = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
                                                             &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(dict0, CFSTR("36C28AB5-6566-4C50-9EBD-CBB920F83843"), dict3);
    n = 3; // num of keys in this GUID
    key[0] = "current-network";
    key[1] = "preferred-count";
    key[2] = "preferred-networks";
    guid = "36C28AB5-6566-4C50-9EBD-CBB920F83843";

    for ( i = 0; i < n; i++ ) {
        snprintf(name, sizeof(name), "%s:%s", guid, key[i]);
        var = CFStringCreateWithCString(NULL, key[i], kCFStringEncodingUTF8);
        result = GetOFVariable(name, &nameRef, &valueRef);
        if (result == KERN_SUCCESS) {
            CFDictionaryAddValue (dict3, var, valueRef);
            CFRelease(valueRef);
        }
        CFRelease(nameRef);
    }
    CFRelease(dict3);

    return dict;
}

int main(int argc, const char * argv[]) {
    CFDataRef data;
    FILE *fp;
    kern_return_t       result;
    mach_port_t         masterPort;

    result = IOMasterPort(bootstrap_port, &masterPort);
    if (result != KERN_SUCCESS) {
        errx(1, "Error getting the IOMaster port: %s",
             mach_error_string(result));
    }

    gOptionsRef = IORegistryEntryFromPath(masterPort, "IODeviceTree:/options");
    if (gOptionsRef == 0) {
        errx(1, "nvram is not supported on this system");
    }
    CFPropertyListRef propertyList = CreateMyDictionary();
    data = CFPropertyListCreateData( kCFAllocatorDefault, propertyList, kCFPropertyListXMLFormat_v1_0, 0, NULL );
    if (data == NULL) {
        errx(1, "Error converting variables to xml");
    }
    fp = fopen ("/tmp/nvram.plist", "w");
    if (fp == NULL) {
        errx(1, "Error opening /tmp/nvram.plist");
    }
    fwrite(CFDataGetBytePtr(data), sizeof(UInt8), CFDataGetLength(data), fp);
    fclose (fp);
    CFRelease(data);
    IOObjectRelease(gOptionsRef);
    return 0;
}

#else

int main() {
    fprintf(stderr, "This utility is only supported on macOS\n");
    return 1;
}

#endif
