Merge pull request 'AltControl implementation' (#7) from altcontrol into main

Reviewed-on: #7
This commit is contained in:
Daniella / Tove 2024-05-31 23:58:11 +02:00
commit c7a00e80eb
25 changed files with 439 additions and 42 deletions

View file

@ -1,5 +1,8 @@
package com.baseband.client;
import com.baseband.client.event.Event;
import com.baseband.client.event.remote.RemoteEvent;
import com.baseband.client.event.remote.RemoteEventManager;
import com.baseband.client.registry.Updater;
import com.baseband.client.event.EventManager;
import com.baseband.client.event.FMLEventHandler;
@ -26,6 +29,7 @@ public class BaseBand {
private static final ArrayList<KeyBind> keyBinds = new ArrayList<>();
public static String buildString = "Broadway";
public static final EventManager eventManager = new EventManager();
public static final RemoteEventManager remoteEventManager = new RemoteEventManager();
public static final FMLEventHandler fmlEventHandlerInstance = new FMLEventHandler();
public static boolean enabled = true;
private static boolean finishedDisabling = false;
@ -68,6 +72,14 @@ public class BaseBand {
ChatUtil.print(text);
}
public static <T extends Event> T publish(T event) {
if(event instanceof RemoteEvent)
remoteEventManager.publish((RemoteEvent) event);
else
eventManager.publish(event);
return event;
}
public void onInit() {
String[] banned = {"0836f9ee-4c5d-45e4-b39c-954880199acb", "18f87992-6459-43b8-8d26-6a4c74bee7ec", "f84e53c5-9143-4934-860c-ea44c9ad0e9f"};
@ -158,4 +170,13 @@ public class BaseBand {
action.run(f);
return true;
}
public static <T extends Feature> boolean ifFeatureEnabled(Class<? extends T> clazz, FeatureAction<T> action) {
T f = getFeature(clazz);
//noinspection ConstantValue
if(f == null || !f.enabled)
return false;
action.run(f);
return true;
}
}

View file

@ -18,11 +18,13 @@ public class Setup {
public final String Name = "BaseBand";
public final String RegistryFilename = "baseband.db";
public final int Port = 7258;
public final Feature[] Features = new Feature[] {
// REQUIRED
new Client(),
// OPTIONAL
new AltControl(),
new AutoEat(),
//new AutoKill(),
//AutoKill.INSTANCE.autoHit,

View file

@ -27,10 +27,10 @@ public class EventManager {
int p1 = 0;
int p2 = 0;
if (o1.method.isAnnotationPresent(Priority.class)) {
p1 = o1.method.getDeclaredAnnotation(Priority.class).priority();
p1 = o1.method.getDeclaredAnnotation(Priority.class).value();
}
if (o2.method.isAnnotationPresent(Priority.class)) {
p2 = o2.method.getDeclaredAnnotation(Priority.class).priority();
p2 = o2.method.getDeclaredAnnotation(Priority.class).value();
}
return Integer.compare(p1, p2);
});

View file

@ -55,9 +55,10 @@ public class FMLEventHandler {
EntityPlayerSP playerLastTick = null;
@SubscribeEvent
public void tick(TickEvent.ClientTickEvent event) {
BaseBand.remoteEventManager.onTick();
if(mc.world == null || mc.player == null) {
if(playerLastTick != null) {
BaseBand.eventManager.publish(new PlayerDestroyEvent(playerLastTick));
BaseBand.publish(new PlayerDestroyEvent(playerLastTick));
}
playerLastTick = null;
return;

View file

@ -5,5 +5,5 @@ import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Priority {
int priority() default 0;
int value() default 0;
}

View file

@ -0,0 +1,33 @@
package com.baseband.client.event.events;
import com.baseband.client.BaseBand;
import com.baseband.client.event.remote.RemoteEvent;
import com.baseband.client.event.remote.RemoteEventManager;
import com.baseband.client.util.interact.BlockUtils;
import com.baseband.client.util.misc.GlobalUtil;
import com.baseband.client.util.type.Selection;
import de.tudbut.obj.Save;
import java.util.Arrays;
public class SelectEvent extends RemoteEvent {
@Save
public Selection selection;
public SelectEvent(Selection selection) {
this.selection = selection;
}
public Selection getMySelection() {
if(selection == null)
return null;
if(BaseBand.remoteEventManager.isConnected()) {
RemoteEventManager manager = BaseBand.remoteEventManager;
Selection[] splitSelection = BlockUtils.splitSelection1D(selection, manager.getPeers());
GlobalUtil.LOGGER.info("Split selection: {}", Arrays.toString(splitSelection));
if(splitSelection.length > manager.getID())
return splitSelection[manager.getID()];
else return null;
} else return selection;
}
}

View file

@ -0,0 +1,7 @@
package com.baseband.client.event.remote;
import com.baseband.client.event.Event;
// any event which might be useful to others and can be serialized (@Save) should extend RemoteEvent
public class RemoteEvent extends Event {
}

View file

@ -0,0 +1,204 @@
package com.baseband.client.event.remote;
import com.baseband.client.BaseBand;
import com.baseband.client.Setup;
import com.baseband.client.event.remote.events.RemoteInitEvent;
import com.baseband.client.feature.client.AltControl;
import de.tudbut.io.TypedInputStream;
import de.tudbut.io.TypedOutputStream;
import de.tudbut.parsing.JSON;
import de.tudbut.parsing.TCN;
import de.tudbut.tools.ConfigSaverTCN2;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class RemoteEventManager {
private static final Setup Setup = com.baseband.client.Setup.get();
public Socket head = null;
public final ArrayList<Socket> clients = new ArrayList<>();
public ServerSocket server = null;
private int id = -1, peers = 0;
private final Queue<RemoteEvent> toSend = new LinkedList<>();
private final Queue<RemoteEvent> toProcess = new LinkedList<>();
public void connect(String ip) {
try {
if(isConnected())
end();
server = new ServerSocket(Setup.Port);
initServer();
} catch (IOException e) {
try {
head = new Socket(ip, Setup.Port);
initClient();
} catch (IOException ex) {
BaseBand.notify("[Remote] Failed to start.");
}
}
}
public void end() {
try {
if(server != null) {
server.close();
}
if(head != null) {
head.close();
}
} catch (IOException ignored) {}
clients.clear();
id = -1;
peers = 0;
server = null;
head = null;
BaseBand.ifFeatureEnabled(AltControl.class, f -> f.setEnabled(false));
}
Thread thread;
private void initServer() {
thread = new Thread(this::runServer, Setup.Name + " RemoteEvent server");
thread.start();
}
private void initClient() {
thread = new Thread(this::runClient, Setup.Name + " RemoteEvent client");
thread.start();
}
private void runServer() {
try {
id = 0;
peers = 1;
server.setSoTimeout(1);
while(server != null) {
try {
Socket s = server.accept();
InputStream stream = s.getInputStream();
if(stream.read() == 'B' && stream.read() == 'B') {
s.getOutputStream().write('B');
s.getOutputStream().write('B');
s.getOutputStream().flush();
s.setSoTimeout(1);
clients.add(s);
publish(new RemoteInitEvent(clients.size() + 1));
BaseBand.notify("[Remote] Client connected.");
} else {
s.close();
}
} catch (InterruptedIOException ignored) {}
while(!toSend.isEmpty()) {
String stringEvent = JSON.write((TCN)ConfigSaverTCN2.write(toSend.poll(), false, false));
for (int i = 0; i < clients.size(); i++) {
Socket client = clients.get(i);
try {
client.getOutputStream().write(i + 1);
new TypedOutputStream(client.getOutputStream()).writeString(stringEvent);
client.getOutputStream().flush();
} catch (IOException e) {
clients.remove(i--).close();
}
}
}
for (int i = 0; i < clients.size(); i++) {
Socket client = clients.get(i);
try {
if (client.getInputStream().read() == -1) {
clients.remove(i--).close();
continue;
}
String stringEvent = new TypedInputStream(client.getInputStream()).readString();
toProcess.offer((RemoteEvent) ConfigSaverTCN2.read(JSON.read(stringEvent), null));
} catch (InterruptedIOException ignored) {
} catch (IOException e) {
BaseBand.notify("[Remote] A peer disconnected.");
clients.remove(i--).close();
}
}
}
} catch (IOException | JSON.JSONFormatException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
private void runClient() {
try {
OutputStream o = head.getOutputStream();
TypedOutputStream tos = new TypedOutputStream(o);
InputStream i = head.getInputStream();
TypedInputStream tis = new TypedInputStream(i);
o.write('B');
o.write('B');
o.flush();
if(!(i.read() == 'B' && i.read() == 'B')) {
BaseBand.notify("[Remote] Unable to connect.");
end();
return;
}
BaseBand.notify("[Remote] Connected.");
while(head != null) {
while(!toSend.isEmpty()) {
o.write(id);
String stringEvent = JSON.write((TCN)ConfigSaverTCN2.write(toSend.poll(), false, false));
tos.writeString(stringEvent);
o.flush();
}
try {
if ((id = i.read()) == -1) {
BaseBand.notify("[Remote] Connection ended.");
end();
return;
}
String stringEvent = tis.readString();
toProcess.offer((RemoteEvent) ConfigSaverTCN2.read(JSON.read(stringEvent), null));
} catch (InterruptedIOException ignored) {}
}
} catch (IOException | JSON.JSONFormatException | ClassNotFoundException e) {
BaseBand.notify("[Remote] Connection ended.");
end();
}
}
public void publish(RemoteEvent event) {
toSend.offer(event);
toProcess.offer(event);
}
public boolean isConnected() {
return id != -1 && (head != null || server != null);
}
public void onTick() {
while(!toProcess.isEmpty()) {
RemoteEvent event = toProcess.poll();
if(event instanceof RemoteInitEvent) {
peers = ((RemoteInitEvent) event).peers;
BaseBand.notify("[Remote] Peers connected: " + peers + ".");
}
else {
BaseBand.eventManager.publish(event);
}
}
if(!isConnected())
toSend.clear();
}
public int getID() {
return id;
}
public int getPeers() {
return peers;
}
}

View file

@ -0,0 +1,13 @@
package com.baseband.client.event.remote.events;
import com.baseband.client.event.remote.RemoteEvent;
import de.tudbut.obj.Save;
public class RemoteInitEvent extends RemoteEvent {
@Save
public int peers;
public RemoteInitEvent(int peers) {
this.peers = peers;
}
}

View file

@ -0,0 +1,14 @@
package com.baseband.client.event.remote.events;
import com.baseband.client.event.remote.RemoteEvent;
import de.tudbut.obj.Save;
public class RemoteSendMessageEvent extends RemoteEvent {
@Save
public String message;
public RemoteSendMessageEvent(String message) {
this.message = message;
}
}

View file

@ -14,7 +14,7 @@ public enum Category {
COMMAND("Commands", Command.class),
RENDER("Render", Render.class),
CHAT("Chat", Chat.class),
CLIENT("Client", Client.class),
CLIENT("Client", ClientCategory.class),
COMBAT("Combat", Combat.class),
MOVEMENT("Movement", Movement.class),
WORLD("World", World.class)

View file

@ -7,5 +7,5 @@ import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Client {
public @interface ClientCategory {
}

View file

@ -0,0 +1,57 @@
package com.baseband.client.feature.client;
import com.baseband.client.BaseBand;
import com.baseband.client.event.remote.events.RemoteSendMessageEvent;
import com.baseband.client.feature.Feature;
import com.baseband.client.feature.category.ClientCategory;
import com.baseband.client.registry.annotation.Config;
import com.baseband.client.registry.annotation.Description;
import com.baseband.client.registry.annotation.Gate;
import com.baseband.client.registry.annotation.Trigger;
import com.baseband.client.util.adapt.Marker;
import com.baseband.client.util.interact.ChatUtil;
@ClientCategory
public class AltControl extends Feature {
@Config("Server IP")
@Description("The IP of the AltControl server to use.")
@Gate(1)
public String ip = "localhost";
@Marker(1)
boolean notEnabled = true;
public void onEnable() {
notEnabled = false;
BaseBand.remoteEventManager.connect(ip);
}
@Trigger("Stop")
@Gate(M_ENABLED)
public void onDisable() {
notEnabled = true;
BaseBand.remoteEventManager.end();
}
public void onRemoteSendChat(RemoteSendMessageEvent event) {
BaseBand.notify("[AltControl] Received a message to send.");
ChatUtil.simulateSend(event.message, false);
}
@Override
public void onCommand(String[] args) {
if(!enabled)
ChatUtil.print("Please first enable " + this + ".");
if(args.length == 0) {
ChatUtil.print("syntax: " + Client.prefix + this + " <message...>");
return;
}
BaseBand.publish(new RemoteSendMessageEvent(String.join(" ", args)));
}
@Override
public String toString() {
return "AltControl";
}
}

View file

@ -3,7 +3,7 @@ package com.baseband.client.feature.client;
import com.baseband.client.registry.annotation.Config;
import com.baseband.client.registry.annotation.Range;
import com.baseband.client.feature.Feature;
import com.baseband.client.feature.category.Client;
import com.baseband.client.feature.category.ClientCategory;
import com.baseband.client.util.adapt.FieldFinder;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
@ -18,7 +18,7 @@ import net.minecraftforge.fml.common.gameevent.TickEvent;
import javax.annotation.Nonnull;
import java.util.List;
@Client
@ClientCategory
public class AutoReconnect extends Feature {
@Config("Time (s)")

View file

@ -1,12 +1,16 @@
package com.baseband.client.feature.client;
import baritone.api.process.IBaritoneProcess;
import baritone.api.selection.ISelectionManager;
import baritone.api.utils.BetterBlockPos;
import com.baseband.client.event.events.SelectEvent;
import com.baseband.client.feature.Feature;
import com.baseband.client.feature.category.Client;
import com.baseband.client.feature.category.ClientCategory;
import com.baseband.client.util.baritone.BaritoneManager;
import com.baseband.client.util.baritone.BaritonePresenceManager;
import com.baseband.client.util.type.Selection;
@Client
@ClientCategory
public class Baritone extends Feature {
@Override
@ -31,6 +35,14 @@ public class Baritone extends Feature {
text = "Baritone §7[" + (baritoneProcess == null ? "IDLE" : baritoneProcess.displayName()) + "]";
}
public void onSelect(SelectEvent event) {
ISelectionManager selectionManager = BaritoneManager.getBaritone().getSelectionManager();
selectionManager.removeAllSelections();
Selection selection = event.getMySelection();
if(selection != null)
selectionManager.addSelection(new BetterBlockPos(selection.pos1), new BetterBlockPos(selection.pos2));
}
@Override
public boolean canExist() {
return BaritonePresenceManager.IS_BARITONE_PRESENT;

View file

@ -1,5 +1,6 @@
package com.baseband.client.feature.client;
import com.baseband.client.feature.category.ClientCategory;
import com.baseband.client.registry.Configuration;
import com.baseband.client.registry.annotation.Config;
import com.baseband.client.registry.annotation.KeyBound;
@ -21,7 +22,7 @@ import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@com.baseband.client.feature.category.Client
@ClientCategory
public class Client extends Feature {
public static EntityLivingBase entityTarget = null;

View file

@ -3,7 +3,7 @@ package com.baseband.client.feature.client;
import com.baseband.client.BaseBand;
import com.baseband.client.registry.annotation.Config;
import com.baseband.client.feature.Feature;
import com.baseband.client.feature.category.Client;
import com.baseband.client.feature.category.ClientCategory;
import com.baseband.client.registry.PlayerDB;
import com.baseband.client.util.interact.ChatUtil;
import de.tudbut.parsing.TCN;
@ -17,7 +17,7 @@ import org.lwjgl.input.Mouse;
import java.util.Objects;
import java.util.function.Consumer;
@Client
@ClientCategory
public class MidClick extends Feature {
public enum PlayerAction {
@ -99,8 +99,13 @@ public class MidClick extends Feature {
doCheck();
}
boolean down = false;
private void doCheck() {
if(Mouse.isButtonDown(2)) {
if(down)
return;
down = true;
RayTraceResult hover = mc.objectMouseOver;
if(hover.entityHit != null) {
if(hover.entityHit instanceof EntityPlayer) {
@ -113,7 +118,8 @@ public class MidClick extends Feature {
}
}
blockAction.action.accept(hover.getBlockPos());
}
} else down = false;
}
@Override

View file

@ -7,14 +7,14 @@ import com.baseband.client.registry.annotation.Gate;
import com.baseband.client.registry.annotation.Range;
import com.baseband.client.event.events.PlayerDestroyEvent;
import com.baseband.client.feature.Feature;
import com.baseband.client.feature.category.Client;
import com.baseband.client.feature.category.ClientCategory;
import com.baseband.client.util.adapt.FieldFinder;
import com.baseband.client.util.adapt.Marker;
import net.minecraft.client.Minecraft;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
@Client
@ClientCategory
public class Timer extends Feature {
@ -77,7 +77,7 @@ public class Timer extends Feature {
}
float t = (adjust ? tps : 20) * m;
setGameTimer(t);
text = "Timer" + (enabled ? " §7[TPS: " + (Math.round(t * 100f) / 100f) + "]" : "");
text = "Timer" + (enabled ? "§7 [TPS: " + (Math.round(t * 100f) / 100f) + "]" : "");
}
}

View file

@ -90,7 +90,7 @@ public class Speed extends Feature {
//Time will tell.
//higher priority than default, so we can get explosion packets before velocity fucks with them
@Priority(priority = 2)
@Priority(value = 2)
public void onPacketReceive(PacketEvent.Read event) {
if(notIngame()) {
return;

View file

@ -1,6 +1,8 @@
package com.baseband.client.feature.world;
import com.baseband.client.BaseBand;
import com.baseband.client.event.Priority;
import com.baseband.client.event.events.SelectEvent;
import com.baseband.client.feature.Feature;
import com.baseband.client.feature.category.World;
import com.baseband.client.util.type.Selection;
@ -13,18 +15,35 @@ public class Select extends Feature {
public static Selection selection;
public static void select(BlockPos b) {
if(begin != null) {
end = b;
BaseBand.notify("Position 2: " + b.getX() + " " + b.getY() + " " + b.getZ());
selection = new Selection(begin, end);
return;
}
if(end != null) {
if(end != null || begin == null) {
begin = b;
end = null;
BaseBand.notify("Selection reset.");
BaseBand.notify("Position 1: " + b.getX() + " " + b.getY() + " " + b.getZ());
BaseBand.publish(new SelectEvent(null));
return;
}
end = b;
BaseBand.notify("Position 2: " + b.getX() + " " + b.getY() + " " + b.getZ());
BaseBand.publish(new SelectEvent(new Selection(begin, end)));
}
@Priority(Integer.MAX_VALUE)
public void onSelect(SelectEvent event) {
selection = event.getMySelection();
}
@Override
public void onTick() {
if(selection != null)
text = this + "§7 [" + selection.area() + "B]";
else
text = this + "§7 [0B]";
}
@Override
public void onDisable() {
text = this.toString();
}
@Override

View file

@ -55,13 +55,13 @@ public class MixinEntityPlayerSP extends AbstractClientPlayer {
@Inject(method = {"onUpdateWalkingPlayer"}, at = {@At(value = "HEAD")})
private void preMotion(CallbackInfo info) {
BaseBand.eventManager.publish(new MotionUpdateEvent.Pre());
BaseBand.publish(new MotionUpdateEvent.Pre());
}
@Inject(method = {"onUpdateWalkingPlayer"}, at = {@At(value = "RETURN")})
private void postMotion(CallbackInfo info) {
BaseBand.eventManager.publish(new MotionUpdateEvent.Post());
BaseBand.publish(new MotionUpdateEvent.Post());
}
@ -70,7 +70,7 @@ public class MixinEntityPlayerSP extends AbstractClientPlayer {
@Inject(method = "move", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/entity/AbstractClientPlayer;move(Lnet/minecraft/entity/MoverType;DDD)V"), cancellable = true)
public void move(MoverType p_70091_1_, double p_70091_2_, double p_70091_4_, double p_70091_6_, CallbackInfo ci) {
MoveEvent event = new MoveEvent(p_70091_1_, p_70091_2_, p_70091_4_, p_70091_6_);
if(!BaseBand.eventManager.publish(event).isCancelled())
if(!BaseBand.publish(event).isCancelled())
super.move(event.type, event.x, event.y, event.z);
ci.cancel();
}

View file

@ -15,13 +15,13 @@ public class MixinNetworkManager {
@Inject(method = "channelRead0(Lio/netty/channel/ChannelHandlerContext;Lnet/minecraft/network/Packet;)V", at = @At("HEAD"), cancellable = true)
public void channelRead0(ChannelHandlerContext p_channelRead0_1_, Packet<?> p_channelRead0_2_, CallbackInfo ci) {
if (BaseBand.eventManager.publish(new PacketEvent.Read(p_channelRead0_2_)).isCancelled())
if (BaseBand.publish(new PacketEvent.Read(p_channelRead0_2_)).isCancelled())
ci.cancel();
}
@Inject(method = "sendPacket(Lnet/minecraft/network/Packet;)V", at = @At("HEAD"), cancellable = true)
public void channelRead0(Packet<?> packetIn, CallbackInfo ci) {
if (BaseBand.eventManager.publish(new PacketEvent.Send(packetIn)).isCancelled())
if (BaseBand.publish(new PacketEvent.Send(packetIn)).isCancelled())
ci.cancel();
}
}

View file

@ -55,7 +55,7 @@ public class BaritoneManager {
@Override
public boolean isActive() {
BaritoneEvent event = new BaritoneEvent();
BaseBand.eventManager.publish(event);
BaseBand.publish(event);
return event.isCancelled();
}

View file

@ -8,17 +8,19 @@ public class BlockUtils {
// returned amount may be smaller!
public static Selection[] splitSelection1D(Selection selection, int amount) {
Vec3i size = selection.size();
int sideLength = (int) ((selection.longestSideH() - 1) / amount + 1); // rounded up
Selection[] selections = new Selection[(int) (selection.longestSideH() / sideLength)];
int selSideLength = selection.longestSideH();
int sectionSideLength = (selSideLength - 1) / amount + 1; // rounded up
System.out.println(sectionSideLength);
Selection[] selections = new Selection[(int) ((selSideLength - 1) / sectionSideLength + 1)]; // rounded up
int xSideLength = 0;
int zSideLength = 0;
if(size.getX() >= size.getZ())
xSideLength = sideLength;
xSideLength = sectionSideLength;
else
zSideLength = sideLength;
for (int i = 0; i <= selections.length; i++) {
zSideLength = sectionSideLength;
for (int i = 0; i < selections.length; i++) {
BlockPos pos1 = selection.pos1.add(xSideLength * i, 0, zSideLength * i);
BlockPos pos2 = selection.pos2.add(Math.min(xSideLength * (i + 1), size.getX()), 0, Math.min(zSideLength * (i + 1), size.getZ()));
BlockPos pos2 = selection.pos1.add((xSideLength == 0 ? size.getX() - 1 : Math.min(xSideLength * (i + 1), selSideLength) - 1), size.getY() - 1, (zSideLength == 0 ? size.getZ() - 1 : Math.min(zSideLength * (i + 1) - 1, selSideLength - 1)));
selections[i] = new Selection(pos1, pos2);
}
return selections;

View file

@ -18,16 +18,21 @@ public class Selection {
public long area() {
Vec3i size = size();
return (long) size.getX() * size.getX() * size.getX();
return (long) size.getX() * size.getY() * size.getZ();
}
public long longestSide() {
Vec3i size = pos2.subtract(pos1);
public int longestSide() {
Vec3i size = size();
return Math.max(Math.max(size.getX(), size.getY()), size.getZ());
}
public long longestSideH() {
Vec3i size = pos2.subtract(pos1);
public int longestSideH() {
Vec3i size = size();
return Math.max(size.getX(), size.getZ());
}
@Override
public String toString() {
return "Selection[" + area() + "B from " + pos1 + " to " + pos2 + "]";
}
}