2012-06-02 9 views
6

Estamos utilizando el motor de juego Love2d Lua que expone una aplicación gráfica a Lua. Estamos tratando de serializar una tabla hash gigante que contiene todos los datos guardados del juego para el mundo del juego. Este hash incluye algunas funciones, y algunas de estas funciones llaman a Love2d C funciones.¿Es posible llamar a la cadena de carga en la cadena de códigos de bytes lua que contiene una referencia a una función C?

Para serializar las funciones en el hash, usamos string.dump, y lo volvemos a cargar con loadstring. Esto funciona bien para funciones Lua puras, pero cuando tratamos de serializar y luego cargar de nuevo en una función que llama a una función envolvente C como una en la API Love2d, la cadena de carga devuelve nil.

Considere el siguiente programa simple que dibuja "hola, mundo" a la pantalla a través de un motor gráfico de Love2d:

function love.load() 
    draw = function() 
     love.graphics.print('hello, world', 10, 10) 
    end 
end 
function love.draw() 
    draw() 
end 

Nos gustaría ser capaz de hacer esto:

function love.load() 
    draw_before_serialize = function() 
     love.graphics.print('hello, world', 10, 10) 
    end 

    out = io.open("serialized.lua", "wb") 
    out:write('draw = load([[' .. string.dump(draw_before_serialize) .. ']])') 
    out:close() 

    require "serialized" 
end 
function love.draw() 
    draw() 
end 

Hacer esto escribe en un archivo Lua en el disco que contiene una mezcla de bytecode Lua y Lua no compilados, que se parece a esto:

draw = load([[^[LJ^A^@  
     @main.lua2^@^@^B^@^B^@^D^E^B^B4^@^@^@%^A^A^@>^@^B^AG^@^A^@^Qhello, world 
     print^A^A^A^B^@^@]]) 

Este método funciona bien con las funciones Lua que no llaman a los módulos C. Creemos que este es el problema, ya que este ejemplo funciona:

function love.load() 
    draw_before_serialize = function() 
     print('hello, world') 
    end 

    out = io.open("serialized.lua", "wb") 
    out:write('draw = load([[' .. string.dump(draw_before_serialize) .. ']])') 
    out:close() 

    require "serialized" 
end 
function love.draw() 
    draw() 
end 

lugar de llamar al método de gráficos Love2d, se hace una impresión en la consola.

Después de más pruebas, que estaban confundidos al ver que este ejemplo funciona:

function love.load() 
    draw_before_serialize = function() 
     love.graphics.print('hello, world', 10, 10) 
    end 

    draw = load(string.dump(draw_before_serialize)) 
end 
function love.draw() 
    draw() 
end 

Aquí en realidad no escribimos la función en el disco, y en su lugar sólo lo descarga e inmediatamente después cargamos de nuevo . Pensamos que quizás el culpable no estaba escribiendo los datos con el indicador de modo de escritura binaria establecido ("wb"), pero como estamos en Linux, esta bandera no tiene ningún efecto.

¿Alguna idea?

+1

"imprime en la consola" ¿qué imprime? Además, ¿está seguro de que el entorno global utilizado por el código anterior es el mismo que el utilizado por 'require'? [ya que depende de que 'draw' se defina en el entorno global] – snogglethorpe

+1

Debe avisar que esto: '[['.. string.dump (draw_before_serialize) ..']]' no necesariamente va a funcionar. El volcado que reciba podría contener * cualquier cosa *, incluidos los caracteres ']]'. Eso terminaría la cadena temprano, rompiendo así las cosas. –

+2

@NicolBolas Una vez vi una solución fácil pero inteligente para eso que acaba de comprobar la cadena para '] (= *)]' y luego enmarcar el volcado con una '=' más que el número máximo de '=' que se encuentra con el partido. – jpjacobs

Respuesta

5

Creo que el problema está en el formateo de la cadena. Nicol Bolas podría tener razón acerca de las comillas [[]] que rodean el volcado de código de bytes, pero esto apunta a un problema mayor; El código de bytes realmente podría ser cualquier cosa, pero lo está tratando como si fuera una cadena normal que se puede escribir y leer desde un archivo de texto. Este problema lo demuestra su última demostración, donde carga la cadena objeto de dumping sin tener que escribirla en el archivo.

This implementación de un serializador para tablas que incluyen funciones que hacen lo que usted quiere, creo, pero también creo que está roto (bueno, no pude hacer que funcione bien de todos modos ...). De todos modos, está en el camino correcto. Debe formatear el bytecode y y luego escribirlo en el archivo.

Estoy seguro de que hay una mejor manera de hacerlo, pero esto funciona:

1. binary = string.dump(some_function) 
2. formatted_binary = "" 
3. for i = 1, string.len(binary) do 
4.  dec, _ = ("\\%3d"):format(binary:sub(i, i):byte()):gsub(' ', '0') 
5.  formatted_binary = formatted_binary .. dec 
6. end 

Este bucle a través de cada carácter en el código de bytes, les da formato como bytes escapado (cada uno es una cadena que contiene un código como "\ 097", que luego de la interpolación escaparía a "a").

La línea 4 de esta muestra es muy densa, así que la analizaré.Primero,

binary:sub(i, i) 

saca el carácter i de la cuerda. A continuación,

binary:sub(i, i):byte() 

devuelve la representación en entero ASCII del carácter i'th. Luego formato con

("\\%3d"):format(binary:sub(i, i):byte()) 

lo que nos da una cadena como "\ 97", por ejemplo, si el personaje fuera "a". Pero esto no escapará correctamente porque necesitamos "\ 097", entonces hacemos un gsub reemplazando "" con "0". El gsub devuelve la cadena resultante y el número de sustituciones que se realizaron, por lo que solo tomamos el primer valor de retorno y lo ponemos en "dec". No estoy seguro de por qué el formato "% 3d" no reemplaza los espacios con "0" por defecto ... bueno.

Luego, para ejecutar la cadena binaria formateada, necesitamos escapar de ella y pasar el resultado a "cargar". Las comillas de weirdo [[]] en Lua no hacen escapes como "" ... de hecho, no estoy seguro de que escapen en absoluto. De manera que para hacer una cadena Lua ejecutable que devolverá una función que hará lo que esté en "some_function", hacemos esto:

executable_string = 'load("' .. formatted_binary .. '")' 

Ok - por lo que poner todo eso junto, creo que podemos hacer su Test- caso funciona así:

1 function love.load() 
    2  draw_before_serialize = function() 
    3   love.graphics.print('hello, world', 10, 10) 
    4  end 
    5 
    6  binary = string.dump(draw_before_serialize) 
    7  formatted_binary = "" 
    8  for i = 1, string.len(binary) do 
    9   dec, _ = ("\\%3d"):format(binary:sub(i, i):byte()):gsub(' ', '0') 
10   formatted_binary = formatted_binary .. dec 
11  end 
12  
13  out = io.open("serialized.lua", "wb") 
14  out:write('draw = load("' .. formatted_binary .. '")') 
15  out:close() 
16  
17  require "serialized" 
18 end 
19 function love.draw() 
20  draw() 
21 end 

Cuando ejecuto esto con Amor obtengo una pantalla OpenGL con "hello world" impreso en la esquina. El archivo resultante "serialized.lua" contiene lo siguiente:

draw = load("\027\076\074\001\000\009\064\109\097\105\110\046\108\117\097\084\000\000\004\000\004\000\008\009\002\002\052\000\000\000\055\000\001\000\055\000\002\000\037\001\003\000\039\002\010\000\039\003\010\000\062\000\004\001\071\000\001\000\017\104\101\108\108\111\044\032\119\111\114\108\100\010\112\114\105\110\116\013\103\114\097\112\104\105\099\115\009\108\111\118\101\001\001\001\001\001\001\001\002\000\000") 
+0

¿Es difícil hacer eso? Tengo una cadena serializada casi como esta que ha publicado y quiero cambiarla por una versión basada en humanos para hacer algunos cambios menores en el script que genera esta cadena. –

Cuestiones relacionadas