Petr Mrázek 2012-06-13 23:56:26 +02:00
commit 5e011ac217
10 changed files with 1456 additions and 1118 deletions

@ -255,7 +255,7 @@ namespace DFHack
void color(Console::color_value index) void color(Console::color_value index)
{ {
if(!rawmode) if(!rawmode)
fprintf(dfout_C,getANSIColor(index)); fprintf(dfout_C, "%s", getANSIColor(index));
else else
{ {
const char * colstr = getANSIColor(index); const char * colstr = getANSIColor(index);

@ -329,17 +329,19 @@ bool sendRemoteMessage(CSimpleSocket *socket, int16_t id, const MessageLite *msg
int size = size_ready ? msg->GetCachedSize() : msg->ByteSize(); int size = size_ready ? msg->GetCachedSize() : msg->ByteSize();
int fullsz = size + sizeof(RPCMessageHeader); int fullsz = size + sizeof(RPCMessageHeader);
std::auto_ptr<uint8_t> data(new uint8_t[fullsz]); uint8_t *data = new uint8_t[fullsz];
RPCMessageHeader *hdr = (RPCMessageHeader*)data.get(); RPCMessageHeader *hdr = (RPCMessageHeader*)data;
hdr->id = id; hdr->id = id;
hdr->size = size; hdr->size = size;
uint8_t *pstart = data.get() + sizeof(RPCMessageHeader); uint8_t *pstart = data + sizeof(RPCMessageHeader);
uint8_t *pend = msg->SerializeWithCachedSizesToArray(pstart); uint8_t *pend = msg->SerializeWithCachedSizesToArray(pstart);
assert((pend - pstart) == size); assert((pend - pstart) == size);
return (socket->Send(data.get(), fullsz) == fullsz); int got = socket->Send(data, fullsz);
delete[] data;
return (got == fullsz);
} }
command_result RemoteFunctionBase::execute(color_ostream &out, command_result RemoteFunctionBase::execute(color_ostream &out,
@ -402,9 +404,9 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
return CR_LINK_FAILURE; return CR_LINK_FAILURE;
} }
std::auto_ptr<uint8_t> buf(new uint8_t[header.size]); uint8_t *buf = new uint8_t[header.size];
if (!readFullBuffer(p_client->socket, buf.get(), header.size)) if (!readFullBuffer(p_client->socket, buf, header.size))
{ {
out.printerr("In call to %s::%s: I/O error in receive %d bytes of data.\n", out.printerr("In call to %s::%s: I/O error in receive %d bytes of data.\n",
this->proto.c_str(), this->name.c_str(), header.size); this->proto.c_str(), this->name.c_str(), header.size);
@ -413,18 +415,20 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
switch (header.id) { switch (header.id) {
case RPC_REPLY_RESULT: case RPC_REPLY_RESULT:
if (!output->ParseFromArray(buf.get(), header.size)) if (!output->ParseFromArray(buf, header.size))
{ {
out.printerr("In call to %s::%s: error parsing received result.\n", out.printerr("In call to %s::%s: error parsing received result.\n",
this->proto.c_str(), this->name.c_str()); this->proto.c_str(), this->name.c_str());
delete[] buf;
return CR_LINK_FAILURE; return CR_LINK_FAILURE;
} }
delete[] buf;
return CR_OK; return CR_OK;
case RPC_REPLY_TEXT: case RPC_REPLY_TEXT:
text_data.Clear(); text_data.Clear();
if (text_data.ParseFromArray(buf.get(), header.size)) if (text_data.ParseFromArray(buf, header.size))
text_decoder.decode(&text_data); text_decoder.decode(&text_data);
else else
out.printerr("In call to %s::%s: received invalid text data.\n", out.printerr("In call to %s::%s: received invalid text data.\n",
@ -434,5 +438,6 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
default: default:
break; break;
} }
delete[] buf;
} }
} }

@ -61,11 +61,11 @@ namespace DFHack
struct DFLibrary; struct DFLibrary;
// Open a plugin library // Open a plugin library
DFLibrary * OpenPlugin (const char * filename); DFHACK_EXPORT DFLibrary * OpenPlugin (const char * filename);
// find a symbol inside plugin // find a symbol inside plugin
void * LookupPlugin (DFLibrary * plugin ,const char * function); DFHACK_EXPORT void * LookupPlugin (DFLibrary * plugin ,const char * function);
// Close a plugin library // Close a plugin library
void ClosePlugin (DFLibrary * plugin); DFHACK_EXPORT void ClosePlugin (DFLibrary * plugin);
struct DFHACK_EXPORT CommandReg { struct DFHACK_EXPORT CommandReg {
const char *name; const char *name;

@ -1,29 +1,33 @@
find_package(Ruby) OPTION(DL_RUBY "download libruby from the internet" ON)
if(RUBY_FOUND) IF (DL_RUBY)
ADD_CUSTOM_COMMAND( IF (UNIX)
OUTPUT ruby-autogen.cpp FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/libruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.tar.gz
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.cpp EXPECTED_MD5 eb2adea59911f68e6066966c1352f291)
DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml codegen.pl EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf libruby187.tar.gz
) WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
ADD_EXECUTABLE(ruby-autogen ruby-autogen.cpp) FILE(RENAME libruby1.8.so.1.8.7 libruby.so)
if(CMAKE_COMPILER_IS_GNUCC) SET(RUBYLIB libruby.so)
set_target_properties (ruby-autogen PROPERTIES COMPILE_FLAGS "-Wno-invalid-offsetof") ELSE (UNIX)
endif(CMAKE_COMPILER_IS_GNUCC) FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/msvcrtruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz
ADD_CUSTOM_COMMAND( EXPECTED_MD5 9f4a1659ac3a5308f32d3a1937bbeeae)
OUTPUT ruby-autogen.offsets EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf msvcrtruby187.tar.gz
COMMAND ruby-autogen ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.offsets WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
DEPENDS ruby-autogen FILE(RENAME msvcrt-ruby18.dll libruby.dll)
) SET(RUBYLIB libruby.dll)
ADD_CUSTOM_COMMAND( ENDIF(UNIX)
OUTPUT ruby-autogen.rb ENDIF(DL_RUBY)
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.offsets ${CMAKE_CURRENT_SOURCE_DIR}/ruby-memstruct.rb
DEPENDS ruby-autogen.offsets ruby-memstruct.rb ADD_CUSTOM_COMMAND(
) OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
ADD_CUSTOM_TARGET(ruby-autogen-rb ALL DEPENDS ruby-autogen.rb) COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
include_directories("${dfhack_SOURCE_DIR}/depends/tthread" ${RUBY_INCLUDE_PATH}) DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl
DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread) COMMENT ruby-autogen.rb
target_link_libraries(ruby ${RUBY_LIBRARY}) )
install(FILES ruby.rb ruby-autogen.rb DESTINATION ${DFHACK_LIBRARY_DESTINATION}) ADD_CUSTOM_TARGET(ruby-autogen-rb DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb)
else(RUBY_FOUND)
MESSAGE(STATUS "Required library (ruby) not found - ruby plugin can't be built.") INCLUDE_DIRECTORIES("${dfhack_SOURCE_DIR}/depends/tthread")
endif(RUBY_FOUND)
DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread)
ADD_DEPENDENCIES(ruby ruby-autogen-rb)
INSTALL(FILES ruby.rb ruby-autogen.rb ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION})

@ -3,7 +3,7 @@ This plugins embeds a ruby interpreter inside DFHack (ie inside Dwarf Fortress).
The plugin maps all the structures available in library/xml/ to ruby objects. The plugin maps all the structures available in library/xml/ to ruby objects.
These objects are described in ruby-autogen.rb, they are all in the DFHack:: These objects are described in ruby-autogen.rb, they are all in the DFHack::
module. The toplevel 'df' method returs the DFHack module. module. The toplevel 'df' method is a shortcut to the DFHack module.
The plugin does *not* map most of dfhack methods (MapCache, ...) ; only direct The plugin does *not* map most of dfhack methods (MapCache, ...) ; only direct
access to the raw DF data structures in memory is provided. access to the raw DF data structures in memory is provided.
@ -11,8 +11,7 @@ access to the raw DF data structures in memory is provided.
Some library methods are stored in the ruby.rb file, with shortcuts to read a Some library methods are stored in the ruby.rb file, with shortcuts to read a
map block, find an unit or an item, etc. map block, find an unit or an item, etc.
Global objects are stored in the GlobalObjects class ; each object accessor is Global objects are accessible through the 'df' accessor (eg df.world).
mirrored as a DFHack module method.
The ruby plugin defines 2 dfhack console commands: The ruby plugin defines 2 dfhack console commands:
rb_load <filename> ; load a ruby script. Ex: rb_load hack/plants.rb (no quotes) rb_load <filename> ; load a ruby script. Ex: rb_load hack/plants.rb (no quotes)
@ -21,17 +20,25 @@ console. Ex: rb_eval df.find_unit.name.first_name
You can use single-quotes for strings ; avoid double-quotes that are parsed You can use single-quotes for strings ; avoid double-quotes that are parsed
and removed by the dfhack console. and removed by the dfhack console.
If dfhack reports 'rb_eval is not a recognized command', check stderr.log. You
need a valid 32-bit ruby library to work, and ruby1.8 is prefered (ruby1.9 may
crash DF on startup for now). Install the library in the df root folder (or
hack/ on linux), the library should be named 'libruby.dll' (.so on linux).
You can download a tested version at http://github.com/jjyg/dfhack/downloads/
The plugin also interfaces with dfhack 'onupdate' hook. The plugin also interfaces with dfhack 'onupdate' hook.
To register ruby code to be run every graphic frame, use: To register ruby code to be run every graphic frame, use:
handle = df.onupdate_register { puts 'i love flood' } handle = df.onupdate_register { puts 'i love flooding the console' }
To stop being called, use: To stop being called, use:
df.onupdate_unregister handle df.onupdate_unregister handle
The same mechanism is available for onstatechange.
Exemples Exemples
-------- --------
For more complex exemples, check the ruby/plugins/ folder. For more complex exemples, check the ruby/plugins/ source folder.
Show info on the currently selected unit ('v' or 'k' DF menu) Show info on the currently selected unit ('v' or 'k' DF menu)
p df.find_unit.flags1 p df.find_unit.flags1
@ -42,6 +49,9 @@ Set a custom nickname to unit with id '123'
Show current unit profession Show current unit profession
p df.find_unit.profession p df.find_unit.profession
Change current unit profession
df.find_unit.profession = :MASON
Center the screen on unit '123' Center the screen on unit '123'
df.center_viewscreen(df.find_unit(123)) df.center_viewscreen(df.find_unit(123))
@ -49,54 +59,42 @@ Find an item at a given position, show its C++ classname
df.find_item(df.cursor)._rtti_classname df.find_item(df.cursor)._rtti_classname
Find the raws name of the plant under cursor Find the raws name of the plant under cursor
plant = df.world.plants.all.find { |p| df.at_cursor?(p) } plant = df.world.plants.all.find { |plt| df.at_cursor?(plt) }
df.world.raws.plants.all[plant.mat_index].id p df.world.raws.plants.all[plant.mat_index].id
Dig a channel under the cursor Dig a channel under the cursor
df.map_designation_at(df.cursor).dig = TileDigDesignation::Channel df.map_designation_at(df.cursor).dig = :Channel
df.map_block_at(df.cursor).flags.designated = true df.map_block_at(df.cursor).flags.designated = true
Compilation Compilation
----------- -----------
The plugin consists of the ruby.rb file including user comfort functions ; The plugin consists of the ruby.rb file including user comfort functions and
ruby-memstruct.rb describing basic classes used by the autogenerated code, and describing basic classes used by the autogenerated code, and ruby-autogen.rb,
embedded at the beginnig of ruby-autogen.rb, and the generated code. the auto-generated code.
The generated code is generated by codegen.pl, which takes the codegen.out.xml The generated code is generated by codegen.pl, which takes the codegen.out.xml
file as input. file as input.
One of the limitations of the xml file is that it does not include structure For exemple,
offsets, as they depend on the compiler. To overcome that, codegen runs in two
passes. The first pass generates a ruby-autogen.cpp file, that will output the
structure offsets ; the second pass will generate the ruby-autogen.rb using the
output of the compiled ruby-autogen.cpp.
For exemple, from
<ld:global-type ld:meta="struct-type" type-name="unit"> <ld:global-type ld:meta="struct-type" type-name="unit">
<ld:field type-name="language_name" name="name" ld:meta="global"/> <ld:field type-name="language_name" name="name" ld:meta="global"/>
<ld:field name="custom_profession" ld:meta="primitive" ld:subtype="stl-string"/> <ld:field name="custom_profession" ld:meta="primitive" ld:subtype="stl-string"/>
<ld:field ld:subtype="enum" base-type="int16_t" name="profession" type-name="profession" ld:meta="global"/> <ld:field ld:subtype="enum" base-type="int16_t" name="profession" type-name="profession" ld:meta="global"/>
We generate the cpp Will generate
printf("%s = %d", "offsetof(df::unit, language_name)", offsetof(df::unit, language_name));
printf("%s = %d", "offsetof(df::unit, custom_profession)", offsetof(df::unit, custom_profession));
printf("%s = %d", "offsetof(df::unit, profession)", offsetof(df::unit, profession));
Which generates (on linux)
offsetof(df::unit, name) = 0
offsetof(df::unit, custom_profession) = 60
offsetof(df::unit, profession) = 64
Which generates
class Unit < MemHack::Compound class Unit < MemHack::Compound
field(:name, 0) { global :LanguageName } field(:name, 0) { global :LanguageName }
field(:custom_profession, 60) { stl_string } field(:custom_profession, 60) { stl_string }
field(:profession, 64) { number 16, true } field(:profession, 64) { number 16, true }
The field method has 2 arguments: the name of the method and the member offset ; The syntax for the 'field' method is:
the block specifies the member type. See ruby-memstruct.rb for more information. 1st argument = name of the method
2nd argument = offset of this field from the beginning of the struct.
The block argument describes the type of the field: uint32, ptr to global...
Primitive type access is done through native methods in ruby.cpp (vector length, Primitive type access is done through native methods in ruby.cpp (vector length,
raw memory access, etc) raw memory access, etc)
@ -104,10 +102,4 @@ MemHack::Pointers are automatically dereferenced ; so a vector of pointer to
Units will yield Units directly. Null pointers yield the 'nil' value. Units will yield Units directly. Null pointers yield the 'nil' value.
This allows to use code such as 'df.world.units.all[0].pos', with 'all' being This allows to use code such as 'df.world.units.all[0].pos', with 'all' being
really a vector of pointer. in fact a vector of *pointers* to DFHack::Unit objects.
Todo
----
Correct c++ object (de)allocation (call ctor etc) ; ability to call vtable methods

File diff suppressed because it is too large Load Diff

@ -2,7 +2,6 @@ module DFHack
# allocate a new building object # allocate a new building object
def self.building_alloc(type, subtype=-1, custom=-1) def self.building_alloc(type, subtype=-1, custom=-1)
type = BuildingType.to_sym(type)
cls = rtti_n2c[BuildingType::Classname[type].to_sym] cls = rtti_n2c[BuildingType::Classname[type].to_sym]
raise "invalid building type #{type.inspect}" if not cls raise "invalid building type #{type.inspect}" if not cls
bld = cls.cpp_new bld = cls.cpp_new

@ -1,747 +0,0 @@
module DFHack
module MemHack
INSPECT_SIZE_LIMIT=16384
class MemStruct
attr_accessor :_memaddr
def _at(addr) ; d = dup ; d._memaddr = addr ; d ; end
def _get ; self ; end
def _cpp_init ; end
end
class Compound < MemStruct
class << self
attr_accessor :_fields, :_rtti_classname, :_sizeof
def field(name, offset)
struct = yield
return if not struct
@_fields ||= []
@_fields << [name, offset, struct]
define_method(name) { struct._at(@_memaddr+offset)._get }
define_method("#{name}=") { |v| struct._at(@_memaddr+offset)._set(v) }
end
def _fields_ancestors
if superclass.respond_to?(:_fields_ancestors)
superclass._fields_ancestors + _fields.to_a
else
_fields.to_a
end
end
def number(bits, signed, initvalue=nil, enum=nil)
Number.new(bits, signed, initvalue, enum)
end
def float
Float.new
end
def bit(shift)
BitField.new(shift, 1)
end
def bits(shift, len, enum=nil)
BitField.new(shift, len, enum)
end
def pointer
Pointer.new((yield if block_given?))
end
def pointer_ary(tglen)
PointerAry.new(tglen, yield)
end
def static_array(len, tglen, indexenum=nil)
StaticArray.new(tglen, len, indexenum, yield)
end
def static_string(len)
StaticString.new(len)
end
def stl_vector(tglen=nil)
tg = yield if tglen
case tglen
when 1; StlVector8.new(tg)
when 2; StlVector16.new(tg)
else StlVector32.new(tg)
end
end
def stl_string
StlString.new
end
def stl_bit_vector
StlBitVector.new
end
def stl_deque(tglen)
StlDeque.new(tglen, yield)
end
def df_flagarray(indexenum=nil)
DfFlagarray.new(indexenum)
end
def df_array(tglen)
DfArray.new(tglen, yield)
end
def df_linked_list
DfLinkedList.new(yield)
end
def global(glob)
Global.new(glob)
end
def compound(name=nil, &b)
m = Class.new(Compound)
DFHack.const_set(name, m) if name
m.instance_eval(&b)
m.new
end
def rtti_classname(n)
DFHack.rtti_register(n, self)
@_rtti_classname = n
end
def sizeof(n)
@_sizeof = n
end
# allocate a new c++ object, return its ruby wrapper
def cpp_new
ptr = DFHack.malloc(_sizeof)
if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname)
DFHack.memory_write_int32(ptr, vt)
# TODO call constructor
end
o = new._at(ptr)
o._cpp_init
o
end
end
def _cpp_init
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
end
def _set(h)
case h
when Hash; h.each { |k, v| send("_#{k}=", v) }
when Array; names = _field_names ; raise 'bad size' if names.length != h.length ; names.zip(h).each { |n, a| send("#{n}=", a) }
when Compound; _field_names.each { |n| send("#{n}=", h.send(n)) }
else raise 'wut?'
end
end
def _fields ; self.class._fields.to_a ; end
def _fields_ancestors ; self.class._fields_ancestors.to_a ; end
def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end
def _rtti_classname ; self.class._rtti_classname ; end
def _sizeof ; self.class._sizeof ; end
@@inspecting = {} # avoid infinite recursion on mutually-referenced objects
def inspect
cn = self.class.name.sub(/^DFHack::/, '')
cn << ' @' << ('0x%X' % _memaddr) if cn != ''
out = "#<#{cn}"
return out << ' ...>' if @@inspecting[_memaddr]
@@inspecting[_memaddr] = true
_fields_ancestors.each { |n, o, s|
out << ' ' if out.length != 0 and out[-1, 1] != ' '
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << inspect_field(n, o, s)
}
out.chomp!(' ')
@@inspecting.delete _memaddr
out << '>'
end
def inspect_field(n, o, s)
if s.kind_of?(BitField) and s._len == 1
send(n) ? n.to_s : ''
elsif s.kind_of?(Pointer)
"#{n}=#{s._at(@_memaddr+o).inspect}"
elsif n == :_whole
"_whole=0x#{_whole.to_s(16)}"
else
v = send(n).inspect
"#{n}=#{v}"
end
rescue
"#{n}=ERR(#{$!})"
end
end
class Enum
# number -> symbol
def self.enum
# ruby weirdness, needed to make the constants 'virtual'
@enum ||= const_get(:ENUM)
end
# symbol -> number
def self.nume
@nume ||= const_get(:NUME)
end
def self.to_i(i)
nume[i] || i
end
def self.to_sym(i)
enum[i] || i
end
end
class Number < MemStruct
attr_accessor :_bits, :_signed, :_initvalue, :_enum
def initialize(bits, signed, initvalue, enum)
@_bits = bits
@_signed = signed
@_initvalue = initvalue
@_enum = enum
end
def _get
v = case @_bits
when 32; DFHack.memory_read_int32(@_memaddr)
when 16; DFHack.memory_read_int16(@_memaddr)
when 8; DFHack.memory_read_int8( @_memaddr)
when 64;(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + (DFHack.memory_read_int32(@_memaddr+4) << 32)
end
v &= (1 << @_bits) - 1 if not @_signed
v = @_enum.to_sym(v) if @_enum
v
end
def _set(v)
v = @_enum.to_i(v) if @_enum
case @_bits
when 32; DFHack.memory_write_int32(@_memaddr, v)
when 16; DFHack.memory_write_int16(@_memaddr, v)
when 8; DFHack.memory_write_int8( @_memaddr, v)
when 64; DFHack.memory_write_int32(@_memaddr, v & 0xffffffff) ; DFHack.memory_write_int32(@memaddr+4, v>>32)
end
end
def _cpp_init
_set(@_initvalue) if @_initvalue
end
end
class Float < MemStruct
def _get
DFHack.memory_read_float(@_memaddr)
end
def _set(v)
DFHack.memory_write_float(@_memaddr, v)
end
def _cpp_init
_set(0.0)
end
end
class BitField < MemStruct
attr_accessor :_shift, :_len, :_enum
def initialize(shift, len, enum=nil)
@_shift = shift
@_len = len
@_enum = enum
end
def _mask
(1 << @_len) - 1
end
def _get
v = DFHack.memory_read_int32(@_memaddr) >> @_shift
if @_len == 1
((v & 1) == 0) ? false : true
else
v &= _mask
v = @_enum.to_sym(v) if @_enum
v
end
end
def _set(v)
if @_len == 1
# allow 'bit = 0'
v = (v && v != 0 ? 1 : 0)
end
v = @_enum.to_i(v) if @_enum
v = (v & _mask) << @_shift
ori = DFHack.memory_read_int32(@_memaddr) & 0xffffffff
DFHack.memory_write_int32(@_memaddr, ori - (ori & ((-1 & _mask) << @_shift)) + v)
end
end
class Pointer < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def _getp
DFHack.memory_read_int32(@_memaddr) & 0xffffffff
end
def _get
addr = _getp
return if addr == 0
@_tg._at(addr)._get
end
# XXX shaky...
def _set(v)
if v.kind_of?(Pointer)
DFHack.memory_write_int32(@_memaddr, v._getp)
elsif v.kind_of?(MemStruct)
DFHack.memory_write_int32(@_memaddr, v._memaddr)
else
_get._set(v)
end
end
def inspect
ptr = _getp
if ptr == 0
'NULL'
else
cn = ''
cn = @_tg.class.name.sub(/^DFHack::/, '').sub(/^MemHack::/, '') if @_tg
cn = @_tg._glob if cn == 'Global'
"#<Pointer #{cn} #{'0x%X' % _getp}>"
end
end
end
class PointerAry < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
def _getp(i=0)
delta = (i != 0 ? i*@_tglen : 0)
(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + delta
end
def _get
addr = _getp
return if addr == 0
self
end
def [](i)
addr = _getp(i)
return if addr == 0
@_tg._at(addr)._get
end
def []=(i, v)
addr = _getp(i)
raise 'null pointer' if addr == 0
@_tg._at(addr)._set(v)
end
def inspect ; ptr = _getp ; (ptr == 0) ? 'NULL' : "#<PointerAry #{'0x%X' % ptr}>" ; end
end
module Enumerable
include ::Enumerable
attr_accessor :_indexenum
def each ; (0...length).each { |i| yield self[i] } ; end
def inspect
out = '['
each_with_index { |e, idx|
out << ', ' if out.length > 1
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << "#{_indexenum.to_sym(idx)}=" if _indexenum
out << e.inspect
}
out << ']'
end
def empty? ; length == 0 ; end
def flatten ; map { |e| e.respond_to?(:flatten) ? e.flatten : e }.flatten ; end
end
class StaticArray < MemStruct
attr_accessor :_tglen, :_length, :_indexenum, :_tg
def initialize(tglen, length, indexenum, tg)
@_tglen = tglen
@_length = length
@_indexenum = indexenum
@_tg = tg
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
def _cpp_init
_length.times { |i| _tgat(i)._cpp_init }
end
alias length _length
alias size _length
def _tgat(i)
@_tg._at(@_memaddr + i*@_tglen) if i >= 0 and i < @_length
end
def [](i)
i = _indexenum.to_i(i) if _indexenum
i += @_length if i < 0
_tgat(i)._get
end
def []=(i, v)
i = _indexenum.to_i(i) if _indexenum
i += @_length if i < 0
_tgat(i)._set(v)
end
include Enumerable
end
class StaticString < MemStruct
attr_accessor :_length
def initialize(length)
@_length = length
end
def _get
DFHack.memory_read(@_memaddr, @_length)
end
def _set(v)
DFHack.memory_write(@_memaddr, v[0, @_length])
end
end
class StlVector32 < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def length
DFHack.memory_vector32_length(@_memaddr)
end
def size ; length ; end # alias wouldnt work for subclasses
def valueptr_at(idx)
DFHack.memory_vector32_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector32_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector32_delete(@_memaddr, idx)
end
def _set(v)
delete_at(length-1) while length > v.length # match lengthes
v.each_with_index { |e, i| self[i] = e } # patch entries
end
def _cpp_init
DFHack.memory_vector_init(@_memaddr)
end
def clear
delete_at(length-1) while length > 0
end
def [](idx)
idx += length if idx < 0
@_tg._at(valueptr_at(idx))._get if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, 0)
elsif idx < 0
raise 'invalid idx'
end
@_tg._at(valueptr_at(idx))._set(v)
end
def push(v)
self[length] = v
self
end
def <<(v) ; push(v) ; end
def pop
l = length
if l > 0
v = self[l-1]
delete_at(l-1)
end
v
end
include Enumerable
# do a binary search in an ordered vector for a specific target attribute
# ex: world.history.figures.binsearch(unit.hist_figure_id)
def binsearch(target, field=:id)
o_start = 0
o_end = length - 1
while o_end >= o_start
o_half = o_start + (o_end-o_start)/2
obj = self[o_half]
oval = obj.send(field)
if oval == target
return obj
elsif oval < target
o_start = o_half+1
else
o_end = o_half-1
end
end
end
end
class StlVector16 < StlVector32
def length
DFHack.memory_vector16_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector16_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector16_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector16_delete(@_memaddr, idx)
end
end
class StlVector8 < StlVector32
def length
DFHack.memory_vector8_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector8_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector8_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector8_delete(@_memaddr, idx)
end
end
class StlBitVector < StlVector32
def initialize ; end
def length
DFHack.memory_vectorbool_length(@_memaddr)
end
def insert_at(idx, val)
DFHack.memory_vectorbool_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vectorbool_delete(@_memaddr, idx)
end
def [](idx)
idx += length if idx < 0
DFHack.memory_vectorbool_at(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, v)
elsif idx < 0
raise 'invalid idx'
else
DFHack.memory_vectorbool_setat(@_memaddr, idx, v)
end
end
end
class StlString < MemStruct
def _get
DFHack.memory_read_stlstring(@_memaddr)
end
def _set(v)
DFHack.memory_write_stlstring(@_memaddr, v)
end
def _cpp_init
DFHack.memory_stlstring_init(@_memaddr)
end
end
class StlDeque < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every
# possible struct size, like for StlVector. Just ignore it for now, deque are rare enough.
def inspect ; "#<StlDeque>" ; end
end
class DfFlagarray < MemStruct
attr_accessor :_indexenum
def initialize(indexenum)
@_indexenum = indexenum
end
def length
DFHack.memory_bitarray_length(@_memaddr)
end
# TODO _cpp_init
def size ; length ; end
def resize(len)
DFHack.memory_bitarray_resize(@_memaddr, len)
end
def [](idx)
idx = _indexenum.to_i(idx) if _indexenum
idx += length if idx < 0
DFHack.memory_bitarray_isset(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx = _indexenum.to_i(idx) if _indexenum
idx += length if idx < 0
if idx >= length or idx < 0
raise 'invalid idx'
else
DFHack.memory_bitarray_set(@_memaddr, idx, v)
end
end
include Enumerable
end
class DfArray < Compound
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_length, 4) { number 16, false }
def length ; _length ; end
def size ; _length ; end
# TODO _cpp_init
def _tgat(i)
@_tg._at(_ptr + i*@_tglen) if i >= 0 and i < _length
end
def [](i)
i += _length if i < 0
_tgat(i)._get
end
def []=(i, v)
i += _length if i < 0
_tgat(i)._set(v)
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
include Enumerable
end
class DfLinkedList < Compound
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_prev, 4) { number 32, false }
field(:_next, 8) { number 32, false }
def item
# With the current xml structure, currently _tg designate
# the type of the 'next' and 'prev' fields, not 'item'.
# List head has item == NULL, so we can safely return nil.
#addr = _ptr
#return if addr == 0
#@_tg._at(addr)._get
end
def item=(v)
#addr = _ptr
#raise 'null pointer' if addr == 0
#@_tg.at(addr)._set(v)
raise 'null pointer'
end
def prev
addr = _prev
return if addr == 0
@_tg._at(addr)._get
end
def next
addr = _next
return if addr == 0
@_tg._at(addr)._get
end
include Enumerable
def each
o = self
while o
yield o.item if o.item
o = o.next
end
end
def inspect ; "#<DfLinkedList #{item.inspect} prev=#{'0x%X' % _prev} next=#{'0x%X' % _next}>" ; end
end
class Global < MemStruct
attr_accessor :_glob
def initialize(glob)
@_glob = glob
end
def _at(addr)
g = DFHack.const_get(@_glob)
g = DFHack.rtti_getclassat(g, addr)
g.new._at(addr)
end
def inspect ; "#<#{@_glob}>" ; end
end
end # module MemHack
# cpp rtti name -> rb class
@rtti_n2c = {}
@rtti_c2n = {}
# cpp rtti name -> vtable ptr
@rtti_n2v = {}
@rtti_v2n = {}
def self.rtti_n2c ; @rtti_n2c ; end
def self.rtti_c2n ; @rtti_c2n ; end
def self.rtti_n2v ; @rtti_n2v ; end
def self.rtti_v2n ; @rtti_v2n ; end
# register a ruby class with a cpp rtti class name
def self.rtti_register(cppname, cls)
@rtti_n2c[cppname] = cls
@rtti_c2n[cls] = cppname
end
# return the ruby class to use for the cpp object at address if rtti info is available
def self.rtti_getclassat(cls, addr)
if addr != 0 and @rtti_c2n[cls]
# rtti info exist for class => cpp object has a vtable
@rtti_n2c[rtti_readclassname(get_vtable_ptr(addr))] || cls
else
cls
end
end
# try to read the rtti classname from an object vtable pointer
def self.rtti_readclassname(vptr)
unless n = @rtti_v2n[vptr]
n = @rtti_v2n[vptr] = get_rtti_classname(vptr).to_sym
@rtti_n2v[n] = vptr
end
n
end
# return the vtable pointer from the cpp rtti name
def self.rtti_getvtable(cppname)
unless v = @rtti_n2v[cppname]
v = get_vtable(cppname.to_s)
@rtti_n2v[cppname] = v
@rtti_v2n[v] = cppname if v != 0
end
v if v != 0
end
def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0)
vmethod_do_call(obj._memaddr, voff, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3))
end
def self.vmethod_arg(arg)
case arg
when nil, false; 0
when true; 1
when Integer; arg
#when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer
when MemHack::Compound; arg._memaddr
else raise "bad vmethod arg #{arg.class}"
end
end
end

@ -11,8 +11,6 @@
#include "tinythread.h" #include "tinythread.h"
#include <ruby.h>
using namespace DFHack; using namespace DFHack;
@ -20,6 +18,8 @@ using namespace DFHack;
// DFHack stuff // DFHack stuff
static int df_loadruby(void);
static void df_unloadruby(void);
static void df_rubythread(void*); static void df_rubythread(void*);
static command_result df_rubyload(color_ostream &out, std::vector <std::string> & parameters); static command_result df_rubyload(color_ostream &out, std::vector <std::string> & parameters);
static command_result df_rubyeval(color_ostream &out, std::vector <std::string> & parameters); static command_result df_rubyeval(color_ostream &out, std::vector <std::string> & parameters);
@ -45,6 +45,11 @@ DFHACK_PLUGIN("ruby")
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{ {
// fail silently instead of spamming the console with 'failed to initialize' if libruby is not present
// the error is still logged in stderr.log
if (!df_loadruby())
return CR_OK;
m_irun = new tthread::mutex(); m_irun = new tthread::mutex();
m_mutex = new tthread::mutex(); m_mutex = new tthread::mutex();
r_type = RB_INIT; r_type = RB_INIT;
@ -74,10 +79,11 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{ {
m_mutex->lock();
if (!r_thread) if (!r_thread)
return CR_OK; return CR_OK;
m_mutex->lock();
r_type = RB_DIE; r_type = RB_DIE;
r_command = 0; r_command = 0;
m_irun->unlock(); m_irun->unlock();
@ -90,6 +96,8 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
m_mutex->unlock(); m_mutex->unlock();
delete m_mutex; delete m_mutex;
df_unloadruby();
return CR_OK; return CR_OK;
} }
@ -128,6 +136,9 @@ static command_result plugin_eval_rb(std::string &command)
DFhackCExport command_result plugin_onupdate ( color_ostream &out ) DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
if (!r_thread)
return CR_OK;
if (!onupdate_active) if (!onupdate_active)
return CR_OK; return CR_OK;
@ -136,6 +147,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event e) DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event e)
{ {
if (!r_thread)
return CR_OK;
std::string cmd = "DFHack.onstatechange "; std::string cmd = "DFHack.onstatechange ";
switch (e) { switch (e) {
#define SCASE(s) case SC_ ## s : cmd += ":" # s ; break #define SCASE(s) case SC_ ## s : cmd += ":" # s ; break
@ -146,6 +160,7 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch
SCASE(VIEWSCREEN_CHANGED); SCASE(VIEWSCREEN_CHANGED);
SCASE(CORE_INITIALIZED); SCASE(CORE_INITIALIZED);
SCASE(BEGIN_UNLOAD); SCASE(BEGIN_UNLOAD);
#undef SCASE
} }
return plugin_eval_rb(cmd); return plugin_eval_rb(cmd);
@ -191,6 +206,113 @@ static command_result df_rubyeval(color_ostream &out, std::vector <std::string>
// ruby stuff // ruby stuff
// ruby-dev on windows is messy
// ruby.h on linux 64 is broken
// so we dynamically load libruby instead of linking it at compile time
// lib path can be set in dfhack.ini to use the system libruby, but by
// default we'll embed our own (downloaded by cmake)
// these ruby definitions are invalid for windows 64bit
typedef unsigned long VALUE;
typedef unsigned long ID;
#define Qfalse ((VALUE)0)
#define Qtrue ((VALUE)2)
#define Qnil ((VALUE)4)
#define INT2FIX(i) ((VALUE)((((long)i) << 1) | 1))
#define FIX2INT(i) (((long)i) >> 1)
#define RUBY_METHOD_FUNC(func) ((VALUE(*)(...))func)
VALUE *rb_eRuntimeError;
void (*ruby_sysinit)(int *, const char ***);
void (*ruby_init)(void);
void (*ruby_init_loadpath)(void);
void (*ruby_script)(const char*);
void (*ruby_finalize)(void);
ID (*rb_intern)(const char*);
VALUE (*rb_raise)(VALUE, const char*, ...);
VALUE (*rb_funcall)(VALUE, ID, int, ...);
VALUE (*rb_define_module)(const char*);
void (*rb_define_singleton_method)(VALUE, const char*, VALUE(*)(...), int);
void (*rb_define_const)(VALUE, const char*, VALUE);
void (*rb_load_protect)(VALUE, int, int*);
VALUE (*rb_gv_get)(const char*);
VALUE (*rb_str_new)(const char*, long);
VALUE (*rb_str_new2)(const char*);
char* (*rb_string_value_ptr)(VALUE*);
VALUE (*rb_eval_string_protect)(const char*, int*);
VALUE (*rb_ary_shift)(VALUE);
VALUE (*rb_float_new)(double);
double (*rb_num2dbl)(VALUE);
VALUE (*rb_int2inum)(long);
VALUE (*rb_uint2inum)(unsigned long);
unsigned long (*rb_num2ulong)(VALUE);
// end of rip(ruby.h)
DFHack::DFLibrary *libruby_handle;
// load the ruby library, initialize function pointers
static int df_loadruby(void)
{
const char *libpath =
#ifdef WIN32
"./libruby.dll";
#else
"hack/libruby.so";
#endif
libruby_handle = OpenPlugin(libpath);
if (!libruby_handle) {
fprintf(stderr, "Cannot initialize ruby plugin: failed to load %s\n", libpath);
return 0;
}
if (!(rb_eRuntimeError = (VALUE*)LookupPlugin(libruby_handle, "rb_eRuntimeError")))
return 0;
// XXX does msvc support decltype ? might need a #define decltype typeof
// or just assign to *(void**)(&s) = ...
// ruby_sysinit is optional (ruby1.9 only)
ruby_sysinit = (decltype(ruby_sysinit))LookupPlugin(libruby_handle, "ruby_sysinit");
#define rbloadsym(s) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #s))) return 0
rbloadsym(ruby_init);
rbloadsym(ruby_init_loadpath);
rbloadsym(ruby_script);
rbloadsym(ruby_finalize);
rbloadsym(rb_intern);
rbloadsym(rb_raise);
rbloadsym(rb_funcall);
rbloadsym(rb_define_module);
rbloadsym(rb_define_singleton_method);
rbloadsym(rb_define_const);
rbloadsym(rb_load_protect);
rbloadsym(rb_gv_get);
rbloadsym(rb_str_new);
rbloadsym(rb_str_new2);
rbloadsym(rb_string_value_ptr);
rbloadsym(rb_eval_string_protect);
rbloadsym(rb_ary_shift);
rbloadsym(rb_float_new);
rbloadsym(rb_num2dbl);
rbloadsym(rb_int2inum);
rbloadsym(rb_uint2inum);
rbloadsym(rb_num2ulong);
#undef rbloadsym
return 1;
}
static void df_unloadruby(void)
{
if (libruby_handle) {
ClosePlugin(libruby_handle);
libruby_handle = 0;
}
}
// ruby thread code // ruby thread code
static void dump_rb_error(void) static void dump_rb_error(void)
{ {
@ -218,6 +340,13 @@ static void df_rubythread(void *p)
{ {
int state, running; int state, running;
if (ruby_sysinit) {
// ruby1.9 specific API
static int argc;
static const char *argv[] = { "dfhack", 0 };
ruby_sysinit(&argc, (const char ***)&argv);
}
// initialize the ruby interpreter // initialize the ruby interpreter
ruby_init(); ruby_init();
ruby_init_loadpath(); ruby_init_loadpath();
@ -353,7 +482,7 @@ static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
*/ */
static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr) static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
{ {
rb_raise(rb_eRuntimeError, "not implemented"); rb_raise(*rb_eRuntimeError, "not implemented");
} }
static VALUE rb_dfget_global_address(VALUE self, VALUE name) static VALUE rb_dfget_global_address(VALUE self, VALUE name)
@ -402,7 +531,7 @@ static VALUE rb_dfmalloc(VALUE self, VALUE len)
{ {
char *ptr = (char*)malloc(FIX2INT(len)); char *ptr = (char*)malloc(FIX2INT(len));
if (!ptr) if (!ptr)
rb_raise(rb_eRuntimeError, "no memory"); rb_raise(*rb_eRuntimeError, "no memory");
memset(ptr, 0, FIX2INT(len)); memset(ptr, 0, FIX2INT(len));
return rb_uint2inum((long)ptr); return rb_uint2inum((long)ptr);
} }
@ -641,7 +770,7 @@ static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE
int ret; int ret;
fptr = (decltype(fptr))*(void**)(*that + rb_num2ulong(cppvoff)); fptr = (decltype(fptr))*(void**)(*that + rb_num2ulong(cppvoff));
ret = fptr(that, rb_num2ulong(a0), rb_num2ulong(a1), rb_num2ulong(a2), rb_num2ulong(a3)); ret = fptr(that, rb_num2ulong(a0), rb_num2ulong(a1), rb_num2ulong(a2), rb_num2ulong(a3));
return rb_uint2inum(ret); return rb_int2inum(ret);
} }

@ -1,5 +1,3 @@
require 'hack/ruby-autogen'
module DFHack module DFHack
class << self class << self
# update the ruby.cpp version to accept a block # update the ruby.cpp version to accept a block
@ -298,5 +296,759 @@ def df
DFHack DFHack
end end
# load user-specified startup file # following: definitions used by ruby-autogen.rb
module DFHack
module MemHack
INSPECT_SIZE_LIMIT=16384
class MemStruct
attr_accessor :_memaddr
def _at(addr) ; d = dup ; d._memaddr = addr ; d ; end
def _get ; self ; end
def _cpp_init ; end
end
class Compound < MemStruct
class << self
attr_accessor :_fields, :_rtti_classname, :_sizeof
def field(name, offset)
struct = yield
return if not struct
@_fields ||= []
@_fields << [name, offset, struct]
define_method(name) { struct._at(@_memaddr+offset)._get }
define_method("#{name}=") { |v| struct._at(@_memaddr+offset)._set(v) }
end
def _fields_ancestors
if superclass.respond_to?(:_fields_ancestors)
superclass._fields_ancestors + _fields.to_a
else
_fields.to_a
end
end
def number(bits, signed, initvalue=nil, enum=nil)
Number.new(bits, signed, initvalue, enum)
end
def float
Float.new
end
def bit(shift)
BitField.new(shift, 1)
end
def bits(shift, len, enum=nil)
BitField.new(shift, len, enum)
end
def pointer
Pointer.new((yield if block_given?))
end
def pointer_ary(tglen)
PointerAry.new(tglen, yield)
end
def static_array(len, tglen, indexenum=nil)
StaticArray.new(tglen, len, indexenum, yield)
end
def static_string(len)
StaticString.new(len)
end
def stl_vector(tglen=nil)
tg = yield if tglen
case tglen
when 1; StlVector8.new(tg)
when 2; StlVector16.new(tg)
else StlVector32.new(tg)
end
end
def stl_string
StlString.new
end
def stl_bit_vector
StlBitVector.new
end
def stl_deque(tglen)
StlDeque.new(tglen, yield)
end
def df_flagarray(indexenum=nil)
DfFlagarray.new(indexenum)
end
def df_array(tglen)
DfArray.new(tglen, yield)
end
def df_linked_list
DfLinkedList.new(yield)
end
def global(glob)
Global.new(glob)
end
def compound(name=nil, &b)
m = Class.new(Compound)
DFHack.const_set(name, m) if name
m.instance_eval(&b)
m.new
end
def rtti_classname(n)
DFHack.rtti_register(n, self)
@_rtti_classname = n
end
def sizeof(n)
@_sizeof = n
end
# allocate a new c++ object, return its ruby wrapper
def cpp_new
ptr = DFHack.malloc(_sizeof)
if _rtti_classname and vt = DFHack.rtti_getvtable(_rtti_classname)
DFHack.memory_write_int32(ptr, vt)
# TODO call constructor
end
o = new._at(ptr)
o._cpp_init
o
end
end
def _cpp_init
_fields_ancestors.each { |n, o, s| s._at(@_memaddr+o)._cpp_init }
end
def _set(h)
case h
when Hash; h.each { |k, v| send("_#{k}=", v) }
when Array; names = _field_names ; raise 'bad size' if names.length != h.length ; names.zip(h).each { |n, a| send("#{n}=", a) }
when Compound; _field_names.each { |n| send("#{n}=", h.send(n)) }
else raise 'wut?'
end
end
def _fields ; self.class._fields.to_a ; end
def _fields_ancestors ; self.class._fields_ancestors.to_a ; end
def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end
def _rtti_classname ; self.class._rtti_classname ; end
def _sizeof ; self.class._sizeof ; end
@@inspecting = {} # avoid infinite recursion on mutually-referenced objects
def inspect
cn = self.class.name.sub(/^DFHack::/, '')
cn << ' @' << ('0x%X' % _memaddr) if cn != ''
out = "#<#{cn}"
return out << ' ...>' if @@inspecting[_memaddr]
@@inspecting[_memaddr] = true
_fields_ancestors.each { |n, o, s|
out << ' ' if out.length != 0 and out[-1, 1] != ' '
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << inspect_field(n, o, s)
}
out.chomp!(' ')
@@inspecting.delete _memaddr
out << '>'
end
def inspect_field(n, o, s)
if s.kind_of?(BitField) and s._len == 1
send(n) ? n.to_s : ''
elsif s.kind_of?(Pointer)
"#{n}=#{s._at(@_memaddr+o).inspect}"
elsif n == :_whole
"_whole=0x#{_whole.to_s(16)}"
else
v = send(n).inspect
"#{n}=#{v}"
end
rescue
"#{n}=ERR(#{$!})"
end
end
class Enum
# number -> symbol
def self.enum
# ruby weirdness, needed to make the constants 'virtual'
@enum ||= const_get(:ENUM)
end
# symbol -> number
def self.nume
@nume ||= const_get(:NUME)
end
def self.int(i)
nume[i] || i
end
def self.sym(i)
enum[i] || i
end
end
class Number < MemStruct
attr_accessor :_bits, :_signed, :_initvalue, :_enum
def initialize(bits, signed, initvalue, enum)
@_bits = bits
@_signed = signed
@_initvalue = initvalue
@_enum = enum
end
def _get
v = case @_bits
when 32; DFHack.memory_read_int32(@_memaddr)
when 16; DFHack.memory_read_int16(@_memaddr)
when 8; DFHack.memory_read_int8( @_memaddr)
when 64;(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + (DFHack.memory_read_int32(@_memaddr+4) << 32)
end
v &= (1 << @_bits) - 1 if not @_signed
v = @_enum.sym(v) if @_enum
v
end
def _set(v)
v = @_enum.int(v) if @_enum
case @_bits
when 32; DFHack.memory_write_int32(@_memaddr, v)
when 16; DFHack.memory_write_int16(@_memaddr, v)
when 8; DFHack.memory_write_int8( @_memaddr, v)
when 64; DFHack.memory_write_int32(@_memaddr, v & 0xffffffff) ; DFHack.memory_write_int32(@memaddr+4, v>>32)
end
end
def _cpp_init
_set(@_initvalue) if @_initvalue
end
end
class Float < MemStruct
def _get
DFHack.memory_read_float(@_memaddr)
end
def _set(v)
DFHack.memory_write_float(@_memaddr, v)
end
def _cpp_init
_set(0.0)
end
end
class BitField < MemStruct
attr_accessor :_shift, :_len, :_enum
def initialize(shift, len, enum=nil)
@_shift = shift
@_len = len
@_enum = enum
end
def _mask
(1 << @_len) - 1
end
def _get
v = DFHack.memory_read_int32(@_memaddr) >> @_shift
if @_len == 1
((v & 1) == 0) ? false : true
else
v &= _mask
v = @_enum.sym(v) if @_enum
v
end
end
def _set(v)
if @_len == 1
# allow 'bit = 0'
v = (v && v != 0 ? 1 : 0)
end
v = @_enum.int(v) if @_enum
v = (v & _mask) << @_shift
ori = DFHack.memory_read_int32(@_memaddr) & 0xffffffff
DFHack.memory_write_int32(@_memaddr, ori - (ori & ((-1 & _mask) << @_shift)) + v)
end
end
class Pointer < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def _getp
DFHack.memory_read_int32(@_memaddr) & 0xffffffff
end
def _get
addr = _getp
return if addr == 0
@_tg._at(addr)._get
end
# XXX shaky...
def _set(v)
if v.kind_of?(Pointer)
DFHack.memory_write_int32(@_memaddr, v._getp)
elsif v.kind_of?(MemStruct)
DFHack.memory_write_int32(@_memaddr, v._memaddr)
else
_get._set(v)
end
end
def inspect
ptr = _getp
if ptr == 0
'NULL'
else
cn = ''
cn = @_tg.class.name.sub(/^DFHack::/, '').sub(/^MemHack::/, '') if @_tg
cn = @_tg._glob if cn == 'Global'
"#<Pointer #{cn} #{'0x%X' % _getp}>"
end
end
end
class PointerAry < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
def _getp(i=0)
delta = (i != 0 ? i*@_tglen : 0)
(DFHack.memory_read_int32(@_memaddr) & 0xffffffff) + delta
end
def _get
addr = _getp
return if addr == 0
self
end
def [](i)
addr = _getp(i)
return if addr == 0
@_tg._at(addr)._get
end
def []=(i, v)
addr = _getp(i)
raise 'null pointer' if addr == 0
@_tg._at(addr)._set(v)
end
def inspect ; ptr = _getp ; (ptr == 0) ? 'NULL' : "#<PointerAry #{'0x%X' % ptr}>" ; end
end
module Enumerable
include ::Enumerable
attr_accessor :_indexenum
def each ; (0...length).each { |i| yield self[i] } ; end
def inspect
out = '['
each_with_index { |e, idx|
out << ', ' if out.length > 1
if out.length > INSPECT_SIZE_LIMIT
out << '...'
break
end
out << "#{_indexenum.sym(idx)}=" if _indexenum
out << e.inspect
}
out << ']'
end
def empty? ; length == 0 ; end
def flatten ; map { |e| e.respond_to?(:flatten) ? e.flatten : e }.flatten ; end
end
class StaticArray < MemStruct
attr_accessor :_tglen, :_length, :_indexenum, :_tg
def initialize(tglen, length, indexenum, tg)
@_tglen = tglen
@_length = length
@_indexenum = indexenum
@_tg = tg
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
def _cpp_init
_length.times { |i| _tgat(i)._cpp_init }
end
alias length _length
alias size _length
def _tgat(i)
@_tg._at(@_memaddr + i*@_tglen) if i >= 0 and i < @_length
end
def [](i)
i = _indexenum.int(i) if _indexenum
i += @_length if i < 0
_tgat(i)._get
end
def []=(i, v)
i = _indexenum.int(i) if _indexenum
i += @_length if i < 0
_tgat(i)._set(v)
end
include Enumerable
end
class StaticString < MemStruct
attr_accessor :_length
def initialize(length)
@_length = length
end
def _get
DFHack.memory_read(@_memaddr, @_length)
end
def _set(v)
DFHack.memory_write(@_memaddr, v[0, @_length])
end
end
class StlVector32 < MemStruct
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
def length
DFHack.memory_vector32_length(@_memaddr)
end
def size ; length ; end # alias wouldnt work for subclasses
def valueptr_at(idx)
DFHack.memory_vector32_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector32_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector32_delete(@_memaddr, idx)
end
def _set(v)
delete_at(length-1) while length > v.length # match lengthes
v.each_with_index { |e, i| self[i] = e } # patch entries
end
def _cpp_init
DFHack.memory_vector_init(@_memaddr)
end
def clear
delete_at(length-1) while length > 0
end
def [](idx)
idx += length if idx < 0
@_tg._at(valueptr_at(idx))._get if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, 0)
elsif idx < 0
raise 'invalid idx'
end
@_tg._at(valueptr_at(idx))._set(v)
end
def push(v)
self[length] = v
self
end
def <<(v) ; push(v) ; end
def pop
l = length
if l > 0
v = self[l-1]
delete_at(l-1)
end
v
end
include Enumerable
# do a binary search in an ordered vector for a specific target attribute
# ex: world.history.figures.binsearch(unit.hist_figure_id)
def binsearch(target, field=:id)
o_start = 0
o_end = length - 1
while o_end >= o_start
o_half = o_start + (o_end-o_start)/2
obj = self[o_half]
oval = obj.send(field)
if oval == target
return obj
elsif oval < target
o_start = o_half+1
else
o_end = o_half-1
end
end
end
end
class StlVector16 < StlVector32
def length
DFHack.memory_vector16_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector16_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector16_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector16_delete(@_memaddr, idx)
end
end
class StlVector8 < StlVector32
def length
DFHack.memory_vector8_length(@_memaddr)
end
def valueptr_at(idx)
DFHack.memory_vector8_ptrat(@_memaddr, idx)
end
def insert_at(idx, val)
DFHack.memory_vector8_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vector8_delete(@_memaddr, idx)
end
end
class StlBitVector < StlVector32
def initialize ; end
def length
DFHack.memory_vectorbool_length(@_memaddr)
end
def insert_at(idx, val)
DFHack.memory_vectorbool_insert(@_memaddr, idx, val)
end
def delete_at(idx)
DFHack.memory_vectorbool_delete(@_memaddr, idx)
end
def [](idx)
idx += length if idx < 0
DFHack.memory_vectorbool_at(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx += length if idx < 0
if idx >= length
insert_at(idx, v)
elsif idx < 0
raise 'invalid idx'
else
DFHack.memory_vectorbool_setat(@_memaddr, idx, v)
end
end
end
class StlString < MemStruct
def _get
DFHack.memory_read_stlstring(@_memaddr)
end
def _set(v)
DFHack.memory_write_stlstring(@_memaddr, v)
end
def _cpp_init
DFHack.memory_stlstring_init(@_memaddr)
end
end
class StlDeque < MemStruct
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
# XXX DF uses stl::deque<some_struct>, so to have a C binding we'd need to single-case every
# possible struct size, like for StlVector. Just ignore it for now, deque are rare enough.
def inspect ; "#<StlDeque>" ; end
end
class DfFlagarray < MemStruct
attr_accessor :_indexenum
def initialize(indexenum)
@_indexenum = indexenum
end
def length
DFHack.memory_bitarray_length(@_memaddr)
end
# TODO _cpp_init
def size ; length ; end
def resize(len)
DFHack.memory_bitarray_resize(@_memaddr, len)
end
def [](idx)
idx = _indexenum.int(idx) if _indexenum
idx += length if idx < 0
DFHack.memory_bitarray_isset(@_memaddr, idx) if idx >= 0 and idx < length
end
def []=(idx, v)
idx = _indexenum.int(idx) if _indexenum
idx += length if idx < 0
if idx >= length or idx < 0
raise 'invalid idx'
else
DFHack.memory_bitarray_set(@_memaddr, idx, v)
end
end
include Enumerable
end
class DfArray < Compound
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
@_tglen = tglen
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_length, 4) { number 16, false }
def length ; _length ; end
def size ; _length ; end
# TODO _cpp_init
def _tgat(i)
@_tg._at(_ptr + i*@_tglen) if i >= 0 and i < _length
end
def [](i)
i += _length if i < 0
_tgat(i)._get
end
def []=(i, v)
i += _length if i < 0
_tgat(i)._set(v)
end
def _set(a)
a.each_with_index { |v, i| self[i] = v }
end
include Enumerable
end
class DfLinkedList < Compound
attr_accessor :_tg
def initialize(tg)
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_prev, 4) { number 32, false }
field(:_next, 8) { number 32, false }
def item
# With the current xml structure, currently _tg designate
# the type of the 'next' and 'prev' fields, not 'item'.
# List head has item == NULL, so we can safely return nil.
#addr = _ptr
#return if addr == 0
#@_tg._at(addr)._get
end
def item=(v)
#addr = _ptr
#raise 'null pointer' if addr == 0
#@_tg.at(addr)._set(v)
raise 'null pointer'
end
def prev
addr = _prev
return if addr == 0
@_tg._at(addr)._get
end
def next
addr = _next
return if addr == 0
@_tg._at(addr)._get
end
include Enumerable
def each
o = self
while o
yield o.item if o.item
o = o.next
end
end
def inspect ; "#<DfLinkedList #{item.inspect} prev=#{'0x%X' % _prev} next=#{'0x%X' % _next}>" ; end
end
class Global < MemStruct
attr_accessor :_glob
def initialize(glob)
@_glob = glob
end
def _at(addr)
g = DFHack.const_get(@_glob)
g = DFHack.rtti_getclassat(g, addr)
g.new._at(addr)
end
def inspect ; "#<#{@_glob}>" ; end
end
end # module MemHack
class BooleanEnum
def self.int(v) ; ((v == true) || (v == 1)) ? 1 : 0 ; end
def self.sym(v) ; (!v || (v == 0)) ? false : true ; end
end
# cpp rtti name -> rb class
@rtti_n2c = {}
@rtti_c2n = {}
# cpp rtti name -> vtable ptr
@rtti_n2v = {}
@rtti_v2n = {}
def self.rtti_n2c ; @rtti_n2c ; end
def self.rtti_c2n ; @rtti_c2n ; end
def self.rtti_n2v ; @rtti_n2v ; end
def self.rtti_v2n ; @rtti_v2n ; end
# register a ruby class with a cpp rtti class name
def self.rtti_register(cppname, cls)
@rtti_n2c[cppname] = cls
@rtti_c2n[cls] = cppname
end
# return the ruby class to use for the cpp object at address if rtti info is available
def self.rtti_getclassat(cls, addr)
if addr != 0 and @rtti_c2n[cls]
# rtti info exist for class => cpp object has a vtable
@rtti_n2c[rtti_readclassname(get_vtable_ptr(addr))] || cls
else
cls
end
end
# try to read the rtti classname from an object vtable pointer
def self.rtti_readclassname(vptr)
unless n = @rtti_v2n[vptr]
n = @rtti_v2n[vptr] = get_rtti_classname(vptr).to_sym
@rtti_n2v[n] = vptr
end
n
end
# return the vtable pointer from the cpp rtti name
def self.rtti_getvtable(cppname)
unless v = @rtti_n2v[cppname]
v = get_vtable(cppname.to_s)
@rtti_n2v[cppname] = v
@rtti_v2n[v] = cppname if v != 0
end
v if v != 0
end
def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0)
vmethod_do_call(obj._memaddr, voff, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3))
end
def self.vmethod_arg(arg)
case arg
when nil, false; 0
when true; 1
when Integer; arg
#when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer
when MemHack::Compound; arg._memaddr
else raise "bad vmethod arg #{arg.class}"
end
end
end
# load autogen'd file
require 'hack/ruby-autogen'
# load optional user-specified startup file
load 'ruby_custom.rb' if File.exist?('ruby_custom.rb') load 'ruby_custom.rb' if File.exist?('ruby_custom.rb')