Decompile JAR, Modify it, Recompile again? - java

I have an older Jar file which I have to modify in order to get it to work.
I have already decompiled it with jd-gui and changed the parts that needed change. I would now like to know what I have to do in order to get a jar back?
I guess that I have to add the libraries, but there is no extra lib directory.
How do I put everything together?
#Stephen C
"Therefore, when you make your changes and recompile, the resulting ".class" file could have unexpected "
How exactly is this relevant? I only want the function of the Jar file and not a 1:1 copy of it.
The Jar file is unsigned. What exactly would be the reason to sign a Jar file anyway? If I start it from another program?

If you are missing dependencies after decompiling the entire jar, that could mean the jar did not include them. When you create a jar, you can choose not to include dependencies in it, but this assumes that they will be available on the classpath at runtime. Check if there are any relationships with other jar files on the classpath when you run the original jar. See this answer for details on how to achieve this. Once you have identified all the dependencies, you can easily compile the jar from an IDE such as IntelliJ/Eclipse or from command line.
As a side note, if the changes you would like to make to the jar are minor or isolated I recommend editing the bytecode (much easier for small edits). I prefer this bytecode editor.
If decompilation fails on some parts of the code, the functionality of the jar will not be restored upon recompilation. In this case, instead of going through all available decompilers and hoping that you can decompile that code, I suggest you identify the set of classes which you are trying to edit, modify the rest of the classes such that they compile and do not have any side effects on the classes which are of interest to you (do not delete anything that is referenced by these) and compile the jar (even if this isn't the jar you want). You can then extract only the class files which you wanted to modify from the jar, and overwrite them in the original jar. This will only work if your changes didn't have any side effects.

If you are asking how to create a JAR:
You can do it using a build tool such as Maven, Gradle, Ant and so on.
You can do it using a Java IDE
You can do it using the command line jar tool; see How to create a JAR file from the official Oracle Java tutorials.
But it there is no guarantee that what you are doing will actually work. Here are a couple of the problems.
If the original JAR file was signed, you won't be able to re-sign the new JAR ... unless you have the private key that was used when signing.
Decompilation is often inaccurate. There is no guarantee that the Java code that it produces is a correct representation of the original class. Therefore, when you make your changes and recompile, the resulting ".class" file could have unexpected / unwanted differences relative to the original.
I guess that I have to add the libraries, but there is no extra lib directory.
I'm not sure what you mean by that. The (new) dependencies of the JAR don't necessarily need to be in the JAR itself. It depends on what kind of JAR it is; e.g. is it a so-called "executable" JAR that you launch using java -jar my.jar ....

Use jar plugins for gradle or maven for example, or build it with intelliJ, here's an answer in another post: How to build jars from IntelliJ properly?

Since I had trouble getting the .jar file completely recompiled and working with my application, I wanted to share here the steps that were missing in the previous answers.
In my specific case, to make things as easy as possible, I wanted to only modify one .java file in the result of the decompilation and update it directly in the .jar file.
I started from the answer from Paulo Ebermann on a similar question, and I ended up with:
javac -source 8 -target 8 -d classdir -cp \
"\
path/to/dep1.jar; \
path/to/dep2.jar; \
path/to/myoriginal.jar \
" path/to/my/java/class.java
cd classdir
jar -uf path/to/myoriginal.jar path/to/my/java/class.class
Notes:
the path/to/myoriginal.jar which is in the path of the dependencies, this forces the java compiler to look for missing classses in the original .jar file rather than in the local decompiled code (which would require recompilation and more dependencies) and therefore I have intentionally left out the . path in the list of class paths
the -source 8 -target 8 which were required to force the class to compile for the appropriate version of the runtime machine.
the -uf which indicates that the jar tool should only update the file with the provided class

Related

Why don't I see code when viewing included Jar libraries in Eclipse?

There doesn't seem to be any code there. I expected to see class declarations so I could see what the code does but instead there's some
Are they somehow precompiled? What's the difference between included Jar file and a pure code?
You are looking at .class file, which is a generated when you compile .java file. To see what the program does, you have to look into .java file. You can refer official java documentation for that.
A .jar file is packaged file with .jar extension, it contains compiled java files and their class files. This file is usually imported into projects to use the classes defined in that package.
You can use "jar xf jar-file" command in command-prompt/terminal to extract the files from jar and look into the package.
A JAR will normally contain compiled class files. It may also contain source files or there may be a separate JAR that contains the source files, but not necessarily so.
If you want to use the library in your project, then a JAR of compiled class files is what you want. If you want the source code, then you'll have to see if it is available from wherever you downloaded this from. If all you want is to see how to use the classes, then probably what you want are JavaDocs for the library you are using. This is an HTML based API documentation.
Well, this is because you haven't attached any source for the mentioned dnsns.jar. You can attach source to existing JAR files in Eclipse. Refer this SO post: Is there an easy way to attach source in Eclipse?
For this specific dnsns.jar, it is part of your JRE, and if you are not able to see its source in your IDE, then it means that the Java that you have setup in IDE lacks the source. If your installation does not have the source (src.zip), then you can get it manually as mentioned on this SO post: Where to find Java JDK Source Code?
EDIT: Alternatively, you can also use a decompiler (e.g. http://jd.benow.ca/) to reverse engineer the source from byte code, though, it may not be the exact match to the original source but you can understand the overall idea. You can add the decompiler as the default program for opening .class files in eclipse Windows > Preferences > General > Editors > File Associations. Select *.class filter and add your decompiler as the program. Though, it is not as clean as attaching the source to JAR, but may work if you don't have access to source.
EDIT2: About your question
What's the difference between included Jar file and a pure code
Eclipse can find .java files for your own code because obviously they are in your workspace. But when you add a JAR file as library, it may have the source (.java) in it or not. If the source is available, eclipse can display it by default. If not, you have to add it manually.

Difference between sourcepath and just specifying the source file?

What is the point of the javac -sourcepath option?
Say you have a file Ball.java, and file Eight.java, there seems to be no difference between running:
javac Eight.java lib/pool/Ball.java
and
javac Eight.java -sourcepath lib
The classpath makes sense to me as it means you just need to distribute class files to developers who won't need to mess with the source, but I would think you're only likely to have/need the source if it's your own/an internal project so can't understand why it's needed.
Do people have large repositories of source code instead of classes?
From further testing I've confirmed that -sourcepath doesn't even take the last modified date of the source into account like the classpath, so couldn't even conceivably save time during building. As when using -classpath, this will build the .class file if it doesn't exist and the source does, or if the .java file is newer than the .class file, whereas any source files on the sourcepath will be built again irregardless of how new they are. And when -sourcepath and -classpath are both specified then it takes the action of the classpath to only rebuild if the source files are newer, so specifying the sourcepath and classpath appears to be completely pointless.
I've also tested to make sure that -sourcepath and -classpath both only build the necessary source files needed for resolution.
The only upside from -sourcepath over specifying the specific .java files I can find is that sourcepath just has to have a directory specified, and any jar or zip files are automatically extracted.
So is the point of -sourcepath due to laziness (not wanting to extract & specify all the source files)?
And is there any upside to using -sourcepath over -classpath? Since they appear to do the same thing but classpath performs it better by saving time when the source doesn't need rebuilding.
The sourcepath is used to locate .java files for classes referenced in the files you asked to compile (Eight.java in your example) for which no .class file exists. The compiler will automatically compile those too. I'm not sure if it compares the modified-time of the .java and corresponding .class file and recompiles the .java if it is newer, but I think it does.
I've found from further colleagues in development that they would generally use this during development if they want to build part of a project or subproject. Instead of building everything from scratch (which could take hours), they instead simply want to build a few projects, then they can specify other directories of the whole project in -sourcepath so that this can resolve classes. This means it can automatically find what dependencies it needs and build these as and when they are needed.
I've also been informed that this can be helpful during maven builds since the order of dependency gathering can be very unorganised. Instead of having missing dependencies because the jars aren't there at time of building, you can specify the entire code tree as the sourcepath. Although I imagine this isn't the best advice, it's helpful when somebody is having problems building.

How to update rt.jar file?

I want to add some class files to rt.jar. How am I able to do that?
Your question indicates you have some misunderstanding of the java platform.
First of all you need to know what the rt.jar is and what it does:
rt.jar is the jar that contains all the classes necessary for the java runtime. Hence it's name rt.jar
Now that you know that, you need to know how your java program runs:
Your java program, all your jars and classes are executed by the java virtual machine.
So as you can see the code you write & the rt.jar which is used by the java run time are completely separate and should remain so.
If you need some functionality you should add it to your jar.
Do not update it. Why do you want to update it?
Well anyhow if you want to update it, I know one way, You can open jar file in winrar and paste updated .class files in archive. But your jar may get in inconsistent state. Do it at your own risk.
You can use this command:
jar uf jar-file input-file(s)
Refer the link for details:
java.sun.com/docs/books/tutorial/deployment/jar/update.html
The best way to update the rt.jar is to install a newer version for Java. ;)
If you want to add your own classes in new packages, you can add these to a jar which is in your class path. This is preferable to changing existing classes.
If the only option is to change existing classes, you can create a "patch" in a jar which you prepend to your boot class path, or you can add the jar to an lib/endorsed directory. I wouldn't do this for production code, only for your own testing.
I've tried jar.exe with the u and 0 (i.e. zero) options and that gets the closest to looking like the original rt.jar file but if I've updated the JDK's JRE's rt.jar I have problems with compiling and jarring after the update. No idea why! Simply running a program with the JRE seems to work.
I also tried -Xbootclasspath/p but couldn't get it to work.
Looking at Replace a class within the Java class library with a custom version I see that there are legal problems with distributing an altered rt.jar to your customers, even if you could figure out how to do it correctly. So I plan to take the advice in that page and create a java agent. That's apparently legal and works.
One reason a person might want to modify rt.jar is to add debugging information after compiling the source that comes with the JDK with the -g option. One may also want to patch something. These would be for one's own use, of course.

How can I test if my jar file has any unresolved dependencies?

I'm writing a custom tool to build jar files from our build tree. We like to build "minimal" jar files that contain only the .class files actually referenced from the "root" class (where main() lives) -- and so on, recursively following dependencies.
Historically we have done this by getting javac to follow source dependencies, but that means recompiling common files many many times. (We build 60 or 70 distinct application jars from a single source tree.) I'm writing a new build system that compiles each source file only once, but that means we need to follow dependencies by parsing .class files.
The good news is, I've got working code that does what I want. But I need to be absolutely sure I didn't screw it up, i.e. I want to ensure that I'm building internally consistent jar files, where "consistent" means that all unresolved references can be resolved with one of our known third-party jars.
So ideally I want a MagicTool that I can run like
MagicTool \
--classpath commons-lang.jar:commons-collections.jar:[...etc...] \
myapp.jar
that will examine every unresolved reference inside myapp.jar and make sure that it can be resolved by one of the third-party jars passed to --classpath. If not, barf.
Better hope nothing in that path includes any sort of reflection, forName, etc.
I use depfind (if I'm not using my own Java spelunking tools) which may or may not provide output in a way that's helpful for you. jdepend is another option, although I've never used it for anything other than package-level dependencies.
Other tools like ProGuard will strip out unused classes (amongst other things), with the same reflective caveats.
I'm very wary of trying too hard to create minimal jar files; there's a point of diminishing returns/increased risk.
I am pretty sure that ProGuard is your magic tool, even if I don't now the exact call syntax.
At one stage some years ago, trying to index a JAR file with jar -i made it throw an exception if there were unresolved dependencies. I can't quickly test the current state of things.

Compiling Java package throws errors for external Jars

Pretty basic problem here. So I have a Java package that I have created that has three classes (one has the main method). I am trying to use a few Apache Jars, and have added these to my build path in Eclipse. However Eclipse wont let me build and run it properly, so I am trying the command line. I have added the env var CLASSPATH and pointed it to my lib directory which hold the Apache Jars. However, when I try to use javac I get a bunch of errors:
package org.apache.xmlrpc does not exist
import org.apache.xmlrpc.client.XmlRpcClient;
I was reading the man page for javac and it said that:
If neither CLASSPATH, -cp nor -classpath is specified, the user class path consists of the current directory.
So I tried copying the Jars to the same location as my three source files, but no change.
Can someone please tell me what I'm doing wrong?
Thanks.
Classpath variable (or command line option of javac) must contain all jars explicitly. It cannot go through jar files stored in specified directory.
You can compile this by specifying the option -cp on the command line:
javac -cp foo.jar:bar.jar foo/bar/Baz.java
You then run it with the same option:
java -cp foo.jar:bar.jar foo.bar.Baz
It sounds like you've just set the classpath to the directory containing the jar files. You need to set it to the individual jar files, or use java.ext.dirs to set an "extension" directory containing jar files. I'd recommend using the specific jar files. Something like:
// Assuming Windows...
CLASSPATH = c:\libs\foo.jar;c:\libs\bar.jar
I'd also personally recommend specifying the classpath on the command line instead of using an environment variable - the latter will work, but it ends up being a bit more fiddly if you want to compile different projects against different libraries.
However, I'd actually recommend getting Eclipse working first, rather than retreating to the command line. It should be fine - if you could give us more information about what's failing in Eclipse, we may be able to help you with that instead.
The jar files in the current directory are not automatically included; that only refers to .class files in normal package/directory hierarchy. Jar files must be added either explicitly, or via a wildcard like javac -cp ./* (Assuming JDK6+)
(Some OSes may require an escape of the * to avoid globbing; OSX does not.)
I agree with previous answers, but I would also recommend to use proper java build tool - like ant (perceived easier to use, but not necessary) or maven ( perceived more difficult to use, but really worth learning )

Categories