Skip to content

Commit e2e0368

Browse files
Fixed a couple bugs in the texture tools
1 parent 426cf4d commit e2e0368

4 files changed

Lines changed: 60 additions & 44 deletions

File tree

python/bntx_editor.py

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,11 @@ def rename_texture(self, old_name: str, new_name: str):
147147
# -- DDS import ----------------------------------------------------------
148148

149149
def import_dds(self, name: str, dds_bytes: bytes):
150-
"""Replace a texture's image data from a DDS file, in place.
150+
"""Replace a texture's image data from a DDS file.
151151
152-
The DDS must match the target texture's compression format family and
153-
dimensions. The image data is re-swizzled and written over the existing
154-
block-linear data, leaving the BNTX structure (string pool, relocation
155-
table, pointers) untouched so the file stays valid.
152+
The DDS must match the target texture's compression format family.
153+
Dimensions and mip count may differ from the original; larger imports
154+
relocate image data to the end of the file.
156155
"""
157156
import dds_io
158157
import texture_swizzle as tsw
@@ -163,9 +162,6 @@ def import_dds(self, name: str, dds_bytes: bytes):
163162
d = ptr + 0x10
164163

165164
cur_format = self._read_fmt("I", d + self._OFF_FORMAT)
166-
cur_width = self._read_fmt("i", d + self._OFF_WIDTH)
167-
cur_height = self._read_fmt("i", d + self._OFF_HEIGHT)
168-
cur_mip_count = self._read_fmt("H", d + self._OFF_MIP_COUNT)
169165
cur_image_size = self._read_fmt("I", d + self._OFF_IMAGE_SIZE)
170166
cur_alignment = self._read_fmt("I", d + self._OFF_ALIGNMENT) or 512
171167
tile_mode = self._read_fmt("H", d + self._OFF_TILE_MODE)
@@ -198,42 +194,50 @@ def import_dds(self, name: str, dds_bytes: bytes):
198194
f"Format mismatch: texture is {cur_key.upper()} but DDS is {dds.key.upper()}. "
199195
f"Re-export the DDS as {cur_key.upper()}."
200196
)
201-
if dds.width != cur_width or dds.height != cur_height:
202-
raise BntxImportError(
203-
f"Size mismatch: texture is {cur_width}x{cur_height} but DDS is "
204-
f"{dds.width}x{dds.height}. Resize the DDS to match."
205-
)
206197

207198
bpb, blk_w, blk_h = dds.block_info
208199

209-
# Use as many mips as fit in the existing data region (no structural growth).
210-
max_mips = min(dds.mip_count, cur_mip_count)
211-
combined, _offsets, bh_log2, image_size = tsw.swizzle_surface_mipmaps(
200+
max_mips = dds.mip_count
201+
combined, offsets, bh_log2, image_size = tsw.swizzle_surface_mipmaps(
212202
dds.width, dds.height, blk_w, blk_h, bpb, max_mips, dds.mips[:max_mips], cur_alignment
213203
)
214204

215205
data_base = self._data_base(d)
216206
if data_base <= 0 or data_base + cur_image_size > len(self._data):
217207
raise BntxImportError("Texture image data is outside the file bounds.")
218-
if len(combined) > cur_image_size:
219-
raise BntxImportError(
220-
"Imported data is larger than the original texture region; "
221-
"replacing with more mip levels than the original is not supported."
222-
)
223-
224-
# Overwrite in place and zero any trailing slack so stale bytes do not leak.
225-
self._data[data_base : data_base + len(combined)] = combined
226-
if len(combined) < cur_image_size:
227-
self._data[data_base + len(combined) : data_base + cur_image_size] = b"\x00" * (
228-
cur_image_size - len(combined)
229-
)
230208

231-
# Refresh the (already-relocated) mip pointer values for the mips we wrote.
232209
ptr_array = self._mip_ptr_array_addr(d)
210+
211+
if len(combined) <= cur_image_size:
212+
# Overwrite in place and zero any trailing slack so stale bytes do not leak.
213+
self._data[data_base : data_base + len(combined)] = combined
214+
if len(combined) < cur_image_size:
215+
self._data[data_base + len(combined) : data_base + cur_image_size] = b"\x00" * (
216+
cur_image_size - len(combined)
217+
)
218+
write_base = data_base
219+
stored_image_size = image_size
220+
else:
221+
# Larger mip chain: relocate image data to the end of the file.
222+
align = cur_alignment or 512
223+
write_base = len(self._data)
224+
pad = (align - (write_base % align)) % align
225+
if pad:
226+
self._data.extend(b"\x00" * pad)
227+
write_base = len(self._data)
228+
self._data.extend(combined)
229+
if image_size > len(combined):
230+
self._data.extend(b"\x00" * (image_size - len(combined)))
231+
stored_image_size = image_size
232+
self.file_size = len(self._data)
233+
233234
for i in range(max_mips):
234-
self._write_fmt("q", ptr_array + i * 8, data_base + _offsets[i])
235+
self._write_fmt("q", ptr_array + i * 8, write_base + offsets[i])
235236

237+
self._write_fmt("i", d + self._OFF_WIDTH, dds.width)
238+
self._write_fmt("i", d + self._OFF_HEIGHT, dds.height)
236239
self._write_fmt("H", d + self._OFF_MIP_COUNT, max_mips)
240+
self._write_fmt("I", d + self._OFF_IMAGE_SIZE, stored_image_size)
237241

238242
# Preserve the existing block-height layout bits, refresh the low 3 bits.
239243
layout = self._read_fmt("I", d + self._OFF_LAYOUT)

src/archiveFsCommands.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1168,7 +1168,7 @@ export function registerArchiveFileCommands(context: vscode.ExtensionContext): v
11681168
}
11691169
const targetUri = entry.resourceUri;
11701170
if (isBntxOrTexToGo(targetUri)) {
1171-
void vscode.window.showWarningMessage('Replace is not supported for BNTX or TexToGo files.');
1171+
await vscode.commands.executeCommand('totk-editor.importTextureDds', targetUri);
11721172
return;
11731173
}
11741174

src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1257,7 +1257,7 @@ export async function activate(context: vscode.ExtensionContext) {
12571257
};
12581258
const onImport = isReadOnly ? undefined : () => importDdsIntoTexture(uri);
12591259
const onExport = () => exportFromArchiveSelection([uri]);
1260-
openTextureViewer(texName, raw, diskArchive, filePath, onSaveCallback, onImport, onExport);
1260+
openTextureViewer(texName, raw, diskArchive, filePath, onSaveCallback, onImport, onExport, uri.fsPath);
12611261
} else {
12621262
void vscode.window.showErrorMessage('Failed to load texture preview.');
12631263
}

src/textureViewer.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@ import type { BntxTextureResult, BntxChannelInfo, BntxImageInfo, BntxMiscInfo }
33
import * as fs from 'fs';
44
import * as path from 'path';
55

6+
interface PanelCallbacks {
7+
onSave?: (data: any) => Promise<void>;
8+
onImport?: () => Promise<void>;
9+
onExport?: () => Promise<void>;
10+
}
11+
612
const panels = new Map<string, vscode.WebviewPanel>();
713
const panelTempFiles = new Map<string, string>();
14+
const panelCallbacks = new Map<string, PanelCallbacks>();
815
let extensionUri: vscode.Uri | undefined;
916

1017
export function initTextureViewer(extUri: vscode.Uri): void {
@@ -18,11 +25,14 @@ export function openTextureViewer(
1825
filePath?: string,
1926
onSave?: (data: any) => Promise<void>,
2027
onImport?: () => Promise<void>,
21-
onExport?: () => Promise<void>
28+
onExport?: () => Promise<void>,
29+
panelKey?: string,
2230
): void {
23-
const key = textureName;
31+
const key = panelKey ?? textureName;
32+
panelCallbacks.set(key, { onSave, onImport, onExport });
33+
2434
const existing = panels.get(key);
25-
35+
2636
if (existing) {
2737
// We are replacing the content of an existing panel.
2838
// Clean up the previous temp file so it doesn't leak.
@@ -32,7 +42,7 @@ export function openTextureViewer(
3242
fs.unlinkSync(oldTempFile);
3343
} catch { }
3444
}
35-
45+
3646
if (result.pngPath) {
3747
panelTempFiles.set(key, result.pngPath);
3848
} else {
@@ -66,6 +76,7 @@ export function openTextureViewer(
6676
panels.set(key, panel);
6777
panel.onDidDispose(() => {
6878
panels.delete(key);
79+
panelCallbacks.delete(key);
6980
const currentTempFile = panelTempFiles.get(key);
7081
if (currentTempFile) {
7182
try {
@@ -76,24 +87,25 @@ export function openTextureViewer(
7687
});
7788

7889
panel.webview.onDidReceiveMessage(async (message) => {
79-
if (message.type === 'save-metadata' && onSave) {
90+
const callbacks = panelCallbacks.get(key);
91+
if (message.type === 'save-metadata' && callbacks?.onSave) {
8092
try {
81-
await onSave(message.data);
93+
await callbacks.onSave(message.data);
8294
vscode.window.showInformationMessage('Texture metadata saved successfully!');
8395
} catch (e) {
8496
const err = e instanceof Error ? e.message : String(e);
8597
vscode.window.showErrorMessage(`Failed to save metadata: ${err}`);
8698
}
87-
} else if (message.type === 'import-dds' && onImport) {
99+
} else if (message.type === 'import-dds' && callbacks?.onImport) {
88100
try {
89-
await onImport();
101+
await callbacks.onImport();
90102
} catch (e) {
91103
const err = e instanceof Error ? e.message : String(e);
92104
vscode.window.showErrorMessage(`DDS import failed: ${err}`);
93105
}
94-
} else if (message.type === 'export-dds' && onExport) {
106+
} else if (message.type === 'export-dds' && callbacks?.onExport) {
95107
try {
96-
await onExport();
108+
await callbacks.onExport();
97109
} catch (e) {
98110
const err = e instanceof Error ? e.message : String(e);
99111
vscode.window.showErrorMessage(`DDS export failed: ${err}`);
@@ -379,8 +391,8 @@ function buildHtml(result: BntxTextureResult, webview: vscode.Webview, isReadOnl
379391
</div>
380392
<div class="props-panel">
381393
<div style="margin-bottom: 12px; display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap;">
382-
${allowExport ? `<button class="save-btn" onclick="exportDds()">Export DDS</button>` : ''}
383-
${allowImport && !isReadOnly ? `<button class="save-btn" onclick="importDds()">Import DDS</button>` : ''}
394+
${allowExport ? `<button class="save-btn" onclick="exportDds()">Export Texture</button>` : ''}
395+
${allowImport && !isReadOnly ? `<button class="save-btn" onclick="importDds()">Import Texture</button>` : ''}
384396
${!isReadOnly ? `<button class="save-btn" onclick="saveMetadata()">Save Changes</button>` : ''}
385397
</div>
386398
${metaSections}

0 commit comments

Comments
 (0)