Из журнала Inferno Guide #5, Рязань/Самара, Май 2004 (текст отличается от оригинального варианта, внесены авторские исправления и информация из Errata последующих номеров журнала) Автор: Alone Coder (Dima Bystrov) Форматы упакованных данных на ZX Spectrum Как я успел понять, моим статьям хвата- ет сухости изложения. В силу этого,как мне удалось прикинуть, даже будучи склеенными вместе, они не соберутся и в самую малень- кую книжку. Возможно, это происходит из-за того, что я опускаю незаметные для себя детали? Если так, тогда то, что я пишу,без моей консультации не прочитать... Прискорбно. Тем не менее, я отважился на создание ещё одного пространного опуса,претендующе- го на полезность в смысле общего развития. Не беда,если ничего в нём написанное вы не сможете запомнить (я и сам мало что оттуда помню, оттого и начал записывать). Это не стихотворение, которое надо выучить на урок, и не похождения с мечом и бластером. Тут просто даётся некоторая рассеянная ин- формация относительно возможности выраже- ния в словах (назовём этот процесс "верба- лизацией") некоторых структур,которые тру- дно в определённом смысле "пощупать".Заод- но по прочтении читатель сможет отдалённо представить себе сам метод анализа и,собс- твенно,изложения такого рода структур,хотя я, как автор,на указанном особенно не кон- центрировался. Обратите также внимание,что материал не исчерпывающ. Следует, помимо всего прочего, избегать взглядов типа "а на самом деле...",особен- но касательно терминологии. Использованную терминологию я приведу здесь же, она обра- зована в контексте лично моего мышления и может не соответствовать интуитивной для читателя,тем более если он обладает специ- фическим образованием в этой области.Одна- ко,насколько известно мне,ранее опытов по- добного рода вербализации почти не произ- водилось, и в силу этого общепринятой тер- минологии не существует. Исходные же текс- ты, распространявшиеся на аналогичных пра- вах, читабельными не являются. Итак, мои термины: disp (displacement - смещение) или dist (distance - расстояние) - характеристика смещения от текущей позиции выходного по- тока (восстанавливаемого файла) до повто- ряемого фрагмента в LZ77-основанных мето- дах.(Каковые методы,собственно,и состоят в том,чтобы время от времени копировать фра- гменты откуда-то сзади куда-то вперёд, а именно "под курсор",то есть в оную текущую позицию.) Если перед disp я ставлю минус, это означает,что данная характеристика уже имеет отрицательный знак и для получения адреса фрагмента ПРИБАВЛЯЕТСЯ, а не отни- мается из текущего адреса. dispH, dispL - старший и младший байты disp. puts или len - длина повторяемого фраг- мента. Фрагмент может копироваться поверх себя. Самый типичный такого рода случай возникает при disp=1 и сводится к повтору одного и того же байта. За указаниями типа disp4 или len7 скры- вается количество бит в данном числе. дерево (tree) - структура, используемая для хранения информации о том,как двоичные коды различных длин представляют некий на- бор символов (литералов). Рассматривались Фано и Хаффманом. Метод построения дерева, предложенный Фано,в общем случае хуже Хаф- фмановского, т. к. для последнего доказана оптимальность. По Хаффману дерево строится последовательным объединением двух редчай- ших узлов с суммированием их частоты для использования этой суммарной частоты на последующих объединениях. По Фано, если читателя это заинтересует, дерево строится последовательным разделением отсортирован- ного по частотам списка литералов ПРИБЛИ- ЗИТЕЛЬНО ПОРОВНУ на левую (нулевую) и пра- вую (единичную) подветвь. Деревья же, если перейти к практике,могут храниться множес- твом способов - указателями, ярусами или вообще неявно. % - двоичное представление; # - шестнадцатиричное представление. битовый поток - данные, извлекаемые по- битно (например,сдвигом); байтовый поток - данные,извлекаемые по- байтно (что,очевидно,быстрее). Существует несколько способов совмест- ного использования обоих типов потоков в одном файле с последовательным доступом. (Кем эта идея реализована впервые, предпо- ложить трудно.) Это, например, простейший способ: 1. Из файла извлекается байт-буфер для битового потока, и указатель смещается; 2 и далее. Байты адресуются указателем. Биты же извлекают из байта-буфера, при его исчерпывании действуют аналогично п.1. ...и его вариация,где вместо "байта-бу- фера битового потока" фигурирует "слово- буфер" (16 бит). В форматах, использующих оба потока в смешанном виде, важен порядок извлечения данных из них. Я не уточняю этот порядок в текстах, просто пишу структуру кода именно в нужном порядке. Т.е. если написано: --------- %xxxx,dispH3 - ссылка такая-то. dispL в байтовом потоке. puts=столько-то, --------- то понимать следует, что сначала из би- тового потока считывается %xxxx,dispH3, и уже потом из байтового - dispL. Как правило,биты извлекаются слева нап- раво (7,6,... или 15,14,... ). Исключением является компрессор RIP 0.2x, где это про- исходит наоборот. Но об этом будет написа- но отдельно. А. Кодер Powerful Code Decreaser v6.2 Упаковщик в своё время использовался для сжатия RGB картинок (.Y). Возможно, им упакованы многие журналы. Возможно, именно здесь появилось стандартное дерево из 16 вариантов,которое впоследствии попало в MS Pack, Hrum, Hrust... Не берусь утверждать ничего... В комплекте с компрессором прилагаются распаковщики в исходниках. Это решает мно- гие проблемы,давая возможность выбрать,как использовать распаковщик. Распаковщики не- достаточно оптимизированы,но наличие исхо- дников позволяет решить и этот вопрос. Имеются битовый и байтовый потоки,бито- вый поток выбирается по 2 байта. Для рабо- ты используется стек (принцип DJNZ:POP HL: LD B,C:ADD HL,HL ). Упакованный блок начинается с 5 байт, относящихся к концу файла, потом лежат 2 первых байта битового потока. Далее согласно битовому потоку: %1 - просто байт (из байтового потока); %000,xxxx - puts=1. Сверхкороткая ссылка с disp=xxxx+1=1..16; %001 - puts=2. Короткая ссылка, dispL изв- лекается из байтового потока. Если -1, то выход из распаковщика,иначе dispL на еди- ницу больше прочитанного; %010 - puts=3. Здесь и далее следующим об- разом определяется disp: из байтового по- тока считывается dispL, а dispH из бито- вого потока по дереву: %0 - dispH=0, %1000 - dispH=1, %1010 - dispH=2, %1100 - dispH=3, %1110 - dispH=4, %1011000 - dispH=5, и т.д. Коды начинаются с единицы и имеют структуру: 2 информационных бита, 1 бит признака окончания (0=окончание) и т.д.До 8 бит dispH. Крупный недостаток - совер- шенно не используются коды,начинающиеся с %1001... %01101 - puts=4; %01110 - puts=5; %0111100 - puts=6; %0111101 - puts=7; %0111110 - puts=8; %011111100 - puts=9; %011111101 - puts=10; %011111110 - puts=11; %01111111100 - puts=12; %01111111101 - puts=13; %01111111110 - puts=14; %01111111111 - puts=15; %01100 - взять некий byte из байтового по- тока. Если 0, то считать 2 байта puts из байтового потока, иначе puts=byte+15. подготовил А. Кодер MS Pack 01.96 По мнению Hrumer'а, автора упаковщиков Hrust, лучшим компрессором текстов на ZX до 2000 года был MS Pack. Жаль,конечно,что Hrumer никогда не писал статей по вопросам компрессии и не стал отвечать на мои воп- росы лично, а ведь его имя и программы всплывают в этом разделе уже не первый раз. Однако,что же представляет собой фор- мат компрессора Microspace? Распаковщик, к сожалению,неоптимален по длине. Структура переходов поддаётся опти- мизации, лишний XOR A, малополезное сохра- нение HL'... Но скорость декомпрессии при- личная - например,для упаковки ПЗУ MS Pack и сегодня почти незаменим. Ведь в нём сло- варь, в отличие от Hrum, составляет всю длину файла. Конечно,это сказалось на ско- рости самого упаковщика. Хэширования для ускорения поиска повторов нет ни в Hrum, ни в MS Pack. Имеются битовый и байтовый потоки,бито- вый поток выбирается по 2 байта. Для рабо- ты используется стек (принцип ADD HL,HL: DJNZ:POP HL:LD B,C ). Упакованный блок должен лежать после распаковщика (Hrum-подобный принцип). Бу- дем отсчитывать байты от начала распаков- щика: +#e9 - адрес,по которому лежат 5 байт кон- ца файла. Точнее,адрес+4, т.к.байты копи- руются через LDDR. Почему 5 байт не лежат непосредственно без указателя? Тем более что 4 байта (или даже 6, если в конце де- компрессора не JP, а RET) до упакованного блока не используются? Если бы 5 байт ко- нца файла лежали впритык за депакером, их не пришлось бы и перебрасывать! Так что это решение авторов довольно странно. +#eb - 2 байта HL для перемещения упако- ванного блока (с помощью LDDR ); +#ed - 2 байта DE, аналогично; +#ef - 2 байта BC, аналогично; +#f1 - адрес, куда распаковывать (DE'); +#f3 - первые 2 байта битового потока. Далее согласно битовому потоку: %0 - просто байт (из байтового потока); %100 - puts=2. Короткая ссылка: disp8 в байтовом потоке (1..256, причём 0 соотве- тствует 1 и т.д.!); %101 - puts=3; %110 - puts=4; %11101 - puts=5; %11110 - puts=6; %1111100 - puts=7; %1111101 - puts=8; %1111110 - puts=9; %111111100 - puts=10; %111111101 - puts=11; %111111110 - puts=12; %11111111100 - puts=13; %11111111101 - puts=14; %11111111110 - puts=15; %11111111111 - puts=16; %11100 - взять некий byte из байтового по- тока. Если -1, то прервать распаковку. Если -2, то следующие 2 байта в байтовом потоке - это puts, иначе puts=byte+17= =17..270. Потом для всех ссылок,кроме puts=2 (для неё выше было написано особо), следующим образом вычисляется disp (учтите, что в MS Pack disp нестандартный - он вычитается из адреса-1! Именно поэтому в puts=2 уточне- но, что чему соответствует!): %1 - аналогично puts=2; %0000 - dispH=1, здесь и далее dispL берё- тся из байтового потока после dispH; %0001 - dispH=2; %00100 - dispH=3; %00101 - dispH=4; %00110 - dispH=5; %00111 - dispH=6; %010000 - dispH=7; %010001 - dispH=8; %010010 - dispH=9; %010011 - dispH=10; %010100 - dispH=11; %010101 - dispH=12; %010110 - dispH=13; %0101110 - dispH=14; %0101111 - dispH=15; %0110000 - dispH=16; ... %0111110 - dispH=30; %0111111 - dispH из байтового потока. подготовил А. Кодер Hrum 3.5i Для распаковщика Hrum автором заявлена особенно высокая скорость: 33 k/с. К сожа- лению,я не обладаю информацией,какие имен- но типы данных имелись в виду,но в общем и целом автор прав - это самый быстрый LZ- распаковщик с битовым потоком. За счёт уп- рощенного в связи с этим формата он также и чрезвычайно короткий. К несчастью,автор- ский пакет не предлагает распаковщика без перекидывания для использования его на не- сколько блоков. Перекидывание - функциональная черта очевидных предшественников Hrum на ниве сжатия 48k игрушек.Идеология этого процес- са такова: кодовый блок ОБЯЗАН находиться непосредственно за распаковщиком;распаков- щик при запуске перекидывает свою активную часть в тихое место (как правило,#5bxx), а упакованный блок - в конец выделенной па- мяти для распаковки; все адреса для этого рассчитаны заранее (пакером) и лежат в те- ле распаковщика. В Hrum введено три оригинальных ухищре- ния: 1. 5 байт конца файла (это важная часть файла, создаваемая для разрыва между адре- сами "откуда" и "куда распаковывать") ко- пируются вместе с распаковщиком в "тихое место" одной командой LDIR. 2. Предложен ещё более короткий способ реализации стандартного дерева из 16 вари- антов, с которым мы ещё встретимся: ------------------------------------------ LD E,1 pair LD A,#80 pair0 RLA JR C,pair0 ;2 прохода CP 3 ;A=0..3 JR C,treeend ADD A,E LD E,A XOR C ;=#10 !!! JR NZ,pair treeend ADD A,E ------------------------------------------ Хитрость в команде XOR C. Т.е. получи- лось короче,чем в PCD6.2 (там было SUB 15) и тем более чем в MS Pack (там не было ци- кла и был лишний XOR A ). Kонстанта C=#10 используется и в другом месте: при обрабо- тке битового потока. 3. Один и тот же цикл взятия 3-4 бит (ближе к концу распаковщика) используе- тся как для взятия dispH (при putsЄ4 ),так и для взятия dispL (при puts=1 ),причём за всю работу этого цикла ни разу не портится флаг Z! В этом флаге и лежит признак, что делать с результатом... Словарь (окно) всего 4k. Имеется два чередующихся потока данных: битовый и байтовый. Битовый поток хранится кусками по 16 бит и снимается в регистро- вую пару HL. Пока HL сдвигается, 16 бит отсчитываются в регистре B командой DJNZ. Когда они кончаются, из потока берётся но- вое значение HL. Упакованный блок с распаковщиком содер- жит: Инициализацию распаковщика. Активную часть распаковщика. 5 байт конца файла. 2 байта начала битового потока. 1 байт - первый байт файла. И далее согласно битовому потоку: %1 - вставить байт из байтового потока. %000,-disp3 - копировать 1 байт с адреса минус 1..8. %001 - копировать 2 байта: -disp8 берется из байтового потока. %010 - копировать 3 байта аналогично. %01100 - копировать N байт ( N берётся из битового потока, N=0 - конец файла).Здесь и далее после определения длины произво- дидся гадание по биту из битового потока: %0 - -dispL берётся из байтового потока; %1 - аналогично, но перед этим -dispH в количестве 4 бит берётся из битового по- тока. Обратите внимание,избыточность фор- мата: -dispH=#FF кодируется двояко, автор сэкономил 1 байт распаковщика. Непонятен также юмор автора с командой RES 5,A, ко- торая могла бы выглядеть как LD A,#1f. %01101 - копировать 4 байта как выше. %01110 - копировать 5. %0111100 - копировать 6. %0111101 - копировать 7. %0111110 - копировать 8. %011111100 - копировать 9. %011111101 - копировать 10. %011111110 - копировать 11. %01111111100 - копировать 12. %01111111101 - копировать 13. %01111111110 - копировать 14. %01111111111 - копировать 15. Hrust 1.x Распаковщик Hrust 1.x - самый хитрый распаковщик на ZX. Несмотря на небольшой размер, он вполне способен запутать любого заглянувшего. Уникальность формата в том,что он прямо предназначен для сжатия кодов.Для этого он снабжён кодами копирования с разрывом, ка- ких больше нигде нет. Дополнительно приме- нены расширяющиеся коды смещений,которые в начале файла короткие и указывают недале- ко, а ближе к концу - длинные. В авторском комплекте имеется два рас- паковщика - с входным потоком в стеке и с входным потоком в IX. Еще один распаковщик в виде бейсик-загрузчика сделал из первого Alone Coder. Все они эквивалентны. Распаковщик начинается с переброски упакованного блока в конец памяти,выделен- ной для распаковки. Переброска идёт только в случае, если адрес конца упакованного блока изначально меньше адреса конца памя- ти для распаковки. Бейсик-вариант этого фрагмента не содержит,там нужно самому ко- нтролировать,чтобы упакованный блок в про- цессе распаковки не портился.Тем не менее, есть шанс, что он запорется и в авторских распаковщиках, после переброски. Для этого конец блока должен плохо паковаться. Битовый поток берётся кусками по 16 бит. C содержит константу 16. D содержит один сброшенный бит,положение которого от- носительно левого края байта определяет длину расширяемых ссылок. Изначально D=#bf (назовём это состояние "2 бита расширяемой ссылки"),далее периодическо происходит RRC D ("3..8 бит расширяемой ссылки"). Упакованные блоки имеют заголовок: +0 "HR". +2 распакованная длина. +4 упакованная длина. +6 шесть последних байт блока. +12 два байта начала битового потока. +14 первый байт блока. +15 сами упакованные данные. Обработка упакованных данных осуществ- ляется согласно указаниям битового потока: %1 - получить байт из байтового потока и поместить куда распаковываем. %000,-disp3 - копировать 1 байт со смеще- ния -1..8 %001 - копировать C=2 байта. disp кодируе- тся так: %00-%01: -dispH=#fd-#fe, -dispL в битовом потоке; %10: -dispH=#ff, далее исходя из байтово- го потока: числа <#e0 понимаются как -dispL (ссылка обычная), >=#e0 хитро: RLCA:XOR C, при A=-1 расширяется длина ссылки, иначе SUB 15 и получаем -dispL= =#b2-#ee (ссылка со вставным байтом:байт из $-disp, байт из байтового потока,байт из $+2-disp ); %11,-disp5: обычная ссылка (через %10 та- кие короткие disp описать нельзя); %010 - копировать C=3 байта. Здесь и далее disp кодируется так: %00: -dispH=#fe, -dispL в битовом потоке; %01: -dispH=#ff, далее исходя из байтово- го потока: числа <#e0 понимаются как -dispL (ссылка обычная), >=#e0 хитро: RLCA:XOR C, при A=-1 расширяется длина ссылки (на самом деле не используется), иначе SUB 15 и получаем -dispL=#b1-#ef (со вставным байтом:байт из $-disp, байт из байтового потока, байт из $+2-disp ); %10,-disp5; %11: расширяющаяся ссылка. -dispH берётся из битового потока, число битов согласно D (#bf..#fe соответствует 2..8 бит). -dispL берётся из байтового потока. В остальном ссылка как ссылка. %01100 - копировать C=4 байта как выше. Здесь и далее коды ссылок со вставным ба- йтом невыгодны, но есть. %01110 - копировать 5. %0111100 - копировать 6. %0111101 - копировать 7. %0111110 - копировать 8. %011111100 - копировать 9. %011111101 - копировать 10. %011111110 - копировать 11. %01111111100 - копировать 12. %01111111101 - копировать 13. %01111111110 - копировать 14. %01111111111 - копировать 15. И особенные случаи: %01101000001111 - конец файла. %0110100,len7 - если len7>15, то len будет равно len7*256+байт_из_байтового_потока. В любом случае результат ляжет в BC, а далее см. случаи с len=3..15. %0110101xxxx - 12-42 (чётное число) байтов из байтового потока. %011011,-disp4: ссылка со вставным байтом: байт из $-disp, байт из байтового потока, байт из $+2-disp. Сделано из-за того, что в %001 предусмотрены только -dispL=#b2.. #ee, причем чётные, а в %010 только #b1.. #ef, нечётные. --Дополнение от Hrumer'а из IG#7-------------------------------- По-моему, Сергей Муллин мне в свое время писал, что код дехруста позволяет и "сужать" коды - если переполнить регистр, который отвечает за хранение длины кодов. Hужно просто вставить подряд несколько кодов расширения, и получим сужение. Hо я не проверял :) Можно это было бы использовать при резкой смене содержимого - код - графика - текст. Если снова встретилась, например, графика - расширяем код. ---------------------------------------------------------------- Hrust 2.x Формат предназначался для сжатия текс- тов.Несмотря на то,что авторский упаковщик был неудобный,а формат (1998) с появлением RIP (2000) и ZXRar (2003) устарел, он всё же стал единственным действительно СТАНДА- РТНЫМ форматом упакованных данных на ZX. И не только для текстов. Одних упаковщиков Hrust 2.x сейчас из- вестно девять (!), все они выросли из кода оригинального упаковщика. Количество же программ,поддерживающих формат на распако- вку, я не определил. Идеология принята противоположная Hrum: выгрузка упакованных блоков ТОЛЬКО без ра- спаковщика, распаковщик отдельный,не испо- льзует стек. Может быть, из-за сочетания этих идей формат победил остальные? На практике имеется две версии заголов- ков упакованных блоков: v2.1 и v2.3. Стан- дартом является первая.Вторая используется только в архивах Hrip. v2.1: смещение длина +0 3 "hr2"; +3 1 "1". Если bit7=1, то файл был просто "сохранен"; (а значит - w[+4]=w[+6]) +4 2 длина исходного файла; +6 2 длина упакованного файла; +8 w[+6] упакованный файл. Перед упакованным блоком хранятся пос- ледние 6 байт файла,сами эти 6 байт не па- куются. Максимальная длина повторяющегося фраг- мента,кодируемого одной ссылкой: 4095 байт (#fff). Максимальная ссылка назад: -65535 байт (окно в авторском упаковщике: 16384 байт, в других известных упаковщиках дос- тигает 32768, но уменьшается при приближе- нии упакованного куска к непакованному - упаковка идет в тот же буфер) 011000nnnn - несколько непакованных байт, а точнее, 12+nnnn*2 (не более 42, как ви- дно), в байтовом потоке - эти байты. 1 - просто один непакованный байт (в бай- товом потоке - этот байт). 000xxx - 1 повтор (xxx=disp3). 001 - 2 повтора (в байтовом потоке disp8). 010... - 3 повтора. 01101... - 4 повтора. 01110... - 5 повторов. 0111100... - 6 повторов. 0111101... - 7 повторов. и т.д. 01111111110... - 14 повторов. 01111111111... - 15 повторов. 011001... - от 16 до 255 повторов (в бай- товом потоке - число этих повторов). 011001... - от 256 до 4097 повторов (в ба- йтовом потоке: сначала старший байт,потом младший байт числа повторов. Очевидно,что старший байт всегда меньше 16, что позво- ляет отличить последовательность от пре- дыдущего случая). 011001 - это также конец файла,если байт в байтовом потоке равен нулю. Из этого, в частности,следует,что последний байт упа- кованного блока всегда равен нулю. Выше под многоточием понималось следую- щее указание disp'а (отрицательного): 1 - disp8 (#ff00-ffff) в байтовом потоке. 011x - disp9 (#fd00-feff), где x - старший разряд (0 соответствует #fd), остальные 8 - в байтовом потоке. 010xx - disp10 (#f900-fcff), где xx - ста- ршие разряды (00 соответствует #f9), ос- тальные 8 - в байтовом потоке. 001xxx - disp11 (#f100-#f8ff), где xxx - старшие разряды (000 соответствует #f1), остальные 8 - в байтовом потоке. 000xxxx - disp12 (#e200-#f0ff), где xxxx - старшие разряды (0001 соответствует #e2), остальные 8 - в байтовом потоке. 0000000 - disp16, хранится в байтовом по- токе,сначала старший байт,потом младший. v2.3/Hrip: Файл разбивается на блоки. У каждого блока свой заголовок.Блоки могут быть раз- ной длины. Желательно, однако, чтобы длина блока была кратна #100. Просто упакованный файл длиной более #а000 также разбивается на блоки. Последовательно записанные блоки образуют упакованный файл,а последователь- ность упакованных файлов - архив. Заголовок блока: -------------------- +0(5) - "Hrst2". +5(1) - байт флагов: bit0=1: блок сохранен без сжатия; bit1=1: блок в файле был последним; bit5=1: файл удален. +6(2) - длина исходного блока. +8(2) - длина упакованного блока (без дли- ны заголовка). +10(1) - длина дополнительной информации: ----- +11(2) - CRC16 упакованного блока (можно быстро проверить сохранность архива); +13(2) - CRC16 исходного блока; +15(14) - дескриптор файла (как в TR-DOS). ----- +[+10]+11 (берем байт из [+10], прибавляем 11) - упакованный блок. Длина дополнительной информации равна: =0 для просто пакованных файлов; =2 +хотим иметь CRC16 упакованного блока; =4 +еще и знаем CRC16 исходного блока; =18 +знаем имя файла. В Hrip максимальный размер блока - 16k без заголовка. Заголовок занимает 29 байт. В заголовке блока хранится следующая допо- лнительная информация: CRC пакованного,CRC исходного, копия заголовка из каталога. Параметр в заголовке файла, отвечающий за реальную длину блока,должен быть кратен 256. Заголовок архива: -------------------- IDARCH DB "HRi" IDALL DB 0 ;количество файлов в архиве ;(используется в Hrip'е при дополнении ;архива, чтобы не было больше 255 файлов) SMESH DB 0 ;см.далее LAST DW 0 ;см.далее CAT DB 0 ;1 - каталог есть в архиве, ;0 - каталог отсутствует. Следующая формула показывает, как можно определить конец архива (в байтах от нача- ла архива): END_ARCH=[LAST]*256-(256-[SMESH]) bytes Расположение каталога,если таков прису- тствует,можно определить по формуле: START_CAT=[LAST]*256 bytes Формат каталога: -------------------- Заголовок каталога: IDCAT DB "Hrip" FILES DB 0 ;файлов в архиве = IDALL SECLEN DB 0 ;длина каталога в секторах. Далее идет список файлов в архиве, в следующем формате: IDFILE DS 11 ;имя файла SMSEC DW 0 ;на сколько секторов от ;начала архива находится файл SMBYTE DB 0 ;в каком байте в этом секторе LENPACK DB 0 ;длина упак. файла в секторах LENREAL DB 0 ;реальная длина файла в сект. Смещение файла относительно начала ар- хива вычисляется по следующей формуле: SM_FILE=[SMSEC]*256+[SMBYTE] bytes Количество файлов задается в переменной FILES (в заголовке каталога), но этого ма- ло,необходимо,чтобы был 0 после последнего описателя файла. Подготовил А. Кодер Real Information Packer 0.2x Автор программы - Роман Петров (Йошкар- Ола), тёзка и однофамилец йошкар-олинского композитора Megus'а. Человек-загадка. На- писав единственную программу, он скрылся в неизвестность, и тут же возникли вопросы: откуда такой уровень программирования? ко- гда и как настолько ювелирно настроены па- раметры сжатия? почему условия инкремента длины ссылки с точностью до плюс-минус ба- йта совпали с аналогичными условиями в фо- рмате Rar2.x, хотя к моменту появления RIP этот формат отнюдь не был обнародован? В любом случае,это один из самых мощных компрессоров на ZX,победить его сейчас мо- жет только ZXRar, причём, будь ZXRar напи- сан раньше RIP, ещё неизвестно,кто бы кого победил... На маленьких файлах RIP принци- пиально выгоднее - из-за меньшего объёма деревьев и длины распаковщика. Существует 3 распаковщика RIP: оригина- льный v0.21 размером #121, с параметрами в HL (откуда) и DE (куда); DERIP+++.H разме- ром #112 (в отличие от предыдущего, позво- ляет задать адрес деревьев независимый от адреса распаковщика); и v0.25, пришиваемый к упакованному файлу наподобие Hrum, дово- льно неоптимальный. Гвоздь формата - настоящие произвольные деревья, хранящиеся в файле в упакованном виде, совсем как в архивах Zip, Rar и т.д. Процедура построения дерева в сущности ге- ниальна. Приводить её здесь не имеет смыс- ла,она повторена в исходниках DERIP, mRIP, RZXSFX и т.п. Без её невероятной лаконич- ности сама идея использования произвольных деревьев в кодовых компрессорах осталась бы бессмысленной. Считалось бы, что таких деревьев достойны лишь архиваторы. Вместе с тем, пользователю нового комп- рессора пришлось привыкнуть к новой проб- леме: найти #5C0 байт (#5C2 в DERIP+++.H) памяти под деревья.Это уже не 256 байт под активную часть распаковщика, как в Hrust1. Нет, такой крупный объём надо где-то выде- лять. Например,на экране.Не всегда это до- пустимо. Вероятно, из-за таких проблем RIP не получил большого распространения при сборке электронной прессы. Другая проблема - скорость распаковки. Она значительно ниже,чем у старых компрес- соров, даже ниже, чем у программы UnRar. Этот недостаток нельзя устранить - он свя- зан с принципиальным отсутствием в упако- ванном файле байтового потока. Начинают влиять циклы обработки деревьев, а ускоре- ние их возможно лишь ценой существенного увеличения распаковщика и буфера под де- ревья. Биты читаются из байта начиная от 0-го, т.е. нестандартно. Формат: +0(2) - UnpLen, распакованная длина; +2(2) - PakLen, упакованная длина, считая со следующей ячейки (+4); +4 - начало информации о деревьях. 18 че- тырёхбитных чисел (тетрад), указывающих количество бит в хаффмановских кодах у дерева,используемого для извлечения рабо- чих деревьев. Для каждого из кодов 0...17 хранится длина в битах ( 0 - код не испо- льзуется; чем чаще код,тем он должен быть короче). Из этих тетрад однозначно строится де- рево,по которому будут считаны аналогичные данные о 288+32 символах рабочих деревьев. Конечно, тех аналогичных данных (они лежат далее) не обязательно 288+32 байт (в смыс- ле, символов Хаффмана) - они хитро закоди- рованы: 0..15 - просто код длины, типа тех тетрад; 16 - конец информации; 17 - повто- рить ещё 2 раза предыдущую длину. Дерево из 288 литералов условно назовём maintree, а дерево из 32 - просто tree. Деревья строятся из длин по следующим правилам: 1. Если дерево нарисовать так,что корень будет сверху, единицы справа,а нули слева, то левые подветви должны быть не длиннее правых. Нижняя же кромка веток дерева дол- жна плавно опускаться (глубина увеличива- ться) слева направо. 2. Внутри ярусов (совокупностей листьев, имещих одинаковую глубину) действует сор- тировка: меньшие коды символов справа от больших.Т.е.наоборот относительно Rar. Это связано с сокращением распаковщика. После того,как вся информация о деревь- ях считана, извлекаются байты конца архива (в обратном порядке) по дереву maintree, сразу же закидываются в стек.Любой литерал Є256 - конец этого процесса, и начинается собственно распаковка. Распаковка происходит циклами,каждый из которых начинается с чтения символа по де- реву maintree: 0..255 - просто байт.Поместить его в выхо- дной поток; 257..260 - puts=1..4. Далее по дереву tree считывается код длины disp: 0 - использовать старый disp; 1..4 - disp=1..4 5,7,...,31 - disp вида %10.., от 3 до 16 бит.Дочитываются недостающие биты disp, начиная со старших; 6,8,...,30 - disp вида %11.., от 3 до 15 бит. Аналогично. Отсюда видно, что раз- мер окна не превышает 49151. Эта кодировка disp полностью соответству- ет формату Rar (см. Inferno#4). Более то- го, почти (256<>257) совпадает следующее: При dispЄ256 инкрементируется puts. При dispЄ#2000 ещё раз инкрементируется puts! Реализуется копирование. 261,263,...,287 - коды длин puts (puts= =%10..) от 3 до 16. Дополнительно считы- ваются (соответственно) 1..14 бит для по- дцепления к %10.. Далее считывается disp, как выше; 262,264,...,286 - аналогично,но puts=%11.. От 3 до 15 бит. Далее disp, как выше; 256 - окончить распаковку. После распаковки из стека вынимаются байты конца файла (максимальное число мне неизвестно). mRIP (mrip4.H) Формат отличается от оригинального RIP следующими деталями: 1. Нет UnpLen, PakLen в начале; 2. Нет байтов конца файла в начале; 3. Нет кода "повторить предыдущий disp"; 4. Нет коррекции puts при dispЄ#2000; 5. Тетрады длин кодов дерева для постро- ения рабочих деревьев лежат в обратном по- рядке. В результате депакер уместился в бей- сик-блоке,и был создан удобный автосборщик для системных программ. На маленьких прог- раммах он выигрывает у RIP'а один сектор за счёт размера распаковщика. На длинных теоретически может проиграть. Но на прак- тике до этого не доходит из-за ограничений на размер файла, пакуемого mRIP. Вот что его ограничивает: mRIP не перемещает блок перед распаков- кой, поэтому адрес окончания упакованного блока должен быть выше адреса окончания распаковки. А адрес окончания упакованного блока зависит не от вас, а от длины этого упакованного блока плюс метка OTKUDA. А эта метка (в текущей версии #AA00 ) в свою очередь зависит от максимального размера блока и метки maintree (в текущей версии #f881 ), т.к. при распаковке генерируются деревья,которые где-то нужно хранить,а за- чернить экран не получается: не хватает места в секторе бейсика. Ограничения есть не только при распако- вке, но и при упаковке: при упаковке очень большого файла может не хватить памяти. Можете надеяться упаковать память от #6000 до #E000 (pg0), но никак не всю память. Для подключения mRIP к программе нужно: 1. Определить метки begin, end, GO - ра- змеры программы и адрес её запуска; 2. Сформировать имя программы в #5CDD; 3. В начале программы установить стек, раскрасить экран и бордюр, разобраться с константами клавиатуры: REPPER и REPDEL... Желательно LD (IY+1),#CC для безбоязненно- го использования вашей программой памяти #5bxx. Сам mRIP всего этого не делает; 4. В конце главного исходника поместить INCLUDE "mrip*". Рекомендуется писать именно "mrip*", а не "mrip4", поскольку версия mRIP может меняться. Если вы будете,например,распрос- транять свои исходники, у получателя может оказаться другая версия,ему придётся куда- то лезть,что-то исправлять... (Большинство народу терпеть не может всякие телодвиже- ния.) И вам же будет удобнее обновлять ве- рсии mRIP на своих дисках.Зачем обновлять? Иногда очень неприятно рыться,искать САМУЮ РАСПОСЛЕДНЮЮ версию по всем дискам, а там сплошь всё старые да древние... Если определена метка make, сборка про- изойдёт и без нажатия Caps Shift - можете использовать для распространения с mkace- подобными сборщиками. Для организации отладочного запуска,ко- торый меняет код программы,вписывайте (ес- ли нужно) после INCLUDE следующее: ORG $ CALL 8026 JP NC,nenado <ваши pokes> JP GO Ограничение на размер - не пересекайте адрес #5c00. подготовил А. Кодер ASC Screen Crusher Один из первых серьёзных экранных паке- ров, ASCLZPAK (1991) определил основные идеи структуры большинства последующих фо- рматов и соответствующих им депакеров. А именно, хотя данные в файле пока и лежат только байтами,но впервые реализова- на довольно оптимальная последовательность просмотра экрана - ломаными столбцами. При этом экран логически разбивается на 3 тре- ти и атрибуты,где атрибуты линейны,а трети состоят из столбцов байт. Этот способ даёт возможность сжатия незначительно хуже спо- соба с полными столбцами, зато здесь проще пересчёт линейной модели в реальный экран. Познакомим читателя с идеей линейной модели ZX экрана. Заключается она в том,что каждому адре- су на экране взаимно однозначно соответст- вует виртуальный адрес в линейной модели, причём последовательность адресов экрана, порождаемая порядком просмотра (в данном случае - ломаными столбцами) выглядит на линейной модели последовательностью подряд идущих виртуальных адресов. Т.е.при после- довательном переборе виртуальных адресов линейной модели - адреса на реальном экра- не будут перебираться ломаными столбцами. Разумеется, для этого нужно использовать процедуры пересчета виртуальных адресов в реальные. Как правило, начальные адреса реального и виртуального экранов совпадают, чтобы адреса атрибутов не пересчитывались. Вот как выглядит пересчёт экранного ад- реса из виртуального в реальный по ASC'у (страшновато,но примите во внимание,что он первый!): ------------------------------------------ LD A,H AND #58 CP #58 JR Z,atr LD C,A LD A,L AND 7 OR C LD C,A ADD HL,HL ADD HL,HL LD A,H AND #1F LD H,A LD A,L AND #E0 OR H LD L,A LD H,C atr ------------------------------------------ Распаковщик ASCLZPAK, согласно тради- ции,перемещаем и для этого настраивает три пары ячеек программы под текущий адрес. Опасного использования стека нет. Перемещение по экрану ведётся в реаль- ных адресах, и для координации этого (нес- тандартное решение) применено 3 регистра- счётчика: A' = 8·номер столбца + 3-номер трети; B' = строка внутри знакоместа; C' = номер ряда знакомест внутри трети. Упакованные данные располагаются непос- редственно после программы.Формат простой: %0bbbbHHH,LLLLLLLL - ссылка назад, disp= =HHHLLLLLLLL, puts=bbbb+3 %10000000 - конец файла %10bbbbbb,байты - bbbbbb байт взять из потока %11bbbbbb,байт - повторить байт bbbbbb+3 раз (можно повторить байт до 66 раз, т.е. больше, чем через ссылку) Maxsoft Screen Packer 1.6 Упаковщик Maxsoft Screen Packer 1.6 поддерживает 3 режима распаковки и в связи с этим 3 различных распаковщика: 1. Непосредственно на экран, ломаными столбцами; 2. Через буфер ломаными столбцами; 3. Непосредственно на экран в экранной структуре. Последний вариант наиболее прост, но имеет,что понятно, худшее качество сжатия. Распаковка же через буфер ничем принципиа- льно не отличается от распаковки на экран. Поэтому ниже будет рассматриваться именно режим распаковки на экран (1) - RTD в терминологии автора программы (RealTime Depack). Следует отметить, что распаковщик MSP1.6 является наиболее запутанным экран- ным распаковщиком из всех, с которыми мы будем иметь дело. Если же сравнивать еще и с кодовыми, то рядом можно поставить разве что deHrust 1.x. В упакованных данных имеется два потока - битовый и байтовый. Выборка битов из по- тока производится парами байт (ADD HL,HL: DJNZ:POP HL:LD B,C), c использованием двух дополнительных регистров: B - счетчик, C - константа 16. Начало байтового потока (DEC SP:POP AF) приходится на третий байт фай- ла,после двух первых байт битового потока. В дальнейшем потоки чередуются в зависимо- сти от структуры файла.Битовый поток - уп- равляющий. Для перемещаемости депакера указатель основного стека сохраняется в регистре IY, а не в памяти. Также две смежных яче- йки программы подстраиваются под адрес её расположения: ------------------------------------------ DI CALL #52 DEC SP,SP POP DE LD HL,#110 ADD HL,DE EX DE,HL LD BC,#98 ;адрес ldto ADD HL,BC ;(см.ниже) LD (HL),E INC HL LD (HL),D ------------------------------------------ Таким образом реализуется подобие вызо- ва подпрограммы nxtde, которая, как вполне понятно, делает обычную операцию перевода указателя DE на следующий байт экрана по структуре ломаных столбцов: ------------------------------------------ nxtde INC DE LD A,D CP #58 JR NC,atr DEC DE INC D LD A,D AND 7 JR NZ,atr LD A,E ADD A,#20 LD E,A JR NC,sub8 INC E BIT 5,E RES 5,E JR NZ,atr sub8 LD A,D SUB 8 LD D,A atr JR ... ;смещение в этом JR ;может принимать 2 разл.знач. ------------------------------------------ (атрибуты - линейно). Вызов происходит так: ------------------------------------------ LD A,#8B ;смещение exjr ldto=$+1 ldjr LD (atr+1),A JR nxtde exjr EX DE,HL LD A,#90 ;смещение okjr JR ldjr okjr EX DE,HL ------------------------------------------ Указанный фрагмент всегда проходится насквозь, то есть решалась задача именно смещения ОБОИХ указателей - HL и DE (вход- ного и выходного при копировании, соответ- ственно).Это связано с тем,что перемещение по экрану ведётся в реальных адресах,как в ASCLZPAK. Заголовка у файла нет, следует он непо- средствено за распаковщиком. Первый байт байтового потока (3-й байт файла) является первым байтом экрана,далее по битовому по- току: %0 - просто байт (из байтового потока) %100 - puts=3 %1010 - puts=2 (обратите внимание, код длиннее предыдущего. Это должно означать, что ссылки длиной 2 встречаются реже ссы- лок с длиной 3 ) %1011,байт: -1 =конец файла, -2 =16-битное значение puts (POP BC), иначе puts=байт+ +10 (puts=10..263) %1100 - puts=4 %1101 - puts=5 %11100 - puts=6 %11101 - puts=7 %11110 - puts=8 %11111 - puts=9 Если puts не равен 2, то производится декодирование старшего байта смещения (по- ложительного disp): %1 - 0 %0000 - 1 %0001 - 2 %00100 - 3 %00101 - 4 %00110 - 5 %00111 - 6 %010000 - 7 %010001 - 8 %010010 - 9 %010011 - 10 %010100 - 11 %010101 - 12 %010110 - 13 %0101110 - 14 %0101111 - 15 %0110000 - 16 %0110001 - 17 %0110010 - 18 %0110011 - 19 %0110100 - 20 %0110101 - 21 %0110110 - 22 %0110111 - 23 %0111000 - 24 %0111001 - 25 %0111010 - 26 %0111011 - 27 %0111100 - 28 %0111101 - 29 %0111110 - 30 %0111111 - 31 (ссылки с длиной 2 все короткие, т.е. dispH=0 ) Коды dispH>27 нельзя использовать в эк- ране,и это придаёт формату некоторую неже- лательную избыточность. Далее байт из байтового потока опреде- ляет младший байт смещения. Вычитание сме- щения из текущего адреса в виртуальном эк- ране ( IX адресует его в линейной модели) реализовано достаточно остроумно: ------------------------------------------ DEC SP POP AF SUB LX CPL CCF LD L,A LD A,HX SBC A,H LD H,A ------------------------------------------ После этого виртуальный адрес в HL пе- ресчитывается в экран и происходит копиро- вание блока. Затем всё описанное повторяе- тся и т.д. Lazy Pack 2.0 К сожалению, этим упаковщиком экранов могут воспользоваться не все - по непонят- ным причинам на большинстве версий TR-DOS программа при сохранении уничтожает ката- лог диска. Но формат заслуживает рассмот- рения. В формате использованы почти все хоро- шие идеи по сжатию экранов, какие только реализованы в LC5.2, но по длине распако- вщика Lazy 2.0, безусловно, проигрывает LC 5.2. Имеются битовый и байтовый потоки в простейшем варианте: битовый поток извле- кается побайтно в регистр D', B'=счётчик, C'=константа 8 для помещения в B'. Первый байт файла принадлежит битовому потоку. Виртуальный адрес экрана в линейной мо- дели содержится в IX, реальный адрес - в DE. Распаковщик не привязан к экрану, вызы- вается с параметром: HL=адрес упакованного экрана. Не использует стек (входные данные в HL' ). Распаковывает на экран #4000, но это число легко исправить на любое кратное #2000. Если немного изменить код, то и на любое кратное #800. Распаковщик достигает перемещаемости, организуя в области MEMBOT системных переменных бейсика три JP на три внутренние подпрограммы распаковщика,усло- вно назовём их get,down и bit. Определение своего адреса распаковщик делает через DEC SP,SP, поэтому нужно проследить,чтобы пре- рывания были выключены. get (+#aa, #5c97) - извлекает из бито- вого потока в аккумулятор число 0...23 по следующему дереву: %1 - 0 %01 - 1 %0010 - 2 %0011 - 3 %000100 - 4 %000101 - 5 %000110 - 6 %000111 - 7 %00001000 - 8 %00001001 - 9 %00001010 - 10 %00001011 - 11 %00001100 - 12 %00001101 - 13 %00001110 - 14 %00001111 - 15 %00000000 - 16 %00000001 - 17 %00000010 - 18 %00000011 - 19 %00000100 - 20 %00000101 - 21 %00000110 - 22 %00000111 - 23 down (+#cc,#5c9a) - пересчитать адрес в DE, чтобы он соответствовал следующему байту на экране (в порядке ломаных столб- цов). bit (+#ea, #5c94) - взять один бит из битового потока (результат во флаге пере- носа). Формат: %1 - просто взять байт (из байтового пото- ка); %0 - вызываем get. Если результат =7: Читаем некий byte из байтового потока. Если он не -1, то puts=byte+25, иначе вызываем get (putsH), потом извлекаем из байтового потока putsL. Если результат <7: Прибавляем 2, получаем puts=2..8. Если результат >7: Прибавляем 1, получаем puts=9..24. Далее вызываем get (dispH) и берём байт dispL из байтового потока.Реализуем полу- ченную ссылку и т.д. Специального кода выхода из распаковщи- ка не предусмотрено. Распаковщик сам конт- ролирует окончание экрана. подготовил А. Кодер Laser Compact 4.0 Я не интересовался более старыми верси- ями, хотя и застал их появление. Поэтому начну сразу с 4-й. Формат у неё простой (информация лежит целыми байтами),зато код распаковщика очень любопытен. Как и в описанных ранее экранных комп- рессорах, LC рассматривает 6912 экран в виде трёх 8-знакоместных линий, состоящих из столбцов байт, и лежащих далее линейно адресуемых атрибутов. Файл начинается байтом,который содержит старший байт значения разрыва между нача- лом атрибутов и окончанием экрана. 0=full screen 1=нижние 2/3 2=нижняя 1/3 8=верхние 2/3 9=средняя 1/3 16=верхняя 1/3 Байт этот используется в распаковщике сам по себе,а заодно пересчитывается в ад- рес начала экрана и его высоту: ------------------------------------------ LD A,(HL) LD LX,A ;разрыв AND 3 RLCA RLCA RLCA OR 64 LD D,A ;начало экрана LD E,0 LD A,(HL) INC HL AND #FC LD C,A LD A,88 SUB C LD C,A ;конец экрана ------------------------------------------ Распаковщик отличает от предшественни- ков тот факт, что адрес на экране пересчи- тывается из виртуального на каждом байте: из регистров HL (виртуальный адрес откуда) и DE (куда) рассчитывается адрес DE'. ------------------------------------------ LD A,H LD A,D CP C ;конец экрана CP C JR NC,------+ +----JR NC, XOR L | | XOR E AND #F8 SCF | AND #F8 XOR L PUSH AF XOR E EXX ADD A,LX EXX LD D,A EXX LD D,A EXX LD D,A EXX XOR L EXX XOR E XOR H POP AF XOR D RLCA LD A,E RLCA RLCA +----JR NC, RLCA EXX---+ LD A,L +--EXX LD E,A JR-----+ LD E,A ------------------------------------------ Напомню,что LX для полного экрана равен 0. Процедура перехода к следующему адресу отсутствует.Это дало возможность,не сильно раздувая распаковщик, реализовать ссылку с копированием байтов задом наперед. Что ещё более интересно,код режима работы основно- го цикла распаковщика всё время хранится во флагах: Z, NC - повтор одного байта A Z, C - копирование HL-- в DE++ NZ, NC - брать байты непосредственно из потока (HL') NZ, C - копирование HL++ в DE++ Число проходов цикла в любом случае ле- жит в B. После первого байта, который описан вы- ше, в потоке лежит упакованный экран,зако- дированный следующим образом: %11000000 - конец экрана; %11xxxxx0,байты (NZ, NC) - просто байты из потока. Число байтов %xxxxx=1..31; %11xxxx01,байт (Z, NC) - %xxxx=2..16 оди- наковых байт; %11000001,длина,байт (NZ,NC) - до 256 оди- наковых байт (0=256); %11xxxx11 (Z, NC) - %xxxx=1..15 нулей; %11000011,длина (Z, NC) - 16..256 нулей; %0pppphhh,%llllllll (NZ,C) - короткая ссы- лка: -disp=%11111hhhllllllll, puts=%pppp+ +1=2..16; %10ppphhh,%llllllll (Z, C) - короткая ссы- лка к копированием в обратном порядке: -disp=%11111hhhllllllll, puts=%ppp+1; %00000HHH, %ppppppph, %llllllll (NZ, C) - длинная ссылка: -disp=%1111HHHhllllllll, len=%ppppppp+1, %10000HHH,%ppppppph,%llllllll(Z,C) - длин- ная ссылка с копированием в обратном по- рядке, аналогично. Недостаток формата в большом количестве способов записать одно и то же.Кроме того, хотя 0 кодируется просто,специальных кодов для #ff не предусмотрено. Идея не доведена до конца... --Дополнение от Hrumer'а из IG#7-------------------------------- LC4 более эффективно сохраняет большие последовательности "непакуемых байтов". ---------------------------------------------------------------- Laser Compact 5.2 В данной версии исключены коды обращён- ного копирования, но добавлена упаковка ссылок по Хаффману.В связи с этим,в сорев- новании между LC4.0 и LC5.2 может выиграть как тот, так и другой. С появлением Burial Laser Compact фор- мат LC5.2 стал почти стандартом. "Почти" потому, что так и не было согласовано рас- ширение (у Hrumer'а .PLC, у Sinn'а .plc ), а универсальный просмотрщик BV так и не научился раскрывать этот формат. Как и раньше, LC рассматривает экран в виде 3 (впрочем, зависит от того,экран это или часть) линий ломаных столбцов плюс ат- рибуты. Преобразование адресов для этого выгля- дит следующим образом (почти полностью со- впадает с LC4.0 ): ------------------------------------------ LD A,D CP HX JR NC,-------+ XOR E | AND #F8 | XOR E | LD B,A EXX XOR E ADD A,E XOR D EXX RLCA LD B,A RLCA LD C,E LD C,A | +----+-----+ | ------------------------------------------ Внутри экрана, если представить его ли- нейным по ломаным столбцам (т.е. так, как его проходит распаковщик), опять действует сжатие из семейства LZ. Размер словаря #1700 байт, максимальная строка - 255/256 байт. (При смещении >768 длина строки ко- дируется как на единицу меньшая.) Битовый и байтовый потоки реализованы аналогично формату Hrust2.x, за исключени- ем того,что в одной из ветвей чтения бита: SLA D JR NZ,$+6 LD D,(HL) INC HL RL D вместо RL используется SLI (т.к. начальное значение D равно 0, а не 128 ). Программа ZX_Emul 0.33 не всегда эмулирует этот фра- гмент правильно. Это происходит,по-видимо- му, из-за неправильного выставления флагов после команды SLI D. Заменой SLI на RL (с предварительным занесением 128 в регистр D) эту проблему удаётся решить. В приложе- нии вы сможете найти патчи к ZX Guide##3,4 в виде секторов, которые нужно записать на указанное место диска. Внутри распаковщика действует не только отрицательное смещение, как это обычно у Hrumer'а, но и отрицательная длина ссылки (цикл INC LX:JR NZ ).Это связано с тем,что старший байт смещения и длина ссылки изв- лекаются одним и тем же участком кода. Ко- дируются же они по следующей таблице: (см. цикл DLC5, который проходится рас- паковщиком 2 раза подряд, один раз для -puts=LX, второй раз для -dispH=A' ) ------------------------------------------ код -disp puts 1 FFxx 2 011 FExx 3 001 FDxx 4 01011 FCxx 5 01001 FBxx 6 00011 FAxx 7 00001 F9xx байт* 0101011 F8xx 8 0101010 F7xx 9 0101001 F6xx 10 0101000 F5xx 11 0100011 F4xx 12 0100010 F3xx 13 0100001 F2xx 14 0100000 F1xx 15 0001011 F0xx 16 0001010 EFxx 17 0001001 EExx 18 0001000 EDxx 19 0000011 ECxx 20 0000010 EBxx 21 0000001 EAxx 22 0000000 E9xx 23 ------------------------------------------ (*) чтение байта из байтового потока; если он равен нулю,распаковка прерывается,иначе после декрементирования взять его за АНТИ- длину ссылки. Т.е. тот самый LX, который увеличивается до достижения нуля. После получения -dispH из байтового по- тока извлекается -dispL. Как уже было от- мечено, при disp>768 увеличивается puts. С учётом всего сказанного формат читае- тся следующим образом: Заголовок: +0 "LCMP5" +5 длина файла,начиная с поля "код размера экрана" +6 длина дополнительной области заголовка, которая может начинаться после этого бай- та.(На практике [+6]=0, и доп.область от- сутствует.) Фрагмент распаковщика, учиты- вающий эту деталь формата,несколько избы- точен: LD A,(HL):INC HL:LD E,A... В неко- торых случаях выгодно отрезать как заго- ловок,так и начало самого распаковщика. +7+[+6] код размера экрана. Благодаря ост- роумному решению равен старшему байту расстояния от окончания пиксельной части экрана до атрибутов.6 возможных значений: 0=full screen 1=нижние 2/3 2=нижняя 1/3 8=верхние 2/3 9=средняя 1/3 16=верхняя 1/3 Декодируется так: x&3=0/1/2 - номер нача- льной трети; x&#FC=0/8/16 - работать до строки 24/16/8 соответственно. +8+[+6] 1-й байт экрана в несжатом виде. +9+[+6] первый байт битового потока и т.д. Битовый поток: %1 - взять байт из байтового потока; %0,-puts,-dispH (-dispL из байтового пото- ка) - ссылка назад с копированием. Коды для disp и puts приведены в таблице выше. При disp=1, очевидно, происходит повторе- ние предыдущего байта и т.п. --Дополнение от Hrumer'а из IG#7-------------------------------- В журнале написано, что в LC5.x исчезли копирования "задом наперед". Это неверно. Флаг берётся вот этим кодом: DLC3 EXA SLA D JR NZ,$+6 LD D,(HL) INC HL SLI D DJNZ DLC7 С переходом на DLC7. После получения -dispH из битового потока извлекается признак направления копирования: 0 - последовательное копирование байтов; 1 - обратное копирование байтов, и помещается в CF. Кусок кода, где используется это значение в дальнейшем: JR NC,$+4 DEC HL DEC HL INC HL Далее из байтового потока извлекается -dispL. Как уже было отмечено, при disp>768 увеличивается puts. Надо исправить так: ---------------------------------------------------------------- Битовый поток: %1 - взять байт из байтового потока; %0, -puts, -dispH, %x (признак направления копирования: 0 - как обычно, 1 - в обратном порядке), -dispL (из байтового потока) - ссылка назад с копированием. Коды для disp и puts приведены в таблице выше. При disp=1 и признаке направления = 0, очеви- дно, происходит повторение предыдущего байта... ---------------------------------------------------------------- //Распаковщик картинок LaserCompact5.2. // Си версия: Hrumer & HalfElf. 25.02.2005 typedef unsigned char BYTE; typedef unsigned short WORD; BYTE *lc_d_input; BYTE lc_d_tagbyte; BYTE lc_d_index; // преобразование 000СгСтолбРядЛин в 000СгЛинРядСтолб WORD getrealadr(WORD virtadr, WORD lb, WORD sc) { WORD realadr; if (virtadr < lb) { WORD Lin = (virtadr & 0x0007) << 8; WORD Ryd = (virtadr & 0x0038) << 2; WORD Stolb = (virtadr & 0x07C0) >> 6; WORD Sg = virtadr & 0x1800; realadr = Sg | Lin | Ryd | Stolb; } else { realadr = virtadr + sc; } return realadr; } BYTE lc_d_getbit(void) { BYTE bit[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; if (lc_d_index == 0) lc_d_tagbyte = *lc_d_input++; BYTE tmp = (lc_d_tagbyte & bit[lc_d_index]) == 0 ? 0 : 1; lc_d_index = (lc_d_index + 1) % 8; return tmp; } BYTE getcode(void) { BYTE tmp = 0xFE; for( int i = 0; i < 3; ++i) { if( lc_d_getbit() ) return tmp+1; tmp = (tmp << 1) | lc_d_getbit(); } return ((tmp << 1) | lc_d_getbit()) + 9; } BYTE getlen(void) { BYTE len = getcode(); if (len == 0x100 - 7) { len = *lc_d_input++; --len; } else { if (len > 0x100 - 7) --len; } return len; } int delc52(BYTE* source, BYTE* destination) { lc_d_index = 0; BYTE *lc_d_output = destination; lc_d_input = source + source[7] + 9 ; WORD sc = (lc_d_input[-1]) << 8; WORD to = (sc & 0x0300) << 3; WORD lb = (sc ^ 0x1800) & 0xFC00; lc_d_output[getrealadr(to++, lb, sc)] = *lc_d_input++; while(true) { if (lc_d_getbit()) { lc_d_output[getrealadr(to++, lb, sc)] = *lc_d_input++; } else { BYTE len = getlen(); if (len == 0xFF) break; WORD dist = getcode() << 8; BYTE napr = lc_d_getbit(); dist |= *lc_d_input++; len = -len; dist = -dist; if (dist > 768) ++len; WORD from = to-dist; do { lc_d_output[getrealadr(to++, lb, sc)] = lc_d_output[getrealadr(from, lb, sc)]; from += napr ? -1 : 1; --len; } while (len>0); } } return 0; } ---------------------------------------------------------------- Несколько слов о самих идеях сжатия... Поскольку относительный процент атрибу- тов в картинке невелик, создание особого способа сжатия не было для них целесообра- зно. Между тем, если использовать сжатие байтового потока по Хаффману, специфичес- кие атрибутные байты в состоянии испортить всё дерево. Это мысль номер раз. Установленный факт, что не обязательно пользоваться упаковщиком LC ради достиже- ния близких к предельным (хотя кто знает, каковы они?) степеней сжатия. Разложив не- сколько экранов по столбцам и наXORив друг на друга соседние байты, мы обычно получа- ли массив, сжимаемый программой ZXRar (в режиме Rar ) до объёмов менее полученных Laser Compact'ом. Но некоторым экранам XOR оказался противопоказан. Конечно, выигрыш надо было бы считать по сравнению с файла- ми LC5.2, упакованными с помощью lazy eva- luation или другого "умного" метода опти- мизации ссылок,но пока такого LC нет (рав- но как и такого Hrust1.x ).Это мысль номер два. В связи с предыдущим высказывалось пре- дположение, что наXORивание 0,1 битов сто- лбца слева на 6,7 биты столбца справа даст более эффективное дерево. Опыты не дали лучшего качества сжатия, возможно, лишь потому, что атрибуты не были отделены. Это мысль номер три и задел для будущих экспе- риментов. подготовил А. Кодер