PageRenderTime 105ms CodeModel.GetById 32ms app.highlight 65ms RepoModel.GetById 1ms app.codeStats 0ms

/src/dfinstance.cpp

https://code.google.com/p/dwarftherapist/
C++ | 965 lines | 755 code | 117 blank | 93 comment | 141 complexity | bb5e7e844c816cd686e2543ef34d7209 MD5 | raw file
  1/*
  2Dwarf Therapist
  3Copyright (c) 2009 Trey Stout (chmod)
  4
  5Permission is hereby granted, free of charge, to any person obtaining a copy
  6of this software and associated documentation files (the "Software"), to deal
  7in the Software without restriction, including without limitation the rights
  8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9copies of the Software, and to permit persons to whom the Software is
 10furnished to do so, subject to the following conditions:
 11
 12The above copyright notice and this permission notice shall be included in
 13all copies or substantial portions of the Software.
 14
 15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21THE SOFTWARE.
 22*/
 23
 24#include <QtGui>
 25#include <QtDebug>
 26#include "defines.h"
 27#include "dfinstance.h"
 28#include "dwarf.h"
 29#include "squad.h"
 30#include "word.h"
 31#include "utils.h"
 32#include "gamedatareader.h"
 33#include "memorylayout.h"
 34#include "cp437codec.h"
 35#include "dwarftherapist.h"
 36#include "memorysegment.h"
 37#include "truncatingfilelogger.h"
 38#include "mainwindow.h"
 39
 40#ifdef Q_WS_WIN
 41#define LAYOUT_SUBDIR "windows"
 42#include "dfinstancewindows.h"
 43#else
 44#ifdef Q_WS_X11
 45#define LAYOUT_SUBDIR "linux"
 46#include "dfinstancelinux.h"
 47#else
 48#ifdef Q_WS_MAC
 49#define LAYOUT_SUBDIR "osx"
 50#include "dfinstanceosx.h"
 51#endif
 52#endif
 53#endif
 54
 55DFInstance::DFInstance(QObject* parent)
 56    : QObject(parent)
 57    , m_pid(0)
 58    , m_memory_correction(0)
 59    , m_stop_scan(false)
 60    , m_is_ok(true)
 61    , m_bytes_scanned(0)
 62    , m_layout(0)
 63    , m_attach_count(0)
 64    , m_heartbeat_timer(new QTimer(this))
 65    , m_memory_remap_timer(new QTimer(this))
 66    , m_scan_speed_timer(new QTimer(this))
 67    , m_dwarf_race_id(0)
 68{
 69    connect(m_scan_speed_timer, SIGNAL(timeout()),
 70            SLOT(calculate_scan_rate()));
 71    connect(m_memory_remap_timer, SIGNAL(timeout()),
 72            SLOT(map_virtual_memory()));
 73    m_memory_remap_timer->start(20000); // 20 seconds
 74    // let subclasses start the heartbeat timer, since we don't want to be
 75    // checking before we're connected
 76    connect(m_heartbeat_timer, SIGNAL(timeout()), SLOT(heartbeat()));
 77
 78    // We need to scan for memory layout files to get a list of DF versions this
 79    // DT version can talk to. Start by building up a list of search paths
 80    QDir working_dir = QDir::current();
 81    QStringList search_paths;
 82    search_paths << working_dir.path();
 83
 84    QString subdir = LAYOUT_SUBDIR;
 85    search_paths << QString("etc/memory_layouts/%1").arg(subdir);
 86
 87    TRACE << "Searching for MemoryLayout ini files in the following directories";
 88    foreach(QString path, search_paths) {
 89        TRACE<< path;
 90        QDir d(path);
 91        d.setNameFilters(QStringList() << "*.ini");
 92        d.setFilter(QDir::NoDotAndDotDot | QDir::Readable | QDir::Files);
 93        d.setSorting(QDir::Name | QDir::Reversed);
 94        QFileInfoList files = d.entryInfoList();
 95        foreach(QFileInfo info, files) {
 96            MemoryLayout *temp = new MemoryLayout(info.absoluteFilePath());
 97            if (temp && temp->is_valid()) {
 98                LOGD << "adding valid layout" << temp->game_version()
 99                        << temp->checksum();
100                m_memory_layouts.insert(temp->checksum().toLower(), temp);
101            }
102        }
103    }
104    // if no memory layouts were found that's a critical error
105    if (m_memory_layouts.size() < 1) {
106        LOGE << "No valid memory layouts found in the following directories..."
107                << QDir::searchPaths("memory_layouts");
108        qApp->exit(ERROR_NO_VALID_LAYOUTS);
109    }
110}
111
112DFInstance::~DFInstance() {
113    LOGD << "DFInstance baseclass virtual dtor!";
114    foreach(MemoryLayout *l, m_memory_layouts) {
115        delete(l);
116    }
117    m_memory_layouts.clear();
118}
119
120DFInstance * DFInstance::newInstance() {
121#ifdef Q_WS_WIN
122    return new DFInstanceWindows();
123#else
124#ifdef Q_WS_MAC
125    return new DFInstanceOSX();
126#else
127#ifdef Q_WS_X11
128    return new DFInstanceLinux();
129#endif
130#endif
131#endif
132}
133
134BYTE DFInstance::read_byte(const VIRTADDR &addr) {
135    QByteArray out;
136    read_raw(addr, sizeof(BYTE), out);
137    return out.at(0);
138}
139
140WORD DFInstance::read_word(const VIRTADDR &addr) {
141    QByteArray out;
142    read_raw(addr, sizeof(WORD), out);
143    return decode_word(out);
144}
145
146VIRTADDR DFInstance::read_addr(const VIRTADDR &addr) {
147    QByteArray out;
148    read_raw(addr, sizeof(VIRTADDR), out);
149    return decode_dword(out);
150}
151
152qint16 DFInstance::read_short(const VIRTADDR &addr) {
153    QByteArray out;
154    read_raw(addr, sizeof(qint16), out);
155    return decode_short(out);
156}
157
158qint32 DFInstance::read_int(const VIRTADDR &addr) {
159    QByteArray out;
160    read_raw(addr, sizeof(qint32), out);
161    return decode_int(out);
162}
163
164QVector<VIRTADDR> DFInstance::scan_mem(const QByteArray &needle, const uint start_addr, const uint end_addr) {
165    // progress reporting
166    m_scan_speed_timer->start(500);
167    m_memory_remap_timer->stop(); // don't remap segments while scanning
168    int total_bytes = 0;
169    m_bytes_scanned = 0; // for global timings
170    int bytes_scanned = 0; // for progress calcs
171    foreach(MemorySegment *seg, m_regions) {
172        total_bytes += seg->size;
173    }
174    int report_every_n_bytes = total_bytes / 1000;
175    emit scan_total_steps(1000);
176    emit scan_progress(0);
177
178
179    m_stop_scan = false;
180    QVector<VIRTADDR> addresses; //! return value
181    QByteArrayMatcher matcher(needle);
182
183    int step_size = 0x1000;
184    QByteArray buffer(step_size, 0);
185    QByteArray back_buffer(step_size * 2, 0);
186
187    QTime timer;
188    timer.start();
189    attach();
190    foreach(MemorySegment *seg, m_regions) {
191        int step = step_size;
192        int steps = seg->size / step;
193        if (seg->size % step)
194            steps++;
195
196        if( seg->end_addr < start_addr ) {
197            continue;
198        }
199
200        if( seg->start_addr > end_addr ) {
201            break;
202        }
203
204        for(VIRTADDR ptr = seg->start_addr; ptr < seg->end_addr; ptr += step) {
205
206            if( ptr < start_addr ) {
207                continue;
208            }
209            if( ptr > end_addr ) {
210                m_stop_scan = true;
211                break;
212            }
213
214            step = step_size;
215            if (ptr + step > seg->end_addr) {
216                step = seg->end_addr - ptr;
217            }
218
219            // move the last thing we read to the front of the back_buffer
220            back_buffer.replace(0, step, buffer);
221
222            // fill the main read buffer
223            int bytes_read = read_raw(ptr, step, buffer);
224            if (bytes_read < step && !seg->is_guarded) {
225                if (m_layout->is_complete()) {
226                    LOGW << "tried to read" << step << "bytes starting at" <<
227                            hexify(ptr) << "but only got" << dec << bytes_read;
228                }
229                continue;
230            }
231            bytes_scanned += bytes_read;
232            m_bytes_scanned += bytes_read;
233
234            // put the main buffer on the end of the back_buffer
235            back_buffer.replace(step, step, buffer);
236
237            int idx = -1;
238            forever {
239                idx = matcher.indexIn(back_buffer, idx+1);
240                if (idx == -1) {
241                    break;
242                } else {
243                    VIRTADDR hit = ptr + idx - step;
244                    if (!addresses.contains(hit)) {
245                        // backbuffer may cause duplicate hits
246                        addresses << hit;
247                    }
248                }
249            }
250
251            if (m_stop_scan)
252                break;
253            emit scan_progress(bytes_scanned / report_every_n_bytes);
254
255        }
256        DT->processEvents();
257        if (m_stop_scan)
258            break;
259    }
260    detach();
261    m_memory_remap_timer->start(20000); // start the remapper again
262    LOGD << QString("Scanned %L1MB in %L2ms").arg(bytes_scanned / 1024 * 1024)
263            .arg(timer.elapsed());
264    return addresses;
265}
266
267bool DFInstance::looks_like_vector_of_pointers(const VIRTADDR &addr) {
268    int start = read_int(addr + 0x4);
269    int end = read_int(addr + 0x8);
270    int entries = (end - start) / sizeof(int);
271    LOGD << "LOOKS LIKE VECTOR? unverified entries:" << entries;
272
273    return start >=0 &&
274           end >=0 &&
275           end >= start &&
276           (end-start) % 4 == 0 &&
277           start % 4 == 0 &&
278           end % 4 == 0 &&
279           entries < 10000;
280
281}
282
283void DFInstance::read_raws() {
284    emit progress_message(tr("Reading raws"));
285
286    LOGI << "Reading some game raws...";
287    GameDataReader::ptr()->read_raws(m_df_dir);
288}
289
290QVector<Dwarf*> DFInstance::load_dwarves() {
291    map_virtual_memory();
292    QVector<Dwarf*> dwarves;
293    if (!m_is_ok) {
294        LOGW << "not connected";
295        detach();
296        return dwarves;
297    }
298
299    // we're connected, make sure we have good addresses
300    VIRTADDR creature_vector = m_layout->address("creature_vector");
301    creature_vector += m_memory_correction;
302    VIRTADDR dwarf_race_index = m_layout->address("dwarf_race_index");
303    dwarf_race_index += m_memory_correction;
304
305    if (!is_valid_address(creature_vector)) {
306        LOGW << "Active Memory Layout" << m_layout->filename() << "("
307                << m_layout->game_version() << ")" << "contains an invalid"
308                << "creature_vector address. Either you are scanning a new "
309                << "DF version or your config files are corrupted.";
310        return dwarves;
311    }
312    if (!is_valid_address(dwarf_race_index)) {
313        LOGW << "Active Memory Layout" << m_layout->filename() << "("
314                << m_layout->game_version() << ")" << "contains an invalid"
315                << "dwarf_race_index address. Either you are scanning a new "
316                << "DF version or your config files are corrupted.";
317        return dwarves;
318    }
319
320    // both necessary addresses are valid, so let's try to read the creatures
321    LOGD << "loading creatures from " << hexify(creature_vector) <<
322            hexify(creature_vector - m_memory_correction) << "(UNCORRECTED)";
323    LOGD << "dwarf race index" << hexify(dwarf_race_index) <<
324            hexify(dwarf_race_index - m_memory_correction) << "(UNCORRECTED)";
325    emit progress_message(tr("Loading Dwarves"));
326
327    attach();
328    // which race id is dwarven?
329    m_dwarf_race_id = read_word(dwarf_race_index);
330    LOGD << "dwarf race:" << hexify(m_dwarf_race_id);
331
332    QVector<VIRTADDR> entries = enumerate_vector(creature_vector);
333    emit progress_range(0, entries.size()-1);
334    TRACE << "FOUND" << entries.size() << "creatures";
335    if (!entries.empty()) {
336        Dwarf *d = 0;
337        int i = 0;
338        foreach(VIRTADDR creature_addr, entries) {
339            d = Dwarf::get_dwarf(this, creature_addr);
340            if (d) {
341                dwarves.append(d);
342                LOGD << "FOUND DWARF" << hexify(creature_addr)
343                     << d->nice_name();
344            } else {
345                TRACE << "FOUND OTHER CREATURE" << hexify(creature_addr);
346            }
347            emit progress_value(i++);
348        }
349    } else {
350        // we lost the fort!
351        m_is_ok = false;
352    }
353    detach();
354    LOGI << "found" << dwarves.size() << "dwarves out of" << entries.size()
355            << "creatures";
356    return dwarves;
357}
358
359QVector<Squad*> DFInstance::load_squads() {
360
361    QVector<Squad*> squads;
362    if (!m_is_ok) {
363        LOGW << "not connected";
364        detach();
365        return squads;
366    }
367
368    // we're connected, make sure we have good addresses
369    VIRTADDR squad_vector = m_layout->address("squad_vector");
370    if(squad_vector == 0xFFFFFFFF) {
371        LOGI << "Squads not supported for this version of Dwarf Fortress";
372        return squads;
373    }
374
375    squad_vector += m_memory_correction;
376
377    if (!is_valid_address(squad_vector)) {
378        LOGW << "Active Memory Layout" << m_layout->filename() << "("
379                << m_layout->game_version() << ")" << "contains an invalid"
380                << "squad_vector address. Either you are scanning a new "
381                << "DF version or your config files are corrupted.";
382        return squads;
383    }
384
385    // both necessary addresses are valid, so let's try to read the creatures
386    LOGD << "loading squads from " << hexify(squad_vector) <<
387            hexify(squad_vector - m_memory_correction) << "(UNCORRECTED)";
388
389    emit progress_message(tr("Loading Squads"));
390
391    attach();
392
393    QVector<VIRTADDR> entries = enumerate_vector(squad_vector);
394    TRACE << "FOUND" << entries.size() << "squads";
395
396    if (!entries.empty()) {
397        emit progress_range(0, entries.size()-1);
398        Squad *s = NULL;
399        int i = 0;
400        foreach(VIRTADDR squad_addr, entries) {
401            s = Squad::get_squad(this, squad_addr);
402            if (s) {
403                TRACE << "FOUND SQUAD" << hexify(squad_addr) << s->name();
404                squads << s;
405            }
406            emit progress_value(i++);
407        }
408    }
409
410    detach();
411    LOGI << "Found" << squads.size() << "squads out of" << entries.size();
412    return squads;
413}
414
415void DFInstance::heartbeat() {
416    // simple read attempt that will fail if the DF game isn't running a fort,
417    // or isn't running at all
418    QVector<VIRTADDR> creatures = enumerate_vector(
419            m_layout->address("creature_vector") + m_memory_correction);
420    if (creatures.size() < 1) {
421        // no game loaded, or process is gone
422        emit connection_interrupted();
423    }
424}
425
426bool DFInstance::is_valid_address(const VIRTADDR &addr) {
427    bool valid = false;
428    foreach(MemorySegment *seg, m_regions) {
429        if (seg->contains(addr)) {
430            valid = true;
431            break;
432        }
433    }
434    return valid;
435}
436
437QByteArray DFInstance::get_data(const VIRTADDR &addr, int size) {
438    QByteArray ret_val(size, 0); // 0 filled to proper length
439    int bytes_read = read_raw(addr, size, ret_val);
440    if (bytes_read != size) {
441        ret_val.clear();
442    }
443    return ret_val;
444}
445
446//! ahhh convenience
447QString DFInstance::pprint(const VIRTADDR &addr, int size) {
448    return pprint(get_data(addr, size), addr);
449}
450
451QString DFInstance::pprint(const QByteArray &ba, const VIRTADDR &start_addr) {
452    QString out = "    ADDR   | 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F | TEXT\n";
453    out.append("------------------------------------------------------------------------\n");
454    int lines = ba.size() / 16;
455    if (ba.size() % 16)
456        lines++;
457    if (lines < 1)
458        lines = 0;
459
460    for(int i = 0; i < lines; ++i) {
461        VIRTADDR offset = start_addr + i * 16;
462        out.append(hexify(offset));
463        out.append(" | ");
464        for (int c = 0; c < 16; ++c) {
465            out.append(ba.mid(i*16 + c, 1).toHex());
466            out.append(" ");
467        }
468        out.append("| ");
469        for (int c = 0; c < 16; ++c) {
470            QByteArray tmp = ba.mid(i*16 + c, 1);
471            if (tmp.at(0) == 0)
472                out.append(".");
473            else if (tmp.at(0) <= 126 && tmp.at(0) >= 32)
474                out.append(tmp);
475            else
476                out.append(tmp.toHex());
477        }
478        //out.append(ba.mid(i*16, 16).toPercentEncoding());
479        out.append("\n");
480    }
481    return out;
482}
483
484Word * DFInstance::read_dwarf_word(const VIRTADDR &addr) {
485    Word * result = NULL;
486    uint word_id = read_int(addr);
487    if(word_id != 0xFFFFFFFF) {
488        result = DT->get_word(word_id);
489    }
490    return result;
491}
492
493QString DFInstance::read_dwarf_name(const VIRTADDR &addr) {
494    QString result = "The";
495
496    //7 parts e.g.  ffffffff ffffffff 000006d4
497    //      ffffffff ffffffff 000002b1 ffffffff
498
499    //Unknown
500    Word * word = read_dwarf_word(addr);
501    if(word)
502        result.append(" " + capitalize(word->base()));
503
504    //Unknown
505    word = read_dwarf_word(addr + 0x04);
506    if(word)
507        result.append(" " + capitalize(word->base()));
508
509    //Verb
510    word = read_dwarf_word(addr + 0x08);
511    if(word) {
512        result.append(" " + capitalize(word->adjective()));
513    }
514
515    //Unknown
516    word = read_dwarf_word(addr + 0x0C);
517    if(word)
518        result.append(" " + capitalize(word->base()));
519
520    //Unknown
521    word = read_dwarf_word(addr + 0x10);
522    if(word)
523        result.append(" " + capitalize(word->base()));
524
525    //Noun
526    word = read_dwarf_word(addr + 0x14);
527    bool singular = false;
528    if(word) {
529        if(word->plural_noun().isEmpty()) {
530            result.append(" " + capitalize(word->noun()));
531            singular = true;
532        } else {
533            result.append(" " + capitalize(word->plural_noun()));
534        }
535    }
536
537    //of verb(noun)
538    word = read_dwarf_word(addr + 0x18);
539    if(word) {
540        if( !word->verb().isEmpty() ) {
541            if(singular) {
542                result.append(" of " + capitalize(word->verb()));
543            } else {
544                result.append(" of " + capitalize(word->present_participle_verb()));
545            }
546        } else {
547            if(singular) {
548                result.append(" of " + capitalize(word->noun()));
549            } else {
550                result.append(" of " + capitalize(word->plural_noun()));
551            }
552        }
553    }
554
555    return result.trimmed();
556}
557
558
559QVector<VIRTADDR> DFInstance::find_vectors_in_range(const int &max_entries,
560                                                const VIRTADDR &start_address,
561                                                const int &range_length) {
562    QByteArray data = get_data(start_address, range_length);
563    QVector<VIRTADDR> vectors;
564    VIRTADDR int1 = 0; // holds the start val
565    VIRTADDR int2 = 0; // holds the end val
566
567    for (int i = 0; i < range_length; i += 4) {
568        memcpy(&int1, data.data() + i, 4);
569        memcpy(&int2, data.data() + i + 4, 4);
570        if (int2 >= int1 && is_valid_address(int1) && is_valid_address(int2)) {
571            int bytes = int2 - int1;
572            int entries = bytes / 4;
573            if (entries > 0 && entries <= max_entries) {
574                VIRTADDR vector_addr = start_address + i - VECTOR_POINTER_OFFSET;
575                QVector<VIRTADDR> addrs = enumerate_vector(vector_addr);
576                bool all_valid = true;
577                foreach(VIRTADDR vec_entry, addrs) {
578                    if (!is_valid_address(vec_entry)) {
579                        all_valid = false;
580                        break;
581                    }
582                }
583                if (all_valid) {
584                    vectors << vector_addr;
585                }
586            }
587        }
588    }
589    return vectors;
590}
591
592QVector<VIRTADDR> DFInstance::find_vectors(int num_entries, int fuzz/* =0 */,
593                                           int entry_size/* =4 */) {
594    /*
595    glibc++ does vectors like so...
596    |4bytes      | 4bytes    | 4bytes
597    START_ADDRESS|END_ADDRESS|END_ALLOCATOR
598
599    MSVC++ does vectors like so...
600    | 4bytes     | 4bytes      | 4 bytes   | 4bytes
601    ALLOCATOR    |START_ADDRESS|END_ADDRESS|END_ALLOCATOR
602    */
603    m_stop_scan = false; //! if ever set true, bail from the inner loop
604    QVector<VIRTADDR> vectors; //! return value collection of vectors found
605    VIRTADDR int1 = 0; // holds the start val
606    VIRTADDR int2 = 0; // holds the end val
607
608    // progress reporting
609    m_scan_speed_timer->start(500);
610    m_memory_remap_timer->stop(); // don't remap segments while scanning
611    int total_bytes = 0;
612    m_bytes_scanned = 0; // for global timings
613    int bytes_scanned = 0; // for progress calcs
614    foreach(MemorySegment *seg, m_regions) {
615        total_bytes += seg->size;
616    }
617    int report_every_n_bytes = total_bytes / 1000;
618    emit scan_total_steps(1000);
619    emit scan_progress(0);
620
621    int scan_step_size = 0x10000;
622    QByteArray buffer(scan_step_size, '\0');
623    QTime timer;
624    timer.start();
625    attach();
626    foreach(MemorySegment *seg, m_regions) {
627        //TRACE << "SCANNING REGION" << hex << seg->start_addr << "-"
628        //        << seg->end_addr << "BYTES:" << dec << seg->size;
629        if ((int)seg->size <= scan_step_size) {
630            scan_step_size = seg->size;
631        }
632        int scan_steps = seg->size / scan_step_size;
633        if (seg->size % scan_step_size) {
634            scan_steps++;
635        }
636        VIRTADDR addr = 0; // the ptr we will read from
637        for(int step = 0; step < scan_steps; ++step) {
638            addr = seg->start_addr + (scan_step_size * step);
639            //LOGD << "starting scan for vectors at" << hex << addr << "step"
640            //        << dec << step << "of" << scan_steps;
641            int bytes_read = read_raw(addr, scan_step_size, buffer);
642            if (bytes_read < scan_step_size) {
643                continue;
644            }
645            for(int offset = 0; offset < scan_step_size; offset += entry_size) {
646                int1 = decode_int(buffer.mid(offset, entry_size));
647                int2 = decode_int(buffer.mid(offset + entry_size, entry_size));
648                if (int1 && int2 && int2 >= int1
649                    && int1 % 4 == 0
650                    && int2 % 4 == 0
651                    //&& is_valid_address(int1)
652                    //&& is_valid_address(int2)
653                    ) {
654                    int bytes = int2 - int1;
655                    int entries = bytes / entry_size;
656                    int diff = entries - num_entries;
657                    if (qAbs(diff) <= fuzz) {
658                        VIRTADDR vector_addr = addr + offset -
659                                               VECTOR_POINTER_OFFSET;
660                        QVector<VIRTADDR> addrs = enumerate_vector(vector_addr);
661                        diff = addrs.size() - num_entries;
662                        if (qAbs(diff) <= fuzz) {
663                            vectors << vector_addr;
664                        }
665                    }
666                }
667                m_bytes_scanned += entry_size;
668                bytes_scanned += entry_size;
669                if (m_stop_scan)
670                    break;
671            }
672            emit scan_progress(bytes_scanned / report_every_n_bytes);
673            DT->processEvents();
674            if (m_stop_scan)
675                break;
676        }
677    }
678    detach();
679    m_memory_remap_timer->start(20000); // start the remapper again
680    m_scan_speed_timer->stop();
681    LOGD << QString("Scanned %L1MB in %L2ms").arg(bytes_scanned / 1024 * 1024)
682            .arg(timer.elapsed());
683    emit scan_progress(100);
684    return vectors;
685}
686
687QVector<VIRTADDR> DFInstance::find_vectors_ext(int num_entries, const char op,
688                              const uint start_addr, const uint end_addr, int entry_size/* =4 */) {
689    /*
690    glibc++ does vectors like so...
691    |4bytes      | 4bytes    | 4bytes
692    START_ADDRESS|END_ADDRESS|END_ALLOCATOR
693
694    MSVC++ does vectors like so...
695    | 4bytes     | 4bytes      | 4 bytes   | 4bytes
696    ALLOCATOR    |START_ADDRESS|END_ADDRESS|END_ALLOCATOR
697    */
698    m_stop_scan = false; //! if ever set true, bail from the inner loop
699    QVector<VIRTADDR> vectors; //! return value collection of vectors found
700    VIRTADDR int1 = 0; // holds the start val
701    VIRTADDR int2 = 0; // holds the end val
702
703    // progress reporting
704    m_scan_speed_timer->start(500);
705    m_memory_remap_timer->stop(); // don't remap segments while scanning
706    int total_bytes = 0;
707    m_bytes_scanned = 0; // for global timings
708    int bytes_scanned = 0; // for progress calcs
709    foreach(MemorySegment *seg, m_regions) {
710        total_bytes += seg->size;
711    }
712    int report_every_n_bytes = total_bytes / 1000;
713    emit scan_total_steps(1000);
714    emit scan_progress(0);
715
716    int scan_step_size = 0x10000;
717    QByteArray buffer(scan_step_size, '\0');
718    QTime timer;
719    timer.start();
720    attach();
721    foreach(MemorySegment *seg, m_regions) {
722        //TRACE << "SCANNING REGION" << hex << seg->start_addr << "-"
723        //        << seg->end_addr << "BYTES:" << dec << seg->size;
724        if ((int)seg->size <= scan_step_size) {
725            scan_step_size = seg->size;
726        }
727        int scan_steps = seg->size / scan_step_size;
728        if (seg->size % scan_step_size) {
729            scan_steps++;
730        }
731
732        if( seg->end_addr < start_addr ) {
733            continue;
734        }
735
736        if( seg->start_addr > end_addr ) {
737            break;
738        }
739
740        VIRTADDR addr = 0; // the ptr we will read from
741        for(int step = 0; step < scan_steps; ++step) {
742            addr = seg->start_addr + (scan_step_size * step);
743            //LOGD << "starting scan for vectors at" << hex << addr << "step"
744            //        << dec << step << "of" << scan_steps;
745            int bytes_read = read_raw(addr, scan_step_size, buffer);
746            if (bytes_read < scan_step_size) {
747                continue;
748            }
749
750            for(int offset = 0; offset < scan_step_size; offset += entry_size) {
751                VIRTADDR vector_addr = addr + offset - VECTOR_POINTER_OFFSET;
752
753                if( vector_addr < start_addr ) {
754                    continue;
755                }
756
757                if( vector_addr > end_addr ) {
758                    m_stop_scan = true;
759                    break;
760                }
761
762                int1 = decode_int(buffer.mid(offset, entry_size));
763                int2 = decode_int(buffer.mid(offset + entry_size, entry_size));
764                if (int1 && int2 && int2 >= int1
765                    && int1 % 4 == 0
766                    && int2 % 4 == 0
767                    //&& is_valid_address(int1)
768                    //&& is_valid_address(int2)
769                    ) {
770                    int bytes = int2 - int1;
771                    int entries = bytes / entry_size;
772                    if (entries > 0 && entries < 1000) {
773                        QVector<VIRTADDR> addrs = enumerate_vector(vector_addr);
774                        if( (op == '=' && addrs.size() == num_entries)
775                                || (op == '<' && addrs.size() < num_entries)
776                                || (op == '>' && addrs.size() > num_entries) ) {
777                            vectors << vector_addr;
778                        }
779                    }
780                }
781                m_bytes_scanned += entry_size;
782                bytes_scanned += entry_size;
783                if (m_stop_scan)
784                    break;
785            }
786            emit scan_progress(bytes_scanned / report_every_n_bytes);
787            DT->processEvents();
788            if (m_stop_scan)
789                break;
790        }
791    }
792    detach();
793    m_memory_remap_timer->start(20000); // start the remapper again
794    m_scan_speed_timer->stop();
795    LOGD << QString("Scanned %L1MB in %L2ms").arg(bytes_scanned / 1024 * 1024)
796            .arg(timer.elapsed());
797    emit scan_progress(100);
798    return vectors;
799}
800
801QVector<VIRTADDR> DFInstance::find_vectors(int num_entries, const QVector<VIRTADDR> & search_set,
802                               int fuzz/* =0 */, int entry_size/* =4 */) {
803
804    m_stop_scan = false; //! if ever set true, bail from the inner loop
805    QVector<VIRTADDR> vectors; //! return value collection of vectors found
806
807    // progress reporting
808    m_scan_speed_timer->start(500);
809    m_memory_remap_timer->stop(); // don't remap segments while scanning
810
811    int total_vectors = vectors.size();
812    m_bytes_scanned = 0; // for global timings
813    int vectors_scanned = 0; // for progress calcs
814
815    emit scan_total_steps(total_vectors);
816    emit scan_progress(0);
817
818    QTime timer;
819    timer.start();
820    attach();
821
822    int vector_size = 8 + VECTOR_POINTER_OFFSET;
823    QByteArray buffer(vector_size, '\0');
824
825    foreach(VIRTADDR addr, search_set) {
826        int bytes_read = read_raw(addr, vector_size, buffer);
827        if (bytes_read < vector_size) {
828            continue;
829        }
830
831        VIRTADDR int1 = 0; // holds the start val
832        VIRTADDR int2 = 0; // holds the end val
833        int1 = decode_int(buffer.mid(VECTOR_POINTER_OFFSET, sizeof(VIRTADDR)));
834        int2 = decode_int(buffer.mid(VECTOR_POINTER_OFFSET+ sizeof(VIRTADDR), sizeof(VIRTADDR)));
835
836        if (int1 && int2 && int2 >= int1
837                && int1 % 4 == 0
838                && int2 % 4 == 0) {
839
840            int bytes = int2 - int1;
841            int entries = bytes / entry_size;
842            int diff = entries - num_entries;
843            if (qAbs(diff) <= fuzz) {
844                QVector<VIRTADDR> addrs = enumerate_vector(addr);
845                diff = addrs.size() - num_entries;
846                if (qAbs(diff) <= fuzz) {
847                    vectors << addr;
848                }
849            }
850        }
851
852        vectors_scanned++;
853
854        if(vectors_scanned % 100 == 0) {
855            emit scan_progress(vectors_scanned);
856            DT->processEvents();
857        }
858
859        if (m_stop_scan)
860            break;
861    }
862
863
864    detach();
865    m_memory_remap_timer->start(20000); // start the remapper again
866    m_scan_speed_timer->stop();
867    LOGD << QString("Scanned %L1 vectors in %L2ms").arg(vectors_scanned)
868            .arg(timer.elapsed());
869    emit scan_progress(100);
870    return vectors;
871}
872
873
874MemoryLayout *DFInstance::get_memory_layout(QString checksum, bool) {
875    checksum = checksum.toLower();
876    LOGD << "DF's checksum is:" << checksum;
877
878    MemoryLayout *ret_val = NULL;
879    ret_val = m_memory_layouts.value(checksum, NULL);
880    m_is_ok = ret_val != NULL && ret_val->is_valid();
881
882    if(!m_is_ok) {
883        LOGD << "Could not find layout for checksum" << checksum;
884        DT->get_main_window()->check_for_layout(checksum);
885    }
886
887    if (m_is_ok) {
888        LOGI << "Detected Dwarf Fortress version"
889                << ret_val->game_version() << "using MemoryLayout from"
890                << ret_val->filename();
891    }
892
893    return ret_val;
894}
895
896bool DFInstance::add_new_layout(const QString & version, QFile & file) {
897    QString newFileName = version;
898    newFileName.replace("(", "").replace(")", "").replace(" ", "_");
899    newFileName +=  ".ini";
900
901    QFileInfo newFile(QDir(QString("etc/memory_layouts/%1").arg(LAYOUT_SUBDIR)), newFileName);
902    newFileName = newFile.absoluteFilePath();
903
904    if(!file.exists()) {
905        LOGW << "Layout file" << file.fileName() << "does not exist!";
906        return false;
907    }
908
909    LOGD << "Copying: " << file.fileName() << " to " << newFileName;
910    if(!file.copy(newFileName)) {
911        LOGW << "Error renaming layout file!";
912        return false;
913    }
914
915    MemoryLayout *temp = new MemoryLayout(newFileName);
916    if (temp && temp->is_valid()) {
917        LOGD << "adding valid layout" << temp->game_version() << temp->checksum();
918        m_memory_layouts.insert(temp->checksum().toLower(), temp);
919    }
920    return true;
921}
922
923void DFInstance::layout_not_found(const QString & checksum) {
924    QString supported_vers;
925
926    // TODO: Replace this with a rich dialog at some point that
927    // is also accessible from the help menu. For now, remove the
928    // extra path information as the dialog is getting way too big.
929    // And make a half-ass attempt at sorting
930    QList<MemoryLayout *> layouts = m_memory_layouts.values();
931    qSort(layouts);
932
933    foreach(MemoryLayout * l, layouts) {
934        supported_vers.append(
935                QString("<li><b>%1</b>(<font color=\"#444444\">%2"
936                        "</font>)</li>")
937                .arg(l->game_version())
938                .arg(l->checksum()));
939    }
940
941    QMessageBox *mb = new QMessageBox(qApp->activeWindow());
942    mb->setIcon(QMessageBox::Critical);
943    mb->setWindowTitle(tr("Unidentified Game Version"));
944    mb->setText(tr("I'm sorry but I don't know how to talk to this "
945        "version of Dwarf Fortress! (checksum:%1)<br><br> <b>Supported "
946        "Versions:</b><ul>%2</ul>").arg(checksum).arg(supported_vers));
947    mb->setInformativeText(tr("<a href=\"%1\">Click Here to find out "
948                              "more online</a>.")
949                           .arg(URL_SUPPORTED_GAME_VERSIONS));
950
951    /*
952    mb->setDetailedText(tr("Failed to locate a memory layout file for "
953        "Dwarf Fortress exectutable with checksum '%1'").arg(checksum));
954    */
955    mb->exec();
956    LOGE << tr("unable to identify version from checksum:") << checksum;
957}
958
959void DFInstance::calculate_scan_rate() {
960    float rate = (m_bytes_scanned / 1024.0f / 1024.0f) /
961                 (m_scan_speed_timer->interval() / 1000.0f);
962    QString msg = QString("%L1MB/s").arg(rate);
963    emit scan_message(msg);
964    m_bytes_scanned = 0;
965}