mirror of
https://github.com/neoStudiosLCE/neoLegacy.git
synced 2026-05-22 00:08:42 +00:00
Add VSync and fullscreen settings, fix swap chain resize and revert lighting changes
- Add VSync and Exclusive Fullscreen toggles to the graphics settings menu - Rewrite D3D11 swap chain to use DXGI flip model with tearing support - Fix black screen on resize by creating new swap chain instead of ResizeBuffers - Revert conditional lighting optimization in Level::setTileAndData back to unconditional checkLight - Revert deferred lightGaps flagging in LevelChunk::recalcHeight back to immediate lightGap calls - Add SWF/ARC editing tools used to add new UI checkboxes
This commit is contained in:
BIN
tools/AddExclusiveFullscreenCheckbox.class
Normal file
BIN
tools/AddExclusiveFullscreenCheckbox.class
Normal file
Binary file not shown.
158
tools/AddExclusiveFullscreenCheckbox.java
Normal file
158
tools/AddExclusiveFullscreenCheckbox.java
Normal file
@@ -0,0 +1,158 @@
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.tags.*;
|
||||
import com.jpexs.decompiler.flash.tags.base.*;
|
||||
import com.jpexs.decompiler.flash.types.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Adds an "ExclusiveFullscreen" checkbox to SettingsGraphicsMenu SWF files.
|
||||
* Clones the existing VSync checkbox, renames it "ExclusiveFullscreen",
|
||||
* positions it below VSync, and shifts sliders down.
|
||||
*/
|
||||
public class AddExclusiveFullscreenCheckbox {
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 1) {
|
||||
System.out.println("Usage: AddExclusiveFullscreenCheckbox <swf_file> [output_file]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String inputPath = args[0];
|
||||
String outputPath = args.length > 1 ? args[1] : inputPath;
|
||||
|
||||
SWF swf = new SWF(new FileInputStream(inputPath), false);
|
||||
|
||||
// Check if ExclusiveFullscreen already exists
|
||||
for (Tag tag : swf.getTags()) {
|
||||
if (tag instanceof PlaceObject3Tag) {
|
||||
PlaceObject3Tag po = (PlaceObject3Tag) tag;
|
||||
if ("ExclusiveFullscreen".equals(po.name)) {
|
||||
System.out.println("ExclusiveFullscreen checkbox already exists in " + inputPath + ", skipping.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the VSync checkbox tag and all slider tags
|
||||
PlaceObject3Tag vsyncTag = null;
|
||||
PlaceObject3Tag customSkinAnimTag = null;
|
||||
List<PlaceObject3Tag> sliderTags = new ArrayList<>();
|
||||
int maxDepth = 0;
|
||||
|
||||
for (Tag tag : swf.getTags()) {
|
||||
if (tag instanceof PlaceObject3Tag) {
|
||||
PlaceObject3Tag po = (PlaceObject3Tag) tag;
|
||||
if ("VSync".equals(po.name)) {
|
||||
vsyncTag = po;
|
||||
}
|
||||
if ("CustomSkinAnim".equals(po.name)) {
|
||||
customSkinAnimTag = po;
|
||||
}
|
||||
if (po.name != null && (po.name.equals("RenderDistance") || po.name.equals("Gamma")
|
||||
|| po.name.equals("FOV") || po.name.equals("InterfaceOpacity"))) {
|
||||
sliderTags.add(po);
|
||||
}
|
||||
if (po.depth > maxDepth) maxDepth = po.depth;
|
||||
}
|
||||
if (tag instanceof PlaceObject2Tag) {
|
||||
PlaceObject2Tag po = (PlaceObject2Tag) tag;
|
||||
if (po.depth > maxDepth) maxDepth = po.depth;
|
||||
}
|
||||
}
|
||||
|
||||
if (vsyncTag == null) {
|
||||
System.err.println("ERROR: Could not find VSync checkbox in " + inputPath);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Calculate checkbox Y-spacing from the gap between CustomSkinAnim and VSync
|
||||
int checkboxSpacing;
|
||||
if (customSkinAnimTag != null) {
|
||||
checkboxSpacing = vsyncTag.matrix.translateY - customSkinAnimTag.matrix.translateY;
|
||||
} else {
|
||||
checkboxSpacing = vsyncTag.matrix.translateY > 8000 ? 1080 : 720;
|
||||
}
|
||||
|
||||
System.out.println("File: " + inputPath);
|
||||
System.out.println(" VSync Y: " + vsyncTag.matrix.translateY);
|
||||
System.out.println(" Checkbox spacing: " + checkboxSpacing);
|
||||
|
||||
// Create the ExclusiveFullscreen PlaceObject3Tag by copying fields from VSync
|
||||
PlaceObject3Tag efTag = new PlaceObject3Tag(swf);
|
||||
efTag.placeFlagHasClassName = vsyncTag.placeFlagHasClassName;
|
||||
efTag.placeFlagHasName = true;
|
||||
efTag.placeFlagHasMatrix = true;
|
||||
efTag.placeFlagHasImage = vsyncTag.placeFlagHasImage;
|
||||
efTag.placeFlagHasCharacter = vsyncTag.placeFlagHasCharacter;
|
||||
efTag.placeFlagMove = vsyncTag.placeFlagMove;
|
||||
efTag.className = vsyncTag.className;
|
||||
efTag.name = "ExclusiveFullscreen";
|
||||
efTag.depth = maxDepth + 1;
|
||||
efTag.characterId = vsyncTag.characterId;
|
||||
|
||||
// Copy and offset the matrix
|
||||
MATRIX m = new MATRIX(vsyncTag.matrix);
|
||||
m.translateY += checkboxSpacing;
|
||||
efTag.matrix = m;
|
||||
|
||||
efTag.setModified(true);
|
||||
|
||||
System.out.println(" ExclusiveFullscreen Y: " + m.translateY);
|
||||
System.out.println(" ExclusiveFullscreen depth: " + efTag.depth);
|
||||
|
||||
// Shift all sliders down by checkboxSpacing
|
||||
for (PlaceObject3Tag slider : sliderTags) {
|
||||
System.out.println(" Shifting " + slider.name + " from Y=" + slider.matrix.translateY
|
||||
+ " to Y=" + (slider.matrix.translateY + checkboxSpacing));
|
||||
slider.matrix.translateY += checkboxSpacing;
|
||||
slider.setModified(true);
|
||||
}
|
||||
|
||||
// Expand the background panel height
|
||||
for (Tag tag : swf.getTags()) {
|
||||
if (tag instanceof PlaceObject2Tag) {
|
||||
PlaceObject2Tag po = (PlaceObject2Tag) tag;
|
||||
if ("BackgroundPanel".equals(po.name)) {
|
||||
int charId = po.characterId;
|
||||
CharacterTag ct = swf.getCharacter(charId);
|
||||
if (ct instanceof DefineSpriteTag) {
|
||||
DefineSpriteTag sprite = (DefineSpriteTag) ct;
|
||||
for (Tag sub : sprite.getTags()) {
|
||||
if (sub instanceof PlaceObject3Tag) {
|
||||
PlaceObject3Tag gridTag = (PlaceObject3Tag) sub;
|
||||
float oldScaleY = gridTag.matrix.scaleY;
|
||||
gridTag.matrix.scaleY += (float) checkboxSpacing / 640.0f;
|
||||
gridTag.setModified(true);
|
||||
System.out.println(" Background panel scaleY: " + oldScaleY + " -> " + gridTag.matrix.scaleY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert ExclusiveFullscreen tag right after VSync
|
||||
ArrayList<Tag> tagList = swf.getTags().toArrayList();
|
||||
int insertIdx = -1;
|
||||
for (int i = 0; i < tagList.size(); i++) {
|
||||
if (tagList.get(i) == vsyncTag) {
|
||||
insertIdx = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertIdx >= 0) {
|
||||
swf.addTag(insertIdx, efTag);
|
||||
System.out.println(" Inserted ExclusiveFullscreen tag at index " + insertIdx);
|
||||
} else {
|
||||
System.err.println("ERROR: Could not find insertion point");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Save
|
||||
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
|
||||
swf.saveTo(fos);
|
||||
}
|
||||
System.out.println(" Saved to: " + outputPath);
|
||||
}
|
||||
}
|
||||
BIN
tools/AddVSyncCheckbox.class
Normal file
BIN
tools/AddVSyncCheckbox.class
Normal file
Binary file not shown.
160
tools/AddVSyncCheckbox.java
Normal file
160
tools/AddVSyncCheckbox.java
Normal file
@@ -0,0 +1,160 @@
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.tags.*;
|
||||
import com.jpexs.decompiler.flash.tags.base.*;
|
||||
import com.jpexs.decompiler.flash.types.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Adds a "VSync" checkbox to SettingsGraphicsMenu SWF files.
|
||||
* Clones the existing CustomSkinAnim checkbox, renames it "VSync",
|
||||
* positions it below CustomSkinAnim, and shifts sliders down.
|
||||
*/
|
||||
public class AddVSyncCheckbox {
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 1) {
|
||||
System.out.println("Usage: AddVSyncCheckbox <swf_file> [output_file]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String inputPath = args[0];
|
||||
String outputPath = args.length > 1 ? args[1] : inputPath;
|
||||
|
||||
SWF swf = new SWF(new FileInputStream(inputPath), false);
|
||||
|
||||
// Check if VSync already exists
|
||||
for (Tag tag : swf.getTags()) {
|
||||
if (tag instanceof PlaceObject3Tag) {
|
||||
PlaceObject3Tag po = (PlaceObject3Tag) tag;
|
||||
if ("VSync".equals(po.name)) {
|
||||
System.out.println("VSync checkbox already exists in " + inputPath + ", skipping.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the CustomSkinAnim checkbox tag and all slider tags
|
||||
PlaceObject3Tag customSkinAnimTag = null;
|
||||
PlaceObject3Tag bedrockFogTag = null;
|
||||
List<PlaceObject3Tag> sliderTags = new ArrayList<>();
|
||||
int maxDepth = 0;
|
||||
|
||||
for (Tag tag : swf.getTags()) {
|
||||
if (tag instanceof PlaceObject3Tag) {
|
||||
PlaceObject3Tag po = (PlaceObject3Tag) tag;
|
||||
if ("CustomSkinAnim".equals(po.name)) {
|
||||
customSkinAnimTag = po;
|
||||
}
|
||||
if ("BedrockFog".equals(po.name)) {
|
||||
bedrockFogTag = po;
|
||||
}
|
||||
if (po.name != null && (po.name.equals("RenderDistance") || po.name.equals("Gamma")
|
||||
|| po.name.equals("FOV") || po.name.equals("InterfaceOpacity"))) {
|
||||
sliderTags.add(po);
|
||||
}
|
||||
if (po.depth > maxDepth) maxDepth = po.depth;
|
||||
}
|
||||
if (tag instanceof PlaceObject2Tag) {
|
||||
PlaceObject2Tag po = (PlaceObject2Tag) tag;
|
||||
if (po.depth > maxDepth) maxDepth = po.depth;
|
||||
}
|
||||
}
|
||||
|
||||
if (customSkinAnimTag == null) {
|
||||
System.err.println("ERROR: Could not find CustomSkinAnim checkbox in " + inputPath);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Calculate checkbox Y-spacing
|
||||
int checkboxSpacing;
|
||||
if (bedrockFogTag != null) {
|
||||
checkboxSpacing = customSkinAnimTag.matrix.translateY - bedrockFogTag.matrix.translateY;
|
||||
} else {
|
||||
checkboxSpacing = customSkinAnimTag.matrix.translateY > 8000 ? 1080 : 720;
|
||||
}
|
||||
|
||||
System.out.println("File: " + inputPath);
|
||||
System.out.println(" CustomSkinAnim Y: " + customSkinAnimTag.matrix.translateY);
|
||||
System.out.println(" Checkbox spacing: " + checkboxSpacing);
|
||||
|
||||
// Create the VSync PlaceObject3Tag by copying fields from CustomSkinAnim
|
||||
PlaceObject3Tag vsyncTag = new PlaceObject3Tag(swf);
|
||||
vsyncTag.placeFlagHasClassName = customSkinAnimTag.placeFlagHasClassName;
|
||||
vsyncTag.placeFlagHasName = true;
|
||||
vsyncTag.placeFlagHasMatrix = true;
|
||||
vsyncTag.placeFlagHasImage = customSkinAnimTag.placeFlagHasImage;
|
||||
vsyncTag.placeFlagHasCharacter = customSkinAnimTag.placeFlagHasCharacter;
|
||||
vsyncTag.placeFlagMove = customSkinAnimTag.placeFlagMove;
|
||||
vsyncTag.className = customSkinAnimTag.className;
|
||||
vsyncTag.name = "VSync";
|
||||
vsyncTag.depth = maxDepth + 1;
|
||||
vsyncTag.characterId = customSkinAnimTag.characterId;
|
||||
|
||||
// Copy and offset the matrix
|
||||
MATRIX m = new MATRIX(customSkinAnimTag.matrix);
|
||||
m.translateY += checkboxSpacing;
|
||||
vsyncTag.matrix = m;
|
||||
|
||||
vsyncTag.setModified(true);
|
||||
|
||||
System.out.println(" VSync Y: " + m.translateY);
|
||||
System.out.println(" VSync depth: " + vsyncTag.depth);
|
||||
System.out.println(" VSync className: " + vsyncTag.className);
|
||||
|
||||
// Shift all sliders down by checkboxSpacing
|
||||
for (PlaceObject3Tag slider : sliderTags) {
|
||||
System.out.println(" Shifting " + slider.name + " from Y=" + slider.matrix.translateY
|
||||
+ " to Y=" + (slider.matrix.translateY + checkboxSpacing));
|
||||
slider.matrix.translateY += checkboxSpacing;
|
||||
slider.setModified(true);
|
||||
}
|
||||
|
||||
// Expand the background panel height
|
||||
for (Tag tag : swf.getTags()) {
|
||||
if (tag instanceof PlaceObject2Tag) {
|
||||
PlaceObject2Tag po = (PlaceObject2Tag) tag;
|
||||
if ("BackgroundPanel".equals(po.name)) {
|
||||
int charId = po.characterId;
|
||||
CharacterTag ct = swf.getCharacter(charId);
|
||||
if (ct instanceof DefineSpriteTag) {
|
||||
DefineSpriteTag sprite = (DefineSpriteTag) ct;
|
||||
for (Tag sub : sprite.getTags()) {
|
||||
if (sub instanceof PlaceObject3Tag) {
|
||||
PlaceObject3Tag gridTag = (PlaceObject3Tag) sub;
|
||||
float oldScaleY = gridTag.matrix.scaleY;
|
||||
// The 9-grid base tile is 640 twips (32px * 20 twips/px)
|
||||
gridTag.matrix.scaleY += (float) checkboxSpacing / 640.0f;
|
||||
gridTag.setModified(true);
|
||||
System.out.println(" Background panel scaleY: " + oldScaleY + " -> " + gridTag.matrix.scaleY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert VSync tag right after CustomSkinAnim
|
||||
ArrayList<Tag> tagList = swf.getTags().toArrayList();
|
||||
int insertIdx = -1;
|
||||
for (int i = 0; i < tagList.size(); i++) {
|
||||
if (tagList.get(i) == customSkinAnimTag) {
|
||||
insertIdx = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertIdx >= 0) {
|
||||
swf.addTag(insertIdx, vsyncTag);
|
||||
System.out.println(" Inserted VSync tag at index " + insertIdx);
|
||||
} else {
|
||||
System.err.println("ERROR: Could not find insertion point");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Save
|
||||
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
|
||||
swf.saveTo(fos);
|
||||
}
|
||||
System.out.println(" Saved to: " + outputPath);
|
||||
}
|
||||
}
|
||||
BIN
tools/DumpSwf.class
Normal file
BIN
tools/DumpSwf.class
Normal file
Binary file not shown.
40
tools/DumpSwf.java
Normal file
40
tools/DumpSwf.java
Normal file
@@ -0,0 +1,40 @@
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.tags.*;
|
||||
import com.jpexs.decompiler.flash.tags.base.*;
|
||||
import com.jpexs.decompiler.flash.types.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class DumpSwf {
|
||||
public static void main(String[] args) throws Exception {
|
||||
String path = args[0];
|
||||
SWF swf = new SWF(new FileInputStream(path), false);
|
||||
|
||||
System.out.println("=== Top-level Tags ===");
|
||||
int idx = 0;
|
||||
for (Tag tag : swf.getTags()) {
|
||||
System.out.println("[" + idx + "] " + tag.getClass().getSimpleName() + " - " + tag);
|
||||
dumpPlaceObject(tag, " ");
|
||||
|
||||
if (tag instanceof DefineSpriteTag) {
|
||||
DefineSpriteTag sprite = (DefineSpriteTag) tag;
|
||||
System.out.println(" spriteId=" + sprite.spriteId + " frames=" + sprite.frameCount);
|
||||
int subIdx = 0;
|
||||
for (Tag sub : sprite.getTags()) {
|
||||
System.out.println(" [" + subIdx + "] " + sub.getClass().getSimpleName() + " - " + sub);
|
||||
dumpPlaceObject(sub, " ");
|
||||
subIdx++;
|
||||
}
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
static void dumpPlaceObject(Tag tag, String indent) {
|
||||
if (tag instanceof PlaceObjectTypeTag) {
|
||||
PlaceObjectTypeTag po = (PlaceObjectTypeTag) tag;
|
||||
System.out.println(indent + "depth=" + po.getDepth() + " charId=" + po.getCharacterId()
|
||||
+ " name=" + po.getInstanceName() + " matrix=" + po.getMatrix());
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
tools/RebuildArc.class
Normal file
BIN
tools/RebuildArc.class
Normal file
Binary file not shown.
168
tools/RebuildArc.java
Normal file
168
tools/RebuildArc.java
Normal file
@@ -0,0 +1,168 @@
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Rebuilds a MediaWindows64.arc archive file by replacing specific SWF files
|
||||
* with updated versions from disk.
|
||||
*
|
||||
* Usage: RebuildArc <arc_file> <media_dir> [file1.swf file2.swf ...]
|
||||
*
|
||||
* If no specific files are given, replaces all SWF files found in media_dir.
|
||||
* The arc format is Java DataOutputStream style: big-endian ints, modified UTF-8 strings.
|
||||
*
|
||||
* Archive format:
|
||||
* int: numberOfFiles
|
||||
* For each file:
|
||||
* UTF: filename (prefixed with '*' if compressed)
|
||||
* int: offset into data section
|
||||
* int: filesize
|
||||
* Raw file data follows the header
|
||||
*/
|
||||
public class RebuildArc {
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 2) {
|
||||
System.out.println("Usage: RebuildArc <arc_file> <media_dir> [file1.swf file2.swf ...]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String arcPath = args[0];
|
||||
String mediaDir = args[1];
|
||||
Set<String> replaceFiles = new HashSet<>();
|
||||
for (int i = 2; i < args.length; i++) {
|
||||
replaceFiles.add(args[i]);
|
||||
}
|
||||
|
||||
// Read the original archive
|
||||
DataInputStream dis = new DataInputStream(new FileInputStream(arcPath));
|
||||
int numberOfFiles = dis.readInt();
|
||||
|
||||
System.out.println("Archive has " + numberOfFiles + " files");
|
||||
|
||||
// Read the index
|
||||
List<String> filenames = new ArrayList<>();
|
||||
List<Integer> offsets = new ArrayList<>();
|
||||
List<Integer> sizes = new ArrayList<>();
|
||||
List<Boolean> compressed = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < numberOfFiles; i++) {
|
||||
String name = dis.readUTF();
|
||||
int offset = dis.readInt();
|
||||
int size = dis.readInt();
|
||||
|
||||
boolean isCompressed = false;
|
||||
if (name.startsWith("*")) {
|
||||
name = name.substring(1);
|
||||
isCompressed = true;
|
||||
}
|
||||
|
||||
filenames.add(name);
|
||||
offsets.add(offset);
|
||||
sizes.add(size);
|
||||
compressed.add(isCompressed);
|
||||
}
|
||||
|
||||
// The header size is the current position - data offsets are relative to file start
|
||||
// Read the entire file to get the raw data
|
||||
dis.close();
|
||||
byte[] arcData = Files.readAllBytes(Paths.get(arcPath));
|
||||
|
||||
// Build replacement data map
|
||||
// For each file, either use the replacement or the original data
|
||||
List<byte[]> fileData = new ArrayList<>();
|
||||
int replacedCount = 0;
|
||||
|
||||
for (int i = 0; i < numberOfFiles; i++) {
|
||||
String name = filenames.get(i);
|
||||
String simpleName = name.contains("\\") ? name.substring(name.lastIndexOf('\\') + 1) : name;
|
||||
|
||||
boolean shouldReplace = false;
|
||||
if (replaceFiles.isEmpty()) {
|
||||
// Replace all SWFs found in mediaDir
|
||||
File diskFile = new File(mediaDir, simpleName);
|
||||
if (diskFile.exists() && simpleName.endsWith(".swf")) {
|
||||
shouldReplace = true;
|
||||
}
|
||||
} else {
|
||||
shouldReplace = replaceFiles.contains(simpleName);
|
||||
}
|
||||
|
||||
if (shouldReplace) {
|
||||
File diskFile = new File(mediaDir, simpleName);
|
||||
if (diskFile.exists()) {
|
||||
byte[] newData = Files.readAllBytes(diskFile.toPath());
|
||||
fileData.add(newData);
|
||||
// Replaced files are NOT compressed (our SWFs are uncompressed)
|
||||
compressed.set(i, false);
|
||||
sizes.set(i, newData.length);
|
||||
System.out.println(" Replacing: " + name + " (" + newData.length + " bytes)");
|
||||
replacedCount++;
|
||||
} else {
|
||||
System.err.println(" WARNING: " + diskFile + " not found, keeping original");
|
||||
byte[] original = new byte[sizes.get(i)];
|
||||
System.arraycopy(arcData, offsets.get(i), original, 0, sizes.get(i));
|
||||
fileData.add(original);
|
||||
}
|
||||
} else {
|
||||
// Keep original data
|
||||
byte[] original = new byte[sizes.get(i)];
|
||||
System.arraycopy(arcData, offsets.get(i), original, 0, sizes.get(i));
|
||||
fileData.add(original);
|
||||
}
|
||||
}
|
||||
|
||||
if (replacedCount == 0) {
|
||||
System.out.println("No files were replaced!");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Calculate new header size to compute data offsets
|
||||
// Header: 4 bytes (numFiles) + for each file: (2 + UTF8 length + 4 + 4) bytes
|
||||
ByteArrayOutputStream headerBuf = new ByteArrayOutputStream();
|
||||
DataOutputStream headerDos = new DataOutputStream(headerBuf);
|
||||
headerDos.writeInt(numberOfFiles);
|
||||
for (int i = 0; i < numberOfFiles; i++) {
|
||||
String name = filenames.get(i);
|
||||
if (compressed.get(i)) {
|
||||
name = "*" + name;
|
||||
}
|
||||
headerDos.writeUTF(name);
|
||||
headerDos.writeInt(0); // placeholder offset
|
||||
headerDos.writeInt(0); // placeholder size
|
||||
}
|
||||
headerDos.flush();
|
||||
int headerSize = headerBuf.size();
|
||||
|
||||
// Now compute real offsets
|
||||
int currentOffset = headerSize;
|
||||
List<Integer> newOffsets = new ArrayList<>();
|
||||
for (int i = 0; i < numberOfFiles; i++) {
|
||||
newOffsets.add(currentOffset);
|
||||
currentOffset += fileData.get(i).length;
|
||||
}
|
||||
|
||||
// Write the final archive
|
||||
String outputPath = arcPath; // overwrite in place
|
||||
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(outputPath)));
|
||||
dos.writeInt(numberOfFiles);
|
||||
for (int i = 0; i < numberOfFiles; i++) {
|
||||
String name = filenames.get(i);
|
||||
if (compressed.get(i)) {
|
||||
name = "*" + name;
|
||||
}
|
||||
dos.writeUTF(name);
|
||||
dos.writeInt(newOffsets.get(i));
|
||||
dos.writeInt(fileData.get(i).length);
|
||||
}
|
||||
|
||||
// Write file data
|
||||
for (int i = 0; i < numberOfFiles; i++) {
|
||||
dos.write(fileData.get(i));
|
||||
}
|
||||
|
||||
dos.flush();
|
||||
dos.close();
|
||||
|
||||
System.out.println("Rebuilt archive: " + outputPath + " (" + replacedCount + " files replaced)");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user