Merge branch 'master' of https://github.com/angavrilov/dfhack
						commit
						1327b4954a
					
				@ -0,0 +1,455 @@
 | 
				
			|||||||
 | 
					-- Utilities for offset scan scripts.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local _ENV = mkmodule('memscan')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local utils = require('utils')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- A length-checked view on a memory buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CheckedArray = CheckedArray or {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function CheckedArray.new(type,saddr,eaddr)
 | 
				
			||||||
 | 
					    local data = df.reinterpret_cast(type,saddr)
 | 
				
			||||||
 | 
					    local esize = data:sizeof()
 | 
				
			||||||
 | 
					    local count = math.floor((eaddr-saddr)/esize)
 | 
				
			||||||
 | 
					    local obj = {
 | 
				
			||||||
 | 
					        type = type, start = saddr, size = count*esize,
 | 
				
			||||||
 | 
					        esize = esize, data = data, count = count
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setmetatable(obj, CheckedArray)
 | 
				
			||||||
 | 
					    return obj
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function CheckedArray:__len()
 | 
				
			||||||
 | 
					    return self.count
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function CheckedArray:__index(idx)
 | 
				
			||||||
 | 
					    if type(idx) == number then
 | 
				
			||||||
 | 
					        if idx >= self.count then
 | 
				
			||||||
 | 
					            error('Index out of bounds: '..tostring(idx))
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return self.data[idx]
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        return CheckedArray[idx]
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function CheckedArray:__newindex(idx, val)
 | 
				
			||||||
 | 
					    if idx >= self.count then
 | 
				
			||||||
 | 
					        error('Index out of bounds: '..tostring(idx))
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    self.data[idx] = val
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function CheckedArray:addr2idx(addr, round)
 | 
				
			||||||
 | 
					    local off = addr - self.start
 | 
				
			||||||
 | 
					    if off >= 0 and off < self.size and (round or (off % self.esize) == 0) then
 | 
				
			||||||
 | 
					        return math.floor(off / self.esize), off
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function CheckedArray:idx2addr(idx)
 | 
				
			||||||
 | 
					    if idx >= 0 and idx < self.count then
 | 
				
			||||||
 | 
					        return self.start + idx*self.esize
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Search methods
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function CheckedArray:find(data,sidx,eidx,reverse)
 | 
				
			||||||
 | 
					    local dcnt = #data
 | 
				
			||||||
 | 
					    sidx = math.max(0, sidx or 0)
 | 
				
			||||||
 | 
					    eidx = math.min(self.count, eidx or self.count)
 | 
				
			||||||
 | 
					    if (eidx - sidx) >= dcnt and dcnt > 0 then
 | 
				
			||||||
 | 
					        return dfhack.with_temp_object(
 | 
				
			||||||
 | 
					            df.new(self.type, dcnt),
 | 
				
			||||||
 | 
					            function(buffer)
 | 
				
			||||||
 | 
					                for i = 1,dcnt do
 | 
				
			||||||
 | 
					                    buffer[i-1] = data[i]
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					                local cnt = eidx - sidx - dcnt
 | 
				
			||||||
 | 
					                local step = self.esize
 | 
				
			||||||
 | 
					                local sptr = self.start + sidx*step
 | 
				
			||||||
 | 
					                local ksize = dcnt*step
 | 
				
			||||||
 | 
					                if reverse then
 | 
				
			||||||
 | 
					                    local idx, addr = dfhack.internal.memscan(sptr + cnt*step, cnt, -step, buffer, ksize)
 | 
				
			||||||
 | 
					                    if idx then
 | 
				
			||||||
 | 
					                        return sidx + cnt - idx, addr
 | 
				
			||||||
 | 
					                    end
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                    local idx, addr = dfhack.internal.memscan(sptr, cnt, step, buffer, ksize)
 | 
				
			||||||
 | 
					                    if idx then
 | 
				
			||||||
 | 
					                        return sidx + idx, addr
 | 
				
			||||||
 | 
					                    end
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function CheckedArray:find_one(data,sidx,eidx,reverse)
 | 
				
			||||||
 | 
					    local idx, addr = self:find(data,sidx,eidx,reverse)
 | 
				
			||||||
 | 
					    if idx then
 | 
				
			||||||
 | 
					        -- Verify this is the only match
 | 
				
			||||||
 | 
					        if reverse then
 | 
				
			||||||
 | 
					            eidx = idx+#data-1
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            sidx = idx+1
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        if self:find(data,sidx,eidx,reverse) then
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    return idx, addr
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function CheckedArray:list_changes(old_arr,old_val,new_val,delta)
 | 
				
			||||||
 | 
					    if old_arr.type ~= self.type or old_arr.count ~= self.count then
 | 
				
			||||||
 | 
					        error('Incompatible arrays')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    local eidx = self.count
 | 
				
			||||||
 | 
					    local optr = old_arr.start
 | 
				
			||||||
 | 
					    local nptr = self.start
 | 
				
			||||||
 | 
					    local esize = self.esize
 | 
				
			||||||
 | 
					    local rv
 | 
				
			||||||
 | 
					    local sidx = 0
 | 
				
			||||||
 | 
					    while true do
 | 
				
			||||||
 | 
					        local idx = dfhack.internal.diffscan(optr, nptr, sidx, eidx, esize, old_val, new_val, delta)
 | 
				
			||||||
 | 
					        if not idx then
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        rv = rv or {}
 | 
				
			||||||
 | 
					        rv[#rv+1] = idx
 | 
				
			||||||
 | 
					        sidx = idx+1
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    return rv
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function CheckedArray:filter_changes(prev_list,old_arr,old_val,new_val,delta)
 | 
				
			||||||
 | 
					    if old_arr.type ~= self.type or old_arr.count ~= self.count then
 | 
				
			||||||
 | 
					        error('Incompatible arrays')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    local eidx = self.count
 | 
				
			||||||
 | 
					    local optr = old_arr.start
 | 
				
			||||||
 | 
					    local nptr = self.start
 | 
				
			||||||
 | 
					    local esize = self.esize
 | 
				
			||||||
 | 
					    local rv
 | 
				
			||||||
 | 
					    for i=1,#prev_list do
 | 
				
			||||||
 | 
					        local idx = prev_list[i]
 | 
				
			||||||
 | 
					        if idx < 0 or idx >= eidx then
 | 
				
			||||||
 | 
					            error('Index out of bounds: '..idx)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        if dfhack.internal.diffscan(optr, nptr, idx, idx+1, esize, old_val, new_val, delta) then
 | 
				
			||||||
 | 
					            rv = rv or {}
 | 
				
			||||||
 | 
					            rv[#rv+1] = idx
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    return rv
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- A raw memory area class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MemoryArea = MemoryArea or {}
 | 
				
			||||||
 | 
					MemoryArea.__index = MemoryArea
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function MemoryArea.new(astart, aend)
 | 
				
			||||||
 | 
					    local obj = {
 | 
				
			||||||
 | 
					        start_addr = astart, end_addr = aend, size = aend - astart,
 | 
				
			||||||
 | 
					        int8_t = CheckedArray.new('int8_t',astart,aend),
 | 
				
			||||||
 | 
					        uint8_t = CheckedArray.new('uint8_t',astart,aend),
 | 
				
			||||||
 | 
					        int16_t = CheckedArray.new('int16_t',astart,aend),
 | 
				
			||||||
 | 
					        uint16_t = CheckedArray.new('uint16_t',astart,aend),
 | 
				
			||||||
 | 
					        int32_t = CheckedArray.new('int32_t',astart,aend),
 | 
				
			||||||
 | 
					        uint32_t = CheckedArray.new('uint32_t',astart,aend)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setmetatable(obj, MemoryArea)
 | 
				
			||||||
 | 
					    return obj
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function MemoryArea:__gc()
 | 
				
			||||||
 | 
					    df.delete(self.buffer)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function MemoryArea:__tostring()
 | 
				
			||||||
 | 
					    return string.format('<MemoryArea: %x..%x>', self.start_addr, self.end_addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function MemoryArea:contains_range(start,size)
 | 
				
			||||||
 | 
					    return start >= self.start_addr and (start+size) <= self.end_addr
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function MemoryArea:contains_obj(obj,count)
 | 
				
			||||||
 | 
					    local size, base = df.sizeof(obj)
 | 
				
			||||||
 | 
					    return size and base and self:contains_range(base, size*(count or 1))
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function MemoryArea:clone()
 | 
				
			||||||
 | 
					    local buffer = df.new('int8_t', self.size)
 | 
				
			||||||
 | 
					    local _, base = buffer:sizeof()
 | 
				
			||||||
 | 
					    local rv = MemoryArea.new(base, base+self.size)
 | 
				
			||||||
 | 
					    rv.buffer = buffer
 | 
				
			||||||
 | 
					    return rv
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function MemoryArea:copy_from(area2)
 | 
				
			||||||
 | 
					    if area2.size ~= self.size then
 | 
				
			||||||
 | 
					        error('Size mismatch')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    dfhack.internal.memmove(self.start_addr, area2.start_addr, self.size)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function MemoryArea:delete()
 | 
				
			||||||
 | 
					    setmetatable(self, nil)
 | 
				
			||||||
 | 
					    df.delete(self.buffer)
 | 
				
			||||||
 | 
					    for k,v in pairs(self) do self[k] = nil end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Static data segment search
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_data_segment()
 | 
				
			||||||
 | 
					    local data_start, data_end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for i,mem in ipairs(dfhack.internal.getMemRanges()) do
 | 
				
			||||||
 | 
					        if data_end then
 | 
				
			||||||
 | 
					            if mem.start_addr == data_end and mem.read and mem.write then
 | 
				
			||||||
 | 
					                data_end = mem.end_addr
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					        elseif mem.read and mem.write
 | 
				
			||||||
 | 
					           and (string.match(mem.name,'/dwarfort%.exe$')
 | 
				
			||||||
 | 
					             or string.match(mem.name,'/Dwarf_Fortress$'))
 | 
				
			||||||
 | 
					        then
 | 
				
			||||||
 | 
					            data_start = mem.start_addr
 | 
				
			||||||
 | 
					            data_end = mem.end_addr
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return data_start, data_end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function get_data_segment()
 | 
				
			||||||
 | 
					    local s, e = find_data_segment()
 | 
				
			||||||
 | 
					    if s and e then
 | 
				
			||||||
 | 
					        return MemoryArea.new(s, e)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Register a found offset, or report an error.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function found_offset(name,val)
 | 
				
			||||||
 | 
					    local cval = dfhack.internal.getAddress(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not val then
 | 
				
			||||||
 | 
					        print('Could not find offset '..name)
 | 
				
			||||||
 | 
					        if not cval and not utils.prompt_yes_no('Continue with the script?') then
 | 
				
			||||||
 | 
					            error('User quit')
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if df.isvalid(val) then
 | 
				
			||||||
 | 
					        _,val = val:sizeof()
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print(string.format('Found offset %s: %x', name, val))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if cval then
 | 
				
			||||||
 | 
					        if cval ~= val then
 | 
				
			||||||
 | 
					            error(string.format('Mismatch with the current value: %x',val))
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        dfhack.internal.setAddress(name, val)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Offset of a field within struct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function field_ref(handle,...)
 | 
				
			||||||
 | 
					    local items = table.pack(...)
 | 
				
			||||||
 | 
					    for i=1,items.n-1 do
 | 
				
			||||||
 | 
					        handle = handle[items[i]]
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    return handle:_field(items[items.n])
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function field_offset(type,...)
 | 
				
			||||||
 | 
					    local handle = df.reinterpret_cast(type,1)
 | 
				
			||||||
 | 
					    local _,addr = df.sizeof(field_ref(handle,...))
 | 
				
			||||||
 | 
					    return addr-1
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function MemoryArea:object_by_field(addr,type,...)
 | 
				
			||||||
 | 
					    if not addr then
 | 
				
			||||||
 | 
					        return nil
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    local base = addr - field_offset(type,...)
 | 
				
			||||||
 | 
					    local obj = df.reinterpret_cast(type, base)
 | 
				
			||||||
 | 
					    if not self:contains_obj(obj) then
 | 
				
			||||||
 | 
					        obj = nil
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    return obj, base
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Validation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function is_valid_vector(ref,size)
 | 
				
			||||||
 | 
					    local ints = df.reinterpret_cast('uint32_t', ref)
 | 
				
			||||||
 | 
					    return ints[0] <= ints[1] and ints[1] <= ints[2]
 | 
				
			||||||
 | 
					       and (size == nil or (ints[1] - ints[0]) % size == 0)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Difference search helper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DiffSearcher = DiffSearcher or {}
 | 
				
			||||||
 | 
					DiffSearcher.__index = DiffSearcher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function DiffSearcher.new(area)
 | 
				
			||||||
 | 
					    local obj = { area = area }
 | 
				
			||||||
 | 
					    setmetatable(obj, DiffSearcher)
 | 
				
			||||||
 | 
					    return obj
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function DiffSearcher:begin_search(type)
 | 
				
			||||||
 | 
					    self.type = type
 | 
				
			||||||
 | 
					    self.old_value = nil
 | 
				
			||||||
 | 
					    self.search_sets = nil
 | 
				
			||||||
 | 
					    if not self.save_area then
 | 
				
			||||||
 | 
					        self.save_area = self.area:clone()
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function DiffSearcher:advance_search(new_value,delta)
 | 
				
			||||||
 | 
					    if self.search_sets then
 | 
				
			||||||
 | 
					        local nsets = #self.search_sets
 | 
				
			||||||
 | 
					        local ovec = self.save_area[self.type]
 | 
				
			||||||
 | 
					        local nvec = self.area[self.type]
 | 
				
			||||||
 | 
					        local new_set
 | 
				
			||||||
 | 
					        if nsets > 0 then
 | 
				
			||||||
 | 
					            local last_set = self.search_sets[nsets]
 | 
				
			||||||
 | 
					            new_set = nvec:filter_changes(last_set,ovec,self.old_value,new_value,delta)
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            new_set = nvec:list_changes(ovec,self.old_value,new_value,delta)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        if new_set then
 | 
				
			||||||
 | 
					            self.search_sets[nsets+1] = new_set
 | 
				
			||||||
 | 
					            self.old_value = new_value
 | 
				
			||||||
 | 
					            self.save_area:copy_from(self.area)
 | 
				
			||||||
 | 
					            return #new_set, new_set
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        self.old_value = new_value
 | 
				
			||||||
 | 
					        self.search_sets = {}
 | 
				
			||||||
 | 
					        self.save_area:copy_from(self.area)
 | 
				
			||||||
 | 
					        return #self.save_area[self.type], nil
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function DiffSearcher:reset()
 | 
				
			||||||
 | 
					    self.search_sets = nil
 | 
				
			||||||
 | 
					    if self.save_area then
 | 
				
			||||||
 | 
					        self.save_area:delete()
 | 
				
			||||||
 | 
					        self.save_area = nil
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					function DiffSearcher:idx2addr(idx)
 | 
				
			||||||
 | 
					    return self.area[self.type]:idx2addr(idx)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Interactive search utility
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function DiffSearcher:find_interactive(prompt,data_type,condition_cb)
 | 
				
			||||||
 | 
					    enum = enum or {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- Loop for restarting search from scratch
 | 
				
			||||||
 | 
					    while true do
 | 
				
			||||||
 | 
					        print('\n'..prompt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self:begin_search(data_type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        local found = false
 | 
				
			||||||
 | 
					        local ccursor = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        -- Loop through choices
 | 
				
			||||||
 | 
					        while true do
 | 
				
			||||||
 | 
					            print('')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            local ok, value, delta = condition_cb(ccursor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ccursor = ccursor + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not ok then
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            -- Search for it in the memory
 | 
				
			||||||
 | 
					            local cnt, set = self:advance_search(value, delta)
 | 
				
			||||||
 | 
					            if not cnt then
 | 
				
			||||||
 | 
					                dfhack.printerr('  Converged to zero candidates; probably a mistake somewhere.')
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					            elseif set and cnt == 1 then
 | 
				
			||||||
 | 
					                -- To confirm, wait for two 1-candidate results in a row
 | 
				
			||||||
 | 
					                if found then
 | 
				
			||||||
 | 
					                    local addr = self:idx2addr(set[1])
 | 
				
			||||||
 | 
					                    print(string.format('  Confirmed address: %x\n', addr))
 | 
				
			||||||
 | 
					                    return addr, set[1]
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                    found = true
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            print('  '..cnt..' candidates remaining.')
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not utils.prompt_yes_no('\nRetry search from the start?') then
 | 
				
			||||||
 | 
					            return nil
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function DiffSearcher:find_menu_cursor(prompt,data_type,choices,enum)
 | 
				
			||||||
 | 
					    enum = enum or {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return self:find_interactive(
 | 
				
			||||||
 | 
					        prompt, data_type,
 | 
				
			||||||
 | 
					        function(ccursor)
 | 
				
			||||||
 | 
					            local choice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            -- Select the next value to search for
 | 
				
			||||||
 | 
					            if type(choices) == 'function' then
 | 
				
			||||||
 | 
					                choice = choices(ccursor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if not choice then
 | 
				
			||||||
 | 
					                    return false
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                choice = choices[(ccursor % #choices) + 1]
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            -- Ask the user to select it
 | 
				
			||||||
 | 
					            if enum ~= 'noprompt' then
 | 
				
			||||||
 | 
					                local cname = enum[choice] or choice
 | 
				
			||||||
 | 
					                if type(choice) == 'string' and type(cname) == 'number' then
 | 
				
			||||||
 | 
					                    choice, cname = cname, choice
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					                if cname ~= choice then
 | 
				
			||||||
 | 
					                    cname = cname..' ('..choice..')'
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                print('  Please select: '..cname)
 | 
				
			||||||
 | 
					                if not utils.prompt_yes_no('  Continue?', true) then
 | 
				
			||||||
 | 
					                    return false
 | 
				
			||||||
 | 
					                end
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true, choice
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function DiffSearcher:find_counter(prompt,data_type,delta,action_prompt)
 | 
				
			||||||
 | 
					    delta = delta or 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return self:find_interactive(
 | 
				
			||||||
 | 
					        prompt, data_type,
 | 
				
			||||||
 | 
					        function(ccursor)
 | 
				
			||||||
 | 
					            if ccursor > 0 then
 | 
				
			||||||
 | 
					                print("  "..(action_prompt or 'Please do the action.'))
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            if not utils.prompt_yes_no('  Continue?', true) then
 | 
				
			||||||
 | 
					                return false
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            return true, nil, delta
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return _ENV
 | 
				
			||||||
@ -1 +1 @@
 | 
				
			|||||||
Subproject commit c381884664c71adefbec44258a734def2c88dacc
 | 
					Subproject commit bc757db69514b54eb66d4d38e9cc1354d3761907
 | 
				
			||||||
@ -0,0 +1,601 @@
 | 
				
			|||||||
 | 
					-- Find some offsets for linux.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local utils = require 'utils'
 | 
				
			||||||
 | 
					local ms = require 'memscan'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local is_known = dfhack.internal.getAddress
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local force_scan = {}
 | 
				
			||||||
 | 
					for _,v in ipairs({...}) do
 | 
				
			||||||
 | 
					    force_scan[v] = true
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					collectgarbage()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print[[
 | 
				
			||||||
 | 
					WARNING: THIS SCRIPT IS STRICTLY FOR DFHACK DEVELOPERS.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Running this script on a new DF version will NOT
 | 
				
			||||||
 | 
					MAKE IT RUN CORRECTLY if any data structures
 | 
				
			||||||
 | 
					changed, thus possibly leading to CRASHES AND/OR
 | 
				
			||||||
 | 
					PERMANENT SAVE CORRUPTION.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This script should be initially started immediately
 | 
				
			||||||
 | 
					after loading the game, WITHOUT first loading a world.
 | 
				
			||||||
 | 
					It expects vanilla game configuration, without any
 | 
				
			||||||
 | 
					custom tilesets or init file changes.
 | 
				
			||||||
 | 
					]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if not utils.prompt_yes_no('Proceed?') then
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					-- Data segment location
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local data = ms.get_data_segment()
 | 
				
			||||||
 | 
					if not data then
 | 
				
			||||||
 | 
					    error('Could not find data segment')
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print('\nData section: '..tostring(data))
 | 
				
			||||||
 | 
					if data.size < 5000000 then
 | 
				
			||||||
 | 
					    error('Data segment too short.')
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local searcher = ms.DiffSearcher.new(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function validate_offset(name,validator,addr,tname,...)
 | 
				
			||||||
 | 
					    local obj = data:object_by_field(addr,tname,...)
 | 
				
			||||||
 | 
					    if obj and not validator(obj) then
 | 
				
			||||||
 | 
					        obj = nil
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    ms.found_offset(name,obj)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function exec_finder(finder, names)
 | 
				
			||||||
 | 
					    if type(names) ~= 'table' then
 | 
				
			||||||
 | 
					        names = { names }
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    local search = force_scan['all']
 | 
				
			||||||
 | 
					    for _,v in ipairs(names) do
 | 
				
			||||||
 | 
					        if force_scan[v] or not is_known(v) then
 | 
				
			||||||
 | 
					            search = true
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    if search then
 | 
				
			||||||
 | 
					        if not dfhack.safecall(finder) then
 | 
				
			||||||
 | 
					            if not utils.prompt_yes_no('Proceed with the rest of the script?') then
 | 
				
			||||||
 | 
					                searcher:reset()
 | 
				
			||||||
 | 
					                error('Quit')
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        print('Already known: '..table.concat(names,', '))
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local ordinal_names = {
 | 
				
			||||||
 | 
					    [0] = '1st entry',
 | 
				
			||||||
 | 
					    [1] = '2nd entry'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					setmetatable(ordinal_names, {
 | 
				
			||||||
 | 
					    __index = function(self,idx) return (idx+1)..'th entry' end
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function list_index_choices(length_func)
 | 
				
			||||||
 | 
					    return function(id)
 | 
				
			||||||
 | 
					        if id > 0 then
 | 
				
			||||||
 | 
					            local ok, len = pcall(length_func)
 | 
				
			||||||
 | 
					            if not ok then
 | 
				
			||||||
 | 
					                len = 5
 | 
				
			||||||
 | 
					            elseif len > 10 then
 | 
				
			||||||
 | 
					                len = 10
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					            return id % len
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            return 0
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Cursor group
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_cursor()
 | 
				
			||||||
 | 
					    print('\nPlease navigate to the title screen to find cursor.')
 | 
				
			||||||
 | 
					    if not utils.prompt_yes_no('Proceed?', true) then
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- Unpadded version
 | 
				
			||||||
 | 
					    local idx, addr = data.int32_t:find_one{
 | 
				
			||||||
 | 
					        -30000, -30000, -30000,
 | 
				
			||||||
 | 
					        -30000, -30000, -30000, -30000, -30000, -30000,
 | 
				
			||||||
 | 
					        df.game_mode.NONE, df.game_type.NONE
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if idx then
 | 
				
			||||||
 | 
					        ms.found_offset('cursor', addr)
 | 
				
			||||||
 | 
					        ms.found_offset('selection_rect', addr + 12)
 | 
				
			||||||
 | 
					        ms.found_offset('gamemode', addr + 12 + 24)
 | 
				
			||||||
 | 
					        ms.found_offset('gametype', addr + 12 + 24 + 4)
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    -- Padded version
 | 
				
			||||||
 | 
					    idx, addr = data.int32_t:find_one{
 | 
				
			||||||
 | 
					        -30000, -30000, -30000, 0,
 | 
				
			||||||
 | 
					        -30000, -30000, -30000, -30000, -30000, -30000, 0, 0,
 | 
				
			||||||
 | 
					        df.game_mode.NONE, 0, 0, 0, df.game_type.NONE
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if idx then
 | 
				
			||||||
 | 
					        ms.found_offset('cursor', addr)
 | 
				
			||||||
 | 
					        ms.found_offset('selection_rect', addr + 0x10)
 | 
				
			||||||
 | 
					        ms.found_offset('gamemode', addr + 0x30)
 | 
				
			||||||
 | 
					        ms.found_offset('gametype', addr + 0x40)
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dfhack.printerr('Could not find cursor.')
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- Announcements
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_announcements()
 | 
				
			||||||
 | 
					    local idx, addr = data.int32_t:find_one{
 | 
				
			||||||
 | 
					        25, 25, 31, 31, 24, 24, 40, 40, 40, 40, 40, 40, 40
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if idx then
 | 
				
			||||||
 | 
					        ms.found_offset('announcements', addr)
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dfhack.printerr('Could not find announcements.')
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_announcements, 'announcements')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- d_init
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function is_valid_d_init(di)
 | 
				
			||||||
 | 
					    if di.sky_tile ~= 178 then
 | 
				
			||||||
 | 
					        print('Sky tile expected 178, found: '..di.sky_tile)
 | 
				
			||||||
 | 
					        if not utils.prompt_yes_no('Ignore?') then
 | 
				
			||||||
 | 
					            return false
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local ann = is_known 'announcements'
 | 
				
			||||||
 | 
					    local size,ptr = di:sizeof()
 | 
				
			||||||
 | 
					    if ann and ptr+size ~= ann then
 | 
				
			||||||
 | 
					        print('Announcements not immediately after d_init.')
 | 
				
			||||||
 | 
					        if not utils.prompt_yes_no('Ignore?') then
 | 
				
			||||||
 | 
					            return false
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_d_init()
 | 
				
			||||||
 | 
					    local idx, addr = data.int16_t:find_one{
 | 
				
			||||||
 | 
					        1,0, 2,0, 5,0, 25,0,   -- path_cost
 | 
				
			||||||
 | 
					        4,4,                   -- embark_rect
 | 
				
			||||||
 | 
					        20,1000,1000,1000,1000 -- store_dist
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if idx then
 | 
				
			||||||
 | 
					        validate_offset('d_init', is_valid_d_init, addr, df.d_init, 'path_cost')
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dfhack.printerr('Could not find d_init')
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_d_init, 'd_init')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- gview
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_gview()
 | 
				
			||||||
 | 
					    local vs_vtable = dfhack.internal.getVTable('viewscreenst')
 | 
				
			||||||
 | 
					    if not vs_vtable then
 | 
				
			||||||
 | 
					        dfhack.printerr('Cannot search for gview - no viewscreenst vtable.')
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local idx, addr = data.uint32_t:find_one{0, vs_vtable}
 | 
				
			||||||
 | 
					    if idx then
 | 
				
			||||||
 | 
					        ms.found_offset('gview', addr)
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dfhack.printerr('Could not find gview')
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_gview, 'gview')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- World
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function is_valid_world(world)
 | 
				
			||||||
 | 
					    if not ms.is_valid_vector(world.units.all, 4)
 | 
				
			||||||
 | 
					    or not ms.is_valid_vector(world.units.bad, 4)
 | 
				
			||||||
 | 
					    or not ms.is_valid_vector(world.history.figures, 4)
 | 
				
			||||||
 | 
					    or not ms.is_valid_vector(world.cur_savegame.map_features, 4)
 | 
				
			||||||
 | 
					    then
 | 
				
			||||||
 | 
					        dfhack.printerr('Vector layout check failed.')
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if #world.units.all == 0 or #world.units.all ~= #world.units.bad then
 | 
				
			||||||
 | 
					        print('Different or zero size of units.all and units.bad:'..#world.units.all..' vs '..#world.units.bad)
 | 
				
			||||||
 | 
					        if not utils.prompt_yes_no('Ignore?') then
 | 
				
			||||||
 | 
					            return false
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_world()
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for world. Please open the stockpile creation
 | 
				
			||||||
 | 
					menu, and select different types as instructed below:]],
 | 
				
			||||||
 | 
					        'int32_t',
 | 
				
			||||||
 | 
					        { 'Corpses', 'Refuse', 'Stone', 'Wood', 'Gems', 'Bars', 'Cloth', 'Leather', 'Ammo', 'Coins' },
 | 
				
			||||||
 | 
					        df.stockpile_category
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    validate_offset('world', is_valid_world, addr, df.world, 'selected_stockpile_type')
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_world, 'world')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- UI
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function is_valid_ui(ui)
 | 
				
			||||||
 | 
					    if not ms.is_valid_vector(ui.economic_stone, 1)
 | 
				
			||||||
 | 
					    or not ms.is_valid_vector(ui.dipscripts, 4)
 | 
				
			||||||
 | 
					    then
 | 
				
			||||||
 | 
					        dfhack.printerr('Vector layout check failed.')
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ui.follow_item ~= -1 or ui.follow_unit ~= -1 then
 | 
				
			||||||
 | 
					        print('Invalid follow state: '..ui.follow_item..', '..ui.follow_unit)
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_ui()
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for ui. Please open the designation
 | 
				
			||||||
 | 
					menu, and switch modes as instructed below:]],
 | 
				
			||||||
 | 
					        'int16_t',
 | 
				
			||||||
 | 
					        { 'DesignateMine', 'DesignateChannel', 'DesignateRemoveRamps', 'DesignateUpStair',
 | 
				
			||||||
 | 
					          'DesignateDownStair', 'DesignateUpDownStair', 'DesignateUpRamp', 'DesignateChopTrees' },
 | 
				
			||||||
 | 
					        df.ui_sidebar_mode
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    validate_offset('ui', is_valid_ui, addr, df.ui, 'main', 'mode')
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_ui, 'ui')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- ui_sidebar_menus
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function is_valid_ui_sidebar_menus(usm)
 | 
				
			||||||
 | 
					    if not ms.is_valid_vector(usm.workshop_job.choices_all, 4)
 | 
				
			||||||
 | 
					    or not ms.is_valid_vector(usm.workshop_job.choices_visible, 4)
 | 
				
			||||||
 | 
					    then
 | 
				
			||||||
 | 
					        dfhack.printerr('Vector layout check failed.')
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if #usm.workshop_job.choices_all == 0
 | 
				
			||||||
 | 
					    or #usm.workshop_job.choices_all ~= #usm.workshop_job.choices_visible then
 | 
				
			||||||
 | 
					        print('Different or zero size of visible and all choices:'..
 | 
				
			||||||
 | 
					              #usm.workshop_job.choices_all..' vs '..#usm.workshop_job.choices_visible)
 | 
				
			||||||
 | 
					        if not utils.prompt_yes_no('Ignore?') then
 | 
				
			||||||
 | 
					            return false
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_ui_sidebar_menus()
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for ui_sidebar_menus. Please open the add job
 | 
				
			||||||
 | 
					ui of Mason, Craftsdwarfs, or Carpenters workshop, and
 | 
				
			||||||
 | 
					select entries in the list:]],
 | 
				
			||||||
 | 
					        'int32_t',
 | 
				
			||||||
 | 
					        { 0, 1, 2, 3, 4, 5, 6 },
 | 
				
			||||||
 | 
					        ordinal_names
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    validate_offset('ui_sidebar_menus', is_valid_ui_sidebar_menus,
 | 
				
			||||||
 | 
					                    addr, df.ui_sidebar_menus, 'workshop_job', 'cursor')
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_ui_sidebar_menus, 'ui_sidebar_menus')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- ui_build_selector
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function is_valid_ui_build_selector(ubs)
 | 
				
			||||||
 | 
					    if not ms.is_valid_vector(ubs.requirements, 4)
 | 
				
			||||||
 | 
					    or not ms.is_valid_vector(ubs.choices, 4)
 | 
				
			||||||
 | 
					    then
 | 
				
			||||||
 | 
					        dfhack.printerr('Vector layout check failed.')
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ubs.building_type ~= df.building_type.Trap
 | 
				
			||||||
 | 
					    or ubs.building_subtype ~= df.trap_type.PressurePlate then
 | 
				
			||||||
 | 
					        print('Invalid building type and subtype:'..ubs.building_type..','..ubs.building_subtype)
 | 
				
			||||||
 | 
					        return false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_ui_build_selector()
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for ui_build_selector. Please start constructing
 | 
				
			||||||
 | 
					a pressure plate, and enable creatures. Then change the min
 | 
				
			||||||
 | 
					weight as requested, remembering that the ui truncates the
 | 
				
			||||||
 | 
					number, so when it shows "Min (5000df", it means 50000:]],
 | 
				
			||||||
 | 
					        'int32_t',
 | 
				
			||||||
 | 
					        { 50000, 49000, 48000, 47000, 46000, 45000, 44000 }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    validate_offset('ui_build_selector', is_valid_ui_build_selector,
 | 
				
			||||||
 | 
					                    addr, df.ui_build_selector, 'plate_info', 'unit_min')
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_ui_build_selector, 'ui_build_selector')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- ui_selected_unit
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_ui_selected_unit()
 | 
				
			||||||
 | 
					    if not is_known 'world' then
 | 
				
			||||||
 | 
					        dfhack.printerr('Cannot search for ui_selected_unit: no world')
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for i,unit in ipairs(df.global.world.units.active) do
 | 
				
			||||||
 | 
					        dfhack.units.setNickname(unit, i)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for ui_selected_unit. Please activate the 'v'
 | 
				
			||||||
 | 
					mode, point it at units, and enter their numeric nickname
 | 
				
			||||||
 | 
					into the prompts below:]],
 | 
				
			||||||
 | 
					        'int32_t',
 | 
				
			||||||
 | 
					        function()
 | 
				
			||||||
 | 
					            return utils.prompt_input('  Enter index: ', utils.check_number)
 | 
				
			||||||
 | 
					        end,
 | 
				
			||||||
 | 
					        'noprompt'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ms.found_offset('ui_selected_unit', addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_ui_selected_unit, 'ui_selected_unit')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- ui_unit_view_mode
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_ui_unit_view_mode()
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for ui_unit_view_mode. Having selected a unit
 | 
				
			||||||
 | 
					with 'v', switch the pages as requested:]],
 | 
				
			||||||
 | 
					        'int32_t',
 | 
				
			||||||
 | 
					        { 'General', 'Inventory', 'Preferences', 'Wounds' },
 | 
				
			||||||
 | 
					        df.ui_unit_view_mode.T_value
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ms.found_offset('ui_unit_view_mode', addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_ui_unit_view_mode, 'ui_unit_view_mode')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- ui_look_cursor
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function look_item_list_count()
 | 
				
			||||||
 | 
					    return #df.global.ui_look_list.items
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_ui_look_cursor()
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for ui_look_cursor. Please activate the 'k'
 | 
				
			||||||
 | 
					mode, find a tile with many items or units on the ground,
 | 
				
			||||||
 | 
					and select list entries as instructed:]],
 | 
				
			||||||
 | 
					        'int32_t',
 | 
				
			||||||
 | 
					        list_index_choices(look_item_list_count),
 | 
				
			||||||
 | 
					        ordinal_names
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ms.found_offset('ui_look_cursor', addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_ui_look_cursor, 'ui_look_cursor')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- ui_building_item_cursor
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function building_item_list_count()
 | 
				
			||||||
 | 
					    return #df.global.world.selected_building.contained_items
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_ui_building_item_cursor()
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for ui_building_item_cursor. Please activate the 't'
 | 
				
			||||||
 | 
					mode, find a cluttered workshop, trade depot, or other building
 | 
				
			||||||
 | 
					with many contained items, and select as instructed:]],
 | 
				
			||||||
 | 
					        'int32_t',
 | 
				
			||||||
 | 
					        list_index_choices(building_item_list_count),
 | 
				
			||||||
 | 
					        ordinal_names
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ms.found_offset('ui_building_item_cursor', addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_ui_building_item_cursor, 'ui_building_item_cursor')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- ui_workshop_in_add
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_ui_workshop_in_add()
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for ui_workshop_in_add. Please activate the 'q'
 | 
				
			||||||
 | 
					mode, find a workshop without jobs (or delete jobs),
 | 
				
			||||||
 | 
					and do as instructed below.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NOTE: After first 3 steps resize the game window.]],
 | 
				
			||||||
 | 
					        'int8_t',
 | 
				
			||||||
 | 
					        { 1, 0 },
 | 
				
			||||||
 | 
					        { [1] = 'enter the add job menu',
 | 
				
			||||||
 | 
					          [0] = 'add job, thus exiting the menu' }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ms.found_offset('ui_workshop_in_add', addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_ui_workshop_in_add, 'ui_workshop_in_add')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- ui_workshop_job_cursor
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function workshop_job_list_count()
 | 
				
			||||||
 | 
					    return #df.global.world.selected_building.jobs
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_ui_workshop_job_cursor()
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for ui_workshop_job_cursor. Please activate the 'q'
 | 
				
			||||||
 | 
					mode, find a workshop with many jobs, and select as instructed:]],
 | 
				
			||||||
 | 
					        'int32_t',
 | 
				
			||||||
 | 
					        list_index_choices(workshop_job_list_count),
 | 
				
			||||||
 | 
					        ordinal_names
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ms.found_offset('ui_workshop_job_cursor', addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_ui_workshop_job_cursor, 'ui_workshop_job_cursor')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- ui_building_in_assign
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_ui_building_in_assign()
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for ui_building_in_assign. Please activate
 | 
				
			||||||
 | 
					the 'q' mode, select a room building (e.g. a bedroom)
 | 
				
			||||||
 | 
					and do as instructed below.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NOTE: After first 3 steps resize the game window.]],
 | 
				
			||||||
 | 
					        'int8_t',
 | 
				
			||||||
 | 
					        { 1, 0 },
 | 
				
			||||||
 | 
					        { [1] = 'enter the Assign owner menu',
 | 
				
			||||||
 | 
					          [0] = 'press Esc to exit assign' }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ms.found_offset('ui_building_in_assign', addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_ui_building_in_assign, 'ui_building_in_assign')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- ui_building_in_resize
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_ui_building_in_resize()
 | 
				
			||||||
 | 
					    local addr = searcher:find_menu_cursor([[
 | 
				
			||||||
 | 
					Searching for ui_building_in_resize. Please activate
 | 
				
			||||||
 | 
					the 'q' mode, select a room building (e.g. a bedroom)
 | 
				
			||||||
 | 
					and do as instructed below.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NOTE: After first 3 steps resize the game window.]],
 | 
				
			||||||
 | 
					        'int8_t',
 | 
				
			||||||
 | 
					        { 1, 0 },
 | 
				
			||||||
 | 
					        { [1] = 'enter the Resize room mode',
 | 
				
			||||||
 | 
					          [0] = 'press Esc to exit resize' }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ms.found_offset('ui_building_in_resize', addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_ui_building_in_resize, 'ui_building_in_resize')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- window_x
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_window_x()
 | 
				
			||||||
 | 
					    local addr = searcher:find_counter([[
 | 
				
			||||||
 | 
					Searching for window_x. Please exit to main dwarfmode menu,
 | 
				
			||||||
 | 
					scroll to the LEFT edge, then do as instructed:]],
 | 
				
			||||||
 | 
					        'int32_t', 10,
 | 
				
			||||||
 | 
					        'Please press Right to scroll right one step.'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ms.found_offset('window_x', addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_window_x, 'window_x')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- window_y
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_window_y()
 | 
				
			||||||
 | 
					    local addr = searcher:find_counter([[
 | 
				
			||||||
 | 
					Searching for window_y. Please exit to main dwarfmode menu,
 | 
				
			||||||
 | 
					scroll to the TOP edge, then do as instructed:]],
 | 
				
			||||||
 | 
					        'int32_t', 10,
 | 
				
			||||||
 | 
					        'Please press Down to scroll down one step.'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ms.found_offset('window_y', addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_window_y, 'window_y')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- window_z
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function find_window_z()
 | 
				
			||||||
 | 
					    local addr = searcher:find_counter([[
 | 
				
			||||||
 | 
					Searching for window_z. Please exit to main dwarfmode menu,
 | 
				
			||||||
 | 
					scroll to ground level, then do as instructed below.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NOTE: After first 3 steps resize the game window.]],
 | 
				
			||||||
 | 
					        'int32_t', -1,
 | 
				
			||||||
 | 
					        "Please press '>' to scroll one Z level down."
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ms.found_offset('window_z', addr)
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exec_finder(find_window_z, 'window_z')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					-- THE END
 | 
				
			||||||
 | 
					--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print('Done.')
 | 
				
			||||||
 | 
					searcher:reset()
 | 
				
			||||||
		Loading…
	
		Reference in New Issue