Работая над утилитой для собственного использования столкнулся с одной «волшебной» ошибкой. Изначально я намеревался сделать программу с возможностями квайна, но этого не получилось, так как даже от строки (из Википедии):
var s:string='var s:string=;begin insert(#39+s+#39,s,14);write(s)end.';begin insert(#39+s+#39,s,14);write(s)end.
мой
В какой-то момент я перепутал код выводимой программы и код программы, которую нужно вывести (ошибся в названии файла). И получил настоящий квайн. Немного допилил до более лучшего вида и пришел сюда поделится своей историей.
Мои инструменты:
Free Pascal Compiler 2.6.4
Notepad++
Соответственно, язык программирования FreePascal (режим objFPC)
Обо всем по порядку. И я расскажу как все было.
Часть первая. «Базовая программа»
Тут немного. Нам нужна программа, чей код мы будем выводить на первых порах. Сойдет любая, даже такая:
program MyProgram;
begin
write('Hello, world');
end.
Я же в качестве «базовой программы» писал немного больше кода.
Преобразуем нашу программу в массив HEX-кода. Для этого используем стандартный data2inc (поставляется вместе с Free Pascal Compiler). Т.к. нам надо будет часто использовать эту утилиту, лучше написать такой bat-скрипт:
D:FPC2.6.4bini386-win32data2inc.exe -B -A myProg.pp myProg.inc MyArray
2) -B для восприятия как массив байтов преобразуемого файла
3) -A для вывода как массив HEX-констант
4) myProg.pp название нашего файла
5) myProg.inc куда генерировать массив
6) MyArray как обозвать получающийся массив
И положим в папку нашей программы. После запуска у меня вышел такой код моей программы:
const MyArray : array[1..407] of byte=(
$2F,$2F,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,
$2D,$2D,$2D,$2D,$2D,$2D,$2D,$0D,$0A,$2F,$2F,$20,$80,$A2,$E2,
$AE,$E0,$3A,$20,$49,$76,$61,$6E,$54,$61,$6D,$65,$72,$6C,$61,
$6E,$0D,$0A,$2F,$2F,$20,$76,$6B,$2E,$63,$6F,$6D,$2F,$49,$76,
$61,$6E,$54,$61,$6D,$65,$72,$6C,$61,$6E,$0D,$0A,$2F,$2F,$2D,
$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,
$2D,$2D,$2D,$2D,$0D,$0A,$70,$72,$6F,$67,$72,$61,$6D,$20,$4D,
$79,$51,$75,$69,$6E,$65,$3B,$0D,$0A,$7B,$24,$6D,$6F,$64,$65,
$20,$6F,$62,$6A,$66,$70,$63,$7D,$0D,$0A,$76,$61,$72,$20,$69,
$3A,$20,$69,$6E,$74,$65,$67,$65,$72,$3B,$0D,$0A,$20,$20,$20,
$20,$73,$31,$2C,$20,$73,$32,$3A,$20,$50,$43,$68,$61,$72,$3B,
$0D,$0A,$0D,$0A,$66,$75,$6E,$63,$74,$69,$6F,$6E,$20,$4D,$79,
$46,$75,$6E,$63,$53,$75,$6D,$28,$61,$2C,$62,$3A,$69,$6E,$74,
$65,$67,$65,$72,$29,$3A,$69,$6E,$74,$65,$67,$65,$72,$3B,$0D,
$0A,$62,$65,$67,$69,$6E,$0D,$0A,$20,$72,$65,$73,$75,$6C,$74,
$3A,$3D,$61,$2B,$62,$3B,$0D,$0A,$65,$6E,$64,$3B,$0D,$0A,$0D,
$0A,$62,$65,$67,$69,$6E,$0D,$0A,$20,$69,$3A,$3D,$20,$4D,$79,
$46,$75,$6E,$63,$53,$75,$6D,$28,$31,$31,$32,$2C,$31,$30,$30,
$35,$30,$30,$29,$3B,$20,$2F,$2F,$20,$AA,$A0,$AA,$A0,$EF,$2D,
$E2,$AE,$20,$E4,$E3,$AD,$AA,$E6,$A8,$EF,$2E,$0D,$0A,$20,$73,
$31,$3A,$3D,$27,$8A,$A0,$AF,$E0,$A0,$AB,$2C,$20,$AC,$EB,$20,
$AF,$E0,$AE,$A8,$A3,$E0,$A0,$AB,$A8,$21,$27,$3B,$0D,$0A,$20,
$73,$32,$3A,$3D,$27,$20,$AD,$A0,$AC,$20,$AD,$A5,$20,$E3,$A4,
$A0,$AB,$AE,$E1,$EC,$20,$AE,$E2,$AF,$E0,$A0,$A2,$A8,$E2,$EC,
$20,$E8,$A8,$E4,$E0,$AE,$A2,$AA,$E3,$20,$AD,$A0,$20,$E5,$A0,
$A1,$E0,$A5,$20,$3D,$28,$27,$3B,$0D,$0A,$20,$77,$72,$69,$74,
$65,$6C,$6E,$28,$73,$31,$2C,$73,$32,$29,$3B,$0D,$0A,$65,$6E,
$64,$2E);
Сразу говорю — я переправил диапазон с [0..406] на [1..407], чтобы в будущем длину файла не уменьшать на единицу каждый раз. Верхнюю границу вынес отдельно в константу.
Часть вторая. Вывод HEX-кода
Пишем программу для вывода HEX-кода программы из первой части.
Пару условий — нужно вывести код различными способами:
1) Преобразовать массив в текст. Получим первоначальный листинг программы.
2) Вывести HEX-код как HEX-код. Т.е. с полным оформлением. В паскале для этого нужно проставить запятые и обозначения HEX-чисел. Можно для красоты и переносы.
Если у нас длина массива от 1 до N, тогда нам нужно такое число K, которое будет удовлетворять условию 1=<K=<N.
Выводим первые K чисел массив как текст. Потом выводим от 1 до N последовательность HEX-кодов. И после этого завершаем вывод от K+1 до N.
Числа K и N оформить в виде констант дабы не бегать редактировать по всему листингу. Их придется править иной раз очень часто.
//--------------------
// Автор: IvanTamerlan
//--------------------
program myQuine;
{$mode objfpc}
var i:integer;
const
N = 00407;
K = 00407;
MyArray : array[1..N] of byte = (
$2F,$2F,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,
$2D,$2D,$2D,$2D,$2D,$2D,$2D,$0D,$0A,$2F,$2F,$20,$80,$A2,$E2,
$AE,$E0,$3A,$20,$49,$76,$61,$6E,$54,$61,$6D,$65,$72,$6C,$61,
$6E,$0D,$0A,$2F,$2F,$20,$76,$6B,$2E,$63,$6F,$6D,$2F,$49,$76,
$61,$6E,$54,$61,$6D,$65,$72,$6C,$61,$6E,$0D,$0A,$2F,$2F,$2D,
$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,
$2D,$2D,$2D,$2D,$0D,$0A,$70,$72,$6F,$67,$72,$61,$6D,$20,$4D,
$79,$51,$75,$69,$6E,$65,$3B,$0D,$0A,$7B,$24,$6D,$6F,$64,$65,
$20,$6F,$62,$6A,$66,$70,$63,$7D,$0D,$0A,$76,$61,$72,$20,$69,
$3A,$20,$69,$6E,$74,$65,$67,$65,$72,$3B,$0D,$0A,$20,$20,$20,
$20,$73,$31,$2C,$20,$73,$32,$3A,$20,$50,$43,$68,$61,$72,$3B,
$0D,$0A,$0D,$0A,$66,$75,$6E,$63,$74,$69,$6F,$6E,$20,$4D,$79,
$46,$75,$6E,$63,$53,$75,$6D,$28,$61,$2C,$62,$3A,$69,$6E,$74,
$65,$67,$65,$72,$29,$3A,$69,$6E,$74,$65,$67,$65,$72,$3B,$0D,
$0A,$62,$65,$67,$69,$6E,$0D,$0A,$20,$72,$65,$73,$75,$6C,$74,
$3A,$3D,$61,$2B,$62,$3B,$0D,$0A,$65,$6E,$64,$3B,$0D,$0A,$0D,
$0A,$62,$65,$67,$69,$6E,$0D,$0A,$20,$69,$3A,$3D,$20,$4D,$79,
$46,$75,$6E,$63,$53,$75,$6D,$28,$31,$31,$32,$2C,$31,$30,$30,
$35,$30,$30,$29,$3B,$20,$2F,$2F,$20,$AA,$A0,$AA,$A0,$EF,$2D,
$E2,$AE,$20,$E4,$E3,$AD,$AA,$E6,$A8,$EF,$2E,$0D,$0A,$20,$73,
$31,$3A,$3D,$27,$8A,$A0,$AF,$E0,$A0,$AB,$2C,$20,$AC,$EB,$20,
$AF,$E0,$AE,$A8,$A3,$E0,$A0,$AB,$A8,$21,$27,$3B,$0D,$0A,$20,
$73,$32,$3A,$3D,$27,$20,$AD,$A0,$AC,$20,$AD,$A5,$20,$E3,$A4,
$A0,$AB,$AE,$E1,$EC,$20,$AE,$E2,$AF,$E0,$A0,$A2,$A8,$E2,$EC,
$20,$E8,$A8,$E4,$E0,$AE,$A2,$AA,$E3,$20,$AD,$A0,$20,$E5,$A0,
$A1,$E0,$A5,$20,$3D,$28,$27,$3B,$0D,$0A,$20,$77,$72,$69,$74,
$65,$6C,$6E,$28,$73,$31,$2C,$73,$32,$29,$3B,$0D,$0A,$65,$6E,
$64,$2E);
MyHEX : array[0..15] of byte=(
$30,$31,$32,$33,$34,$35,$36,$37,
$38,$39,$41,$42,$43,$44,$45,$46);
function ByteToHex (const b:byte):string;
begin
result := #$24 + char(MyHEX[b div 16]) + char(MyHEX[b mod 16]);
end;
begin
for i:=1 to K do
write(char(MyArray[i]));
write(#$0D#$0A#$20#$20);
for i:=1 to N do
begin
if ((i mod 10) = 0) then write(#$0D#$0A#$20#$20);
write(ByteToHex(MyArray[i]));
if i<N then write(', ');
end;
for i:=K + 1 to N do
write(char(MyArray[i]));
end.
Компилируем, запускаем, программа отображает текст «базовой» программы, а потом ее HEX-последовательность. Псевдо-квайн готов!
Часть третья. Волшебство!
Нас интересуют эти 2 константы:
N = 00407;
K = 00407;
Сейчас нам нужно будет изменить эти 2 числа. Должно быть минимум 5 знаков. Если не хватает — допишите нули. Они нужны, если у нас будет меняться размер файла, тогда изменится разрядность и нам придется заново переправлять файл. Дабы не уходить в рекурсию, лучше сделать сразу так, чтобы изменение разрядности не влияло на основную программу.
Удаляем все, что между скобок в массиве, оставив только это:
MyArray: array[1..N] of byte = ();
В свойствах файла можно посмотреть количество байт. Записываем его в N. Сохраняем файл.
Всё, что от начала и до открывающей скобки — копируем в новый файл и смотрим сколько байт он весит (этот новый файл нам больше не пригодится).
Записываем длину усеченного файла в K.
Теперь с помощью data2inc (в начале статьи я рассказывал, как с ним работать) преобразуем файл в HEX-массив. Новый HEX-массив ставим, где когда-то давно был другой.
Компилируем и получаем настоящий квайн.
Вывод
Для того, чтобы написать квайн, не обязательно писать квайн. Можно написать программу, которая выводит какой-то документ, я специально зашифровал его в виде HEX-последовательности. Можно заменить на другие способы кодирования. Это избавляет от необходимости экранировать служебные символы.
P.S. Лично у меня вышло не с первого раза, да и этой инструкции не было. Поэтому пришлось несколько раз перекодировать и редактировать вручную HEX-код. В этой инструкции я от редактирования HEX-кода избавился.
Автор: IvanTamerlan