2525
2626import com .fox2code .foxloader .dependencies .DependencyHelper ;
2727import com .fox2code .foxloader .launcher .BuildConfig ;
28+ import com .fox2code .foxloader .utils .io .IOUtils ;
2829import org .objectweb .asm .ClassReader ;
2930import org .objectweb .asm .ClassWriter ;
3031import org .objectweb .asm .Opcodes ;
3334import java .io .*;
3435import java .nio .file .Files ;
3536import java .util .Locale ;
37+ import java .util .zip .ZipEntry ;
38+ import java .util .zip .ZipInputStream ;
39+ import java .util .zip .ZipOutputStream ;
3640
3741// Allows us to do stuff java doesn't allow us to do.
3842public final class FoxLoaderPatches implements Opcodes {
3943 private static final boolean DEBUG = false ;
4044
4145 public static void main (String [] args ) throws IOException {
42- patchFoxLoaderClassesDir (new File (args [0 ]));
43- patchFoxLoaderResourcesDir (new File (args [1 ]));
46+ if (args .length == 2 ) {
47+ patchFoxLoaderClassesDir (new File (args [0 ]));
48+ patchFoxLoaderResourcesDir (new File (args [1 ]));
49+ } else {
50+ patchFoxLoaderFinalJar (new File (args [0 ]));
51+ }
4452 }
4553
4654 public static void patchFoxLoaderClassesDir (File classesDir ) throws IOException {
@@ -51,6 +59,85 @@ public static void patchFoxLoaderResourcesDir(File resourcesDir) throws IOExcept
5159 generateDependenciesResources (resourcesDir );
5260 }
5361
62+ // Patching this late allow FoxLoader to inline BuildConfig values while having mods not inline them.
63+ public static void patchFoxLoaderFinalJar (File foxloaderJar ) throws IOException {
64+ File backup = new File (foxloaderJar .getParentFile (), foxloaderJar .getName () + ".bak" );
65+ if (backup .exists () && !backup .delete ()) {
66+ throw new IOException ("Failed to delete backup jar" );
67+ }
68+ if (!foxloaderJar .renameTo (backup )) {
69+ throw new IOException ("Failed to rename loader jar" );
70+ }
71+ try (ZipInputStream zipInputStream = new ZipInputStream (
72+ new BufferedInputStream (Files .newInputStream (backup .toPath ())));
73+ ZipOutputStream zipOutputStream = new ZipOutputStream (
74+ new BufferedOutputStream (Files .newOutputStream (foxloaderJar .toPath ())))) {
75+ zipOutputStream .setLevel (9 );
76+ ZipEntry input ;
77+ while ((input = zipInputStream .getNextEntry ()) != null ) {
78+ if (input .isDirectory ()) continue ;
79+ String pathName = input .getName ();
80+ ZipEntry output = new ZipEntry (pathName );
81+ zipOutputStream .putNextEntry (output );
82+ if ("com/fox2code/foxloader/launcher/BuildConfig.class" .equals (pathName )) {
83+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream ();
84+ IOUtils .copy (zipInputStream , byteArrayOutputStream );
85+ zipOutputStream .write (patchBuildConfig (byteArrayOutputStream .toByteArray ()));
86+ } else {
87+ IOUtils .copy (zipInputStream , zipOutputStream );
88+ }
89+ zipOutputStream .closeEntry ();
90+ zipInputStream .closeEntry ();
91+ }
92+ zipOutputStream .finish ();
93+ }
94+ if (backup .exists () && !backup .delete ()) {
95+ throw new IOException ("Failed to delete backup jar" );
96+ }
97+ }
98+
99+ private static byte [] patchBuildConfig (byte [] buildConfig ) {
100+ ClassNode classNode = new ClassNode ();
101+ new ClassReader (buildConfig ).accept (classNode , 0 );
102+ if (TransformerUtils .findMethod (classNode , "<clinit>" ) != null ) {
103+ throw new RuntimeException ("<clinit>()V detected in BuildConfig!" );
104+ }
105+ MethodNode clInit = new MethodNode (ACC_STATIC , "<clinit>" , "()V" , null , null );
106+ for (FieldNode fieldNode : classNode .fields ) {
107+ if (fieldNode .access != (ACC_PUBLIC | ACC_STATIC | ACC_FINAL )) {
108+ throw new RuntimeException ("Invalid field access: " + fieldNode .access );
109+ }
110+ AbstractInsnNode constInsn ;
111+ switch (fieldNode .desc ) {
112+ case "Ljava/lang/String;" :
113+ constInsn = new LdcInsnNode (fieldNode .value );
114+ break ;
115+ case "I" :
116+ case "Z" :
117+ if (fieldNode .value instanceof Integer ) {
118+ constInsn = TransformerUtils .getNumberInsn ((Integer ) fieldNode .value );
119+ } else if (fieldNode .value instanceof Boolean ) {
120+ constInsn = TransformerUtils .getBooleanInsn ((Boolean ) fieldNode .value );
121+ } else {
122+ throw new RuntimeException ("WTF???" );
123+ }
124+ break ;
125+ default :
126+ throw new RuntimeException ("Unsupported desc: \" " + fieldNode .desc + "\" " );
127+ }
128+ fieldNode .value = null ;
129+ clInit .instructions .add (constInsn );
130+ clInit .instructions .add (new FieldInsnNode (PUTSTATIC ,
131+ "com/fox2code/foxloader/launcher/BuildConfig" ,
132+ fieldNode .name , fieldNode .desc ));
133+ }
134+ clInit .instructions .add (new InsnNode (RETURN ));
135+ classNode .methods .add (clInit );
136+ ClassWriter classWriter = new ClassWriter (ClassWriter .COMPUTE_MAXS );
137+ classNode .accept (classWriter );
138+ return classWriter .toByteArray ();
139+ }
140+
54141 private static void patchFoxClassLoader (File classesDir ) throws IOException {
55142 File foxClassLoader = new File (classesDir , "com/fox2code/foxloader/launcher/FoxClassLoader.class" );
56143 File source = foxClassLoader ;
@@ -139,7 +226,7 @@ private static void generateDependenciesResources(File resourcesDir) throws IOEx
139226 private static void generateDependenciesResource (
140227 File file , DependencyHelper .Dependency [] dependencies ) throws IOException {
141228 try (PrintStream printStream = new PrintStream (file , "UTF-8" )) {
142- printStream .println ("# Autogenerated by FoxLoader " + BuildConfig .FOXLOADER_VERSION );
229+ printStream .println ("# Autogenerated by " + BuildConfig .FOXLOADER_DISPLAY );
143230 for (DependencyHelper .Dependency dependency : dependencies ) {
144231 printStream .println (dependency .javaSupport + " " + dependency .name );
145232 }
0 commit comments