Initial commit: 在 Minecraft Java 版接入 NVIDIA DLSS 超分与帧生成
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
package dev.wpyw.dlss.cfg;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Line-preserving, section-aware editor for ReShade's ini files.
|
||||
*
|
||||
* <p>Two shapes have to be handled, and they are different:
|
||||
* <ul>
|
||||
* <li>{@code ReShade.ini} is a normal ini - every key lives under a {@code [Section]}.</li>
|
||||
* <li>{@code ReShadePreset.ini} puts {@code Techniques=} and {@code TechniqueSorting=} in the
|
||||
* file's <b>preamble</b>, before the first {@code [Effect.fx]} section. Those two keys are
|
||||
* the ones that matter most: {@code Techniques=} says what is enabled, and
|
||||
* {@code TechniqueSorting=} defines execution order - which is what decides whether the
|
||||
* motion-vector provider runs before the shader that consumes it.</li>
|
||||
* </ul>
|
||||
* A {@code null} section therefore means "the preamble". Replacing either file wholesale would
|
||||
* destroy state we do not own (there are hundreds of keys here, and ReShade regenerates defaults
|
||||
* on exit), so edits are applied line by line and everything unknown is preserved verbatim.
|
||||
*/
|
||||
public final class IniFile {
|
||||
|
||||
private final Path path;
|
||||
private final List<String> lines = new ArrayList<>();
|
||||
|
||||
private IniFile(Path path, List<String> lines) {
|
||||
this.path = path;
|
||||
this.lines.addAll(lines);
|
||||
}
|
||||
|
||||
public static IniFile load(Path path) throws IOException {
|
||||
if (Files.isRegularFile(path)) {
|
||||
return new IniFile(path, Files.readAllLines(path, StandardCharsets.UTF_8));
|
||||
}
|
||||
return new IniFile(path, new ArrayList<>());
|
||||
}
|
||||
|
||||
public Path path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public boolean isNew() {
|
||||
return lines.isEmpty();
|
||||
}
|
||||
|
||||
private static boolean isHeader(String line) {
|
||||
String t = line.trim();
|
||||
return t.length() >= 3 && t.startsWith("[") && t.endsWith("]");
|
||||
}
|
||||
|
||||
/** Index of the given section's header line, or -1. */
|
||||
private int headerIndex(String section) {
|
||||
if (section == null || section.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
String want = "[" + section + "]";
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
if (lines.get(i).trim().equalsIgnoreCase(want)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Exclusive end of a section body (the next header, or the end of file). */
|
||||
private int bodyEnd(int headerIdx) {
|
||||
for (int i = headerIdx + 1; i < lines.size(); i++) {
|
||||
if (isHeader(lines.get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return lines.size();
|
||||
}
|
||||
|
||||
/** For the preamble: [first, last) index range that may contain keys. */
|
||||
private int preambleEnd() {
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
if (isHeader(lines.get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return lines.size();
|
||||
}
|
||||
|
||||
private int findKey(String section, String key) {
|
||||
int from;
|
||||
int to;
|
||||
if (section == null || section.isEmpty()) {
|
||||
from = 0;
|
||||
to = preambleEnd();
|
||||
} else {
|
||||
int h = headerIndex(section);
|
||||
if (h < 0) {
|
||||
return -1;
|
||||
}
|
||||
from = h + 1;
|
||||
to = bodyEnd(h);
|
||||
}
|
||||
for (int i = from; i < to; i++) {
|
||||
String line = lines.get(i);
|
||||
int eq = line.indexOf('=');
|
||||
if (eq > 0 && line.substring(0, eq).trim().equalsIgnoreCase(key)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public String get(String section, String key, String fallback) {
|
||||
int i = findKey(section, key);
|
||||
if (i < 0) {
|
||||
return fallback;
|
||||
}
|
||||
String line = lines.get(i);
|
||||
return line.substring(line.indexOf('=') + 1).trim();
|
||||
}
|
||||
|
||||
/** Sets a key, creating the section (or preamble entry) when needed. */
|
||||
public boolean set(String section, String key, String value) {
|
||||
String wanted = key + "=" + value;
|
||||
int i = findKey(section, key);
|
||||
if (i >= 0) {
|
||||
if (lines.get(i).equals(wanted)) {
|
||||
return false;
|
||||
}
|
||||
lines.set(i, wanted);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (section == null || section.isEmpty()) {
|
||||
// Insert at the end of the preamble, i.e. immediately before the first header.
|
||||
lines.add(preambleEnd(), wanted);
|
||||
return true;
|
||||
}
|
||||
|
||||
int h = headerIndex(section);
|
||||
if (h < 0) {
|
||||
if (!lines.isEmpty() && !lines.get(lines.size() - 1).isBlank()) {
|
||||
lines.add("");
|
||||
}
|
||||
lines.add("[" + section + "]");
|
||||
lines.add(wanted);
|
||||
return true;
|
||||
}
|
||||
lines.add(bodyEnd(h), wanted);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates one element of a comma-separated list value, preserving every other element
|
||||
* and their order. Used for {@code PreprocessorDefinitions} and {@code [ADDON] LoadFromDllMain},
|
||||
* where other tools legitimately own entries too.
|
||||
*
|
||||
* @return true when something changed
|
||||
*/
|
||||
public boolean addToListElement(String section, String key, String element) {
|
||||
String current = get(section, key, "");
|
||||
List<String> parts = new ArrayList<>();
|
||||
for (String p : current.split(",")) {
|
||||
String t = p.trim();
|
||||
if (!t.isEmpty()) {
|
||||
parts.add(t);
|
||||
}
|
||||
}
|
||||
String wantedName = element.contains("=") ? element.substring(0, element.indexOf('=')).trim() : element;
|
||||
for (int k = 0; k < parts.size(); k++) {
|
||||
String p = parts.get(k);
|
||||
String name = p.contains("=") ? p.substring(0, p.indexOf('=')).trim() : p;
|
||||
if (name.equalsIgnoreCase(wantedName)) {
|
||||
if (p.equals(element)) {
|
||||
return false;
|
||||
}
|
||||
parts.set(k, element);
|
||||
return set(section, key, String.join(",", parts));
|
||||
}
|
||||
}
|
||||
parts.add(element);
|
||||
return set(section, key, String.join(",", parts));
|
||||
}
|
||||
|
||||
/** Moves an element to the front of a comma-separated list. Used for TechniqueSorting. */
|
||||
public boolean moveToFront(String section, String key, String element) {
|
||||
String current = get(section, key, "");
|
||||
if (current.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
List<String> parts = new ArrayList<>();
|
||||
for (String p : current.split(",")) {
|
||||
String t = p.trim();
|
||||
if (!t.isEmpty()) {
|
||||
parts.add(t);
|
||||
}
|
||||
}
|
||||
int idx = parts.indexOf(element);
|
||||
if (idx == 0) {
|
||||
return false;
|
||||
}
|
||||
if (idx > 0) {
|
||||
parts.remove(idx);
|
||||
}
|
||||
parts.add(0, element);
|
||||
return set(section, key, String.join(",", parts));
|
||||
}
|
||||
|
||||
public void removeKey(String section, String key) {
|
||||
int i = findKey(section, key);
|
||||
if (i >= 0) {
|
||||
lines.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
/** All {@code key=value} pairs of a section (or of the preamble when section is null). */
|
||||
public Map<String, String> section(String section) {
|
||||
Map<String, String> m = new LinkedHashMap<>();
|
||||
int from;
|
||||
int to;
|
||||
if (section == null || section.isEmpty()) {
|
||||
from = 0;
|
||||
to = preambleEnd();
|
||||
} else {
|
||||
int h = headerIndex(section);
|
||||
if (h < 0) {
|
||||
return m;
|
||||
}
|
||||
from = h + 1;
|
||||
to = bodyEnd(h);
|
||||
}
|
||||
for (int i = from; i < to; i++) {
|
||||
String line = lines.get(i);
|
||||
int eq = line.indexOf('=');
|
||||
if (eq > 0) {
|
||||
m.put(line.substring(0, eq).trim(), line.substring(eq + 1).trim());
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
public List<String> rawLines() {
|
||||
return List.copyOf(lines);
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
Path parent = path.getParent();
|
||||
if (parent != null) {
|
||||
Files.createDirectories(parent);
|
||||
}
|
||||
List<String> out = new ArrayList<>(lines);
|
||||
while (!out.isEmpty() && out.get(out.size() - 1).isBlank()) {
|
||||
out.remove(out.size() - 1);
|
||||
}
|
||||
// ReShade writes CRLF; match it so its own diffs stay clean.
|
||||
Files.write(path, (String.join("\r\n", out) + "\r\n").getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user