Browse Source

Add `UnflectApi`

pull/12/head
Anton Tarasenko 2 years ago
parent
commit
ced0f1dd99
  1. 2
      config/AcediaSystem.ini
  2. 277
      sources/BaseAPI/API/SideEffects/SideEffect.uc
  3. 194
      sources/BaseAPI/API/SideEffects/SideEffectAPI.uc
  4. 76
      sources/BaseAPI/API/Unflect/FunctionReplacement.uc
  5. 30
      sources/BaseAPI/API/Unflect/Tests/MockInitialClass.uc
  6. 33
      sources/BaseAPI/API/Unflect/Tests/MockReplacerClass.uc
  7. 87
      sources/BaseAPI/API/Unflect/Tests/TEST_Unflect.uc
  8. 29
      sources/BaseAPI/API/Unflect/TypeCast.uc
  9. 49
      sources/BaseAPI/API/Unflect/UClass.uc
  10. 30
      sources/BaseAPI/API/Unflect/UClassCast.uc
  11. 28
      sources/BaseAPI/API/Unflect/UField.uc
  12. 30
      sources/BaseAPI/API/Unflect/UFieldCast.uc
  13. 35
      sources/BaseAPI/API/Unflect/UFunction.uc
  14. 30
      sources/BaseAPI/API/Unflect/UFunctionCast.uc
  15. 41
      sources/BaseAPI/API/Unflect/UProperty.uc
  16. 30
      sources/BaseAPI/API/Unflect/UPropertyCast.uc
  17. 30
      sources/BaseAPI/API/Unflect/UState.uc
  18. 30
      sources/BaseAPI/API/Unflect/UStateCast.uc
  19. 48
      sources/BaseAPI/API/Unflect/UStruct.uc
  20. 30
      sources/BaseAPI/API/Unflect/UStructCast.uc
  21. 28
      sources/BaseAPI/API/Unflect/UTextBuffer.uc
  22. 32
      sources/BaseAPI/API/Unflect/Unflect.uc
  23. 409
      sources/BaseAPI/API/Unflect/UnflectApi.uc
  24. 4
      sources/BaseAPI/Global.uc
  25. 1
      sources/ClientAPI/ClientAcediaAdapter.uc
  26. 1
      sources/Manifest.uc
  27. 1
      sources/ServerAPI/ServerAcediaAdapter.uc
  28. 2
      sources/ServerAPI/ServerGlobal.uc

2
config/AcediaSystem.ini

@ -3,7 +3,7 @@
[AcediaCore.AcediaEnvironment]
debugMode=false
[AcediaCore.SideEffects]
[AcediaCore.ServerSideEffects]
; Acedia requires adding its own `GameRules` to listen to many different
; game events.
; It's normal for a mod to add its own game rules: game rules are

277
sources/BaseAPI/API/SideEffects/SideEffect.uc

@ -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 {
}

194
sources/BaseAPI/API/SideEffects/SideEffectAPI.uc

@ -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 {
}

76
sources/BaseAPI/API/Unflect/FunctionReplacement.uc

@ -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 {
}

30
sources/BaseAPI/API/Unflect/Tests/MockInitialClass.uc

@ -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 {
}

33
sources/BaseAPI/API/Unflect/Tests/MockReplacerClass.uc

@ -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 {
}

87
sources/BaseAPI/API/Unflect/Tests/TEST_Unflect.uc

@ -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"
}

29
sources/BaseAPI/API/Unflect/TypeCast.uc

@ -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 {
}

49
sources/BaseAPI/API/Unflect/UClass.uc

@ -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 {
}

30
sources/BaseAPI/API/Unflect/UClassCast.uc

@ -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 {
}

28
sources/BaseAPI/API/Unflect/UField.uc

@ -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 {
}

30
sources/BaseAPI/API/Unflect/UFieldCast.uc

@ -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 {
}

35
sources/BaseAPI/API/Unflect/UFunction.uc

@ -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 {
}

30
sources/BaseAPI/API/Unflect/UFunctionCast.uc

@ -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 {
}

41
sources/BaseAPI/API/Unflect/UProperty.uc

@ -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 {
}

30
sources/BaseAPI/API/Unflect/UPropertyCast.uc

@ -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 {
}

30
sources/BaseAPI/API/Unflect/UState.uc

@ -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 {
}

30
sources/BaseAPI/API/Unflect/UStateCast.uc

@ -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 {
}

48
sources/BaseAPI/API/Unflect/UStruct.uc

@ -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 {
}

30
sources/BaseAPI/API/Unflect/UStructCast.uc

@ -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 {
}

28
sources/BaseAPI/API/Unflect/UTextBuffer.uc

@ -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 {
}

32
sources/BaseAPI/API/Unflect/Unflect.uc

@ -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 {
}

409
sources/BaseAPI/API/Unflect/UnflectApi.uc

@ -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.")
}

4
sources/BaseAPI/Global.uc

@ -35,6 +35,7 @@ class Global extends Object;
// For getting instance of [`Global`] from any object.
var protected Global myself;
var public UnflectApi unflect;
var public SideEffectAPI sideEffects;
var public RefAPI ref;
var public BoxAPI box;
@ -76,7 +77,7 @@ public final static function Global GetInstance() {
protected function Initialize() {
// Special case that we cannot spawn with memory API since it obviously
// does not exist yet!
memory = new class'MemoryAPI';
memory = new class'MemoryAPI';
memory._constructor();
// `TextAPI` and `CollectionsAPI` need to be loaded before `LoggerAPI`
sideEffects = SideEffectAPI(memory.Allocate(class'SideEffectAPI'));
@ -85,6 +86,7 @@ protected function Initialize() {
text = TextAPI(memory.Allocate(class'TextAPI'));
math = MathAPI(memory.Allocate(class'MathAPI'));
collections = CollectionsAPI(memory.Allocate(class'CollectionsAPI'));
unflect = UnflectAPI(memory.Allocate(class'UnflectAPI'));
json = JsonAPI(memory.Allocate(class'JsonAPI'));
logger = LoggerAPI(memory.Allocate(class'LoggerAPI'));
color = ColorAPI(memory.Allocate(class'ColorAPI'));

1
sources/ClientAPI/ClientAcediaAdapter.uc

@ -27,7 +27,6 @@ var public const class<InteractionAPI> clientInteractionAPIClass;
defaultproperties
{
sideEffectAPIClass = class'KF1_SideEffectAPI'
timeAPIClass = class'KF1_TimeAPI'
dbAPIClass = class'DBAPI'
clientUnrealAPIClass = class'KF1_ClientUnrealAPI'

1
sources/Manifest.uc

@ -59,4 +59,5 @@ defaultproperties
testCases(30) = class'TEST_AcediaConfig'
testCases(31) = class'TEST_UTF8EncoderDecoder'
testCases(32) = class'TEST_AvariceStreamReader'
testCases(33) = class'TEST_Unflect'
}

1
sources/ServerAPI/ServerAcediaAdapter.uc

@ -30,7 +30,6 @@ var public const class<MutatorAPI> serverMutatorAPIClass;
defaultproperties
{
sideEffectAPIClass = class'KF1_SideEffectAPI'
timeAPIClass = class'KF1_TimeAPI'
dbAPIClass = class'DBAPI'
serverUnrealAPIClass = class'KF1_ServerUnrealAPI'

2
sources/ServerAPI/ServerGlobal.uc

@ -83,7 +83,7 @@ public final function bool ConnectServerLevelCore()
return false;
}
Initialize();
if (class'SideEffects'.default.allowHookingIntoMutate)
if (class'ServerSideEffects'.default.allowHookingIntoMutate)
{
_ = class'Global'.static.GetInstance();
class'InfoQueryHandler'.static.StaticConstructor();

Loading…
Cancel
Save