❤ 0 Auteur : Zeus81
Logiciel : RPG Maker XP
Nombre de scripts : 1
Attention, ce script est réservé aux scripteurs ! Si vous ne comprenez pas son utilité ou ne savez pas scripter, il vous sera totalement inutile.
Explication
A la base j'ai fait ça pour moi et mes prochains scripts ultimes mais comme je vois qu'il y en a certains dans le coin qui font des scripts utilisant des dll en C je le partage avant l'heure.
Ce script permet de créer/lire/altérer des données typées C en Ruby.
Les avantages sont que c'est plus simple et efficace à utiliser que des String avec pack/unpack, les données pouvant être lues ET modifiées aussi bien en C qu'en Ruby et puis tout est automatique.
Et tout cela avec ce script de seulement 203 lignes, vous me direz sûrement "Jamais 203" et je vous répondrai "Oh que si !".
Installation
A placer au-dessus de Main.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
| module C # v 5.1 by Zeus81
RtlMoveMemory = Win32API.new('kernel32', 'RtlMoveMemory', 'iii', '')
def self.memcpy(destination, source, size)
RtlMoveMemory.call(
destination.is_a?(String) ? string_address(destination) : destination,
source.is_a?(String) ? string_address(source) : source, size)
destination
rescue raise($!, $!.message, caller)
end
def self.string_address(string) [string].pack('p').unpack('L')[0]
rescue raise($!, $!.message, caller)
end
@anonymous_class_count = 0
def self.name_anonymous_class(type)
if type.name.empty?
type.superclass.name =~ /.*::(.*)/
const_set("Anonymous#{$1}_%02X" % @anonymous_class_count+=1, type)
end
rescue raise($!, $!.message, caller)
end
def self.make_accessors(klass, data, child=nil)
data.each_with_index do |d,i|
if d[2] != nil
a = d[2] == :[] ? 'i,' : ''
b = d[2] == :[] ? '[i]' : child ? ".#{d[2]}" : "[#{i}]"
b = "[#{child}]#{b}" if child
c = b + (child ? '=' : '.set')
b << '.get' unless child or d[0] < CData
klass << "
define_method(:#{d[2]}) {|#{a[0,1]}| @children#{b}}
define_method(:#{d[2]}=) {|#{a}v| @children#{c}(v)}"
break if d[2] == :[]
else make_accessors(klass, d[0].data, child || i)
end
end
rescue raise($!, $!.message, caller)
end
class CType
def self.format() self::FORMAT end
def format() type.format end
def self.size() self::SIZE end
def size() type.size end
def initialize(*v, &b)
@address = b ? b.call.to_int : C.string_address(@string=("\0"*size).freeze)
@children = data.map {|t,o| t.new {to_int+o}} if type < C::CData
set(*v) unless v.empty?
rescue raise($!, $!.message, caller)
end
def get() unpack
rescue raise($!, $!.message, caller)
end
def set(v) pack(v)
rescue raise($!, $!.message, caller)
end
def unpack() to_str.unpack(format)[0].freeze
rescue raise($!, $!.message, caller)
end
def pack(v) C.memcpy(self, v.is_a?(type) ? v : [v].pack(format), size)
rescue raise($!, $!.message, caller)
end
def to_int() @address
rescue raise($!, $!.message, caller)
end
def to_str() @string or C.memcpy(("\0"*size).freeze, self, size)
rescue raise($!, $!.message, caller)
end
end
class CData < CType
def self.data() self::DATA end
def data() type.data end
def get() @children.map {|c| c.get}.freeze
rescue raise($!, $!.message, caller)
end
def set(*v)
if v[0].is_a?(type); super(v[0])
elsif v[0].is_a?(Hash)
v[0].each {|n,v| n.is_a?(Integer) ? @children[n].set(*v) : send("#{n}=",v)}
else v.each_with_index {|v,i| @children[i].set(*v)}
end
rescue raise($!, $!.message, caller)
end
def [](i) data[i][0] < CData ? @children[i] : @children[i].get
rescue raise($!, $!.message, caller)
end
def []=(i,v) @children[i].set(*v)
rescue raise($!, $!.message, caller)
end
end
CStruct, CUnion, CArray = Class.new(CData), Class.new(CData), Class.new(CData)
def self.Class(type, klass)
raise(TypeError,"Type expected, got #{type.type}") unless type.is_a?(Class) and
type <= CType
Class.new(type) {class_eval(klass, __FILE__, __LINE__)}
rescue raise($!, $!.message, caller)
end
def self.Type(format, size)
raise(TypeError,"String expected, got #{format.type}") unless format.is_a?(String)
raise(TypeError,"Integer expected, got #{size.type}") unless size.is_a?(Integer)
Class(CType, "SIZE, FORMAT = #{size}, '#{format}'.freeze")
rescue raise($!, $!.message, caller)
end
def self.Enum(type, *variables)
h, v = {}, -1
variables.each_with_index do |n,i|
next unless n.is_a?(Symbol)
h[n] = v = (next_v=variables[i+1]).is_a?(Symbol) ? v.succ : next_v
end
Class(type, "DATA = #{h.inspect}.freeze
def self.method_missing(sym, *args) DATA[sym] or super
rescue raise($!, $!.message, caller)
end")
rescue raise($!, $!.message, caller)
end
def self.Data(type, variables, array_size=nil)
size, data, t = 0, [], nil
variables.each_with_index do |n,i|
if n.is_a?(Class) and n < CType
t, n = n, nil
next if variables[i+1].is_a?(Symbol)
end
next unless t != nil and (n==nil or n.is_a?(Symbol))
name_anonymous_class(t)
if type == CStruct
data << [t,size,n]
size = size+t.size
elsif type == CUnion
data << [t,0,n]
size = t.size if t.size > size
elsif type == CArray
data.replace(Array.new(array_size) {|j| [t,j*t.size,n]})
size = t.size*array_size
end
end
klass = "SIZE, FORMAT, DATA = #{size}, 'a#{size}'.freeze, #{data.inspect}.freeze"
make_accessors(klass, data) unless type == CArray
Class(type, klass)
rescue raise($!, $!.message, caller)
end
def self.Struct(*variables) Data(CStruct, variables)
rescue raise($!, $!.message, caller)
end
def self.Union (*variables) Data(CUnion , variables)
rescue raise($!, $!.message, caller)
end
def self.Array (type, *dimensions)
dimensions.reverse_each {|s| type=Data(CArray , [type,:[]], s)}
type
rescue raise($!, $!.message, caller)
end
CHAR = Type('c',1)
UCHAR = BYTE = Type('C',1)
SHORT = Type('s',2)
USHORT = WORD = Type('S',2)
INT = Type('i',4)
UINT = Type('I',4)
LONG = Type('l',4)
ULONG = DWORD = Type('L',4)
LONGLONG = Type('q',8)
ULONGLONG = DWORDLONG = Type('Q',8)
FLOAT = Type('f',4)
DOUBLE = Type('d',8)
BOOLEAN = Type('C',1)
BOOL = Type('i',4)
POINTER = Type('L',4)
class BOOLEAN
def unpack() super==0 ? false : true
rescue raise($!, $!.message, caller)
end
def pack(v) super(!v || v==0 ? 0 : 1)
rescue raise($!, $!.message, caller)
end
end
class BOOL
def unpack() super==0 ? false : true
rescue raise($!, $!.message, caller)
end
def pack(v) super(!v || v==0 ? 0 : 1)
rescue raise($!, $!.message, caller)
end
end
class POINTER
def unpack() @pointer
rescue raise($!, $!.message, caller)
end
def pack(v) super(@pointer=v ? v.to_int : 0)
rescue raise($!, $!.message, caller)
end
end
end |
Type :
On a 15 types de données prédéfinis et il n'y en a pas besoin de plus.
CHAR = C signed char
UCHAR = BYTE = C unsigned char
SHORT = C signed short
USHORT = WORD = C unsigned short
INT = C signed int
UINT = C unsigned int
LONG = C signed long
ULONG = DWORD = C unsigned long
LONGLONG = C signed long long
ULONGLONG = DWORDLONG = C unsigned long long
FLOAT = C float
DOUBLE = C double
BOOLEAN qui en fait est un UCHAR mais qui gère true et false de Ruby.
BOOL qui en fait est un INT mais qui gère true et false de Ruby.
POINTER qui peut pointer sur n'importe quel objet fourni par le module C.
N'importe quel autre type peut être converti en un de ceux là (vous pouvez vous aider de ça).
Enum :
C.Enum(type, [nom, [valeur, [nom, [valeur ...]]]])
C'est pas indispensable, c'est juste pour faciliter la création de constantes.
type est le Type qu'aura l'Enum au cas où on l'utiliserait dans un Struct (généralement c'est INT).
Les noms doivent être passés sous forme de symbole.
Les valeurs doivent être des entiers.
Si une valeur est omise la variable prendra la valeur précédente+1, ou 0 si c'est la première.
1
2
3
| Couleur = C.Enum(C::INT, :rouge, 0,
:vert , 1,
:bleu , 2) |
équivaut à :
1
2
3
| Couleur = C.Enum(C::INT, :rouge,
:vert,
:bleu) |
et pour lire une valeur on fait :
Array :
C.Array(type, [dimension1, [dimension2 ...]])
type c'est n'importe quel Type du module C pour tous les éléments du tableau.
On peut spécifier autant de dimensions qu'on veut avec des entiers.
1
2
| I4 = C.Array(C::INT, 4)
tab = I4.new |
I4 est la classe qui me permet de créer des tableaux de 4 entiers.
En général pour les tableaux on utilisera des classes anonymes en faisant directement :
1
| tab = C.Array(C::INT, 4).new |
Par défaut un tableau est initialisé avec tout à 0, il existe plusieurs moyens de changer les valeurs.
Directement lors de l'initialisation on peut passer des paramètres dans l'ordre :
1
| tab = I4.new(60, 61, 62, ...) |
dans le désordre :
1
| tab = I4.new(2=>62, 0=>60, ...) |
ou avec un autre tableau de la même classe :
Une fois l'objet créé on peut toujours modifier ses données avec la fonction set (qui fonctionne comme new) ou avec l'opérateur []=
Pour lire les données on utilise []
ou la méthode get qui retournera un tableau Ruby de toutes les valeurs
1
| tab.get # => [60, 61, 62, 63] |
Les tableaux multidimensionnels ne sont en fait que des tableaux de tableaux :
équivaut à :
1
| C.Array(C.Array(C::INT, 3), 4) |
Et donc on peut modifier les données de plusieurs façons différentes :
1
2
3
4
5
6
| tab = C.Array(C::INT, 4, 3).new
tab[0][2] = 1
tab[1].set(2, 3, 4)
tab[2] = [5, 6, 7]
tab.set(3=>[8, 9])
tab.get # => [[0, 0, 1], [2, 3, 4], [5, 6, 7], [8, 9, 0]] |
Je sais, j'explique très mal, mais normalement ça devrait être assez logique.
Struct :
C.Struct([type, [nom, [type, [nom, ...]]]])
type peut être un INT, CHAR, etc... mais aussi Struct, Union, Enum, Array.
Les noms doivent être passés sous forme de symbole.
Si un type est omis la variable prendra le type précédent.
Si un nom est omis la variable sera anonyme.
Si la variable anonyme est un Struct, Union ou Array, ses fonctions seront hérités (comme en C).
En gros un struct c'est comme un Array sauf que chaque élément a un type et un nom différent.
1
2
| Fruit = C.Struct(C::FLOAT, :diametre,
C::BOOL , :pepins) |
Comme pour un Array il y a plusieurs manières de configurer un Struct :
1
2
3
4
5
6
| pomme = Fruit.new
pomme.set(5.8, true)
pomme.set(:diametre=>5.8, :pepins=>true)
pomme.set(0=>5.8, 1=>true)
pomme[0] = 5.8 # Attention si le Struct contient un Array anonyme, cela n'est plus possible
pomme.pepins = true |
Même avec un Struct get renvoie toujours un tableau des données :
1
| pomme.get # => [5.8, true] |
Attention, si un Struct contient un autre Struct il faudra grouper les données comme pour les tableaux multidimensionnels.
1
2
3
4
5
6
| Arbre = C.Struct(C::INT , :taille,
Fruit , :fruit,
Couleur, :couleur)
Arbre.new(9, 5.8, true, Couleur.vert) # => erreur
Arbre.new(9, [5.8, true], Couleur.vert) # => ok
Arbre.new(9, pomme, Couleur.vert) # => ok |
Union :
Marche comme Struct à quelques détails près.
Exemple d'un Union avec Struct et Array anonymes :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| MATRIX = C.Union(C.Struct(FLOAT, :_11, :_12, :_13, :_14,
:_21, :_22, :_23, :_24,
:_31, :_32, :_33, :_34,
:_41, :_42, :_43, :_44),
C.Array(FLOAT,4,4))
m = MATRIX.new(0=>[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16])
# Attention, il faut choisir selon quel élément de l'Union on va modifier les données,
# vu que les deux sont anonymes je fais avec les id, ici 0 c'est le Struct, pour l'Array j'aurais dû faire :
# m = MATRIX.new(1=>[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]])
m._43 == m[3][2] # => true
# Vu que les éléments sont anonymes leurs fonctions sont hérités.
# m[3] fait non pas appel à l'élément 3 de m mais à la ligne 3 de l'Array.
m.get # => [ [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],
# [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]] ]
# le get d'un Union retourne un tableau contenant tous ses formats. |
Pointer :
Un petit topo sur les pointeurs vu que c'est pas forcément évident.
1
2
3
4
5
6
7
8
9
| A = C.Struct(C::POINTER, :ptr)
a = A.new
b = C::CHAR.new(7) # char*
a.ptr = b
a.ptr.get # => 7
b.set(45)
a.ptr.get # => 45
a.ptr.set(-16)
b.get # => -16 |
Je pense que c'est suffisamment explicite.
Autre exemple sur ce qui correspondrait à une double indirection :
1
2
| a.ptr = C::POINTER.new(C::INT.new(128)) # int**
a.ptr.get.get # => 128 |
Et comme dit plus haut, un pointeur peut pointer sur n'importe quel objet du module C :
1
2
| a.ptr = a
a.ptr.get # => a |
Autre chose sur les pointeurs, si on récupère un pointeur via une Api :
1
| ptr = Win32API.new('dll', 'GetPointer', '', 'i').call |
On peut récupérer/modifier la valeur vers où il pointe quel que soit le type, par exemple si c'est un float :
1
2
3
| f = C::FLOAT.new {ptr}
f.get # => récupère la valeur en mémoire
f.set(0.1) # la modifie |
Et ça marche aussi avec les Struct/Array/Union :
1
2
| MonStruct = C.Struct(...)
s = MonStruct.new {ptr} |
Bien sûr il faut que MonStruct soit l'exacte réplique du struct C vers qui il pointe.
Attention cependant aux allocations de mémoire, si je récupère le pointeur vers une donnée de la dll qui a été libérée il risque d'y avoir des problèmes.
Exemple simple et concret d'utilisation, si je veux reproduire cette structure et utiliser cette Api je fais :
1
2
3
4
5
| POINT = C.Struct(C::LONG, :x, :y)
GetCursorPos = Win32API.new('user32', 'GetCursorPos', 'i', 'i') # on met 'i' pour le struct et non 'p' pour éviter les copies dans certains cas
$cursor = POINT.new
GetCursorPos.call($cursor) # on passe directement l'objet
$cursor.x # retourne la position x du curseur |
Bon bien sûr ça vaut pas vraiment le coût dans cet exemple d'utiliser mon script de 203 lignes (si si) alors qu'avec un string il nous en faudrait 4 mais c'est pour ceux qui veulent faire des trucs hautement plus compliqués.
J'avais un peu la flemme de tout expliquer en détail alors si vous avez des questions (sur le module uniquement) n'hésitez pas à les poser les ici.
Mis à jour le 18 novembre 2020.
|