[PS CC2025] Не могу декодировать строки из PSD

jazzy

Участник
Топикстартер
Сообщения
418
Реакции
260
Понимаю, что тема слабо относится к Фотошопу, но...

Суть проблемы: мне нужно получить полные пути линкованных файлов прямо из внутренностей PSD (прочитать бинарный файл).

Я ищу originalPathTEXT, успешно нахожу, получаю строку. С латиницей всё просто - что записано, то и прочитано. С кириллицей (и скорее всего с другими локалями) начинаются заморочки - Adobe, судя по всему, сокращает байты, заменяя для некоторых символов (пробел, слэш, точку и скорее всего что-то еще) на сокращенную запись, например:

E:\_Output\22\ИмяФайла.psd
читается как
E:\_Output\22ќИмяФайл0.psd
из-за того что байты записаны следующим образом:
45 00 == E
3a 00 == :
5c 00 == \
5f 00 == _
4f 00 == O
75 00 == u
74 00 == t
70 00 == p
75 00 == u
74 00 == t
5c 00 == \
32 00 == 2
32 00 == 2
5c 04 == ќ
18 04 == И
3c 04 == м
4f 04 == я
24 04 == Ф
30 04 == а
39 04 == й
3b 04 == л
30 00 == 0
2e 00 == .
70 00 == p
73 00 == s
64 00 == d
* видно что как только появляется кириллица начинаются сокращения для определенных символов: слэш 5c 00 получает 04 от кириллицы и становится 5c 04 == ќ, буква а перед точкой наоборот теряет свою 04 и превращается в 30 00 == 0 и т.п.

Я вроде бы глазами и головой принцип понимаю, но не могу сформулировать в коде.

Возможно у кого-то есть идеи, как это расшифровать чтобы символы не повреждались.

Скрипт для Фотошопа ниже, файл чтобы поиграться здесь: PSD с линкованными файлами

JavaScript:
#target photoshop
var BLOCK_START = 'originalPathTEXT\0\0\0',
    BLOCK_END = String.fromCharCode('00', '07'),
    CHUNK_SIZE = 64 * 1024,
    OVERLAP = 512;
var file = File.openDialog("Выберите PSD-файл");
if (file && file.exists) {
    file.encoding = 'BINARY';
    if (!file.open("r")) {
        alert("Не удалось открыть файл.");
    } else {
        var size = file.length,
            position = size,
            buffer = '',
            strings = [],
            symbols = [];
        while (position > 0) {
            var readSize = Math.min(CHUNK_SIZE, position);
            position -= readSize;
            file.seek(position);
            var chunk = file.read(readSize);
            buffer = chunk + buffer.slice(0, OVERLAP);
            var searchPos = 0;
            while (true) {
                var startIndex = buffer.indexOf(BLOCK_START, searchPos);
                if (startIndex === -1) break;
                var endIndex = buffer.indexOf(BLOCK_END, startIndex + BLOCK_START.length);
                if (endIndex === -1) break;
                endIndex += BLOCK_END.length;
                strings.push(decodeBytes(buffer.slice(startIndex, endIndex)));
                symbols.push(decodeBytesToCharCodes(buffer.slice(startIndex, endIndex)))
                searchPos = endIndex;
            }
            buffer = chunk;
        }
        file.close();
        if (strings.length > 0) {
            var log = new File("~/Desktop/output.txt");
            log.open("w");
            for (var i = 0; i < strings.length; i++) {
                log.writeln(strings[i]);
                log.writeln(symbols[i]);
            }
            log.close();
            alert("Найдено строк: " + strings.length + "\nСохранено в output.txt на рабочем столе.");
        } else {
            alert("Строки не найдены.");
        }
    }
}
function decodeBytes(bytes) {
    var pathBytes = bytes.slice(21),
        decoded = '';
    for (var i = 0; i < pathBytes.length - 1; i += 2) {
        var lo = pathBytes.charCodeAt(i),
            hi = pathBytes.charCodeAt(i + 1),
            code = lo + (hi << 8);
        if (code === 0) continue;
        if (lo === 0 && hi === 7) break;
        decoded += String.fromCharCode(code);
    }
    return decoded;
}
function decodeBytesToCharCodes(bytes) {
    var pathBytes = bytes.slice(21),
        decoded = '';
    for (var i = 0; i < pathBytes.length - 1; i += 2) {
        var lo = pathBytes.charCodeAt(i),
            hi = pathBytes.charCodeAt(i + 1),
            code = lo + (hi << 8);
        if (code === 0) continue;
        if (lo === 0 && hi === 7) break;
        decoded += toHex([lo, hi]) + ' == ' + String.fromCharCode(code) + '\n';
    }
    return decoded;
}
function toHex(charCodes) {
    var hexString = '';
    for (var i = 0; i < charCodes.length; i++) {
        var hex = charCodes[i].toString(16);
        if (hex.length < 2) {
            hex = '0' + hex;
        }
        hexString += hex + ' ';
    }
    return hexString;
}
 
А вы не думаете, что у вас порядок байт перепутан? Если сперва идет старший байт, потом младший, то все становится на свои места.
 
  • Спасибо
Реакции: zollinger и jazzy
А вам именно при помощи фотошопа нужно это делать?
Код:
file:///E:/_Output/22/File%20name.psd
file:///E:/_Output/22/%D0%98%D0%BC%D1%8F%D0%A4%D0%B0%D0%B9%D0%BB%D0%B0.psd
file:///E:/_Output/22/%D0%9F%D0%B0%D0%BF%D0%BA%D0%B0/%D0%98%D0%BC%D1%8F%D0%A4%D0%B0%D0%B9%D0%BB%D0%B0%D0%92%D0%9F%D0%B0%D0%BF%D0%BA%D0%B5.psd
file:///E:/_Output/22/%D0%9F%D0%B0%D0%BF%D0%BA%D0%B0/%D0%98%D0%BC%D1%8F%20%D1%84%D0%B0%D0%B9%D0%BB%D0%B0%20%D0%B2%20%D0%BF%D0%B0%D0%BF%D0%BA%D0%B5%20%D0%B8%20%D1%81%20%D0%BF%D1%80%D0%BE%D0%B1%D0%B5%D0%BB%D0%B0%D0%BC%D0%B8.psd
 
  • Спасибо
Реакции: jazzy
А вы не думаете, что у вас порядок байт перепутан? Если сперва идет старший байт, потом младший, то все становится на свои места.
Спасибо!!!! 'alil' Вот тот самый слон в комнате, которого я не заметил... действительно, обратный порядок байт.

E:\_Output\22\Папка\Имя файла в папке и с пробелами.psd
E:\_Output\22\File name.psd
E:\_Output\22\Папка\ИмяФайлаВПапке.psd
E:\_Output\22\ИмяФайла.psd

Код:
#target photoshop
var BLOCK_START = 'originalPathTEXT\0\0\0',
    BLOCK_END = String.fromCharCode('00', '07'),
    CHUNK_SIZE = 64 * 1024,
    OVERLAP = 512;
var file = File.openDialog("Выберите PSD-файл");
if (file && file.exists) {
    file.encoding = 'BINARY';
    if (!file.open("r")) {
        alert("Не удалось открыть файл.");
    } else {
        var size = file.length,
            position = size,
            buffer = '',
            strings = [],
            symbols = [];
        while (position > 0) {
            var readSize = Math.min(CHUNK_SIZE, position);
            position -= readSize;
            file.seek(position);
            var chunk = file.read(readSize);
            buffer = chunk + buffer.slice(0, OVERLAP);
            var searchPos = 0;
            while (true) {
                var startIndex = buffer.indexOf(BLOCK_START, searchPos);
                if (startIndex === -1) break;
                var endIndex = buffer.indexOf(BLOCK_END, startIndex + BLOCK_START.length);
                if (endIndex === -1) break;
                endIndex += BLOCK_END.length;
                strings.push(decodeBytes(buffer.slice(startIndex, endIndex)));
                searchPos = endIndex;
            }
            buffer = chunk;
        }
        file.close();
        if (strings.length > 0) {
            var log = new File("~/Desktop/output.txt");
            log.open("w");
            for (var i = 0; i < strings.length; i++) {
                log.writeln(strings[i]);
            }
            log.close();
            alert("Найдено строк: " + strings.length + "\nСохранено в output.txt на рабочем столе.");
        } else {
            alert("Строки не найдены.");
        }
    }
}
function decodeBytes(bytes) {
    var pathBytes = bytes.slice(20),
        decoded = '';
    for (var i = 0; i < pathBytes.length - 1; i += 2) {
        var lo = pathBytes.charCodeAt(i + 1),
            hi = pathBytes.charCodeAt(i),
            code = lo + (hi << 8);
        if (code === 0 || (lo === 7 && hi === 0)) break;
        decoded += String.fromCharCode(code);
    }
    return decoded;
}
 
Да, конечно, можно вытащить их другими способами, но у меня есть скрипт для перелинковки, люди просят поддержку последних версий Фотошопа (где эти нехорошие индусы добавили диалоговое окно, блокирующее работу), искал костыль чтобы убрать это окно (сгенерировать все потерянные файлы с нулевым размером в том же месте где и мастер-файл), хотелось остаться в рамках ECMA 3
 
  • Спасибо
Реакции: zollinger
Стоит глянуть в спецификацию, а то вдруг оно может быть и be и le, а то и utf8 вовсе.
 
Стоит глянуть в спецификацию, а то вдруг оно может быть и be и le, а то и utf8 вовсе.
Спецификацию искать лень, но, скорее всего, от платформы фотошопа зависит.
 
Возможно. Официальную спецификацию получить сложно, всякие сторонние парсеры читать долго.
Планирую просто вручную проверить на нескольких версиях Фотошопа под виндой и маком, ну и имена в разных локалях покидаю.
 
Древняя виндовая версия не поддерживала линкованные файлы и смарт объекты :) По опыту работы с другими типами Адобовских файлов кодирование блоков ресурсов которые они помечают '8BIM' практически не меняется и не зависит от платформы, по крайней мере они стараются поддерживать совместимость со всем что новее 4 Фотошопа (вот там они знатно перелопатили структуру файлов). В глубину веков лезть не планирую...
 
Последнее редактирование:
Видел. Теперь уже постфактум присмотрелся внимательнее - да, под виндой big endian, текст в unicode. Про мак как-то мутно написано, но думаю что так же (это уже не проблема).

Ещё раз спасибо!