commit f3faa0350a17655d26a49298a1e33a16e2bd2bf2 Author: cspark Date: Fri Mar 15 11:30:24 2024 +0000 Init nix config diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..577b0a0e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +hardware-configuration.nix diff --git a/configurations/desktop/configuration.nix b/configurations/desktop/configuration.nix new file mode 100644 index 00000000..784c9360 --- /dev/null +++ b/configurations/desktop/configuration.nix @@ -0,0 +1,129 @@ +# Edit this configuration file to define what should be installed on +# your system. Help is available in the configuration.nix(5) man page +# and in the NixOS manual (accessible by running ‘nixos-help’). + +{ config, pkgs, lib, inputs, ... }: + +{ + imports = + [ # Include the results of the hardware scan. + ./../../hardware-configuration.nix + ./../../modules/cspark-single-gpu-passthru/module.nix + inputs.home-manager.nixosModules.default + ]; + + # Bootloader. + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + boot.initrd.luks.devices."luks-9658340a-9d7d-4dbd-be1c-c8c7393ddfc3".device = "/dev/disk/by-uuid/9658340a-9d7d-4dbd-be1c-c8c7393ddfc3"; + + networking.hostName = "cspark-nixos-desktop"; # Define your hostname. + + # Enable networking + networking.networkmanager.enable = true; + # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant. + + # Enable the X11 windowing system. + services.xserver.enable = true; + + # Enable the KDE Plasma Desktop Environment. + services.xserver.displayManager.sddm.enable = true; + services.xserver.displayManager.sddm.wayland.enable = true; + services.desktopManager.plasma6.enable = true; + + # Enable touchpad support (enabled default in most desktopManager). + # services.xserver.libinput.enable = true; + + # GTK themes are not applied in Wayland applications / Window Decorations missing / Cursor looks different + programs.dconf.enable = true; + + # Enable CUPS to print documents. + services.printing.enable = true; + + # Enable sound with pipewire. + sound.enable = true; + hardware.pulseaudio.enable = false; + security.rtkit.enable = true; + services.pipewire = { + enable = true; + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + # If you want to use JACK applications, uncomment this + jack.enable = true; + + # use the example session manager (no others are packaged yet so this is enabled by default, + # no need to redefine it in your config for now) + #media-session.enable = true; + }; + + # My audio interface has issues after coming out from suspend/hibernate, this will reset it to fix it when necessary. + systemd.services.audiointerface-reset = { + description = "Resets audio interface to clear up any issues"; + wantedBy = [ "suspend.target" "hibernate.target" "hybrid-sleep.target" ]; + after = [ "suspend.target" "hibernate.target" "hybrid-sleep.target" ]; + path = with pkgs; [ bash coreutils kmod ]; + enable = true; + serviceConfig = { + User = "root"; + Group = "root"; + ExecStart = + let + script = pkgs.writeShellScript "audiointerface-reset" + '' + # Unbind audio interface + echo 3-3 > /sys/bus/usb/drivers/usb/unbind + # Remove snd_usb_audio once no longer in use + while ! modprobe -r snd_usb_audio; do + sleep 1 + done + + modprobe snd_usb_audio + # Rebind audio interface + echo 3-3 > /sys/bus/usb/drivers/usb/bind + ''; + in + "${script}"; + }; + }; + + # Home Manager Configuration + home-manager = { + extraSpecialArgs = { inherit inputs; }; + users = { + "cspark" = import ./home.nix; + }; + }; + + # Steam Game Platform + programs.steam = { + enable = true; + # remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play + # dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server + gamescopeSession.enable = true; + }; + + # Single GPU Passthru Configuration + cspark-single-gpu-passthru.enable = true; + cspark-single-gpu-passthru.vmName = "win11"; + cspark-single-gpu-passthru.gpuPCI = "0000:08:00.0"; + cspark-single-gpu-passthru.gpuAudioPCI = "0000:08:00.1"; + + # Waydroid Android Emulation + virtualisation.waydroid.enable = true; + + # List packages installed in system profile. To search, run: + # $ nix search wget + environment.systemPackages = with pkgs; [ + git + vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. + cifs-utils + neofetch + appimage-run + wl-clipboard + python3 + usbutils + nvtop-amd + ]; + +} diff --git a/configurations/desktop/home.nix b/configurations/desktop/home.nix new file mode 100644 index 00000000..cefc20fc --- /dev/null +++ b/configurations/desktop/home.nix @@ -0,0 +1,81 @@ +{ config, pkgs, ... }: + +{ + imports = + [ + # Global Config, always to be included + ./../../global-home.nix + ]; + + # KDE Connect + services.kdeconnect.enable = true; + services.kdeconnect.indicator = true; + + # Home Manager is pretty good at managing dotfiles. The primary way to manage + # plain files is through 'home.file'. + home.file = { + # # Building this configuration will create a copy of 'dotfiles/screenrc' in + # # the Nix store. Activating the configuration will then make '~/.screenrc' a + # # symlink to the Nix store copy. + # ".screenrc".source = dotfiles/screenrc; + + # # You can also set the file content immediately. + # ".gradle/gradle.properties".text = '' + # org.gradle.console=verbose + # org.gradle.daemon.idletimeout=3600000 + # ''; + ".local/share/applications/ncmpcpp.desktop".text = '' + [Desktop Entry] + Name=NCMPCPP + Comment=Run ncurses music player CPP + Categories=Other; + Icon=konsole + Type=Application + Exec=konsole -e ncmpcpp + Terminal=false + ''; + }; + + # Roblox Curl Service + systemd.user.services.roblox-curl = { + Unit = { + Description = "Check what roblox users are online"; + }; + Service = { + ExecStart = "%h/Documents/RobloxCurlOnline.sh"; + }; + }; + systemd.user.timers.roblox-curl = { + Unit = { + Description = "Check what roblox users are online - hourly"; + }; + Timer = { + OnCalendar = "hourly"; + }; + Install = { + WantedBy = [ "timers.target" ]; + }; + }; + + # VRChat Curl Service + systemd.user.services.vrchat-curl = { + Unit = { + Description = "Check what vrchat users are online"; + }; + Service = { + ExecStart = "%h/Documents/VRChatCurlOnline.sh"; + }; + }; + systemd.user.timers.vrchat-curl = { + Unit = { + Description = "Check what vrchat users are online - hourly"; + }; + Timer = { + OnCalendar = "hourly"; + }; + Install = { + WantedBy = [ "timers.target" ]; + }; + }; + +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..728c4895 --- /dev/null +++ b/flake.lock @@ -0,0 +1,48 @@ +{ + "nodes": { + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1710452332, + "narHash": "sha256-+lKOoQ89fD6iz6Ro7Adml4Sx6SqQcTWII4t1rvVtdjs=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "096d9c04b3e9438855aa65e24129b97a998bd3d9", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1710272261, + "narHash": "sha256-g0bDwXFmTE7uGDOs9HcJsfLFhH7fOsASbAuOzDC+fhQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "0ad13a6833440b8e238947e47bea7f11071dc2b2", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "home-manager": "home-manager", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..5df4a139 --- /dev/null +++ b/flake.nix @@ -0,0 +1,24 @@ +{ + description = "Nixos config flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, ... }@inputs: { + nixosConfigurations = { + desktop = nixpkgs.lib.nixosSystem { + specialArgs = {inherit inputs;}; + modules = [ + ./global.nix + ./configurations/desktop/configuration.nix + ]; + }; + }; + }; +} diff --git a/global-home.nix b/global-home.nix new file mode 100644 index 00000000..8f8e6537 --- /dev/null +++ b/global-home.nix @@ -0,0 +1,124 @@ +{ config, pkgs, ... }: + +let + homedir = "/home/cspark"; +in +{ + # Home Manager needs a bit of information about you and the paths it should + # manage. + home.username = "cspark"; + home.homeDirectory = "${homedir}"; + + # This value determines the Home Manager release that your configuration is + # compatible with. This helps avoid breakage when a new Home Manager release + # introduces backwards incompatible changes. + # + # You should not change this value, even if you update Home Manager. If you do + # want to update the value, then make sure to first check the Home Manager + # release notes. + home.stateVersion = "23.11"; # Please read the comment before changing. + + # Allow unfree packages + nixpkgs.config.allowUnfree = true; + + # The home.packages option allows you to install Nix packages into your + # environment. + home.packages = with pkgs; [ + # # Adds the 'hello' command to your environment. It prints a friendly + # # "Hello, world!" when run. + # pkgs.hello + + # # It is sometimes useful to fine-tune packages, for example, by applying + # # overrides. You can do that directly here, just don't forget the + # # parentheses. Maybe you want to install Nerd Fonts with a limited number of + # # fonts? + # (pkgs.nerdfonts.override { fonts = [ "FantasqueSansMono" ]; }) + + # # You can also create simple shell scripts directly inside your + # # configuration. For example, this adds a command 'my-hello' to your + # # environment: + # (pkgs.writeShellScriptBin "my-hello" '' + # echo "Hello, ${config.home.username}!" + # '') + # wineWowPackages.full + # native wayland support (unstable) + wineWowPackages.waylandFull + + firefox + librewolf + ungoogled-chromium + emacs + telegram-desktop + discord + lutris + protonup-qt + qpwgraph + ardour + lsp-plugins + yabridge + yabridgectl + kdenlive + vlc + + pass-wayland + thunderbird + + # Just for utils + pulseaudio + + # For curl scripts + jq + libnotify + ]; + + services.mpd = { + enable = true; + musicDirectory = "${homedir}/Spool2_Secret/Music/Flac"; + network.startWhenNeeded = true; + extraConfig = '' + audio_output { + type "pipewire" + name "PipeWire Output" + } + ''; + }; + services.mpd-discord-rpc.enable = true; + programs.ncmpcpp.enable = true; + + # Home Manager can also manage your environment variables through + # 'home.sessionVariables'. If you don't want to manage your shell through Home + # Manager then you have to manually source 'hm-session-vars.sh' located at + # either + # + # ~/.nix-profile/etc/profile.d/hm-session-vars.sh + # + # or + # + # ~/.local/state/nix/profiles/profile/etc/profile.d/hm-session-vars.sh + # + # or + # + # /etc/profiles/per-user/cspark/etc/profile.d/hm-session-vars.sh + # + home.sessionVariables = { + EDITOR = "emacs -nw"; + }; + + programs.bash = { + enable = true; + enableCompletion = true; + bashrcExtra = "neofetch"; + initExtra = '' + . "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh" + ''; + }; + + programs.git = { + enable = true; + userName = "cspark"; + userEmail = "git@cspark.dev"; + }; + + # Let Home Manager install and manage itself. + programs.home-manager.enable = true; +} diff --git a/global.nix b/global.nix new file mode 100644 index 00000000..b5749443 --- /dev/null +++ b/global.nix @@ -0,0 +1,92 @@ +# Edit this configuration file to define what should be installed on +# your system. Help is available in the configuration.nix(5) man page +# and in the NixOS manual (accessible by running ‘nixos-help’). + +{ config, pkgs, lib, inputs, ... }: + +{ + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + + # Set your time zone. + time.timeZone = "Europe/London"; + + # Select internationalisation properties. + i18n.defaultLocale = "en_GB.UTF-8"; + + i18n.extraLocaleSettings = { + LC_ADDRESS = "en_GB.UTF-8"; + LC_IDENTIFICATION = "en_GB.UTF-8"; + LC_MEASUREMENT = "en_GB.UTF-8"; + LC_MONETARY = "en_GB.UTF-8"; + LC_NAME = "en_GB.UTF-8"; + LC_NUMERIC = "en_GB.UTF-8"; + LC_PAPER = "en_GB.UTF-8"; + LC_TELEPHONE = "en_GB.UTF-8"; + LC_TIME = "en_GB.UTF-8"; + }; + + # Configure keymap in X11 + services.xserver.xkb = { + layout = "gb"; + variant = ""; + }; + + # Configure console keymap + console.keyMap = "uk"; + + # Define a user account. Don't forget to set a password with ‘passwd’. + users.users.cspark = { + isNormalUser = true; + description = "Curt Spark"; + extraGroups = [ "networkmanager" "wheel" "libvirtd" "libvirt" "kvm" "input" ]; + }; + + # Allow unfree packages + nixpkgs.config.allowUnfree = true; + + # Due to NixOS not using FHS paths, many DAWs will not know where to look for VSTs and other plugins. You can solve this by setting + environment.variables = let + makePluginPath = format: + (lib.strings.makeSearchPath format [ + "$HOME/.nix-profile/lib" + "/run/current-system/sw/lib" + "/etc/profiles/per-user/$USER/lib" + ]) + + ":$HOME/.${format}"; + in { + DSSI_PATH = makePluginPath "dssi"; + LADSPA_PATH = makePluginPath "ladspa"; + LV2_PATH = makePluginPath "lv2"; + LXVST_PATH = makePluginPath "lxvst"; + VST_PATH = makePluginPath "vst"; + VST3_PATH = makePluginPath "vst3"; + }; + + # Some programs need SUID wrappers, can be configured further or are + # started in user sessions. + # programs.mtr.enable = true; + programs.gnupg.agent = { + enable = true; + enableSSHSupport = true; + }; + + # List services that you want to enable: + + # Enable the OpenSSH daemon. + services.openssh.enable = true; + + # Open ports in the firewall. + # networking.firewall.allowedTCPPorts = [ ... ]; + # networking.firewall.allowedUDPPorts = [ ... ]; + # Or disable the firewall altogether. + # networking.firewall.enable = false; + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "23.11"; # Did you read the comment? + +} diff --git a/modules/cspark-single-gpu-passthru/module.nix b/modules/cspark-single-gpu-passthru/module.nix new file mode 100644 index 00000000..ba26243e --- /dev/null +++ b/modules/cspark-single-gpu-passthru/module.nix @@ -0,0 +1,112 @@ +{ lib, config, pkgs, modulesPath, ... }: + +let + cfg = config.cspark-single-gpu-passthru; +in +{ + options.cspark-single-gpu-passthru = { + enable + = lib.mkEnableOption "Enable Single GPU Passthrough Setup"; + + vmName = lib.mkOption { + default = "win11"; + description = '' + Name of the virtual machine + ''; + }; + + gpuPCI = lib.mkOption { + default = "0000:08:00.0"; + description = '' + PCI ID of GPU VGA + ''; + }; + + gpuAudioPCI = lib.mkOption { + default = "0000:08:00.1"; + description = '' + PCI ID of GPU Audio + ''; + }; + }; + + config = lib.mkIf cfg.enable { + # Virtualisation, Virt Manager and Waydroid + virtualisation.libvirtd = { + enable = true; + qemu = { + package = pkgs.qemu_kvm; + runAsRoot = true; + swtpm.enable = true; + ovmf = { + enable = true; + packages = [(pkgs.OVMF.override { + secureBoot = true; + tpmSupport = true; + }).fd]; + }; + }; + }; + programs.virt-manager.enable = true; + systemd.services.libvirtd = { + # Add binaries to path so that hooks can use it + path = let + env = pkgs.buildEnv { + name = "qemu-hook-env"; + paths = with pkgs; [ + bash + libvirt + kmod + systemd + ripgrep + sd + procps + gawk + ]; + }; + in + [ env ]; + + preStart = let + qemuscript_source = builtins.readFile ./resources/libvirt/hooks/qemu; + startscript_source = builtins.readFile ./resources/libvirt/hooks/qemu.d/vm/prepare/begin/start.sh; + revertscript_source = builtins.readFile ./resources/libvirt/hooks/qemu.d/vm/release/end/revert.sh; + qemuscript = pkgs.writeShellScript "cspark-single-gpu-passthru-qemu" revertscript_source; + startscript = pkgs.writeShellScript "cspark-single-gpu-passthru-start.sh" startscript_source; + revertscript = pkgs.writeShellScript "cspark-single-gpu-passthru-revert.sh" revertscript_source; + gpuvbios = ./resources/libvirt/vgabios/gpuvbios.rom; + in + '' + mkdir -p /var/lib/libvirt/hooks + mkdir -p /var/lib/libvirt/hooks/qemu.d/${cfg.vmName}/prepare/begin + mkdir -p /var/lib/libvirt/hooks/qemu.d/${cfg.vmName}/release/end + mkdir -p /var/lib/libvirt/vgabios + + ln -sf ${qemuscript} /var/lib/libvirt/hooks/qemu + ln -sf ${startscript} /var/lib/libvirt/hooks/qemu.d/${cfg.vmName}/prepare/begin/start.sh + ln -sf ${revertscript} /var/lib/libvirt/hooks/qemu.d/${cfg.vmName}/release/end/revert.sh + ln -sf ${gpuvbios} /var/lib/libvirt/vgabios/gpuvbios.rom + +# chmod +x /var/lib/libvirt/hooks/qemu +# chmod +x /var/lib/libvirt/hooks/qemu.d/${cfg.vmName}/prepare/begin/start.sh +# chmod +x /var/lib/libvirt/hooks/qemu.d/${cfg.vmName}/release/end/revert.sh + ''; + }; + # Vendor Reset Patches for Single GPU Passthrough - https://github.com/gnif/vendor-reset + boot.extraModulePackages = with config.boot.kernelPackages; [ vendor-reset ]; + # Ensures GPU is configured to use vendor reset module + systemd.services.vendor-reset-config = { + description = "Ensures GPU is configured to use vendor reset module"; + wantedBy = [ "multi-user.target" ]; + path = [ pkgs.coreutils ]; + enable = true; + serviceConfig = { + User = "root"; + Group = "root"; + }; + script = ''echo device_specific > "/sys/bus/pci/devices/${cfg.gpuPCI}/reset_method"''; + }; + hardware.opengl.enable = true; + virtualisation.spiceUSBRedirection.enable = true; + }; +} diff --git a/modules/cspark-single-gpu-passthru/resources/libvirt/bootstrap.sh b/modules/cspark-single-gpu-passthru/resources/libvirt/bootstrap.sh new file mode 100755 index 00000000..a3b454bb --- /dev/null +++ b/modules/cspark-single-gpu-passthru/resources/libvirt/bootstrap.sh @@ -0,0 +1,13 @@ +sudo cp ~/libvirt/hooks/qemu /var/lib/libvirt/hooks/qemu +sudo chmod +x /var/lib/libvirt/hooks/qemu + +sudo mkdir -p /var/lib/libvirt/hooks/qemu.d/win11/prepare/begin +sudo cp ~/libvirt/hooks/qemu.d/win11/prepare/begin/start.sh /var/lib/libvirt/hooks/qemu.d/win11/prepare/begin/start.sh +sudo chmod +x /var/lib/libvirt/hooks/qemu.d/win11/prepare/begin/start.sh + +sudo mkdir -p /var/lib/libvirt/hooks/qemu.d/win11/release/end +sudo cp ~/libvirt/hooks/qemu.d/win11/release/end/stop.sh /var/lib/libvirt/hooks/qemu.d/win11/release/end/stop.sh +sudo chmod +x /var/lib/libvirt/hooks/qemu.d/win11/release/end/stop.sh + +sudo mkdir -p /var/lib/libvirt/vgabios +sudo cp ~/libvirt/vgabios/gpuvbios.rom /var/lib/libvirt/vgabios/gpuvbios.rom diff --git a/modules/cspark-single-gpu-passthru/resources/libvirt/hooks/qemu b/modules/cspark-single-gpu-passthru/resources/libvirt/hooks/qemu new file mode 100755 index 00000000..c23d76ba --- /dev/null +++ b/modules/cspark-single-gpu-passthru/resources/libvirt/hooks/qemu @@ -0,0 +1,19 @@ +#!/run/current-system/sw/bin/bash + +GUEST_NAME="$1" +HOOK_NAME="$2" +STATE_NAME="$3" +MISC="${@:4}" + +BASEDIR="$(dirname $0)" + +HOOKPATH="$BASEDIR/qemu.d/$GUEST_NAME/$HOOK_NAME/$STATE_NAME" +set -e # If a script exits with an error, we should as well. + +if [ -f "$HOOKPATH" ]; then +eval \""$HOOKPATH"\" "$@" +elif [ -d "$HOOKPATH" ]; then +while read file; do + eval \""$file"\" "$@" +done <<< "$(find -L "$HOOKPATH" -maxdepth 1 -type f -executable -print;)" +fi diff --git a/modules/cspark-single-gpu-passthru/resources/libvirt/hooks/qemu.d/vm/prepare/begin/start.sh b/modules/cspark-single-gpu-passthru/resources/libvirt/hooks/qemu.d/vm/prepare/begin/start.sh new file mode 100755 index 00000000..0e2ee937 --- /dev/null +++ b/modules/cspark-single-gpu-passthru/resources/libvirt/hooks/qemu.d/vm/prepare/begin/start.sh @@ -0,0 +1,25 @@ +set -x + +# Stop desktop environment and display manager +pkill kwin_wayland +systemctl --user -M cspark stop plasma* +systemctl stop display-manager +pkill sddm +pkill sddm-helper + +# Log out of session +session=`loginctl session-status | awk 'NR==1{print $1}'` +loginctl terminate-session $session + +# My audio interface has issues after coming out from a suspended type state, this will reset it to fix it when necessary. +# Unbind audio interface +echo 3-3 > /sys/bus/usb/drivers/usb/unbind +# Remove snd_usb_audio once no longer in use +while ! modprobe -r snd_usb_audio; do + sleep 1 +done + +# Remove amdgpu once no longer in use +while ! modprobe -r amdgpu; do + sleep 1 +done diff --git a/modules/cspark-single-gpu-passthru/resources/libvirt/hooks/qemu.d/vm/release/end/revert.sh b/modules/cspark-single-gpu-passthru/resources/libvirt/hooks/qemu.d/vm/release/end/revert.sh new file mode 100755 index 00000000..35310dc3 --- /dev/null +++ b/modules/cspark-single-gpu-passthru/resources/libvirt/hooks/qemu.d/vm/release/end/revert.sh @@ -0,0 +1,19 @@ +set -x + +# My audio interface has issues after coming out from a suspended type state, this will reset it to fix it when necessary. +modprobe snd_usb_audio +# Rebind audio interface +echo 3-3 > /sys/bus/usb/drivers/usb/bind + +# Remove vfio modules +modprobe -r vfio_pci +modprobe -r vfio + +# Re-add amdgpu +modprobe amdgpu + +# Avoid race condition whilst re-initalising amdgpu driver +sleep 5 + +# Restart Display Manager +systemctl start display-manager diff --git a/modules/cspark-single-gpu-passthru/resources/libvirt/vgabios/gpuvbios.rom b/modules/cspark-single-gpu-passthru/resources/libvirt/vgabios/gpuvbios.rom new file mode 100644 index 00000000..e1391189 Binary files /dev/null and b/modules/cspark-single-gpu-passthru/resources/libvirt/vgabios/gpuvbios.rom differ