Data-driven metaprogramming
May. 20th, 2009 01:18 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
На днях доделывал новый кодек, в одном месте занятная загогулина получилась.
Нужно, чтобы незарегистрированная версия при кодировании через N кадров или M секунд после начала работы (что произойдет раньше) рисовала на видео похабную надпись большими красивыми буквами, тем самым побуждая к регистрации. Рисовать надпись обычными средствами GDI плохо - векторным шрифтом это делать медленно, да и нужного шрифта у пользователя может не быть. Быстрый вариант - накладывать картинку с прозрачностью. Но ее слишком легко заменить на полностью прозрачную, сам так делал с одним скринсейвером. :) К тому же, хочется какой-то защиты от взлома - чтобы так просто надпись было не убрать.
Воспользовался привычным решением, которое успешно применял в предыдущих продуктах - функциональность, которую хочется защитить от взлома, пишется на моем DSEL, который компилится в байткод простенькой регистровой виртуальной машины. Получилось следующее. Нарисовал нужную картинку нужным шрифтом в графическом редакторе, превратил в монохромную, сохранил. Дальше моя программка на Окамле прочитала картинку и прошлась по ней чем-то вроде RLE, превратив в такой примерно текст:
Код, который будет выполняться в ВМ, у меня пишется на С-образном язычке, являющимся DSEL Руби. Т.е. синтаксически это код на Руби, который в результате выполнения порождает скомпилированный код ВМ. А раз это код на Руби, то у меня есть полная свобода действий во время компиляции - Руби выступает в качестве мета-языка. И вот, во время такой компиляции я читаю из файла описание картинки в виде того массива и прохожусь по нему, на каждой итерации цикла порождая кусочек кода для ВМ:
Здесь синим обозначены конструкции, которые генерят код для ВМ, это как бы run-time, а черным - то, что происходит в compile-time, в частности, выражения типа bpp * pair[0] превращаются в сгенеренном коде в константы. Compile-time функция drawwm вызывается дважды - с разными значениями числа байт на пиксел, что приводит к генерации двух рисующих кусков кода, отличающихся лишь конкретными значениями констант. Т.е. налицо partial evaluation. В итоге, для каждой пары чисел из того массива, например [4,5], генерится такой вот код (bpp=3):
И, как ни удивительно, все это даже работает, и надпись рисуется как надо в разных цветовых режимах.
Нужно, чтобы незарегистрированная версия при кодировании через N кадров или M секунд после начала работы (что произойдет раньше) рисовала на видео похабную надпись большими красивыми буквами, тем самым побуждая к регистрации. Рисовать надпись обычными средствами GDI плохо - векторным шрифтом это делать медленно, да и нужного шрифта у пользователя может не быть. Быстрый вариант - накладывать картинку с прозрачностью. Но ее слишком легко заменить на полностью прозрачную, сам так делал с одним скринсейвером. :) К тому же, хочется какой-то защиты от взлома - чтобы так просто надпись было не убрать.
Воспользовался привычным решением, которое успешно применял в предыдущих продуктах - функциональность, которую хочется защитить от взлома, пишется на моем DSEL, который компилится в байткод простенькой регистровой виртуальной машины. Получилось следующее. Нарисовал нужную картинку нужным шрифтом в графическом редакторе, превратил в монохромную, сохранил. Дальше моя программка на Окамле прочитала картинку и прошлась по ней чем-то вроде RLE, превратив в такой примерно текст:
[[ [119,5] ], [ [117,3], [3,4] ], [ [67,1], [56,4], [69,1], [9,19], [3,19], [4,5], [7,5], [5,5], [17,5] ], [ [11,2], [6,2], [9,2], [6,2], [8,2], [6,2], [7,4], [5,3], [3,3], [17,5], [2,6], [15,5] ], [ [11,2], [5,3], [8,3], [5,3], [8,3], [5,3], [6,5], [4,3] ], ... ]Каждая строка картинки кодируется последовательностью пар чисел - число прозрачных точек, число непрозрачных. Записаны данные намеренно в синтаксисе Руби. А дальше происходит вот что.
Код, который будет выполняться в ВМ, у меня пишется на С-образном язычке, являющимся DSEL Руби. Т.е. синтаксически это код на Руби, который в результате выполнения порождает скомпилированный код ВМ. А раз это код на Руби, то у меня есть полная свобода действий во время компиляции - Руби выступает в качестве мета-языка. И вот, во время такой компиляции я читаю из файла описание картинки в виде того массива и прохожусь по нему, на каждой итерации цикла порождая кусочек кода для ВМ:
____________________________________________________ def drawwm(x, y, pSrc, stride, lines, bpp) sy = _int(y/2 - 30)
sx = _int(x/2 - 128)
pPic = _pbyte
j = _int for ny in 0...lines.length if lines[ny].length > 0 pPic.point(pSrc + (sy+ny)*stride + sx*bpp) for pair in lines[ny] pPic.add(bpp * pair[0])
_for(j <= 0, j < bpp * pair[1], j.inc) {
pPic <= 81
pPic.inc
} end end end end lines = open('lines.rb') {|f| eval(f.read) } _if(draw > 0) {
_if(bytespp.eq(2)) {
drawwm(x, y, pSrc, stride, lines, 2)
}.else {
drawwm(x, y, pSrc, stride, lines, 3)
}
} ____________________________________________________
Здесь синим обозначены конструкции, которые генерят код для ВМ, это как бы run-time, а черным - то, что происходит в compile-time, в частности, выражения типа bpp * pair[0] превращаются в сгенеренном коде в константы. Compile-time функция drawwm вызывается дважды - с разными значениями числа байт на пиксел, что приводит к генерации двух рисующих кусков кода, отличающихся лишь конкретными значениями констант. Т.е. налицо partial evaluation. В итоге, для каждой пары чисел из того массива, например [4,5], генерится такой вот код (bpp=3):
ADD|RV, 23, 12, # перепрыгиваем через 4 точки MOV|RV, 24, 0, # обнуляем счетчик цикла LE|RV, 24, 15, # цикл на 5 точек - 15 байт JZ, 13, MOVB|PV, 23, 81, # пишем в картинку ADD|RV, 23, 1, # увеличиваем указатель на место рисования и счетчик цикла ADD|RV, 24, 1, JMP, -14, # конец цикла
И, как ни удивительно, все это даже работает, и надпись рисуется как надо в разных цветовых режимах.
no subject
Date: 2009-05-20 01:33 pm (UTC)no subject
Date: 2009-05-20 01:41 pm (UTC)no subject
Date: 2009-07-17 08:41 pm (UTC)no subject
Date: 2009-07-18 09:03 am (UTC)