Example #1
0
def test_double_corrupt(pid: int, otId: int) -> bool:
    """ Determine if the pid, OTId pair can be used for double corruption.

    Args:
        pid (int): PID of pokemon to corrupt.
        otId (int): OTId of trainer.

    Returns:
        bool: True if the pair can be used, False otherwise.
    """
    box_mon = BoxMon()
    box_mon.personality = pid
    box_mon.otId = otId
    box_mon.sub(0).type0.species = 308
    box_mon.sub(0).type0.experience = 2195
    box_mon.sub(0).type0.friendship = 70
    sub1 = box_mon.sub(1).type1
    sub1.moves[0] = 33
    sub1.moves[1] = 253
    sub1.moves[2] = 185
    sub1.pp[0] = 35
    sub1.pp[1] = 10
    sub1.pp[2] = 20
    sub2 = box_mon.sub(2).type2
    sub2.attackEV = 22
    sub2.hpEV = 8
    sub3 = box_mon.sub(3).type3
    sub3.metLocation = 28
    sub3.metLevel = 14
    sub3.metGame = 3
    sub3.pokeBall = 2
    sub3.otGender = 1
    sub3.unk = 977594907
    box_mon.checksum = box_mon.calc_checksum()
    sum1 = box_mon.checksum
    box_mon.encrypt()
    box_mon.personality |= 0x40000000
    box_mon.decrypt()
    sum2 = box_mon.calc_checksum()
    box_mon.encrypt()
    box_mon.otId |= 0x40000000
    box_mon.decrypt()
    sum3 = box_mon.calc_checksum()
    if sum1 == sum2 == sum3 and box_mon.sub(3).type3.isEgg == 0:
        box_mon.encrypt()
        return True
    return False
Example #2
0
def test_double_corrupt(pid, otId):  # Test double corruption of Mudkip
    mon = BoxMon()
    mon.personality = pid
    mon.otId = otId
    mon.sub(0).type0.species = 283
    mon.sub(0).type0.experience = 135
    mon.sub(0).type0.friendship = 70
    sub1 = mon.sub(1).type1
    sub1.moves[0] = 33
    sub1.moves[1] = 45
    sub1.pp[0] = 35
    sub1.pp[1] = 40
    sub2 = mon.sub(2).type2
    sub2.attackEV = 0x31
    sub2.hpEV = 0x10
    sub3 = mon.sub(3).type3
    sub3.metLocation = 16
    sub3.metLevel = 5
    sub3.metGame = 3
    sub3.pokeBall = 4
    sub3.otGender = 1
    mon.checksum = mon.calc_checksum()
    sum1 = mon.checksum
    mon.encrypt()
    mon.personality |= 0x40000000
    mon.decrypt()
    sum2 = mon.calc_checksum()
    mon.encrypt()
    mon.otId |= 0x40000000
    mon.decrypt()
    sum3 = mon.calc_checksum()
    if sum1 == sum2 == sum3 and mon.sub(3).type3.isEgg == 0:
        move = mon.sub(1).type1.moves[0]
        if move != 0x3110:
            mon.export()
        return move == 0x3110
    return False
Example #3
0
class GuiWindow(Ui_MainWindow):
    def __init__(self):
        Ui_MainWindow.__init__(self)
        self.old_cycle = 0
        self.encrypted = False
        self.mon = BoxMon()
        self.last_mon_dir = os.path.expanduser('~')

    def setupUi(self, MainWindow):
        Ui_MainWindow.setupUi(self, MainWindow)
        self.tabWidget.currentChanged.connect(self.switch_tabs)
        self.openAction = QtWidgets.QAction(QtGui.QIcon.fromTheme('folder'), '&Open...')
        self.openAction.setShortcut('Ctrl+O')
        self.menuFile.addAction(self.openAction)
        self.saveAction = QtWidgets.QAction(QtGui.QIcon.fromTheme('document-save-as'), '&Save as...')
        self.saveAction.setShortcut('Ctrl+S')
        self.menuFile.addAction(self.saveAction)
        self.switch_tabs()
        # Setup RNG search
        self.rng.editingFinished.connect(self.rng_changed)
        self.cycle.valueChanged.connect(self.cycle_changed)
        self.cycle.editingFinished.connect(self.update_table)
        # Link fields with frame table
        for name in ('limit', 'move_acc', 'acc_stage', 'evade', 'min_dmg', 'chance', 'slots', 'rate'):
            getattr(self, name).editingFinished.connect(self.update_table)
        for tri in ('hit', 'crit', 'quick_claw'):  # Set tristates
            getattr(self, tri).setCheckState(1)
        for field in ('hit', 'crit', 'quick_claw', 'diff', 'bike'):  # Link checkboxes with frame table
            getattr(self, field).stateChanged.connect(self.update_table)
        self.tabWidget_3.currentChanged.connect(self.update_table)  # Switching tabs updates table
        self.frameTable.cellDoubleClicked.connect(self.cell_clicked)  # Link double click to frame table
        # Pokemon tab
        self.raw.setValidator(QtGui.QRegExpValidator(raw_re, self.raw))
        self.raw.editingFinished.connect(self.load_raw)
        self.pid.editingFinished.connect(self.update_pid)
        self.checksum.valueChanged.connect(self.check_legal)
        for hex_field in ('species', 'heldItem', 'move0', 'move1', 'move2', 'move3'):
            getattr(self, hex_field).valueChanged.connect(field_closure(hex_field, self.update_hex_box))
        for field in MON_FIELDS + SUB0_FIELDS + SUB1_FIELDS + SUB2_FIELDS + SUB3_FIELDS:
            if field in ('personality',):
                continue
            try:
                getattr(self, field).editingFinished.connect(field_closure(field, self.update_raw))
            except AttributeError:
                getattr(self, field).stateChanged.connect(field_closure(field, self.update_raw))
        action = QtWidgets.QAction('Corrupt PID', self.toolButton)
        action.triggered.connect(field_closure('pid', self.corrupt_id))
        self.toolButton.addAction(action)
        action = QtWidgets.QAction('Corrupt OTID', self.toolButton)
        action.triggered.connect(field_closure('otId', self.corrupt_id))
        self.toolButton.addAction(action)
        action = QtWidgets.QAction('Make legal', self.toolButton)
        action.triggered.connect(self.make_legal)
        self.toolButton.addAction(action)

    def rng_changed(self):
        self.old_cycle = None

    def cycle_changed(self, value):
        if self.old_cycle is None:
            self.old_cycle = value
            return
        seed = int(self.rng.text(), 16) if self.rng.text() else 0
        if value > self.old_cycle:  # Advance RNG
            seed = next(seeds(seed, value-self.old_cycle, 1))
            self.rng.setText(f'{seed:08X}')
        elif value < self.old_cycle:  # Reverse RNG
            for _ in range(self.old_cycle-value):
                seed = 0xffffffff & (seed - 0x6073) * 0xeeb9eb65
            self.rng.setText(f'{seed:08X}')
        self.old_cycle = value

    def switch_tabs(self, *args, **kwargs):
        tab = self.tabWidget.currentWidget()
        if tab is self.rngTab:
            self.openAction.setEnabled(False)
            self.saveAction.setEnabled(False)
        elif tab is self.pokemonTab:
            self.openAction.setEnabled(True)
            self.saveAction.setEnabled(True)
            try:
                self.openAction.disconnect()
            except:
                pass
            self.openAction.triggered.connect(self.open_mon)
            try:
                self.saveAction.disconnect()
            except:
                pass
            self.saveAction.triggered.connect(self.save_mon)

    def update_table(self, *args, **kwargs):
        self.frameTable.clearContents()
        self.frameTable.setRowCount(0)
        currentTab = self.tabWidget_3.currentWidget()
        self.cycle.setSingleStep(currentTab.property('step_cycles'))
        if currentTab is self.acc_tab:
            self.acc_table()
        elif currentTab is self.crit_tab:
            self.crit_table()
        elif currentTab is self.quick_tab:
            self.quick_table()
        else:
            self.wild_table()

    def acc_table(self):
        seed = int(self.rng.text(), 16) if self.rng.text() else 0
        hit_filter = self.hit.checkState()
        table = self.frameTable
        table.horizontalHeaderItem(3).setText('Hit')
        table.horizontalHeaderItem(4).setText('Acc')
        offset = 3  # 2 frames are skipped
        for i, base in enumerate(battle_seeds(seed, limit=self.limit.value())):
            rng = rand(base, offset)
            hit, acc = acc_calc(rng, self.acc_stage.value(), self.evade.value(), self.move_acc.value())
            if hit_filter == 0 and hit or hit_filter == 2 and not hit:
                continue
            row = table.rowCount()
            table.insertRow(row)
            table.setItem(row, 0, QtWidgets.QTableWidgetItem(f'{base:08X}'))
            table.setItem(row, 1, QtWidgets.QTableWidgetItem(f'{2*i+self.cycle.value()}'))
            table.setItem(row, 2, QtWidgets.QTableWidgetItem(f'{i}'))
            table.setItem(row, 3, QtWidgets.QTableWidgetItem("Yes" if hit else "No"))
            table.setItem(row, 4, QtWidgets.QTableWidgetItem(f'{acc:03d}'))

    def cell_clicked(self, row, column):
        item = self.frameTable.item(row, 1)
        if item is None:
            return
        try:
            self.cycle.setValue(int(item.text(), base=10))
        except ValueError:
            pass

    def crit_table(self):
        seed = int(self.rng.text(), 16) if self.rng.text() else 0
        crit_filter, min_dmg, chance = self.crit.checkState(), self.min_dmg.value(), self.chance.value()
        table = self.frameTable
        table.horizontalHeaderItem(3).setText('Crit')
        table.horizontalHeaderItem(4).setText('Damage')
        offset = 3  # 2 frames are skipped
        for i, base in enumerate(battle_seeds(seed, limit=self.limit.value())):
            rng = rand(base, offset)
            crit, dmg = crit_dmg_calc(rng, chance)  # TODO: Add chance
            if crit_filter == 0 and crit or crit_filter == 2 and not crit:
                continue
            elif dmg < min_dmg:
                continue
            row = table.rowCount()
            table.insertRow(row)
            table.setItem(row, 0, QtWidgets.QTableWidgetItem(f'{base:08X}'))
            table.setItem(row, 1, QtWidgets.QTableWidgetItem(f'{2*i+self.cycle.value()}'))
            table.setItem(row, 2, QtWidgets.QTableWidgetItem(f'{i}'))
            table.setItem(row, 3, QtWidgets.QTableWidgetItem("Yes" if crit else "No"))
            table.setItem(row, 4, QtWidgets.QTableWidgetItem(f'{dmg:03d}'))

    def quick_table(self):
        seed = int(self.rng.text(), 16) if self.rng.text() else 0
        quick_filter = self.quick_claw.checkState()
        table = self.frameTable
        table.horizontalHeaderItem(3).setText('Quick')
        table.horizontalHeaderItem(4).setText('Value')
        offset = 0
        for i, base in enumerate(battle_seeds(seed, limit=self.limit.value())):
            rng = rand(base, offset)
            value = next(rng)
            quick = value < 0x3333
            if quick_filter == 0 and quick or quick_filter == 2 and not quick:
                continue
            row = table.rowCount()
            table.insertRow(row)
            table.setItem(row, 0, QtWidgets.QTableWidgetItem(f'{base:08X}'))
            table.setItem(row, 1, QtWidgets.QTableWidgetItem(f'{2*i+self.cycle.value()}'))
            table.setItem(row, 2, QtWidgets.QTableWidgetItem(f'{i}'))
            table.setItem(row, 3, QtWidgets.QTableWidgetItem("Yes" if quick else "No"))
            table.setItem(row, 4, QtWidgets.QTableWidgetItem(f'{value:04X}'))

    def wild_table(self):
        seed = int(self.rng.text(), 16) if self.rng.text() else 0
        diff = self.diff.checkState() != 0
        bike = self.bike.checkState() != 0
        if self.slots.text():
            slots = {int(s.strip()) for s in self.slots.text().split(',')}
        else:
            slots = None
        table = self.frameTable
        table.horizontalHeaderItem(3).setText('Slot')
        table.horizontalHeaderItem(4).setText('PID')
        for i, base, slot, pid in wild_mons(seed, rate=self.rate.value(), diff=diff, bike=bike, slots=slots,
                                            limit=self.limit.value()):
            row = table.rowCount()
            table.insertRow(row)
            table.setItem(row, 0, QtWidgets.QTableWidgetItem(f'{base:08X}'))
            table.setItem(row, 1, QtWidgets.QTableWidgetItem(f'{i+self.cycle.value()}'))
            table.setItem(row, 2, QtWidgets.QTableWidgetItem(f'{i}'))
            table.setItem(row, 3, QtWidgets.QTableWidgetItem(f'{slot}'))
            table.setItem(row, 4, QtWidgets.QTableWidgetItem(f'{pid:08X}'))

    def load_raw(self):
        buffer = bytearray.fromhex(self.raw.text())
        self.mon = BoxMon.from_buffer(buffer)
        self.encrypted = True
        self.show_mon()

    def open_mon(self):
        options = QtWidgets.QFileDialog.Options()
        options |= QtWidgets.QFileDialog.DontUseNativeDialog
        tup = QtWidgets.QFileDialog.getOpenFileName(self.centralwidget, 'Open pokemon', options=options,
                                                    directory=self.last_mon_dir,
                                                    filter='Decrypted pokemon (*.pkm *.pk3);;Binary file (*)')
        path, filetype = tup
        if not path:
            return
        self.last_mon_dir = os.path.dirname(path)
        with open(path, 'rb') as f:
            buffer = f.read(80)
        if len(buffer) != 80:  # TODO: Show alert
            return
        buffer = bytearray(buffer[:80])
        if '*.pk3' in filetype:  # Convert PKHeX .pk3
            self.mon = BoxMon.from_pk3(buffer)
        else:
            self.mon = BoxMon.from_buffer(buffer)
        # Test if mon is already decrypted
        self.encrypted = not (self.mon.checksum == self.mon.calc_checksum())  # If equal, mon is already decrypted
        self.show_mon()

    def save_mon(self):  # Save pokemon to file
        options = QtWidgets.QFileDialog.Options()
        options |= QtWidgets.QFileDialog.DontUseNativeDialog
        tup = QtWidgets.QFileDialog.getSaveFileName(self.centralwidget, 'Save pokemon', options=options,
                                                    directory=self.last_mon_dir,
                                                    filter='PKHeX pokemon (*.pk3);;Encrypted pokemon (*.bin)')
        path, filetype = tup
        if not path:
            return
        self.last_mon_dir = os.path.dirname(path)
        base, ext = os.path.splitext(path)
        if not ext:  # Pick ext from filetype
            if '*.pk3' in filetype:
                path = base + '.pk3'
            else:
                path = base + '.bin'
        if '*.pk3' in filetype:
            self.decrypt()
            buffer = self.mon.to_pk3()
        else:
            self.encrypt()
            buffer = bytes(self.mon)
        with open(path, 'wb') as f:
            f.write(buffer)

    def encrypt(self):
        if self.encrypted:
            return
        self.mon.encrypt()
        self.encrypted = True

    def decrypt(self):
        if not self.encrypted:
            return
        self.mon.decrypt()
        self.encrypted = False

    def substruct(self, n):  # Safe substructure access
        return self.mon.sub(n)

    def show_mon(self):
        mon = self.mon
        self.encrypt()  # Encrypt before displaying mon
        b = bytes(self.mon)
        h = ''.join('%02X' % x for x in b)
        self.raw.setText(h)
        self.pid.setText(f'{mon.personality:08X}')
        self.otId.setText(f'{mon.otId:08X}')
        self.nickname.setText(decode_str(mon.nickname))
        self.otName.setText(decode_str(mon.otName))
        self.language.setValue(mon.language)
        self.isBadEgg.setCheckState(mon.isBadEgg)
        self.isEgg.setCheckState(mon.isEgg)
        self.hasSpecies.setCheckState(mon.hasSpecies)
        self.checksum.setValue(mon.checksum)
        self.check_legal()
        self.decrypt()
        for i, pos in enumerate(perms[mon.personality % 24]):
            title = ['Growth', 'Attacks', 'EVs/Condition', 'Misc.'][i]
            self.tabWidget_2.setTabText(i, f'{title} [{pos}]')
        # Growth
        growth = self.substruct(0).type0
        for name in ('experience', 'friendship', 'heldItem', 'ppBonuses', 'species'):
            if name == 'experience':
                getattr(self, name).setText(str(getattr(growth, name)))
            else:
                getattr(self, name).setValue(getattr(growth, name))
        # Attacks
        attacks = self.substruct(1).type1
        for i, name in enumerate(('move0', 'move1', 'move2', 'move3')):
            getattr(self, name).setValue(attacks.moves[i])
        for i, name in enumerate(('pp0', 'pp1', 'pp2', 'pp3')):
            getattr(self, name).setValue(attacks.pp[i])
        # EVs & Condition
        evs = self.substruct(2).type2
        for name in SUB2_FIELDS:
            getattr(self, name).setValue(getattr(evs, name))
        # Miscellaneous
        misc = self.substruct(3).type3
        for name in ('pokerus', 'metLocation', 'metLevel', 'metGame', 'otGender',
                     'hpIV', 'attackIV', 'defenseIV', 'spAttackIV', 'spDefenseIV', 'speedIV'):
            getattr(self, name).setValue(getattr(misc, name))
        self.isEgg_3.setCheckState(misc.isEgg)

    def update_raw(self, name, *args):  # Updating any field updates the raw value
        field = getattr(self, name)
        # Get the value to set
        try:
            base = 16 if name in ('checksum', 'otId') else 10
            value = int(field.text(), base)
        except:
            try:
                value = field.value()
            except:
                value = field.checkState()
        self.decrypt()  # Decrypt before modifying fields
        if name in MON_FIELDS:
            setattr(self.mon, name, value)
        elif name in SUB0_FIELDS:  # Growth
            growth = self.substruct(0).type0
            setattr(growth, name, value)
        elif name in SUB1_FIELDS:  # Attacks
            attacks = self.substruct(1).type1
            index = int(name[-1])
            if 'move' in name:
                attacks.moves[index] = value
            else: # pp
                attacks.pp[index] = value
        elif name in SUB2_FIELDS:  # EVS/Condition
            evs = self.substruct(2).type2
            setattr(evs, name, value)
        elif name in SUB3_FIELDS:  # Misc
            misc = self.substruct(3).type3
            if name == 'isEgg_3':
                name = 'isEgg'
            setattr(misc, name, value)
        self.check_legal()
        self.encrypt()  # Encrypt before displaying
        h = ''.join('%02X' % x for x in bytes(self.mon))
        self.raw.setText(h)
        print(f'{name} Raw updated!')

    def update_hex_box(self, field, *args):  # Add hex prefix to spin boxes
        attr = getattr(self, field)
        value = attr.value()
        attr.setPrefix(f'(0x{value:X}) ')

    def update_pid(self):
        self.encrypt()  # Encrypt with old PID before changing
        pid = int(self.pid.text(), 16)
        self.mon.personality = pid
        self.show_mon()

    def check_legal(self):  # Display whether actual and calculated checksums match
        checksum = self.checksum.value()
        self.decrypt()  # Decrypt before calculating
        self.checksum.setPrefix('' if checksum == self.mon.calc_checksum() else '! ')

    def corrupt_id(self, name, *args):  # Corrupt PID/TID
        value = int(getattr(self, name).text(), 16)
        value ^= 0x40000000
        self.encrypt()  # Encrypt with old PID/TID
        if name == 'pid':
            self.mon.personality = value
        else:
            self.mon.otId = value
        self.show_mon()

    def make_legal(self):  # Force legality
        self.decrypt()  # Must decrypt before checksum calc
        self.checksum.setValue(self.mon.calc_checksum())
        self.update_raw('checksum')