thedeemon: (office)
[personal profile] thedeemon
На днях доделывал новый кодек, в одном месте занятная загогулина получилась.
Нужно, чтобы незарегистрированная версия при кодировании через 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,        # конец цикла

И, как ни удивительно, все это даже работает, и надпись рисуется как надо в разных цветовых режимах.

Date: 2009-05-20 01:33 pm (UTC)
wizzard: (Default)
From: [personal profile] wizzard
прикольно. идея трассирующих компиляторов мне нравится. правда, не представляю, как к такому можно прикручивать оптимизатор

Date: 2009-05-20 01:41 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Там код генерится не сразу ассемблерный, а сначало промежуточный в виде дерева, а тот уже отдельным проходом в ассемблер. Между этими стадиями можно попробовать вставить оптимизатор, но у меня такой задачи не стояло - часто наоборот нужно нагенерить побольше лишнего кода в целях обфускации. См. статью из предыдущего поста.

Date: 2009-07-17 08:41 pm (UTC)
From: [identity profile] qz.livejournal.com
А как там со скоростью? Насколько быстрее получается если отказаться от чудо-VM и ничего не рисовать в каждом кадре?

Date: 2009-07-18 09:03 am (UTC)
From: [identity profile] thedeemon.livejournal.com
В данном случае ВМ на каждом кадре выполняет ~13000 команд (рисование плюс другая полезная работа), что занимает 0,6 мс. А сжатие всего кадра - порядка 6 мс. Для данной задачи вполне приемлемо.

Profile

thedeemon: (Default)
Dmitry Popov

May 2025

S M T W T F S
    123
45678910
11 121314151617
18192021222324
25262728293031

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 15th, 2025 07:23 pm
Powered by Dreamwidth Studios