-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add dynamic mixins #12
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mattco98
force-pushed
the
dynamic-mixins
branch
10 times, most recently
from
June 5, 2023 00:28
9dce01a
to
9bea427
Compare
mattco98
force-pushed
the
dynamic-mixins
branch
9 times, most recently
from
June 16, 2023 15:09
cf4d110
to
652f3ac
Compare
Some typing info on the mixin lib, will probably use this as a base for a wiki entry: Typings
declare const Java: {
/**
* Returns the Java class associated with [className]. During mixin application, does
* not allow loading mapped class type (types in the `net.minecraft` or
* `com.mojang.blaze3d` packages).
*/
type(className: string): any /* Class */;
};
declare const Opcodes: {
[key: string]: number;
};
declare const Condition: {
[key: string]: object;
};
////////////////////////
// Descriptor helpers //
////////////////////////
declare const void_: 'V';
declare const boolean: 'Z';
declare const char: 'C';
declare const byte: 'B';
declare const short: 'S';
declare const int: 'I';
declare const float: 'F';
declare const long: 'J';
declare const double: 'D';
declare function desc(options: {
owner?: string;
name?: string;
/**
* Used for field descriptors to specify the field type. Cannot be used with `args` or `ret`.
*/
type?: string;
args?: string[];
ret?: string;
}): string;
/**
* Helper for creating class descriptors. Instead of writing
* "La/b/c;", this allows you to write "J`a.b.c`"
*/
declare function J(strings: string[], exprs: string[]): string;
////////////
// Mixins //
////////////
/**
* The entry point to a mixin. Note that CT mixins for the most part do not support
* single-target injections, hence why target is not an array.
*/
declare const Mixin: IMixin;
/**
* See org.spongepowered.asm.mixin.Mixin for more information on each parameter.
* The string type is treated as `{ target: <string value> }`.
*/
interface IMixin {
new (options: string | {
target: string;
priority?: number;
remap?: boolean;
}): IMixin;
/**
* See org.spongepowered.asm.mixin.injection.Inject for more information on each parameter.
*/
inject(options: {
method: string;
id?: string;
slices?: Slice | Slice[];
at?: At | At[];
cancellable?: boolean;
locals?: Local | Local[];
remap?: boolean;
require?: number;
expect?: number;
allow?: number;
constraints?: string;
}): IMixinCallback;
/**
* See org.spongepowered.asm.mixin.injection.Redirect for more information on each parameter.
*/
redirect(options: {
method: string;
slice?: Slice;
at?: At;
args?: string[];
returnType: string;
locals?: Local | Local[];
require?: number;
expect?: number;
allow?: number;
constraints?: string;
}): IMixinCallbackReturnable;
/**
* See org.spongepowered.asm.mixin.injection.ModifyArg for more information on each parameter.
*/
modifyArg(options: {
method: string;
slice?: Slice;
at?: At;
index?: number;
/**
* Will cause all parameters of the targeted method to be captured and passed to the callback
* instead of just the parameter given by `index`.
*/
captureAllParams?: boolean;
locals?: Local | Local[];
remap?: boolean;
expect?: number;
allow?: number;
constraints?: string;
}): IMixinCallbackReturnable;
/**
* See org.spongepowered.asm.mixin.injection.ModifyArgs for more information on each parameter.
* Note that because the argument types will be abstracted away at runtime behind the single
* `Args` object, special care must be taken to provide the correct types. For example, all numbers in
* Rhino are `double`s, so if you need to set a float argument, you would need to do something
* along the lines of `args.set(0, new java.lang.Float(1))`.
*/
modifyArgs(options: {
method: string;
slice?: Slice;
at: At;
locals?: Local | Local[];
require?: number;
remap?: boolean;
expect?: number;
allow?: number;
constraints?: string;
}): IMixinCallbackReturnable;
/**
* See org.spongepowered.asm.mixin.injection.ModifyConstant for more information on each parameter.
*/
modifyConstant(options: {
method: string;
slice?: Slice | Slice[];
constant: Constant;
locals?: Local | Local[];
require?: number;
remap?: boolean;
expect?: number;
allow?: number;
constraints?: string;
}): IMixinCallbackReturnable;
/**
* See com.llamalad7.mixinextras.injector.ModifyExpressionValue for more information on each parameter.
*/
modifyExpressionValue(options: {
method: string;
at: At;
slices?: Slice | Slice[];
locals?: Local | Local[];
remap?: boolean;
require?: number;
expect?: number;
allow?: number;
}): IMixinCallbackReturnable;
/**
* See com.llamalad7.mixinextras.injector.ModifyReceiver for more information on each parameter.
*/
modifyReceiver(options: {
method: string;
at: At;
slices?: Slice | Slice[];
locals?: Local | Local[];
remap?: boolean;
require?: number;
expect?: number;
allow?: number;
}): IMixinCallbackReturnable;
/**
* See com.llamalad7.mixinextras.injector.ModifyReturnValue for more information on each parameter.
*/
modifyReturnValue(options: {
method: string;
at: At;
slice?: Slice | Slice[];
locals?: Local | Local[];
remap?: boolean;
require?: number;
expect?: number;
allow?: number;
}): IMixinCallbackReturnable;
/**
* See org.spongepowered.asm.mixin.injection.ModifyVariable for more information on each parameter.
*/
modifyVariable(options: {
method: string;
at: At;
slice?: Slice;
/**
* See [Local]
*/
print?: boolean;
/**
* See [Local]
*/
ordinal?: number;
/**
* See [Local]
*/
index?: number;
/**
* See [Local]
*/
type?: string;
/**
* See [Local]
*/
parameterName?: string;
locals?: Local | Local[];
remap?: boolean;
require?: number;
expect?: number;
allow?: number;
constraints?: string;
}): IMixinCallbackReturnable
/**
* See com.llamalad7.mixinextras.injector.WrapOperation for more information on each parameter.
*/
wrapOperation(options: {
method: string;
at?: At;
constant?: Constant;
slice?: Slice | Slice[];
locals?: Local | Local[];
remap?: boolean;
require?: number;
expect?: number;
allow?: number;
}): IMixinCallbackReturnable;
/**
* See com.llamalad7.mixinextras.injector.WrapWithCondition for more information on each parameter.
*/
wrapWithCondition(options: {
method: string;
at: At;
slice?: Slice | Slice[];
locals?: Local | Local[];
remap?: boolean;
require?: number;
expect?: number;
allow?: number;
}): IMixinCallbackReturnable;
/**
* Makes the given field public. If [isMutable] is true, the field will have its `final` modifier removed.
*/
widenField(name: string, isMutable?: boolean): void;
/**
* Makes the given method public. If [isMutable] is true, the method will have its `final` modifier removed.
*/
widenMethod(name: string, isMutable?: boolean): void;
};
/**
* Object returned from mixin injectors that allows registering (and re-registering)
* the callback which will be called when the mixin injector is called. `args` will
* always start with the receiver object (the `this` value of the class that the
* mixin is applied to) unless the mixin is targeting a static context.
*/
interface IMixinCallback {
attach(callback: (...args: any[]) => void): void;
release(): void;
};
/**
* Similar to [IMixinCallback], but requires the callback to return a value. The exact
* type of that value depends on the mixin.
*/
interface IMixinCallbackReturnable {
attach(callback: (...args: any[]) => any): void;
release(): void;
};
/**
* See org.spongepowered.asm.mixin.injection.At for more information on each
* option. Note that the string type is treated as `{ value: <string value> }`.
*/
interface At {
new (options: string | {
value: string;
id?: string;
slice?: string;
by?: number;
args?: string[];
target?: string;
ordinal?: number;
opcode?: number;
remap?: boolean;
}): At;
};
/**
* See org.spongepowered.asm.mixin.injection.Slice for more information on each option.
*/
interface Slice {
new (options: {
id?: string;
from?: At;
to?: At;
}): Slice;
};
type LocalOptions =
string | // treated as { parameterName: <string value> }
number | // treated as { index: <number value> }
{
/**
* Will cause the local variable table (LVT) of this method to be printed. Causes
* this injection to not apply.
*
* TODO: Is the entire injection aborted? Or just this local attaching
*/
print?: boolean;
/**
* The name of a parameter to capture. This cannot be used with non-parameter
* locals, hence the name. Cannot be used with `index`, `ordinal`, or `type`.
*/
parameterName?: string;
/**
* The index of the variable in the LVT. Cannot be used with `ordinal`. If used,
* `type` must also be included.
*/
index?: number;
/**
* The ordinal of the variable of the given type in the method. Cannot be used
* with `index`. If used, `type` must also be included.
*/
ordinal?: number;
/**
* The types of variable being captured. Pairs with either `index` or `ordinal`.
* Note that you do not need to specify mutable type wrappers manually; instead,
* prefer setting the `mutable` flag below to `true`.
*/
type?: string;
/**
* Whether the local should be captured mutably. This will cause the local variable
* type to be wrapped in the appropriate
* [mutable wrapper](https://github.com/LlamaLad7/MixinExtras/tree/master/src/main/java/com/llamalad7/mixinextras/sugar/ref).
*/
mutable?: boolean;
};
interface Local {
new (options: LocalOptions): Local;
};
/**
* See org.spongepowered.asm.mixin.injection.Constant for more information on each option.
*/
interface Constant {
new (options: {
nullValue?: boolean;
intValue?: number;
floatValue?: number;
longValue?: number;
doubleValue?: number;
stringValue?:
/**
* The entry point to a mixin. Note that CT mixins for the most part do not support
* single-target injections, hence why target is not an array.
*/
declare const Mixin: IMixin;
/**
* See org.spongepowered.asm.mixin.Mixin for more information on each parameter.
* The string type is treated as `{ target: <string value> }`.
*/
interface IMixin {
new (options: string | {
target: string;
priority?: number;
remap?: boolean;
}): IMixin;
/**
* See org.spongepowered.asm.mixin.injection.Inject for more information on each parameter.
*/
inject(options: {
method: string;
id?: string;
slices?: Slice | Slice[];
at?: At | At[];
cancellable?: boolean;
locals?: Local | Local[];
remap?: boolean;
require?: number;
expect?: number;
allow?: number;
constraints?: string;
}): IMixinCallback;
/**
* See org.spongepowered.asm.mixin.injection.Redirect for more information on each parameter.
*/
redirect(options: {
method: string;
slice?: Slice;
at?: At;
args?: string[];
returnType: string;
locals?: Local | Local[];
require?: number;
expect?: number;
allow?: number;
constraints?: string;
}): IMixinCallbackReturnable;
/**
* See org.spongepowered.asm.mixin.injection.ModifyArg for more information on each parameter.
*/
modifyArg(options: {
method: string;
slice?: Slice;
at?: At;
index?: number;
/**
* Will cause all parameters of the targeted method to be captured and passed to the callback
* instead of just the parameter given by `index`.
*/
captureAllParams?: boolean;
locals?: Local | Local[];
remap?: boolean;
expect?: number;
allow?: number;
constraints?: string;
}): IMixinCallbackReturnable;
/**
* See org.spongepowered.asm.mixin.injection.ModifyArgs for more information on each parameter.
* Note that because the argument types will be abstracted away at runtime behind the single
* `Args` object, special care must be taken to provide the correct types. For example, all numbers in
* Rhino are `double`s, so if you need to set a float argument, you would need to do something
* along the lines of `args.set(0, new java.lang.Float(1))`.
*/
modifyArgs(options: {
method: string;
slice?: Slice;
at: At;
locals?: Local | Local[];
require?: number;
remap?: boolean;
expect?: number;
allow?: number;
constraints?: string;
}): IMixinCallbackReturnable;
/**
* See com.llamalad7.mixinextras.injector.ModifyExpressionValue for more information on each parameter.
*/
modifyExpressionValue(options: {
method: string;
at: At;
slices?: Slice | Slice[];
locals?: Local | Local[];
remap?: boolean;
require?: number;
expect?: number;
allow?: number;
}): IMixinCallbackReturnable;
/**
* See com.llamalad7.mixinextras.injector.ModifyReceiver for more information on each parameter.
*/
modifyReceiver(options: {
method: string;
at: At;
slices?: Slice | Slice[];
locals?: Local | Local[];
remap?: boolean;
require?: number;
expect?: number;
allow?: number;
}): IMixinCallbackReturnable;
/**
* Makes the given field public. If [isMutable] is true, the field will have its `final` modifier removed.
*/
widenField(name: string, isMutable?: boolean): void;
/**
* Makes the given method public. If [isMutable] is true, the method will have its `final` modifier removed.
*/
widenMethod(name: string, isMutable?: boolean): void;
};
/**
* Object returned from mixin injectors that allows registering (and re-registering)
* the callback which will be called when the mixin injector is called. `args` will
* always start with the receiver object (the `this` value of the class that the
* mixin is applied to) unless the mixin is targeting a static context.
*/
interface IMixinCallback {
attach(callback: (...args: any[]) => void): void;
release(): void;
};
/**
* Similar to [IMixinCallback], but requires the callback to return a value. The exact
* type of that value depends on the mixin.
*/
interface IMixinCallbackReturnable {
attach(callback: (...args: any[]) => any): void;
release(): void;
};
/**
* See org.spongepowered.asm.mixin.injection.At for more information on each
* option. Note that the string type is treated as `{ value: <string value> }`.
*/
interface At {
new (options: string | {
value: string;
id?: string;
slice?: string;
by?: number;
args?: string[];
target?: string;
ordinal?: number;
opcode?: number;
remap?: boolean;
}): At;
};
/**
* See org.spongepowered.asm.mixin.injection.Slice for more information on each option.
*/
interface Slice {
new (options: {
id?: string;
from?: At;
to?: At;
}): Slice;
};
type LocalOptions =
string | // treated as { parameterName: <string value> }
number | // treated as { index: <number value> }
{
/**
* Will cause the local variable table (LVT) of this method to be printed. Causes
* this injection to not apply.
*
* TODO: Is the entire injection aborted? Or just this local attaching
*/
print?: boolean;
/**
* The name of a parameter to capture. This cannot be used with non-parameter
* locals, hence the name. Cannot be used with `index`, `ordinal`, or `type`.
*/
parameterName?: string;
/**
* The index of the variable in the LVT. Cannot be used with `ordinal`. If used,
* `type` must also be included.
*/
index?: number;
/**
* The ordinal of the variable of the given type in the method. Cannot be used
* with `index`. If used, `type` must also be included.
*/
ordinal?: number;
/**
* The types of variable being captured. Pairs with either `index` or `ordinal`.
* Note that you do not need to specify mutable type wrappers manually; instead,
* prefer setting the `mutable` flag below to `true`.
*/
type?: string;
/**
* Whether the local should be captured mutably. This will cause the local variable
* type to be wrapped in the appropriate
* [mutable wrapper](https://github.com/LlamaLad7/MixinExtras/tree/master/src/main/java/com/llamalad7/mixinextras/sugar/ref).
*/
mutable?: boolean;
};
interface Local {
new (options: LocalOptions): Local;
}; string;
classValue?: string;
ordinal?: number;
slice?: string;
expandZeroConditions?: boolean;
log?: boolean;
}): Constant;
}; |
mattco98
force-pushed
the
dynamic-mixins
branch
9 times, most recently
from
June 17, 2023 20:18
5c3b1ac
to
9003e59
Compare
Wrote a wiki for this: https://github.com/ChatTriggers/ctjs/wiki/Dynamic-Mixins |
Fixes a few issues: proper .equals/.hashCode for arrays, fix for tagged template literals not working, and allows undefined to convert to Java's `null`.
Other things are interested in this
This is a bit cleaner, and keep the Mapping class nice and focused
This will be replaced by a more robust dynamic mixin system
MC does it on its own, but it happens in its main() function, which doesn't get called until after mixin application. So we mimic its behavior ourselves.
Also adds mixinProvidedLibs.js, which will contain the glue between the JSLoader and the JS language.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR removed the old scuffed mixin system and replaces it with a fully dynamic mixin system. The user can specify fully custom injections and subscribe to them in the main module code. Example:
metadata.json
:mixins.js
:index.js
:Note that the user must specify the descriptors of any locals they wish to capture in addition to the
LocalCapture
enum value, as I don't think it is possible to determine this information automatically without copy pasting a lot of Mixin codeTODO:
@Inject
)Handle multiple injection points (i.e. multipleNot worth the trouble@At
annotations)Can Mixin throw errors that don't crash the game? If so, buffer these (and any other early-phase errors) so we can print them to the console laterNew console buffers errors automatically@Local
capture method parameters as well? If so, remove auto-parameter capture from all the injectors and just let the user tell us about the parameters they care about.