Anton Tarasenko
2 years ago
28 changed files with 1612 additions and 5 deletions
@ -0,0 +1,277 @@
|
||||
/** |
||||
* Author: dkanus |
||||
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore |
||||
* License: GPL |
||||
* Copyright 2022-2023 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class SideEffect extends AcediaObject; |
||||
|
||||
//! Defines the concept of "side effects" in the context of the Acedia and its derivative mods. |
||||
//! |
||||
//! Side effects are changes that are not part of the mod's main functionality, but rather something |
||||
//! necessary to enable that functionality, while also possibly affecting how other mods work. |
||||
//! Documenting these side effects helps developers and server admins understand changes performed |
||||
//! by Acedia or mods based on it, and anticipate any potential conflicts or issues that may arise. |
||||
//! |
||||
//! It should be noted that what constitutes a side effect is loosely defined, and it is simply |
||||
//! a tool to inform others that something unexpected has happened, possibly breaking other mods. |
||||
//! AcediaCore aims to leave a minimal footprint, but still needs to make some changes |
||||
//! (e.g., adding GameRules, patching code of some functions), and [`SideEffects`] can be used to |
||||
//! document them. |
||||
//! Similarly, [`SideEffect`]s can be used to document changes made by AcediaFixes, a package meant |
||||
//! only for fixing bugs that inevitably needs to make many under-the-hood changes to achieve |
||||
//! that goal. |
||||
//! |
||||
//! On the other hand gameplay mods like Futility or Ire can make a lot of changes, but they can all |
||||
//! be just expected part of its direct functionality: we expect feature that shares dosh of leavers |
||||
//! to alter players' dosh values, so this is not a side effect. |
||||
//! Such mods are likely not going to have to specify any side effects whatsoever. |
||||
|
||||
var private Text name; |
||||
var private Text description; |
||||
var private Text package; |
||||
var private Text source; |
||||
var private Text status; |
||||
var private bool initialized; |
||||
|
||||
protected function Finalizer() { |
||||
_.memory.Free(name); |
||||
_.memory.Free(description); |
||||
_.memory.Free(package); |
||||
_.memory.Free(source); |
||||
_.memory.Free(status); |
||||
name = none; |
||||
description = none; |
||||
package = none; |
||||
source = none; |
||||
status = none; |
||||
initialized = false; |
||||
} |
||||
|
||||
/// Checks whether caller [`SideEffect`] was initialized. |
||||
/// |
||||
/// Initialization must happen directly after creation and only initialized instances should |
||||
/// ever be used. |
||||
public final function bool IsInitialized() { |
||||
return initialized; |
||||
} |
||||
|
||||
/// This function is used to set the initial values of the [`SideEffect`] object properties when it |
||||
/// is first created. |
||||
/// |
||||
/// All arguments must be not `none`. |
||||
/// |
||||
/// Returns `true` if the initialization was successful, `false` otherwise (including the case where |
||||
/// the [`SideEffect`] object has already been initialized). |
||||
public final function bool Initialize( |
||||
BaseText sideEffectName, |
||||
BaseText sideEffectDescription, |
||||
BaseText sideEffectPackage, |
||||
BaseText sideEffectSource, |
||||
BaseText sideEffectStatus |
||||
) { |
||||
if (initialized) return false; |
||||
if (sideEffectName == none) return false; |
||||
if (sideEffectDescription == none) return false; |
||||
if (sideEffectPackage == none) return false; |
||||
if (sideEffectSource == none) return false; |
||||
if (sideEffectStatus == none) return false; |
||||
|
||||
name = sideEffectName.Copy(); |
||||
description = sideEffectDescription.Copy(); |
||||
package = sideEffectPackage.Copy(); |
||||
source = sideEffectSource.Copy(); |
||||
status = sideEffectStatus.Copy(); |
||||
initialized = true; |
||||
return true; |
||||
} |
||||
|
||||
/// This function is used to set the initial values of the [`SideEffect`] object properties when it |
||||
/// is first created. |
||||
/// |
||||
/// Returns `true` if the initialization was successful, `false` otherwise (including the case where |
||||
/// the [`SideEffect`] object has already been initialized). |
||||
public final function bool Initialize_S( |
||||
string sideEffectName, |
||||
string sideEffectDescription, |
||||
string sideEffectPackage, |
||||
string sideEffectSource, |
||||
string sideEffectStatus |
||||
) { |
||||
name = _.text.FromString(sideEffectName); |
||||
description = _.text.FromString(sideEffectDescription); |
||||
package = _.text.FromString(sideEffectPackage); |
||||
source = _.text.FromString(sideEffectSource); |
||||
status = _.text.FromString(sideEffectStatus); |
||||
initialized = true; |
||||
return true; |
||||
} |
||||
|
||||
/// Returns a brief summary that conveys the purpose of the caller [SideEffect] to the user in |
||||
/// a clear and concise manner. |
||||
/// |
||||
/// While there is no hard limit on the length of this value, it is recommended to keep it under 80 |
||||
/// characters for readability. |
||||
/// |
||||
/// Returned value for initialized [`SideEffect`] is guaranteed to not be `none`, as it is |
||||
/// a required property of the [`SideEffect`] object. |
||||
public final function Text GetName() { |
||||
if (initialized) { |
||||
return name.Copy(); |
||||
} |
||||
return none; |
||||
} |
||||
|
||||
/// Returns a brief summary that conveys the purpose of the caller [SideEffect] to the user in |
||||
/// a clear and concise manner. |
||||
/// |
||||
/// While there is no hard limit on the length of this value, it is recommended to keep it under 80 |
||||
/// characters for readability. |
||||
public final function string GetName_S() { |
||||
if (initialized && name != none) { |
||||
return name.ToString(); |
||||
} |
||||
return ""; |
||||
} |
||||
|
||||
/// Returns the detailed description of the caller [`SideEffect`], which describes what was done |
||||
/// and why the relevant change was necessary. |
||||
/// |
||||
/// Returned value for initialized [`SideEffect`] is guaranteed to not be `none`, as it is |
||||
/// a required property of the [`SideEffect`] object. |
||||
public final function Text GetDescription() { |
||||
if (initialized) { |
||||
return description.Copy(); |
||||
} |
||||
return none; |
||||
} |
||||
|
||||
/// Returns the detailed description of the caller [`SideEffect`], which describes what was done |
||||
/// and why the relevant change was necessary. |
||||
public final function string GetDescription_S() { |
||||
if (initialized && description != none) { |
||||
return description.ToString(); |
||||
} |
||||
return ""; |
||||
} |
||||
|
||||
/// Returns the name of the package ("*.u" file) that introduced the changes |
||||
/// represented by the caller `SideEffect`. |
||||
/// |
||||
/// It should be noted that even if a different package requested the functionality that led to |
||||
/// the changes being made, the package responsible for the side effect is the one that performed |
||||
/// the changes. |
||||
/// |
||||
/// Returned value for initialized [`SideEffect`] is guaranteed to not be `none`, as it is |
||||
/// a required property of the [`SideEffect`] object. |
||||
public final function Text GetPackage() { |
||||
if (initialized) { |
||||
return package.Copy(); |
||||
} |
||||
return none; |
||||
} |
||||
|
||||
/// Returns the name of the package ("*.u" file) that introduced the changes |
||||
/// represented by the caller `SideEffect`. |
||||
/// |
||||
/// It should be noted that even if a different package requested the functionality that led to |
||||
/// the changes being made, the package responsible for the side effect is the one that performed |
||||
/// the changes. |
||||
public final function string GetPackage_S() { |
||||
if (initialized && package != none) { |
||||
return package.ToString(); |
||||
} |
||||
return ""; |
||||
} |
||||
|
||||
/// The origin of this change within the package is specified, and for larger packages, additional |
||||
/// details can be provided to clarify the cause of the change. |
||||
/// |
||||
/// Returned value for initialized [`SideEffect`] is guaranteed to not be `none`, as it is |
||||
/// a required property of the [`SideEffect`] object. |
||||
public final function Text GetSource() { |
||||
if (initialized) { |
||||
return source.Copy(); |
||||
} |
||||
return none; |
||||
} |
||||
|
||||
/// The origin of this change within the package is specified, and for larger packages, additional |
||||
/// details can be provided to clarify the cause of the change. |
||||
public final function string GetSource_S() { |
||||
if (initialized && source != none) { |
||||
return source.ToString(); |
||||
} |
||||
return ""; |
||||
} |
||||
|
||||
/// The status of the caller [`SideEffect`], that is used to differentiate between different ways |
||||
/// that a side effect may have been introduced, allowing for better tracking and management of |
||||
/// the effect. |
||||
/// |
||||
/// Returned value for initialized [`SideEffect`] is guaranteed to not be `none`, as it is |
||||
/// a required property of the [`SideEffect`] object. |
||||
public final function Text GetStatus() { |
||||
if (initialized) { |
||||
return status.Copy(); |
||||
} |
||||
return none; |
||||
} |
||||
|
||||
/// The status of the caller [`SideEffect`], that is used to differentiate between different ways |
||||
/// that a side effect may have been introduced, allowing for better tracking and management of |
||||
/// the effect. |
||||
public final function string GetStatus_S() { |
||||
if (initialized && status != none) { |
||||
return status.ToString(); |
||||
} |
||||
return ""; |
||||
} |
||||
|
||||
public function bool IsEqual(Object other) { |
||||
local SideEffect otherSideEffect; |
||||
|
||||
if (self == other) return true; |
||||
otherSideEffect = SideEffect(other); |
||||
if (otherSideEffect == none) return false; |
||||
if (!otherSideEffect.initialized) return false; |
||||
if (GetHashCode() != otherSideEffect.GetHashCode()) return false; |
||||
if (!name.Compare(otherSideEffect.name,, SFORM_SENSITIVE)) return false; |
||||
if (!package.Compare(otherSideEffect.package,, SFORM_SENSITIVE)) return false; |
||||
if (!source.Compare(otherSideEffect.source,, SFORM_SENSITIVE)) return false; |
||||
if (!status.Compare(otherSideEffect.status,, SFORM_SENSITIVE)) return false; |
||||
if (!description.Compare(otherSideEffect.description,, SFORM_SENSITIVE)) return false; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
protected function int CalculateHashCode() { |
||||
local int result; |
||||
|
||||
if (initialized) { |
||||
result = name.GetHashCode(); |
||||
result = CombineHash(result, description.GetHashCode()); |
||||
result = CombineHash(result, package.GetHashCode()); |
||||
result = CombineHash(result, source.GetHashCode()); |
||||
result = CombineHash(result, status.GetHashCode()); |
||||
return result; |
||||
} |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,194 @@
|
||||
/** |
||||
* Author: dkanus |
||||
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore |
||||
* License: GPL |
||||
* Copyright 2022-2023 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class SideEffectAPI extends AcediaObject; |
||||
|
||||
var private array<SideEffect> activeSideEffects; |
||||
|
||||
/// Returns an array containing all SideEffect objects that have been registered up to this point. |
||||
/// |
||||
/// The order of the elements in the array is not guaranteed. |
||||
public function array<SideEffect> GetAll() { |
||||
local int i; |
||||
|
||||
for (i = 0; i < activeSideEffects.length; i += 1) { |
||||
activeSideEffects[i].NewRef(); |
||||
} |
||||
return activeSideEffects; |
||||
} |
||||
|
||||
/// Returns all registered [`SideEffects`] that are associated with the specified package name |
||||
/// (case-insensitive). |
||||
public function array<SideEffect> GetFromPackage(BaseText packageName) { |
||||
local int i; |
||||
local Text nextPackage; |
||||
local array<SideEffect> result; |
||||
|
||||
if (packageName == none) { |
||||
return result; |
||||
} |
||||
for (i = 0; i < activeSideEffects.length; i += 1) { |
||||
nextPackage = activeSideEffects[i].GetPackage(); |
||||
if (packageName.Compare(nextPackage, SCASE_INSENSITIVE)) { |
||||
activeSideEffects[i].NewRef(); |
||||
result[result.length] = activeSideEffects[i]; |
||||
} |
||||
_.memory.Free(nextPackage); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
/// Adds a new side effect to the list of active side effects. |
||||
/// |
||||
/// This method will fail if any of its arguments are `none` or a side effect with that exact |
||||
/// contents was already added. |
||||
public function SideEffect Add( |
||||
BaseText sideEffectName, |
||||
BaseText sideEffectDescription, |
||||
BaseText sideEffectPackage, |
||||
BaseText sideEffectSource, |
||||
BaseText sideEffectStatus |
||||
) { |
||||
local bool initialized; |
||||
local SideEffect newSideEffect; |
||||
|
||||
newSideEffect = SideEffect(_.memory.Allocate(class'SideEffect')); |
||||
initialized = newSideEffect.Initialize( |
||||
sideEffectName, |
||||
sideEffectDescription, |
||||
sideEffectPackage, |
||||
sideEffectSource, |
||||
sideEffectStatus); |
||||
if (initialized) { |
||||
if (!AddInstance(newSideEffect)) { |
||||
_.memory.Free(newSideEffect); |
||||
return none; |
||||
} |
||||
} else { |
||||
_.memory.Free(newSideEffect); |
||||
return none; |
||||
} |
||||
return newSideEffect; |
||||
} |
||||
|
||||
/// Adds a new side effect to the list of active side effects. |
||||
/// |
||||
/// This method will fail if a side effect with that exact contents was already added. |
||||
public function SideEffect Add_S( |
||||
string sideEffectName, |
||||
string sideEffectDescription, |
||||
string sideEffectPackage, |
||||
string sideEffectSource, |
||||
string sideEffectStatus |
||||
) { |
||||
local bool initialized; |
||||
local SideEffect newSideEffect; |
||||
|
||||
newSideEffect = SideEffect(_.memory.Allocate(class'SideEffect')); |
||||
initialized = newSideEffect.Initialize_S( |
||||
sideEffectName, |
||||
sideEffectDescription, |
||||
sideEffectPackage, |
||||
sideEffectSource, |
||||
sideEffectStatus); |
||||
if (initialized) { |
||||
if (!AddInstance(newSideEffect)) { |
||||
_.memory.Free(newSideEffect); |
||||
return none; |
||||
} |
||||
} else { |
||||
return none; |
||||
} |
||||
return newSideEffect; |
||||
} |
||||
|
||||
/// Adds a new side effect to the list of active side effects. |
||||
/// |
||||
/// This method will fail if its argument is `none`, non-initialized or a side effect with that |
||||
/// exact contents was already added. |
||||
public function bool AddInstance(SideEffect newSideEffect) { |
||||
local int i; |
||||
|
||||
if (newSideEffect == none) return false; |
||||
if (!newSideEffect.IsInitialized()) return false; |
||||
|
||||
for (i = 0; i < activeSideEffects.length; i += 1) { |
||||
if (activeSideEffects[i].IsEqual(newSideEffect)) { |
||||
return false; |
||||
} |
||||
} |
||||
newSideEffect.NewRef(); |
||||
activeSideEffects[activeSideEffects.length] = newSideEffect; |
||||
LogAddingSideEffectChange(newSideEffect, true); |
||||
return true; |
||||
} |
||||
|
||||
/// Removes a side effect from the list of active side effects. |
||||
/// |
||||
/// This method will fail if its argument is `none`, non-initialized or a side effect with its |
||||
/// contents isn't in the records. |
||||
public function bool RemoveInstance(SideEffect inactiveSideEffect) { |
||||
local int i; |
||||
local bool foundInstance; |
||||
|
||||
if (inactiveSideEffect == none) { |
||||
return false; |
||||
} |
||||
for (i = 0; i < activeSideEffects.length; i += 1) { |
||||
if (activeSideEffects[i].IsEqual(inactiveSideEffect)) { |
||||
LogAddingSideEffectChange(activeSideEffects[i], false); |
||||
_.memory.Free(activeSideEffects[i]); |
||||
activeSideEffects.Remove(i, 1); |
||||
foundInstance = true; |
||||
} |
||||
} |
||||
return foundInstance; |
||||
} |
||||
|
||||
private function LogAddingSideEffectChange(SideEffect effect, bool added) { |
||||
local MutableText builder; |
||||
local Text sideEffectData; |
||||
|
||||
if (effect == none) { |
||||
return; |
||||
} |
||||
builder = _.text.Empty(); |
||||
if (added) { |
||||
builder.Append(P("NEW SIDE EFFECT: ")); |
||||
} else { |
||||
builder.Append(P("REMOVED SIDE EFFECT: ")); |
||||
} |
||||
sideEffectData = effect.GetName(); |
||||
builder.Append(sideEffectData); |
||||
_.memory.Free(sideEffectData); |
||||
sideEffectData = effect.GetStatus(); |
||||
if (sideEffectData != none) { |
||||
builder.Append(P(" {")); |
||||
builder.Append(sideEffectData); |
||||
_.memory.Free(sideEffectData); |
||||
builder.Append(P("}")); |
||||
} |
||||
_.logger.Info(builder); |
||||
builder.FreeSelf(); |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,76 @@
|
||||
/** |
||||
* Config class for storing map lists. |
||||
* Copyright 2023 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class FunctionReplacement extends AcediaObject; |
||||
|
||||
var private Text replaced; |
||||
var private Text replacer; |
||||
var private SideEffect effect; |
||||
|
||||
protected function Finalizer() { |
||||
_.memory.Free(replaced); |
||||
_.memory.Free(replacer); |
||||
_.memory.Free(effect); |
||||
replaced = none; |
||||
replacer = none; |
||||
effect = none; |
||||
} |
||||
|
||||
public static final function FunctionReplacement Make( |
||||
BaseText oldFunction, |
||||
BaseText newFunction, |
||||
SideEffect sideEffect |
||||
) { |
||||
local FunctionReplacement newReplacement; |
||||
|
||||
if (oldFunction == none) return none; |
||||
if (newFunction == none) return none; |
||||
if (sideEffect == none) return none; |
||||
|
||||
newReplacement = FunctionReplacement(__().memory.Allocate(class'FunctionReplacement')); |
||||
newReplacement.replaced = oldFunction.Copy(); |
||||
newReplacement.replacer = newFunction.Copy(); |
||||
sideEffect.NewRef(); |
||||
newReplacement.effect = sideEffect; |
||||
return newReplacement; |
||||
} |
||||
|
||||
public final function Text GetReplacedFunctionName() { |
||||
return replaced.Copy(); |
||||
} |
||||
|
||||
public final function string GetReplacedFunctionName_S() { |
||||
return replaced.ToString(); |
||||
} |
||||
|
||||
public final function Text GetReplacerFunctionName() { |
||||
return replacer.Copy(); |
||||
} |
||||
|
||||
public final function string GetReplacerFunctionName_S() { |
||||
return replacer.ToString(); |
||||
} |
||||
|
||||
public final function SideEffect GetSideEffect() { |
||||
effect.NewRef(); |
||||
return effect; |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,30 @@
|
||||
/** |
||||
* Config class for storing map lists. |
||||
* Copyright 2023 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class MockInitialClass extends AcediaObject; |
||||
|
||||
var public int counter; |
||||
|
||||
public final function int DoIt() { |
||||
counter += 1; |
||||
return counter; |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,33 @@
|
||||
/** |
||||
* Config class for storing map lists. |
||||
* Copyright 2023 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class MockReplacerClass extends MockInitialClass; |
||||
|
||||
public final function int DoIt2() { |
||||
counter += 2; |
||||
return -counter; |
||||
} |
||||
|
||||
public final function int DoIt3() { |
||||
counter -= 1; |
||||
return 7; |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,87 @@
|
||||
/** |
||||
* Set of tests for `Command` class. |
||||
* Copyright 2021 - 2022 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class TEST_Unflect extends TestCase |
||||
abstract; |
||||
|
||||
protected static function TESTS() { |
||||
local MockInitialClass obj; |
||||
|
||||
obj = MockInitialClass(__().memory.Allocate(class'MockInitialClass')); |
||||
Context("Replacing functions with `UnflectApi`"); |
||||
Test_InitialReplacement(obj); |
||||
Test_SecondReplacement(obj); |
||||
Test_ReplacementWithSelf(obj); |
||||
Test_RevertingReplacement(obj); |
||||
} |
||||
|
||||
protected static function Test_InitialReplacement(MockInitialClass obj) { |
||||
Issue("Functions aren't being replaced correctly the first time."); |
||||
TEST_ExpectTrue(__().unflect.ReplaceFunction_S( |
||||
"AcediaCore.MockInitialClass.DoIt", |
||||
"AcediaCore.MockReplacerClass.DoIt2", |
||||
"testing")); |
||||
TEST_ExpectTrue(obj.DoIt() == -2); |
||||
TEST_ExpectTrue(obj.counter == 2); |
||||
TEST_ExpectTrue(obj.DoIt() == -4); |
||||
TEST_ExpectTrue(obj.counter == 4); |
||||
} |
||||
|
||||
protected static function Test_SecondReplacement(MockInitialClass obj) { |
||||
Issue("Functions aren't being replaced correctly in case they were already replaced."); |
||||
TEST_ExpectTrue(__().unflect.ReplaceFunction_S( |
||||
"AcediaCore.MockInitialClass.DoIt", |
||||
"AcediaCore.MockReplacerClass.DoIt3", |
||||
"testing")); |
||||
TEST_ExpectTrue(obj.DoIt() == 7); |
||||
TEST_ExpectTrue(obj.counter == 3); |
||||
TEST_ExpectTrue(obj.DoIt() == 7); |
||||
TEST_ExpectTrue(obj.counter == 2); |
||||
} |
||||
|
||||
protected static function Test_ReplacementWithSelf(MockInitialClass obj) { |
||||
Issue("Attempting to replacing function with itself makes unexpected change."); |
||||
TEST_ExpectFalse(__().unflect.ReplaceFunction_S( |
||||
"AcediaCore.MockInitialClass.DoIt", |
||||
"AcediaCore.MockInitialClass.DoIt", |
||||
"testing")); |
||||
TEST_ExpectTrue(obj.DoIt() == 7); |
||||
TEST_ExpectTrue(obj.counter == 1); |
||||
} |
||||
|
||||
protected static function Test_RevertingReplacement(MockInitialClass obj) { |
||||
Issue("Reverting replaced function doesn't work."); |
||||
TEST_ExpectTrue(__().unflect.RevertFunction_S("AcediaCore.MockInitialClass.DoIt")); |
||||
TEST_ExpectTrue(obj.DoIt() == 2); |
||||
TEST_ExpectTrue(obj.counter == 2); |
||||
TEST_ExpectTrue(obj.DoIt() == 3); |
||||
TEST_ExpectTrue(obj.counter == 3); |
||||
|
||||
Issue("Reverting already reverted function ends in success."); |
||||
TEST_ExpectFalse(__().unflect.RevertFunction_S("AcediaCore.MockInitialClass.DoIt")); |
||||
|
||||
Issue("Reverting already reverted function leads to unexpected results."); |
||||
TEST_ExpectTrue(obj.DoIt() == 4); |
||||
TEST_ExpectTrue(obj.counter == 4); |
||||
} |
||||
|
||||
defaultproperties { |
||||
caseName = "Function replacement" |
||||
caseGroup = "Unflect" |
||||
} |
@ -0,0 +1,29 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class TypeCast extends Object; |
||||
|
||||
var Object nativeType; |
||||
|
||||
final function NativeCast(Object type) { |
||||
nativeType = type; |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,49 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UClass extends UState within Package; |
||||
|
||||
var int classFlags; |
||||
var int classUnique; |
||||
var Guid classGuid; |
||||
var UClass classWithin; |
||||
var name classConfigName; |
||||
|
||||
var array<struct RepRecord { |
||||
var UProperty property; |
||||
var int index; |
||||
}> classReps; |
||||
|
||||
var array<UField> netFields; |
||||
|
||||
var array<struct Dependency { |
||||
var UClass class; |
||||
var int deep; |
||||
var int scriptTextCRC; |
||||
}> dependencies; |
||||
|
||||
var array<name> packageImports; |
||||
var array<byte> defaults; |
||||
var array<name> hideCategories; |
||||
var array<name> dependentOn; |
||||
|
||||
var string defaultPropText; |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,30 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UClassCast extends Object; |
||||
|
||||
var UClass nativeType; |
||||
|
||||
final function UClass Cast(Class type) { |
||||
super(TypeCast).NativeCast(type); |
||||
return nativeType; |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,28 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UField extends Object |
||||
abstract; |
||||
|
||||
var UField superField; |
||||
var UField next; |
||||
var UField hashNext; |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,30 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UFieldCast extends Object; |
||||
|
||||
var UField nativeType; |
||||
|
||||
final function UField Cast(Field type) { |
||||
super(TypeCast).NativeCast(type); |
||||
return nativeType; |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,35 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UFunction extends UStruct within UState |
||||
dependson(Unflect); |
||||
|
||||
var byte functionMD5Digest[16]; |
||||
|
||||
var int functionFlags; |
||||
var Unflect.Int16 nativeIndex; |
||||
var Unflect.Int16 repOffset; |
||||
var byte operPrecedence; |
||||
|
||||
var byte numParms; |
||||
var Unflect.Int16 parmsSize; |
||||
var Unflect.Int16 returnValueOffset; |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,30 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UFunctionCast extends Object; |
||||
|
||||
var UFunction nativeType; |
||||
|
||||
final function UFunction Cast(Function type) { |
||||
super(TypeCast).NativeCast(type); |
||||
return nativeType; |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,41 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UProperty extends UField within UField |
||||
abstract; |
||||
|
||||
var int arrayDim; |
||||
var int elementSize; |
||||
var int propertyFlags; |
||||
var name category; |
||||
|
||||
var byte repOffset[2]; |
||||
var byte repIndex[2]; |
||||
|
||||
var transient int offset; |
||||
var transient UProperty propertyLinkNext; |
||||
var transient UProperty configLinkNext; |
||||
var transient UProperty constructorLinkNext; |
||||
var transient UProperty nextRef; |
||||
var transient UProperty repOwner; |
||||
|
||||
var string commentString; |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,30 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UPropertyCast extends Object; |
||||
|
||||
var UProperty nativeType; |
||||
|
||||
final function UProperty Cast(Property type) { |
||||
super(TypeCast).NativeCast(type); |
||||
return nativeType; |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,30 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UState extends UStruct |
||||
dependson(Unflect); |
||||
|
||||
var Unflect.Int64 probeMask; |
||||
var Unflect.Int64 ignoreMask; |
||||
var int stateFlags; |
||||
var Unflect.Int16 labelTableOffset; |
||||
var UField vfHash[256]; |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,30 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UStateCast extends Object; |
||||
|
||||
var UState nativeType; |
||||
|
||||
final function UState Cast(State type) { |
||||
super(TypeCast).NativeCast(type); |
||||
return nativeType; |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,48 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UStruct extends UField; |
||||
|
||||
var UTextBuffer scriptText; |
||||
var UTextBuffer cppText; |
||||
var UField children; |
||||
var int propertiesSize; |
||||
var name friendlyName; |
||||
var array<byte> script; |
||||
|
||||
var int textPos; |
||||
var int line; |
||||
var struct EStructFlags { |
||||
var bool native; |
||||
var bool export; |
||||
var bool long; |
||||
var bool init; |
||||
var bool unused1; |
||||
var bool unused2; |
||||
var bool unused3; |
||||
var bool unused4; |
||||
} StructFlags; |
||||
|
||||
var Property refLink; |
||||
var Property propertyLink; |
||||
var Property configLink; |
||||
var Property constructorLink; |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,30 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UStructCast extends Object; |
||||
|
||||
var UStruct nativeType; |
||||
|
||||
final function UStruct Cast(/*Core.Struct*/ Object type) { |
||||
super(TypeCast).NativeCast(type); |
||||
return nativeType; |
||||
} |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,28 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UTextBuffer extends Object; |
||||
|
||||
var private native const pointer outputDeviceVtbl; |
||||
|
||||
var int pos, top; |
||||
var string text; |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,32 @@
|
||||
/** |
||||
* One of the original Unflect files. |
||||
* Copyright 2022-2023 EliotVU |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class Unflect extends Object |
||||
abstract; |
||||
|
||||
struct Int16 { |
||||
var byte h, l; |
||||
}; |
||||
|
||||
struct Int64 { |
||||
var int h, l; |
||||
}; |
||||
|
||||
defaultproperties { |
||||
} |
@ -0,0 +1,409 @@
|
||||
/** |
||||
* Config class for storing map lists. |
||||
* Copyright 2020 bibibi |
||||
* 2020-2023 Shtoyan |
||||
* 2023 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class UnflectApi extends AcediaObject; |
||||
|
||||
//! This API offers advanced reflection capabilities for Unreal Script. |
||||
//! |
||||
//! Currently, the API supports the ability to replace the code of existing functions with |
||||
//! custom code. |
||||
//! This can greatly simplify the process of bug fixing and hooking into game events. |
||||
|
||||
/// A variable responsible for replacing function code in real-time. |
||||
/// This variable is used to support dynamic function replacement/patching and event interception |
||||
/// at runtime. |
||||
var private UFunctionCast functionCaster; |
||||
|
||||
/// Maps lower case function name (specifies by the full path "package.class.functionName") |
||||
/// to a `FunctionRule` that completely describes how it was replaced |
||||
var private HashTable completedReplacements; |
||||
/// Maps lower case function name (specifies by the full path "package.class.functionName") |
||||
/// to the `ByteArrayBox` with that function's original code. |
||||
var private HashTable originalScriptCodes; |
||||
|
||||
var private LoggerAPI.Definition warnSameFunction; |
||||
var private LoggerAPI.Definition warnOverridingReplacement, errFailedToFindFunction; |
||||
var private LoggerAPI.Definition errReplacementWithoutSources, errCannotCreateReplacementRule; |
||||
|
||||
protected function Constructor() { |
||||
functionCaster = new class'UFunctionCast'; |
||||
completedReplacements = _.collections.EmptyHashTable(); |
||||
originalScriptCodes = _.collections.EmptyHashTable(); |
||||
} |
||||
|
||||
protected function Finalizer() { |
||||
_.memory.Free(completedReplacements); |
||||
_.memory.Free(originalScriptCodes); |
||||
completedReplacements = none; |
||||
originalScriptCodes = none; |
||||
functionCaster = none; |
||||
} |
||||
|
||||
/// Reverts the replacement of the function's code, restoring its original behavior. |
||||
/// |
||||
/// The function to be reverted should be specified using its full path, in the format |
||||
/// "package.class.functionName" (e.g. "KFMod.KFPawn.TossCash"). |
||||
/// |
||||
/// It's worth noting that several function replacements do not stack. |
||||
/// Even if [`ReplaceFunction()`] was called multiple times in a row to replace the same function, |
||||
/// this method will cancel all the changes at once. |
||||
/// |
||||
/// This method returns true if the specified function was previously replaced and has now been |
||||
/// successfully reverted. |
||||
/// |
||||
/// # Errors |
||||
/// |
||||
/// If the specified function cannot be found (but [`functionName`] isn't `none`), or if |
||||
/// UnflectApi has not yet replaced it with any other function, this method will log an error. |
||||
public final function bool RevertFunction(BaseText functionName) { |
||||
local bool result; |
||||
local FunctionReplacement storedReplacement; |
||||
local ByteArrayBox storedSources; |
||||
local Text functionNameLowerCase; |
||||
local UFunction functionInstance; |
||||
local SideEffect sideEffect; |
||||
|
||||
if (functionName == none) { |
||||
return false; |
||||
} |
||||
functionNameLowerCase = functionName.LowerCopy(); |
||||
storedReplacement = FunctionReplacement(completedReplacements.GetItem(functionNameLowerCase)); |
||||
if (storedReplacement != none) { |
||||
storedSources = ByteArrayBox(originalScriptCodes.GetItem(functionNameLowerCase)); |
||||
if (storedSources == none) { |
||||
_.logger.Auto(errReplacementWithoutSources).Arg(functionNameLowerCase.Copy()); |
||||
} else { |
||||
functionInstance = FindFunction(functionNameLowerCase); |
||||
if (functionInstance != none) { |
||||
functionInstance.script = storedSources.Get(); |
||||
completedReplacements.RemoveItem(functionNameLowerCase); |
||||
sideEffect = storedReplacement.GetSideEffect(); |
||||
_.sideEffects.RemoveInstance(sideEffect); |
||||
result = true; |
||||
} else { |
||||
_.logger.Auto(errFailedToFindFunction).Arg(functionNameLowerCase.Copy()); |
||||
} |
||||
} |
||||
} |
||||
_.memory.Free4(storedReplacement, functionNameLowerCase, storedSources, sideEffect); |
||||
return result; |
||||
} |
||||
|
||||
/// Reverts the replacement of the function's code, restoring its original behavior. |
||||
/// |
||||
/// The function to be reverted should be specified using its full path, in the format |
||||
/// "package.class.functionName" (e.g. "KFMod.KFPawn.TossCash"). |
||||
/// |
||||
/// It's worth noting that several function replacements do not stack. |
||||
/// Even if [`ReplaceFunction()`] was called multiple times in a row to replace the same function, |
||||
/// this method will cancel all the changes at once. |
||||
/// |
||||
/// This method returns true if the specified function was previously replaced and has now been |
||||
/// successfully reverted. |
||||
/// |
||||
/// # Errors |
||||
/// |
||||
/// If the specified function cannot be found (but [`functionName`] isn't `none`), or if |
||||
/// UnflectApi has not yet replaced it with any other function, this method will log an error. |
||||
public final function bool RevertFunction_S(string functionName) { |
||||
local bool result; |
||||
local MutableText wrapper; |
||||
|
||||
wrapper = _.text.FromStringM(functionName); |
||||
result = RevertFunction(wrapper); |
||||
_.memory.Free(wrapper); |
||||
return result; |
||||
} |
||||
|
||||
/// Determines whether the specified function has been replaced by UnflectApi. |
||||
/// |
||||
/// The function to be checked should be specified using its full path, in the format |
||||
/// "package.class.functionName" (e.g. "KFMod.KFPawn.TossCash"). |
||||
/// |
||||
/// If the function has been replaced, this method will return `true`; |
||||
/// otherwise, it will return `false`. |
||||
public final function bool IsFunctionReplaced(BaseText functionName) { |
||||
local bool result; |
||||
local Text functionNameLowerCase; |
||||
|
||||
if (functionName == none) { |
||||
return false; |
||||
} |
||||
functionNameLowerCase = functionName.LowerCopy(); |
||||
result = completedReplacements.HasKey(functionNameLowerCase); |
||||
_.memory.Free(functionNameLowerCase); |
||||
return result; |
||||
} |
||||
|
||||
/// Determines whether the specified function has been replaced by UnflectApi. |
||||
/// |
||||
/// The function to be checked should be specified using its full path, in the format |
||||
/// "package.class.functionName" (e.g. "KFMod.KFPawn.TossCash"). |
||||
/// |
||||
/// If the function has been replaced, this method will return `true`; |
||||
/// otherwise, it will return `false`. |
||||
public final function bool IsFunctionReplaced_S(string functionName) { |
||||
local bool result; |
||||
local MutableText wrapper; |
||||
|
||||
wrapper = _.text.FromStringM(functionName); |
||||
result = IsFunctionReplaced(wrapper); |
||||
_.memory.Free(wrapper); |
||||
return result; |
||||
} |
||||
|
||||
/// Replaces one function with another by modifying its script code in real-time. |
||||
/// |
||||
/// The reason for replacement must be specified and will serve as a human-readable explanation |
||||
/// for a [SideEffect] associated with the replacement. |
||||
/// |
||||
/// If you need to replace a function in a class, follow these steps: |
||||
/// |
||||
/// 1. Create a new class that extends the class in which the function you want to replace is |
||||
/// located. |
||||
/// 2. Declare that function in the created class. |
||||
/// 3. **DO NOT** change the function declaration and argument types/amount. |
||||
/// 4. **DO NOT** create new local variables, as this can cause random crashes. |
||||
/// If you need additional variables, make them global and access them using the |
||||
/// `class'myNewClass'.default.myNewVariable` syntax. |
||||
/// 5. If you want to call or override parent code, make sure to always specify the desired parent |
||||
/// class name. |
||||
/// For example, use `super(TargetClass).PostBeginPlay()` instead of `super.PostBeginPlay()`. |
||||
/// This will prevent runaway loop crashes. |
||||
/// 6. Make your edits to the function's code, and then call the replacement function: |
||||
/// ```unrealscript |
||||
/// _.unflect.ReplaceFunction( |
||||
/// "package.class.targetFunction", |
||||
/// "myNewPackage.myNewClass.newFunction"); |
||||
/// ``` |
||||
/// |
||||
/// Following these steps will help ensure that your code changes are compatible with the rest of |
||||
/// the codebase and do not cause unexpected crashes. |
||||
/// |
||||
/// # Errors |
||||
/// |
||||
/// This method can log error messages in cases where: |
||||
/// |
||||
/// * The specified function(s) cannot be found. |
||||
/// * An attempt is made to replace a function with itself. |
||||
/// * An attempt is made to replace a function that has already been replaced. |
||||
public final function bool ReplaceFunction( |
||||
BaseText oldFunction, |
||||
BaseText newFunction, |
||||
BaseText replacementReason |
||||
) { |
||||
local bool result; |
||||
local Text oldFunctionLowerCase, newFunctionLowerCase; |
||||
|
||||
if (oldFunction == none) return false; |
||||
if (newFunction == none) return false; |
||||
|
||||
oldFunctionLowerCase = oldFunction.LowerCopy(); |
||||
newFunctionLowerCase = newFunction.LowerCopy(); |
||||
result = _replaceFunction(oldFunctionLowerCase, newFunctionLowerCase); |
||||
if (result) { |
||||
RecordNewReplacement(oldFunctionLowerCase, newFunctionLowerCase, replacementReason); |
||||
} |
||||
_.memory.Free2(oldFunctionLowerCase, newFunctionLowerCase); |
||||
return result; |
||||
} |
||||
|
||||
/// Replaces one function with another by modifying its script code in real-time. |
||||
/// |
||||
/// The reason for replacement must be specified and will serve as a human-readable explanation |
||||
/// for a [SideEffect] associated with the replacement. |
||||
/// |
||||
/// If you need to replace a function in a class, follow these steps: |
||||
/// |
||||
/// 1. Create a new class that extends the class in which the function you want to replace is |
||||
/// located. |
||||
/// 2. Declare that function in the created class. |
||||
/// 3. **DO NOT** change the function declaration and argument types/amount. |
||||
/// 4. **DO NOT** create new local variables, as this can cause random crashes. |
||||
/// If you need additional variables, make them global and access them using the |
||||
/// `class'myNewClass'.default.myNewVariable` syntax. |
||||
/// 5. If you want to call or override parent code, make sure to always specify the desired parent |
||||
/// class name. |
||||
/// For example, use `super(TargetClass).PostBeginPlay()` instead of `super.PostBeginPlay()`. |
||||
/// This will prevent runaway loop crashes. |
||||
/// 6. Make your edits to the function's code, and then call the replacement function: |
||||
/// ```unrealscript |
||||
/// _.unflect.ReplaceFunction( |
||||
/// "package.class.targetFunction", |
||||
/// "myNewPackage.myNewClass.newFunction"); |
||||
/// ``` |
||||
/// |
||||
/// Following these steps will help ensure that your code changes are compatible with the rest of |
||||
/// the codebase and do not cause unexpected crashes. |
||||
/// |
||||
/// # Errors |
||||
/// |
||||
/// This method can log error messages in cases where: |
||||
/// |
||||
/// * The specified function(s) cannot be found. |
||||
/// * An attempt is made to replace a function with itself. |
||||
/// * An attempt is made to replace a function that has already been replaced. |
||||
public final function bool ReplaceFunction_S( |
||||
string oldFunction, |
||||
string newFunction, |
||||
string replacementReason |
||||
) { |
||||
local Text oldWrapper, newWrapper, reasonWrapper; |
||||
local bool result; |
||||
|
||||
oldWrapper = _.text.FromString(oldFunction); |
||||
newWrapper = _.text.FromString(newFunction); |
||||
reasonWrapper = _.text.FromString(replacementReason); |
||||
result = ReplaceFunction(oldWrapper, newWrapper, reasonWrapper); |
||||
_.memory.Free3(oldWrapper, newWrapper, reasonWrapper); |
||||
return result; |
||||
} |
||||
|
||||
// Does actual work for function replacement. |
||||
// Arguments are assumed to be not `none` and in lower case. |
||||
private final function bool _replaceFunction(Text oldFunctionLowerCase, Text newFunctionLowerCase) { |
||||
local ByteArrayBox initialCode; |
||||
local UFunction replace, with; |
||||
|
||||
replace = FindFunction(oldFunctionLowerCase); |
||||
if (replace == none) { |
||||
_.logger.Auto(errFailedToFindFunction).Arg(oldFunctionLowerCase.Copy()); |
||||
return false; |
||||
} |
||||
with = FindFunction(newFunctionLowerCase); |
||||
if (with == none) { |
||||
_.logger.Auto(errFailedToFindFunction).Arg(newFunctionLowerCase.Copy()); |
||||
return false; |
||||
} |
||||
if (replace == with) { |
||||
_.logger.Auto(warnSameFunction).Arg(oldFunctionLowerCase.Copy()); |
||||
return false; |
||||
} |
||||
// Remember old code, if haven't done so yet. |
||||
// Since we attempt it on each replacement, the first recorded `script` value will be |
||||
// the initial code. |
||||
if (!originalScriptCodes.HasKey(oldFunctionLowerCase)) { |
||||
initialCode = _.box.ByteArray(replace.script); |
||||
originalScriptCodes.SetItem(oldFunctionLowerCase, initialCode); |
||||
_.memory.Free(initialCode); |
||||
} |
||||
replace.script = with.script; |
||||
return true; |
||||
} |
||||
|
||||
// Arguments assumed to be not `none` and in lower case. |
||||
private final function UFunction FindFunction(Text functionNameLowerCase) { |
||||
local string stringName; |
||||
|
||||
stringName = functionNameLowerCase.ToString(); |
||||
// We need to make sure functions are loaded before performing the replacement. |
||||
DynamicLoadObject(GetClassName(stringName), class'class', true); |
||||
return functionCaster.Cast(function(FindObject(stringName, class'Function'))); |
||||
} |
||||
|
||||
// Arguments are assumed to be not `none`. |
||||
// `oldFunctionLowerCase` and `newFunctionLowerCase` are assumed to be in lower case. |
||||
private final function RecordNewReplacement( |
||||
Text oldFunctionLowerCase, |
||||
Text newFunctionLowerCase, |
||||
BaseText replacementReason |
||||
) { |
||||
local SideEffect oldSideEffect, newSideEffect; |
||||
local FunctionReplacement oldRule, newRule; |
||||
|
||||
// Remove old `FunctionReplacement`, if there is any |
||||
oldRule = FunctionReplacement(completedReplacements.GetItem(oldFunctionLowerCase)); |
||||
if (oldRule != none) { |
||||
_.logger |
||||
.Auto(warnOverridingReplacement) |
||||
.Arg(oldFunctionLowerCase.Copy()) |
||||
.Arg(oldRule.GetReplacerFunctionName()) |
||||
.Arg(newFunctionLowerCase.Copy()); |
||||
oldSideEffect = oldRule.GetSideEffect(); |
||||
_.sideEffects.RemoveInstance(oldSideEffect); |
||||
_.memory.Free2(oldRule, oldSideEffect); |
||||
} |
||||
// Create replacement instance |
||||
newSideEffect = MakeSideEffect(oldFunctionLowerCase, newFunctionLowerCase, replacementReason); |
||||
newRule = class'FunctionReplacement'.static |
||||
.Make(oldFunctionLowerCase, newFunctionLowerCase, newSideEffect); |
||||
completedReplacements.SetItem(oldFunctionLowerCase, newRule); |
||||
if (newRule == none) { |
||||
_.logger |
||||
.Auto(errCannotCreateReplacementRule) |
||||
.Arg(oldFunctionLowerCase.Copy()) |
||||
.Arg(newFunctionLowerCase.Copy()); |
||||
} |
||||
} |
||||
|
||||
// Arguments are assumed to be not `none`. |
||||
// `oldFunctionLowerCase` and `newFunctionLowerCase` are assumed to be in lower case. |
||||
private final function SideEffect MakeSideEffect( |
||||
Text oldFunctionLowerCase, |
||||
Text newFunctionLowerCase, |
||||
BaseText replacementReason |
||||
) { |
||||
local SideEffect sideEffect; |
||||
local MutableText status; |
||||
|
||||
// Add side effect |
||||
status = oldFunctionLowerCase.MutableCopy(); |
||||
status.Append(P(" -> ")); |
||||
status.Append(newFunctionLowerCase); |
||||
sideEffect = _.sideEffects.Add( |
||||
P("Changed function's code"), |
||||
replacementReason, |
||||
P("AcediaCore"), |
||||
P("UnflectAPI"), |
||||
status |
||||
); |
||||
_.memory.Free(status); |
||||
return sideEffect; |
||||
} |
||||
|
||||
// Get the "package + dot + class" string for DynamicLoadObject() |
||||
private final static function string GetClassName(string input) { |
||||
local array<string> parts; |
||||
|
||||
// create an array |
||||
Split(input, ".", parts); |
||||
|
||||
// state functions |
||||
if (parts.length < 3) { |
||||
return ""; |
||||
} |
||||
if (parts.length == 4) { |
||||
ReplaceText(input, "." $ parts[2], ""); |
||||
ReplaceText(input, "." $ parts[3], ""); |
||||
} else { |
||||
ReplaceText(input, "." $ parts[2], ""); |
||||
} |
||||
return input; |
||||
} |
||||
|
||||
|
||||
defaultproperties { |
||||
warnOverridingReplacement = (l=LOG_Error,m="Attempt to replace a function `%1` with function `%3` after it has already been replaced with `%2`.") |
||||
warnSameFunction = (l=LOG_Error,m="Attempt to replace a function `%1` with itself.") |
||||
errFailedToFindFunction = (l=LOG_Error,m="`UnflectApi` has failed to find function `%1`.") |
||||
errReplacementWithoutSources = (l=LOG_Error,m="Cannot restore function `%1` - its initial source code wasn't preserved. This most likely means that it wasn't yet replaced.") |
||||
errCannotCreateReplacementRule = (l=LOG_Error,m="`Cannot create new rule for replacing function `%1` with `%2` even though code was successfully replaces. This should happen, please report this.") |
||||
} |
Loading…
Reference in new issue