ReVanced removing licenses and copyright notices

作成日 差分は期限切れになりません
46 削除
535
45 追加
537
/*
package app.revanced.util
* Copyright 2025 Morphe.
* https://github.com/MorpheApp/morphe-patches
*
* File-Specific License Notice (GPLv3 Section 7 Terms)
*
* This file is part of the Morphe patches project and is licensed under
* the GNU General Public License version 3 (GPLv3), with the Additional
* Terms under Section 7 described in the Morphe patches
* LICENSE file: https://github.com/MorpheApp/morphe-patches/blob/main/NOTICE
*
* https://www.gnu.org/licenses/gpl-3.0.html
*
* File-Specific Exception to Section 7b:
* -------------------------------------
* Section 7b (Attribution Requirement) of the Morphe patches LICENSE
* does not apply to THIS FILE. Use of this file does NOT require any
* user-facing, in-application, or UI-visible attribution.
*
* For this file only, attribution under Section 7b is satisfied by
* retaining this comment block in the source code of this file.
*
* Distribution and Derivative Works:
* ----------------------------------
* This comment block MUST be preserved in all copies, distributions,
* and derivative works of this file, whether in source or modified
* form.
*
* All other terms of the Morphe Patches LICENSE, including Section 7c
* (Project Name Restriction) and the GPLv3 itself, remain fully
* applicable to this file.
*/

package app.morphe.util


import app.morphe.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.getInstruction
import app.morphe.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.extensions.instructions
import app.morphe.util.FreeRegisterProvider.Companion.conditionalBranchOpcodes
import app.revanced.util.FreeRegisterProvider.Companion.conditionalBranchOpcodes
import app.morphe.util.FreeRegisterProvider.Companion.logFreeRegisterSearch
import app.revanced.util.FreeRegisterProvider.Companion.logFreeRegisterSearch
import app.morphe.util.FreeRegisterProvider.Companion.returnOpcodes
import app.revanced.util.FreeRegisterProvider.Companion.returnOpcodes
import app.morphe.util.FreeRegisterProvider.Companion.switchOpcodes
import app.revanced.util.FreeRegisterProvider.Companion.switchOpcodes
import app.morphe.util.FreeRegisterProvider.Companion.unconditionalBranchOpcodes
import app.revanced.util.FreeRegisterProvider.Companion.unconditionalBranchOpcodes
import app.morphe.util.FreeRegisterProvider.Companion.writeOpcodes
import app.revanced.util.FreeRegisterProvider.Companion.writeOpcodes
import com.android.tools.smali.dexlib2.Format
import com.android.tools.smali.dexlib2.Format
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.Opcode.*
import com.android.tools.smali.dexlib2.Opcode.*
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.OffsetInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OffsetInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstruction
import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import java.util.EnumSet
import java.util.EnumSet
import java.util.LinkedList
import java.util.LinkedList


/**
/**
* Finds free registers at a specific index in a method.
* Finds free registers at a specific index in a method.
* Allows allocating multiple free registers for a given index.
* Allows allocating multiple free registers for a given index.
*
*
* If you only need a single free register, instead use [findFreeRegister].
* If you only need a single free register, instead use [findFreeRegister].
*
*
* @param index Index you need a use a free register at.
* @param index Index you need a use a free register at.
* @param numberOfFreeRegistersNeeded The maximum number of free registers you may get using
* @param numberOfFreeRegistersNeeded The maximum number of free registers you may get using
* [FreeRegisterProvider.getFreeRegister].
* [FreeRegisterProvider.getFreeRegister].
* @param registersToExclude Registers to exclude, and consider as used. For most use cases,
* @param registersToExclude Registers to exclude, and consider as used. For most use cases,
* all registers used in injected code should be specified.
* all registers used in injected code should be specified.
*
*
* @throws IllegalArgumentException If no free registers can be found at the given index.
* @throws IllegalArgumentException If no free registers can be found at the given index.
* This includes unusual method indexes that read from every register
* This includes unusual method indexes that read from every register
* before any registers are wrote to, or if a switch statement is
* before any registers are wrote to, or if a switch statement is
* encountered before any free registers are found.
* encountered before any free registers are found.
*/
*/
fun Method.getFreeRegisterProvider(index: Int, numberOfFreeRegistersNeeded: Int, registersToExclude: List<Int>) =
fun Method.getFreeRegisterProvider(index: Int, numberOfFreeRegistersNeeded: Int, registersToExclude: List<Int>) =
FreeRegisterProvider(this, index, numberOfFreeRegistersNeeded, registersToExclude)
FreeRegisterProvider(this, index, numberOfFreeRegistersNeeded, registersToExclude)


/**
/**
* Finds free registers at a specific index in a method.
* Finds free registers at a specific index in a method.
* Allows allocating multiple free registers for a given index.
* Allows allocating multiple free registers for a given index.
*
*
* If you only need a single free register, instead use [findFreeRegister].
* If you only need a single free register, instead use [findFreeRegister].
*
*
* @param index Index you need a use a free register at.
* @param index Index you need a use a free register at.
* @param numberOfFreeRegistersNeeded The minimum free registers to find.
* @param numberOfFreeRegistersNeeded The minimum free registers to find.
* @param registersToExclude Registers to exclude, and consider as used. For most use cases,
* @param registersToExclude Registers to exclude, and consider as used. For most use cases,
* all registers used in injected code should be specified.
* all registers used in injected code should be specified.
*
*
* @throws IllegalArgumentException If no free registers can be found at the given index.
* @throws IllegalArgumentException If no free registers can be found at the given index.
* This includes unusual method indexes that read from every register
* This includes unusual method indexes that read from every register
* before any registers are wrote to, or if a switch statement is
* before any registers are wrote to, or if a switch statement is
* encountered before any free registers are found.
* encountered before any free registers are found.
*/
*/
fun Method.getFreeRegisterProvider(index: Int, numberOfFreeRegistersNeeded: Int, vararg registersToExclude: Int) =
fun Method.getFreeRegisterProvider(index: Int, numberOfFreeRegistersNeeded: Int, vararg registersToExclude: Int) =
FreeRegisterProvider(this, index, numberOfFreeRegistersNeeded, *registersToExclude)
FreeRegisterProvider(this, index, numberOfFreeRegistersNeeded, *registersToExclude)


class FreeRegisterProvider internal constructor(
class FreeRegisterProvider internal constructor(
val method: Method,
val method: Method,
index: Int,
index: Int,
numberOfFreeRegistersNeeded: Int,
numberOfFreeRegistersNeeded: Int,
registersToExclude: List<Int>
registersToExclude: List<Int>
) {
) {
internal constructor(
internal constructor(
method: Method,
method: Method,
index: Int,
index: Int,
numberOfFreeRegistersNeeded: Int,
numberOfFreeRegistersNeeded: Int,
vararg registersToExclude: Int
vararg registersToExclude: Int
) : this(method, index, numberOfFreeRegistersNeeded, registersToExclude.toList())
) : this(method, index, numberOfFreeRegistersNeeded, registersToExclude.toList())


private var freeRegisters: MutableList<Int> = LinkedList(
private var freeRegisters: MutableList<Int> = LinkedList(
method.findFreeRegisters(index, numberOfFreeRegistersNeeded, registersToExclude)
method.findFreeRegisters(index, numberOfFreeRegistersNeeded, registersToExclude)
)
)


private val originallyExcludedRegisters = registersToExclude
private val originallyExcludedRegisters = registersToExclude
private val allocatedFreeRegisters = mutableListOf<Int>()
private val allocatedFreeRegisters = mutableListOf<Int>()


/**
/**
* Returns a free register and removes it from the available list.
* Returns a free register and removes it from the available list.
*
*
* @return A free register number
* @return A free register number
* @throws IllegalStateException if no free registers are available
* @throws IllegalStateException if no free registers are available
*/
*/
fun getFreeRegister(): Int {
fun getFreeRegister(): Int {
if (freeRegisters.isEmpty()) {
if (freeRegisters.isEmpty()) {
throw IllegalStateException("No free registers available")
throw IllegalStateException("No free registers available")
}
}
val register = freeRegisters.removeFirst()
val register = freeRegisters.removeFirst()
allocatedFreeRegisters.add(register)
allocatedFreeRegisters.add(register)
return register
return register
}
}


/**
/**
* Returns all registers that have been allocated via [getFreeRegister].
* Returns all registers that have been allocated via [getFreeRegister].
* This does not include the originally excluded registers.
* This does not include the originally excluded registers.
*
*
* @return List of registers that have been allocated, in allocation order
* @return List of registers that have been allocated, in allocation order
*/
*/
fun getAllocatedFreeRegisters(): List<Int> = allocatedFreeRegisters.toList()
fun getAllocatedFreeRegisters(): List<Int> = allocatedFreeRegisters.toList()


/**
/**
* Returns all registers that are considered "in use" or unsafe to use. This includes the
* Returns all registers that are considered "in use" or unsafe to use. This includes the
* excluded registers originally passed in, all registers that are unsuitable to use
* excluded registers originally passed in, all registers that are unsuitable to use
* (registers are read from by the original code), and all free registers previously provided
* (registers are read from by the original code), and all free registers previously provided
* by this class using [getFreeRegister].
* by this class using [getFreeRegister].
*
*
* @return List of all registers that are unsafe to use at this time.
* @return List of all registers that are unsafe to use at this time.
*/
*/
fun getUsedAndUnAvailableRegisters(): List<Int> {
fun getUsedAndUnAvailableRegisters(): List<Int> {
val allRegisters = 0 until method.implementation!!.registerCount
val allRegisters = 0 until method.implementation!!.registerCount
return (allocatedFreeRegisters + originallyExcludedRegisters + (allRegisters - freeRegisters.toSet()))
return (allocatedFreeRegisters + originallyExcludedRegisters + (allRegisters - freeRegisters.toSet()))
.distinct()
.distinct()
}
}


/**
/**
* @return The number of free registers still available.
* @return The number of free registers still available.
*/
*/
fun availableCount(): Int = freeRegisters.size
fun availableCount(): Int = freeRegisters.size


/**
/**
* Checks if there are any free registers available.
* Checks if there are any free registers available.
*/
*/
fun hasFreeRegisters(): Boolean = freeRegisters.isNotEmpty()
fun hasFreeRegisters(): Boolean = freeRegisters.isNotEmpty()


internal companion object {
internal companion object {
val conditionalBranchOpcodes: EnumSet<Opcode> = EnumSet.of(
val conditionalBranchOpcodes: EnumSet<Opcode> = EnumSet.of(
IF_EQ, IF_NE, IF_LT, IF_GE, IF_GT, IF_LE,
IF_EQ, IF_NE, IF_LT, IF_GE, IF_GT, IF_LE,
IF_EQZ, IF_NEZ, IF_LTZ, IF_GEZ, IF_GTZ, IF_LEZ
IF_EQZ, IF_NEZ, IF_LTZ, IF_GEZ, IF_GTZ, IF_LEZ
)
)


val unconditionalBranchOpcodes: EnumSet<Opcode> = EnumSet.of(
val unconditionalBranchOpcodes: EnumSet<Opcode> = EnumSet.of(
GOTO, GOTO_16, GOTO_32
GOTO, GOTO_16, GOTO_32
)
)


val switchOpcodes: EnumSet<Opcode> = EnumSet.of(
val switchOpcodes: EnumSet<Opcode> = EnumSet.of(
PACKED_SWITCH, SPARSE_SWITCH
PACKED_SWITCH, SPARSE_SWITCH
)
)


val returnOpcodes: EnumSet<Opcode> = EnumSet.of(
val returnOpcodes: EnumSet<Opcode> = EnumSet.of(
RETURN_VOID, RETURN, RETURN_WIDE, RETURN_OBJECT, RETURN_VOID_NO_BARRIER,
RETURN_VOID, RETURN, RETURN_WIDE, RETURN_OBJECT, RETURN_VOID_NO_BARRIER,
THROW
THROW
)
)


val writeOpcodes: EnumSet<Opcode> = EnumSet.of(
val writeOpcodes: EnumSet<Opcode> = EnumSet.of(
ARRAY_LENGTH,
ARRAY_LENGTH,
INSTANCE_OF,
INSTANCE_OF,
NEW_INSTANCE, NEW_ARRAY,
NEW_INSTANCE, NEW_ARRAY,
MOVE, MOVE_FROM16, MOVE_16, MOVE_WIDE, MOVE_WIDE_FROM16, MOVE_WIDE_16, MOVE_OBJECT,
MOVE, MOVE_FROM16, MOVE_16, MOVE_WIDE, MOVE_WIDE_FROM16, MOVE_WIDE_16, MOVE_OBJECT,
MOVE_OBJECT_FROM16, MOVE_OBJECT_16, MOVE_RESULT, MOVE_RESULT_WIDE, MOVE_RESULT_OBJECT, MOVE_EXCEPTION,
MOVE_OBJECT_FROM16, MOVE_OBJECT_16, MOVE_RESULT, MOVE_RESULT_WIDE, MOVE_RESULT_OBJECT, MOVE_EXCEPTION,
CONST, CONST_4, CONST_16, CONST_HIGH16, CONST_WIDE_16, CONST_WIDE_32,
CONST, CONST_4, CONST_16, CONST_HIGH16, CONST_WIDE_16, CONST_WIDE_32,
CONST_WIDE, CONST_WIDE_HIGH16, CONST_STRING, CONST_STRING_JUMBO,
CONST_WIDE, CONST_WIDE_HIGH16, CONST_STRING, CONST_STRING_JUMBO,
IGET, IGET_WIDE, IGET_OBJECT, IGET_BOOLEAN, IGET_BYTE, IGET_CHAR, IGET_SHORT,
IGET, IGET_WIDE, IGET_OBJECT, IGET_BOOLEAN, IGET_BYTE, IGET_CHAR, IGET_SHORT,
IGET_VOLATILE, IGET_WIDE_VOLATILE, IGET_OBJECT_VOLATILE,
IGET_VOLATILE, IGET_WIDE_VOLATILE, IGET_OBJECT_VOLATILE,
SGET, SGET_WIDE, SGET_OBJECT, SGET_BOOLEAN, SGET_BYTE, SGET_CHAR, SGET_SHORT,
SGET, SGET_WIDE, SGET_OBJECT, SGET_BOOLEAN, SGET_BYTE, SGET_CHAR, SGET_SHORT,
SGET_VOLATILE, SGET_WIDE_VOLATILE, SGET_OBJECT_VOLATILE,
SGET_VOLATILE, SGET_WIDE_VOLATILE, SGET_OBJECT_VOLATILE,
AGET, AGET_WIDE, AGET_OBJECT, AGET_BOOLEAN, AGET_BYTE, AGET_CHAR, AGET_SHORT,
AGET, AGET_WIDE, AGET_OBJECT, AGET_BOOLEAN, AGET_BYTE, AGET_CHAR, AGET_SHORT,
// Arithmetic and logical operations.
// Arithmetic and logical operations.
ADD_DOUBLE_2ADDR, ADD_DOUBLE, ADD_FLOAT_2ADDR, ADD_FLOAT, ADD_INT_2ADDR,
ADD_DOUBLE_2ADDR, ADD_DOUBLE, ADD_FLOAT_2ADDR, ADD_FLOAT, ADD_INT_2ADDR,
ADD_INT_LIT8, ADD_INT, ADD_LONG_2ADDR, ADD_LONG, ADD_INT_LIT16,
ADD_INT_LIT8, ADD_INT, ADD_LONG_2ADDR, ADD_LONG, ADD_INT_LIT16,
AND_INT_2ADDR, AND_INT_LIT8, AND_INT_LIT16, AND_INT, AND_LONG_2ADDR, AND_LONG,
AND_INT_2ADDR, AND_INT_LIT8, AND_INT_LIT16, AND_INT, AND_LONG_2ADDR, AND_LONG,
DIV_DOUBLE_2ADDR, DIV_DOUBLE, DIV_FLOAT_2ADDR, DIV_FLOAT, DIV_INT_2ADDR,
DIV_DOUBLE_2ADDR, DIV_DOUBLE, DIV_FLOAT_2ADDR, DIV_FLOAT, DIV_INT_2ADDR,
DIV_INT_LIT16, DIV_INT_LIT8, DIV_INT, DIV_LONG_2ADDR, DIV_LONG,
DIV_INT_LIT16, DIV_INT_LIT8, DIV_INT, DIV_LONG_2ADDR, DIV_LONG,
DOUBLE_TO_FLOAT, DOUBLE_TO_INT, DOUBLE_TO_LONG,
DOUBLE_TO_FLOAT, DOUBLE_TO_INT, DOUBLE_TO_LONG,
FLOAT_TO_DOUBLE, FLOAT_TO_INT, FLOAT_TO_LONG,
FLOAT_TO_DOUBLE, FLOAT_TO_INT, FLOAT_TO_LONG,
INT_TO_BYTE, INT_TO_CHAR, INT_TO_DOUBLE, INT_TO_FLOAT, INT_TO_LONG, INT_TO_SHORT,
INT_TO_BYTE, INT_TO_CHAR, INT_TO_DOUBLE, INT_TO_FLOAT, INT_TO_LONG, INT_TO_SHORT,
LONG_TO_DOUBLE, LONG_TO_FLOAT, LONG_TO_INT,
LONG_TO_DOUBLE, LONG_TO_FLOAT, LONG_TO_INT,
MUL_DOUBLE_2ADDR, MUL_DOUBLE, MUL_FLOAT_2ADDR, MUL_FLOAT, MUL_INT_2ADDR,
MUL_DOUBLE_2ADDR, MUL_DOUBLE, MUL_FLOAT_2ADDR, MUL_FLOAT, MUL_INT_2ADDR,
MUL_INT_LIT16, MUL_INT_LIT8, MUL_INT, MUL_LONG_2ADDR, MUL_LONG,
MUL_INT_LIT16, MUL_INT_LIT8, MUL_INT, MUL_LONG_2ADDR, MUL_LONG,
NEG_DOUBLE, NEG_FLOAT, NEG_INT, NEG_LONG,
NEG_DOUBLE, NEG_FLOAT, NEG_INT, NEG_LONG,
NOT_INT, NOT_LONG,
NOT_INT, NOT_LONG,
OR_INT_2ADDR, OR_INT_LIT16, OR_INT_LIT8, OR_INT, OR_LONG_2ADDR, OR_LONG,
OR_INT_2ADDR, OR_INT_LIT16, OR_INT_LIT8, OR_INT, OR_LONG_2ADDR, OR_LONG,
REM_DOUBLE_2ADDR, REM_DOUBLE, REM_FLOAT_2ADDR, REM_FLOAT, REM_INT_2ADDR,
REM_DOUBLE_2ADDR, REM_DOUBLE, REM_FLOAT_2ADDR, REM_FLOAT, REM_INT_2ADDR,
REM_INT_LIT16, REM_INT_LIT8, REM_INT, REM_LONG_2ADDR, REM_LONG,
REM_INT_LIT16, REM_INT_LIT8, REM_INT, REM_LONG_2ADDR, REM_LONG,
RSUB_INT_LIT8, RSUB_INT,
RSUB_INT_LIT8, RSUB_INT,
SHL_INT_2ADDR, SHL_INT_LIT8, SHL_INT, SHL_LONG_2ADDR, SHL_LONG,
SHL_INT_2ADDR, SHL_INT_LIT8, SHL_INT, SHL_LONG_2ADDR, SHL_LONG,
SHR_INT_2ADDR, SHR_INT_LIT8, SHR_INT, SHR_LONG_2ADDR, SHR_LONG,
SHR_INT_2ADDR, SHR_INT_LIT8, SHR_INT, SHR_LONG_2ADDR, SHR_LONG,
SUB_DOUBLE_2ADDR, SUB_DOUBLE, SUB_FLOAT_2ADDR, SUB_FLOAT, SUB_INT_2ADDR,
SUB_DOUBLE_2ADDR, SUB_DOUBLE, SUB_FLOAT_2ADDR, SUB_FLOAT, SUB_INT_2ADDR,
SUB_INT, SUB_LONG_2ADDR, SUB_LONG,
SUB_INT, SUB_LONG_2ADDR, SUB_LONG,
USHR_INT_2ADDR, USHR_INT_LIT8, USHR_INT, USHR_LONG_2ADDR, USHR_LONG,
USHR_INT_2ADDR, USHR_INT_LIT8, USHR_INT, USHR_LONG_2ADDR, USHR_LONG,
XOR_INT_2ADDR, XOR_INT_LIT16, XOR_INT_LIT8, XOR_INT, XOR_LONG_2ADDR, XOR_LONG,
XOR_INT_2ADDR, XOR_INT_LIT16, XOR_INT_LIT8, XOR_INT, XOR_LONG_2ADDR, XOR_LONG,
)
)


/**
/**
* For debugging and development.
* For debugging and development.
*/
*/
internal const val logFreeRegisterSearch = false
internal const val logFreeRegisterSearch = false
}
}
}
}


/**
/**
* Starting from and including the instruction at index [index],
* Starting from and including the instruction at index [index],
* finds the next register that is written to and not read from. If a return instruction
* finds the next register that is written to and not read from. If a return instruction
* is encountered, then the lowest unused register is returned.
* is encountered, then the lowest unused register is returned.
*
*
* This method should work for all situations including inserting at a branch statement,
* This method should work for all situations including inserting at a branch statement,
* but this may not work if the index is at or just before a switch statement or if the branch
* but this may not work if the index is at or just before a switch statement or if the branch
* paths have no common free registers.
* paths have no common free registers.
*
*
* If you need multiple free registers, then instead use [Method.getFreeRegisterProvider].
* If you need multiple free registers, then instead use [Method.getFreeRegisterProvider].
*
*
* @param index Index you need a use a free register at.
* @param index Index you need a use a free register at.
* @param registersToExclude Registers to exclude, and consider as used. For most use cases,
* @param registersToExclude Registers to exclude, and consider as used. For most use cases,
* all registers used in injected code should be specified.
* all registers used in injected code should be specified.
* @return The lowest register number (usually a 4-bit register) that is free at the given index.
* @return The lowest register number (usually a 4-bit register) that is free at the given index.
* @throws IllegalArgumentException If no free registers can be found at the given index.
* @throws IllegalArgumentException If no free registers can be found at the given index.
* This includes unusual method indexes that read from every register
* This includes unusual method indexes that read from every register
* before any registers are wrote to, or if a switch statement is
* before any registers are wrote to, or if a switch statement is
* encountered before any free registers are found, or if the index is
* encountered before any free registers are found, or if the index is
* at/before a branch statement and the method has an unusually high
* at/before a branch statement and the method has an unusually high
* amount of branching where no common free registers exist in both branch paths.
* amount of branching where no common free registers exist in both branch paths.
*/
*/
fun Method.findFreeRegister(
fun Method.findFreeRegister(
index: Int,
index: Int,
vararg registersToExclude: Int
vararg registersToExclude: Int
) = findFreeRegisters(
) = findFreeRegisters(
startIndex = index,
startIndex = index,
numberOfFreeRegistersNeeded = 1,
numberOfFreeRegistersNeeded = 1,
registersToExclude = registersToExclude.toList()
registersToExclude = registersToExclude.toList()
).first()
).first()


/**
/**
* Starting from and including the instruction at index [index],
* Starting from and including the instruction at index [index],
* finds the next register that is written to and not read from. If a return instruction
* finds the next register that is written to and not read from. If a return instruction
* is encountered, then the lowest unused register is returned.
* is encountered, then the lowest unused register is returned.
*
*
* This method should work for all situations including inserting at a branch statement,
* This method should work for all situations including inserting at a branch statement,
* but this may not work if the index is at or just before a switch statement or if the branch
* but this may not work if the index is at or just before a switch statement or if the branch
* paths have no common free registers.
* paths have no common free registers.
*
*
* If you need multiple free registers, then instead use [Method.getFreeRegisterProvider].
* If you need multiple free registers, then instead use [Method.getFreeRegisterProvider].
*
*
* @param index Index you need a use a free register at.
* @param index Index you need a use a free register at.
* @param registersToExclude Registers to exclude, and consider as used. For most use cases,
* @param registersToExclude Registers to exclude, and consider as used. For most use cases,
* all registers used in injected code should be specified.
* all registers used in injected code should be specified.
* @return The lowest register number (usually a 4-bit register) that is free at the given index.
* @return The lowest register number (usually a 4-bit register) that is free at the given index.
* @throws IllegalArgumentException If no free registers can be found at the given index.
* @throws IllegalArgumentException If no free registers can be found at the given index.
* This includes unusual method indexes that read from every register
* This includes unusual method indexes that read from every register
* before any registers are wrote to, or if a switch statement is
* before any registers are wrote to, or if a switch statement is
* encountered before any free registers are found, or if the index is
* encountered before any free registers are found, or if the index is
* at/before a branch statement and the method has an unusually high
* at/before a branch statement and the method has an unusually high
* amount of branching where no common free registers exist in both branch paths.
* amount of branching where no common free registers exist in both branch paths.
*/
*/
fun Method.findFreeRegister(
fun Method.findFreeRegister(
index: Int,
index: Int,
registersToExclude: List<Int>
registersToExclude: List<Int>
) = findFreeRegisters(
) = findFreeRegisters(
startIndex = index,
startIndex = index,
numberOfFreeRegistersNeeded = 1,
numberOfFreeRegistersNeeded = 1,
registersToExclude = registersToExclude
registersToExclude = registersToExclude
).first()
).first()


private fun Method.findFreeRegisters(
private fun Method.findFreeRegisters(
startIndex: Int,
startIndex: Int,
numberOfFreeRegistersNeeded: Int,
numberOfFreeRegistersNeeded: Int,
registersToExclude: List<Int>
registersToExclude: List<Int>
): List<Int> {
): List<Int> {
if (logFreeRegisterSearch) println("Searching startIndex: $startIndex method: $this")
if (logFreeRegisterSearch) println("Searching startIndex: $startIndex method: $this")


val freeRegisters = findFreeRegistersInternal(
val freeRegisters = findFreeRegistersInternal(
startIndex = startIndex,
startIndex = startIndex,
numberOfFreeRegistersNeeded = numberOfFreeRegistersNeeded,
numberOfFreeRegistersNeeded = numberOfFreeRegistersNeeded,
currentDepth = 0,
currentDepth = 0,
foundFreeRegistersAtIndex = mutableMapOf(),
foundFreeRegistersAtIndex = mutableMapOf(),
registersToExclude = registersToExclude,
registersToExclude = registersToExclude,
offsetArray = buildInstructionOffsetArray()
offsetArray = buildInstructionOffsetArray()
)
)


if (freeRegisters.isEmpty()) {
if (freeRegisters.isEmpty()) {
// Should only happen if a switch statement is encountered before enough free registers are found.
// Should only happen if a switch statement is encountered before enough free registers are found.
throw IllegalArgumentException("Could not find a free register from startIndex: " +
throw IllegalArgumentException("Could not find a free register from startIndex: " +
"$startIndex excluding: $registersToExclude")
"$startIndex excluding: $registersToExclude")
}
}


if (logFreeRegisterSearch) println("Final free registers found: $freeRegisters")
if (logFreeRegisterSearch) println("Final free registers found: $freeRegisters")


// Use 4-bit registers first, but keep sorting stable among 4-bit vs not 4-bit.
// Use 4-bit registers first, but keep sorting stable among 4-bit vs not 4-bit.
return freeRegisters.sortedWith { first, second ->
return freeRegisters.sortedWith { first, second ->
val firstIsFourBit = first < 16
val firstIsFourBit = first < 16
val secondIsFourBit = second < 16
val secondIsFourBit = second < 16
if (firstIsFourBit == secondIsFourBit) {
if (firstIsFourBit == secondIsFourBit) {
return@sortedWith 0
return@sortedWith 0
}
}


if (firstIsFourBit) -1 else 1
if (firstIsFourBit) -1 else 1
}
}
}
}


/**
/**
* Returns all free registers found starting from [startIndex].Follows branches up to [maxDepth].
* Returns all free registers found starting from [startIndex].Follows branches up to [maxDepth].
*
*
* @param startIndex Inclusive starting index.
* @param startIndex Inclusive starting index.
* @param numberOfFreeRegistersNeeded The minimum free registers to ensure will be returned.
* @param numberOfFreeRegistersNeeded The minimum free registers to ensure will be returned.
* @param currentDepth Current branching depth. Value of zero means no branching has been followed yet.
* @param currentDepth Current branching depth. Value of zero means no branching has been followed yet.
* @param foundFreeRegistersAtIndex Map of instruction index to list of free registers previously found.
* @param foundFreeRegistersAtIndex Map of instruction index to list of free registers previously found.
* @param registersToExclude Registers to exclude from consideration.
* @param registersToExclude Registers to exclude from consideration.
* @param offsetArray Map from instruction index to code offset.
* @param offsetArray Map from instruction index to code offset.
* @return List of all free registers found.
* @return List of all free registers found.
*/
*/
private fun Method.findFreeRegistersInternal(
private fun Method.findFreeRegistersInternal(
startIndex: Int,
startIndex: Int,
numberOfFreeRegistersNeeded: Int,
numberOfFreeRegistersNeeded: Int,
currentDepth: Int,
currentDepth: Int,
foundFreeRegistersAtIndex: MutableMap<Int, Set<Int>?>,
foundFreeRegistersAtIndex: MutableMap<Int, Set<Int>?>,
registersToExclude: List<Int>,
registersToExclude: List<Int>,
offsetArray: IntArray
offsetArray: IntArray
): List<Int> {
): List<Int> {
check(implementation != null) {
check(implementation != null) {
"Method has no implementation: $this"
"Method has no implementation: $this"
}
}
check(startIndex >= 0 && startIndex < instructions.count()) {
check(startIndex > 0 && startIndex < instructions.count()) {
"startIndex out of bounds: $startIndex methodInstructionCount: ${instructions.count()}"
"startIndex out of bounds: $startIndex methodInstructionCount: ${instructions.count()}"
}
}
check(numberOfFreeRegistersNeeded > 0) {
check(numberOfFreeRegistersNeeded > 0) {
"numberOfFreeRegistersNeeded must be greater than zero: $numberOfFreeRegistersNeeded"
"numberOfFreeRegistersNeeded must be greater than zero: $numberOfFreeRegistersNeeded"
}
}


fun Collection<Int>.numberOf4BitRegisters() = this.count { it < 16 }
fun Collection<Int>.numberOf4BitRegisters() = this.count { it < 16 }


foundFreeRegistersAtIndex[startIndex]?.let {
foundFreeRegistersAtIndex[startIndex]?.let {
// Recursive call to a branch index that has already been explored.
// Recursive call to a branch index that has already been explored.
return (it - registersToExclude.toSet()).toList()
return (it - registersToExclude.toSet()).toList()
}
}


val usedRegisters = registersToExclude.toMutableSet()
val usedRegisters = registersToExclude.toMutableSet()
val freeRegisters = mutableSetOf<Int>()
val freeRegisters = mutableSetOf<Int>()
foundFreeRegistersAtIndex[startIndex] = freeRegisters
foundFreeRegistersAtIndex[startIndex] = freeRegisters


for (i in startIndex until instructions.count()) {
for (i in startIndex until instructions.count()) {
val instruction = getInstruction(i)
val instruction = getInstruction(i)
val instructionRegisters = instruction.registersUsed
val instructionRegisters = instruction.registersUsed


// Check for write-only register.
// Check for write-only register.
val writeRegister = instruction.writeRegister
val writeRegister = instruction.writeRegister
if (writeRegister != null && writeRegister !in usedRegisters) {
if (writeRegister != null && writeRegister !in usedRegisters) {
// Check if this register is ONLY written to (not also read)
// Check if this register is ONLY written to (not also read)
// Count occurrences of writeRegister in instructionRegisters.
// Count occurrences of writeRegister in instructionRegisters.
val occurrences = instructionRegisters.count { it == writeRegister }
val occurrences = instructionRegisters.count { it == writeRegister }
// If it appears only once, it's write-only (to write).
// If it appears only once, it's write-only (to write).
// If it appears more than once, it's also read.
// If it appears more than once, it's also read.
if (occurrences <= 1) {
if (occurrences <= 1) {
if (logFreeRegisterSearch) println("Found free register at $i: $writeRegister " +
if (logFreeRegisterSearch) println("Found free register at $i: $writeRegister " +
"opcode: " + instruction.opcode + " reference: " + (instruction.getReference()))
"opcode: " + instruction.opcode + " reference: " + (instruction.getReference()))
freeRegisters.add(writeRegister)
freeRegisters.add(writeRegister)
// If the requested number of free registers is found and this is not a branch,
// If the requested number of free registers is found and this is not a branch,
// then no additional searching is needed.
// then no additional searching is needed.
// But if this is a branch, then this all free registers should be found
// But if this is a branch, then this all free registers should be found
// because the intersection of free registers from different branches may be
// because the intersection of free registers from different branches may be
// less than the requested number of registers.
// less than the requested number of registers.
if (currentDepth == 0 && freeRegisters.numberOf4BitRegisters() >= numberOfFreeRegistersNeeded) {
if (currentDepth == 0 && freeRegisters.numberOf4BitRegisters() >= numberOfFreeRegistersNeeded) {
return freeRegisters.toList()
return freeRegisters.toList()
}
}
}
}
}
}


// Mark all registers used by this instruction as "used".
// Mark all registers used by this instruction as "used".
usedRegisters.addAll(instructionRegisters)
usedRegisters.addAll(instructionRegisters)


// If we hit a return, all unused registers on this path are free.
// If we hit a return, all unused registers on this path are free.
if (instruction.isReturnInstruction) {
if (instruction.isReturnInstruction) {
val allRegisters = (0 until implementation!!.registerCount).toList()
val allRegisters = (0 until implementation!!.registerCount).toList()
val unusedRegisters = allRegisters - usedRegisters
val unusedRegisters = allRegisters - usedRegisters
freeRegisters.addAll(unusedRegisters)
freeRegisters.addAll(unusedRegisters)
if (logFreeRegisterSearch) println("Encountered return index: $i and found: $freeRegisters")
if (logFreeRegisterSearch) println("Encountered return index: $i and found: $freeRegisters")
return freeRegisters.toList()
return freeRegisters.toList()
}
}


if (instruction.isSwitchInstruction) {
if (instruction.isSwitchInstruction) {
// For now, do not handle the complexity of a switch statement and handle as a leaf node.
// For now, do not handle the complexity of a switch statement and handle as a leaf node.
if (logFreeRegisterSearch) println("Encountered switch index: $i opcode: " + instruction.opcode)
if (logFreeRegisterSearch) println("Encountered switch index: $i opcode: " + instruction.opcode)
return freeRegisters.toList()
return freeRegisters.toList()
}
}


if (instruction.isUnconditionalBranchInstruction) {
if (instruction.isUnconditionalBranchInstruction) {
if (logFreeRegisterSearch) println("encountered unconditional branch index: $i opcode: " + instruction.opcode)
if (logFreeRegisterSearch) println("encountered unconditional branch index: $i opcode: " + instruction.opcode)


// Continue searching from the go-to index.
// Continue searching from the go-to index.
return (freeRegisters + findFreeRegistersInternal(
return (freeRegisters + findFreeRegistersInternal(
startIndex = getBranchTargetInstructionIndex(instruction, i, offsetArray),
startIndex = getBranchTargetInstructionIndex(instruction, i, offsetArray),
numberOfFreeRegistersNeeded = numberOfFreeRegistersNeeded,
numberOfFreeRegistersNeeded = numberOfFreeRegistersNeeded,
currentDepth = currentDepth, // Same depth since it's a continuation of single path.
currentDepth = currentDepth, // Same depth since it's a continuation of single path.
foundFreeRegistersAtIndex = foundFreeRegistersAtIndex,
foundFreeRegistersAtIndex = foundFreeRegistersAtIndex,
registersToExclude = usedRegisters.toList(),
registersToExclude = usedRegisters.toList(),
offsetArray = offsetArray
offsetArray = offsetArray
)).toList()
)).toList()
}
}


if (instruction.isConditionalBranchInstruction) {
if (instruction.isConditionalBranchInstruction) {
if (logFreeRegisterSearch) println("encountered conditional branch index: $i opcode: " + instruction.opcode)
if (logFreeRegisterSearch) println("encountered conditional branch index: $i opcode: " + instruction.opcode)
val usedRegistersList = usedRegisters.toList()
val usedRegistersList = usedRegisters.toList()


val branchFreeRegisters = findFreeRegistersInternal(
val branchFreeRegisters = findFreeRegistersInternal(
startIndex = getBranchTargetInstructionIndex(instruction, i, offsetArray),
startIndex = getBranchTargetInstructionIndex(instruction, i, offsetArray),
numberOfFreeRegistersNeeded = numberOfFreeRegistersNeeded,
numberOfFreeRegistersNeeded = numberOfFreeRegistersNeeded,
currentDepth = currentDepth + 1,
currentDepth = currentDepth + 1,
foundFreeRegistersAtIndex = foundFreeRegistersAtIndex,
foundFreeRegistersAtIndex = foundFreeRegistersAtIndex,
registersToExclude = usedRegistersList,
registersToExclude = usedRegistersList,
offsetArray = offsetArray
offsetArray = offsetArray
)
)
if (logFreeRegisterSearch) println("branch registers: $branchFreeRegisters")
if (logFreeRegisterSearch) println("branch registers: $branchFreeRegisters")


val fallThruFreeRegisters = findFreeRegistersInternal(
val fallThruFreeRegisters = findFreeRegistersInternal(
startIndex = i + 1,
startIndex = i + 1,
numberOfFreeRegistersNeeded = numberOfFreeRegistersNeeded,
numberOfFreeRegistersNeeded = numberOfFreeRegistersNeeded,
currentDepth = currentDepth + 1,
currentDepth = currentDepth + 1,
foundFreeRegistersAtIndex = foundFreeRegistersAtIndex,
foundFreeRegistersAtIndex = foundFreeRegistersAtIndex,
registersToExclude = usedRegistersList,
registersToExclude = usedRegistersList,
offsetArray = offsetArray
offsetArray = offsetArray
)
)
if (logFreeRegisterSearch) println("fall thru registers: $fallThruFreeRegisters")
if (logFreeRegisterSearch) println("fall thru registers: $fallThruFreeRegisters")


return (freeRegisters + branchFreeRegisters.intersect(fallThruFreeRegisters.toSet())).toList()
return (freeRegisters + branchFreeRegisters.intersect(fallThruFreeRegisters.toSet())).toList()
}
}
}
}


// A return or branch instruction will be encountered before all instructions can be iterated.
// A return or branch instruction will be encountered before all instructions can be iterated.
// Some methods have switch payload instructions after the last actual instruction,
// Some methods have switch payload instructions after the last actual instruction,
// but these cannot be reached through normal control flow.
// but these cannot be reached through normal control flow.
throw IllegalArgumentException("Start index is outside normal control flow: $startIndex")
throw IllegalArgumentException("Start index is outside normal control flow: $startIndex")
}
}


private fun Method.buildInstructionOffsetArray(): IntArray {
private fun Method.buildInstructionOffsetArray(): IntArray {
val instructionCount = instructions.count()
val instructionCount = instructions.count()
val offsetArray = IntArray(instructionCount) { -1 }
val offsetArray = IntArray(instructionCount) { -1 }
var currentOffset = 0
var currentOffset = 0


for (i in 0 until instructionCount) {
for (i in 0 until instructionCount) {
val instruction = getInstruction(i)
val instruction = getInstruction(i)
val format = instruction.opcode.format
val format = instruction.opcode.format


if (!format.isPayloadFormat) {
if (!format.isPayloadFormat) {
offsetArray[i] = currentOffset
offsetArray[i] = currentOffset


// Get size in bytes from format.
// Get size in bytes from format.
val sizeInBytes = format.size
val sizeInBytes = format.size
val sizeInCodeUnits = when {
val sizeInCodeUnits = when {
sizeInBytes > 0 -> sizeInBytes / 2 // Normal instruction
sizeInBytes > 0 -> sizeInBytes / 2 // Normal instruction
format == Format.UnresolvedOdexInstruction -> 1 // Default size
format == Format.UnresolvedOdexInstruction -> 1 // Default size
else -> 1 // Fallback for any other edge case
else -> 1 // Fallback for any other edge case
}
}


currentOffset += sizeInCodeUnits
currentOffset += sizeInCodeUnits
}
}
// Skip payloads
// Skip payloads
}
}


return offsetArray
return offsetArray
}
}


/**
/**
* Returns an instruction index for a given branch instruction.
* Returns an instruction index for a given branch instruction.
*
*
* @param instruction The branch instruction
* @param instruction The branch instruction
* @param index Current instruction index
* @param index Current instruction index
* @param offsetArray Array mapping instruction index to code offset.
* @param offsetArray Array mapping instruction index to code offset.
*/
*/
private fun Method.getBranchTargetInstructionIndex(
private fun Method.getBranchTargetInstructionIndex(
instruction: Instruction,
instruction: Instruction,
index: Int,
index: Int,
offsetArray: IntArray
offsetArray: IntArray
): Int {
): Int {
check (index >0 && index < offsetArray.size) {
check (index >0 && index < offsetArray.size) {
"Invalid index: $index"
"Invalid index: $index"
}
}
val currentOffset = offsetArray[index]
val currentOffset = offsetArray[index]


return when (instruction.opcode) {
return when (instruction.opcode) {
GOTO, GOTO_16, GOTO_32,
GOTO, GOTO_16, GOTO_32,
IF_EQ, IF_NE, IF_LT, IF_GE, IF_GT, IF_LE,
IF_EQ, IF_NE, IF_LT, IF_GE, IF_GT, IF_LE,
IF_EQZ, IF_NEZ, IF_LTZ, IF_GEZ, IF_GTZ, IF_LEZ -> {
IF_EQZ, IF_NEZ, IF_LTZ, IF_GEZ, IF_GTZ, IF_LEZ -> {
val offset = (instruction as OffsetInstruction).codeOffset
val offset = (instruction as OffsetInstruction).codeOffset
val targetOffset = currentOffset + offset
val targetOffset = currentOffset + offset
// Find the instruction index at this offset.
// Find the instruction index at this offset.
findInstructionIndexByOffset(targetOffset, offsetArray)
findInstructionIndexByOffset(targetOffset, offsetArray)
}
}
// These need special handling - they jump to payloads
// These need special handling - they jump to payloads
// which then have their own target lists.
// which then have their own target lists.
// PACKED_SWITCH, SPARSE_SWITCH -> // TODO?
// PACKED_SWITCH, SPARSE_SWITCH -> // TODO?
else -> throw IllegalStateException("Unsupported opcode: ${instruction.opcode}")
else -> throw IllegalStateException("Unsupported opcode: ${instruction.opcode}")
}
}
}
}


/**
/**
* Finds the instruction index for a given code offset.
* Finds the instruction index for a given code offset.
*
*
* @param targetOffset Target code offset in 16-bit units
* @param targetOffset Target code offset in 16-bit units
* @param offsetArray Array mapping instruction index to code offset (-1 for payloads)
* @param offsetArray Array mapping instruction index to code offset (-1 for payloads)
* @return Instruction index at the target offset, or null if not found
* @return Instruction index at the target offset, or null if not found
*/
*/
private fun Method.findInstructionIndexByOffset(
private fun Method.findInstructionIndexByOffset(
targetOffset: Int,
targetOffset: Int,
offsetArray: IntArray
offsetArray: IntArray
): Int {
): Int {
// Simple linear search using indexOfFirst
// Simple linear search using indexOfFirst
val index = offsetArray.indexOfFirst { it == targetOffset }
val index = offsetArray.indexOfFirst { it == targetOffset }
if (index >= 0) {
if (index >= 0) {
return index
}

// Should never happen.
// Code has been tested on hundreds of random methods on all instruction indices,
// but maybe some weird code exists that this has overlooked.
throw IllegalArgumentException("Could not find exact instruction offset for method: " +
"$this at offset: $targetOffset."
)
}

/**
* @return The registers used by this instruction.
*/
val Instruction.registersUsed: List<Int>
get() = when (this) {
is FiveRegisterInstruction -> {
when (registerCount) {
0 -> listOf()
1 -> listOf(registerC)
2 -> listOf(registerC, registerD)
3 -> listOf(registerC, registerD, registerE)
4 -> listOf(registerC, registerD, registerE, registerF)
else -> listOf(registerC, registerD, registerE, registerF, registerG)
}
}

is ThreeRegisterInstruction -> listOf(registerA, registerB, registerC)
is TwoRegisterInstruction -> listOf(registerA, registerB)
is OneRegisterInstruction -> listOf(registerA)
is RegisterRangeInstruction -> (startRegister until (startRegister + registerCount)).toList()
else -> emptyList()
}

/**
* @return The register that is written to by this instructio