├── .editorconfig ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ └── crashing---not-working.md ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── CHANGELOG.md ├── LICENSE ├── MTS.cmd ├── MTS.sh ├── MTS_8u51.cmd ├── README.md ├── _run.bat ├── mvnw ├── mvnw.cmd ├── pom.xml └── src └── main ├── java ├── com │ ├── evacipated │ │ └── cardcrawl │ │ │ └── modthespire │ │ │ ├── ByteArrayMapClassPath.java │ │ │ ├── CyclicDependencyException.java │ │ │ ├── DownloadAndRestarter.java │ │ │ ├── DuplicateModIDException.java │ │ │ ├── EnumBusterReflect.java │ │ │ ├── GameBetaFinder.java │ │ │ ├── GameVersionFinder.java │ │ │ ├── GithubUpdateChecker.java │ │ │ ├── GraphTS.java │ │ │ ├── Loader.java │ │ │ ├── MTSClassLoader.java │ │ │ ├── MTSClassPool.java │ │ │ ├── MissingDependencyException.java │ │ │ ├── MissingModIDException.java │ │ │ ├── ModInfo.java │ │ │ ├── ModList.java │ │ │ ├── ModUpdate.java │ │ │ ├── OutJar.java │ │ │ ├── PackageJar.java │ │ │ ├── Patcher.java │ │ │ ├── ReflectionHelper.java │ │ │ ├── UpdateChecker.java │ │ │ ├── finders │ │ │ ├── InOrderFinder.java │ │ │ ├── InOrderMultiFinder.java │ │ │ └── MatchFinderExprEditor.java │ │ │ ├── lib │ │ │ ├── ByRef.java │ │ │ ├── ConfigUtils.java │ │ │ ├── LineFinder.java │ │ │ ├── Matcher.java │ │ │ ├── SpireConfig.java │ │ │ ├── SpireEnum.java │ │ │ ├── SpireField.java │ │ │ ├── SpireInitializer.java │ │ │ ├── SpireInsertLocator.java │ │ │ ├── SpireInsertPatch.java │ │ │ ├── SpireInstrumentPatch.java │ │ │ ├── SpireOverride.java │ │ │ ├── SpirePatch.java │ │ │ ├── SpirePatch2.java │ │ │ ├── SpirePatches.java │ │ │ ├── SpirePatches2.java │ │ │ ├── SpirePostfixPatch.java │ │ │ ├── SpirePrefixPatch.java │ │ │ ├── SpireRawPatch.java │ │ │ ├── SpireReturn.java │ │ │ ├── SpireSideload.java │ │ │ ├── SpireSuper.java │ │ │ └── StaticSpireField.java │ │ │ ├── patcher │ │ │ ├── ByRefParameterNotArrayException.java │ │ │ ├── ClassPatchInfo.java │ │ │ ├── Expectation.java │ │ │ ├── InsertPatchInfo.java │ │ │ ├── InstrumentPatchInfo.java │ │ │ ├── LocatorInfo.java │ │ │ ├── MissingParamTypesException.java │ │ │ ├── NonStaticPatchMethodException.java │ │ │ ├── ParamInfo.java │ │ │ ├── ParamInfo2.java │ │ │ ├── ParameterPatchInfo.java │ │ │ ├── PatchInfo.java │ │ │ ├── PatchInfoComparator.java │ │ │ ├── PatchingException.java │ │ │ ├── PostfixPatchInfo.java │ │ │ ├── PrefixPatchInfo.java │ │ │ ├── RawPatchInfo.java │ │ │ ├── ReplacePatchInfo.java │ │ │ └── javassist │ │ │ │ ├── MyCodeConverter.java │ │ │ │ └── convert │ │ │ │ └── TransformSpecialCallVirtual.java │ │ │ ├── patches │ │ │ ├── AllowWatcherUnlock.java │ │ │ ├── AlwaysEnableCustomMode.java │ │ │ ├── CatchCrash.java │ │ │ ├── ChangeWindowTitle.java │ │ │ ├── CreditsModList.java │ │ │ ├── HandleCrash.java │ │ │ ├── HeapSize.java │ │ │ ├── IsModded.java │ │ │ ├── MainMenuModList.java │ │ │ ├── SkipIntro.java │ │ │ ├── TopPanelModList.java │ │ │ ├── lwjgl2 │ │ │ │ ├── CatchCrash.java │ │ │ │ └── Lwjgl2DisableGdxForceExit.java │ │ │ ├── lwjgl3 │ │ │ │ ├── CatchCrash.java │ │ │ │ ├── CloseMTSWindow.java │ │ │ │ ├── FixController.java │ │ │ │ ├── FramerateLimiter.java │ │ │ │ ├── Lwjgl2To3.java │ │ │ │ ├── SaveWindowPosition.java │ │ │ │ └── Sync.java │ │ │ └── modsscreen │ │ │ │ ├── BaseMod │ │ │ │ ├── DisableBaseModBadges.java │ │ │ │ └── ModBadgeOnClick.java │ │ │ │ ├── MainMenuItem.java │ │ │ │ ├── ModMenuButton.java │ │ │ │ ├── ModsScreen.java │ │ │ │ ├── ModsScreenUpdateRender.java │ │ │ │ └── SaveBaseModBadges.java │ │ │ ├── steam │ │ │ ├── SteamSearch.java │ │ │ └── SteamWorkshop.java │ │ │ └── ui │ │ │ ├── JModPanelCheckBoxList.java │ │ │ ├── MessageConsole.java │ │ │ ├── ModPanel.java │ │ │ ├── ModSelectWindow.java │ │ │ ├── TextFieldWithPlaceholder.java │ │ │ └── UpdateWindow.java │ └── megacrit │ │ └── cardcrawl │ │ └── desktop │ │ └── DesktopLauncher.java ├── javassist │ └── bytecode │ │ └── annotation │ │ └── MemberValue.java └── org │ └── clapper │ └── util │ ├── classutil │ ├── AbstractClassFilter.java │ ├── AndClassFilter.java │ ├── ClassFilter.java │ ├── ClassFinder.java │ ├── ClassInfo.java │ ├── ClassInfoClassVisitor.java │ ├── ClassLoaderBuilder.java │ ├── ClassModifiersClassFilter.java │ ├── ClassUtil.java │ ├── ClassUtilException.java │ ├── FieldInfo.java │ ├── InterfaceOnlyClassFilter.java │ ├── MethodInfo.java │ ├── NotClassFilter.java │ ├── OrClassFilter.java │ ├── RegexClassFilter.java │ └── SubclassClassFilter.java │ ├── cmdline │ ├── CommandLineException.java │ ├── CommandLineUsageException.java │ ├── CommandLineUserException.java │ ├── CommandLineUtility.java │ ├── OptionComparator.java │ ├── OptionInfo.java │ ├── Package.java │ ├── ParameterHandler.java │ ├── ParameterParser.java │ └── UsageInfo.java │ ├── config │ ├── Configuration.java │ ├── ConfigurationException.java │ ├── EnvSection.java │ ├── NoSuchSectionException.java │ ├── NoSuchVariableException.java │ ├── Package.java │ ├── ProgramSection.java │ ├── Section.java │ ├── SectionExistsException.java │ ├── SystemSection.java │ ├── ValueSegment.java │ ├── Variable.java │ └── package.html │ ├── html │ └── HTMLUtil.java │ ├── io │ ├── AndFileFilter.java │ ├── AndFilenameFilter.java │ ├── CombinationFileFilter.java │ ├── CombinationFilenameFilter.java │ ├── CombinationFilterMode.java │ ├── DirectoryFilter.java │ ├── FileFilterMatchType.java │ ├── FileNameComparator.java │ ├── FileOnlyFilter.java │ ├── FileUtil.java │ ├── IOExceptionExt.java │ ├── JustifyStyle.java │ ├── JustifyTextWriter.java │ ├── MultipleRegexFilenameFilter.java │ ├── NotFileFilter.java │ ├── NotFilenameFilter.java │ ├── OrFileFilter.java │ ├── OrFilenameFilter.java │ ├── Package.java │ ├── RecursiveFileFinder.java │ ├── RegexFileFilter.java │ ├── RegexFilenameFilter.java │ ├── RollingFileWriter.java │ ├── WordWrapWriter.java │ ├── XMLWriter.java │ ├── Zipper.java │ └── package.html │ ├── logging │ ├── JavaUtilLoggingTextFormatter.java │ ├── LogLevel.java │ ├── Logger.java │ └── package.html │ ├── misc │ ├── ArrayIterator.java │ ├── BuildInfo.java │ ├── BundleUtil.java │ ├── EnumerationIterator.java │ ├── FileHashMap.java │ ├── FileHashMapEntry.java │ ├── LRUMap.java │ ├── MIMETypeUtil.java │ ├── MultiIterator.java │ ├── MultiValueMap.java │ ├── NestedException.java │ ├── ObjectExistsException.java │ ├── ObjectLockSemaphore.java │ ├── ObjectRemovalEvent.java │ ├── ObjectRemovalListener.java │ ├── OrderedHashMap.java │ ├── Package.java │ ├── PropertiesMap.java │ ├── Semaphore.java │ ├── SemaphoreException.java │ ├── SparseArrayList.java │ ├── Version.java │ ├── VersionBase.java │ ├── VersionMismatchException.java │ ├── XDate.java │ ├── XResourceBundle.java │ └── package.html │ ├── regex │ ├── Package.java │ ├── RegexException.java │ └── RegexUtil.java │ └── text │ ├── AbstractVariableSubstituter.java │ ├── Duration.java │ ├── MapVariableDereferencer.java │ ├── MultipleMapVariableDereferencer.java │ ├── Package.java │ ├── TextUtil.java │ ├── UndefinedVariableException.java │ ├── Unicode.java │ ├── UnixShellVariableSubstituter.java │ ├── VariableDereferencer.java │ ├── VariableNameChecker.java │ ├── VariableSubstituter.java │ ├── VariableSubstitutionException.java │ ├── VariableSyntaxException.java │ ├── WindowsCmdVariableSubstituter.java │ ├── XStringBufBase.java │ ├── XStringBuffer.java │ ├── XStringBuilder.java │ └── package.html └── resources ├── META-INF ├── licences │ ├── javassist │ │ └── License.html │ └── scannotation │ │ └── LICENSE └── version.prop ├── ModTheSpire.json ├── assets ├── ajax-loader.gif ├── download.gif ├── error.gif ├── good.gif ├── icon.png ├── update.gif ├── warning.gif └── workshop.gif └── steam_appid.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | insert_final_newline=true 5 | indent_style=space 6 | indent_size=4 7 | 8 | [{.babelrc,.stylelintrc,.eslintrc,jest.config,*.bowerrc,*.jsb3,*.jsb2,*.json}] 9 | indent_style=space 10 | indent_size=2 11 | 12 | [{*.applejs,*.js}] 13 | indent_style=space 14 | indent_size=2 15 | 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.cmd eol=crlf 3 | *.sh eol=lf 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/crashing---not-working.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Crashing / Not Working 3 | about: Is ModTheSpire crashing for you? 4 | 5 | --- 6 | 7 | **Before submitting, have you read the [Troubleshooting guide](https://github.com/kiooeht/ModTheSpire/wiki/Troubleshooting)?** 8 | Yes/No. 9 | 10 | **Crash Log** 11 | ``` 12 | Paste the ENTIRE log here. Including the very top of the log and the full error. 13 | ``` 14 | 15 | 16 | **Additional context** 17 | Add any other context about the problem here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | # IntelliJ 25 | .idea/ 26 | out/ 27 | ModTheSpire.iml 28 | 29 | # Eclipse 30 | .classpath 31 | .project 32 | .settings/ 33 | .factorypath 34 | 35 | # maven build target 36 | target/ 37 | 38 | # Custom 39 | _archive/ 40 | backup/ 41 | bin/ 42 | crashlogs/ 43 | decompiled/ 44 | mods/ 45 | preferences/ 46 | saves/ 47 | tools/ 48 | _build.bat 49 | _decompile.bat 50 | _runJ9.bat 51 | *.displayconfig 52 | 53 | # maven other 54 | dependency-reduced-pom.xml 55 | 56 | # maven wrapper 57 | !/.mvn/wrapper/maven-wrapper.jar 58 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiooeht/ModTheSpire/c6201995b8f5d97620cdb00f28a724a95b7742a6/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anthony Moore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MTS.cmd: -------------------------------------------------------------------------------- 1 | start .\jre\bin\javaw.exe -jar .\ModTheSpire.jar -------------------------------------------------------------------------------- /MTS.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ./jre/bin/java -jar ModTheSpire.jar -------------------------------------------------------------------------------- /MTS_8u51.cmd: -------------------------------------------------------------------------------- 1 | start .\jre1.8.0_51\bin\javaw.exe -jar .\ModTheSpire.jar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModTheSpire # 2 | ModTheSpire is a tool to load external mods for Slay the Spire without modifying the base game files. 3 | 4 | ## Usage ## 5 | ### Installation ### 6 | 1. Download the latest [Release](https://github.com/kiooeht/ModTheSpire/releases). 7 | 2. Copy `ModTheSpire.jar` to your Slay the Spire install directory. 8 | * For Windows, copy `MTS.cmd` to your Slay the Spire install directory. 9 | * For Linux, copy `MTS.sh` to your Slay the Spire install directory and make it executable. 10 | 3. Create a `mods` directory. Place mod JAR files into the `mods` directory. 11 | 12 | ### Running Mods ### 13 | 1. Run ModTheSpire. 14 | * For Windows, run `MTS.cmd`. 15 | * For Linux, run `MTS.sh`. 16 | * Or run `ModTheSpire.jar` with Java 8. 17 | 2. Select the mod(s) you want to use. 18 | 3. Press 'Play'. 19 | 20 | --- 21 | 22 | ## For Modders ## 23 | ### Requirements ### 24 | * JDK 8 25 | * Maven 26 | 27 | ### General ### 28 | * ModTheSpire automatically sets the Settings.isModded flag to true, so there is no need to do that yourself. 29 | * [Wiki](https://github.com/kiooeht/ModTheSpire/wiki/SpirePatch) 30 | 31 | ### Building ### 32 | 1. Run `mvnw package` 33 | 34 | --- 35 | 36 | ## Changelog ## 37 | See [CHANGELOG](CHANGELOG.md) 38 | 39 | ## Contributors ## 40 | * kiooeht - Original author 41 | * t-larson - Multi-loading, mod initialization, some UI work 42 | * test447 - Some launcher UI work, Locator 43 | * reckter - Maven setup 44 | * FlipskiZ - Mod initialization 45 | * pk27602017 - UTF-8 support in ModInfo 46 | -------------------------------------------------------------------------------- /_run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | java -jar "ModTheSpire.jar" 3 | pause 4 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/ByteArrayMapClassPath.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | import javassist.CannotCompileException; 4 | import javassist.ClassPath; 5 | import javassist.CtClass; 6 | import javassist.NotFoundException; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.net.URL; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | public class ByteArrayMapClassPath implements ClassPath 16 | { 17 | protected Map classes = new HashMap<>(); 18 | 19 | public ByteArrayMapClassPath() {} 20 | 21 | public void addClass(CtClass ctClass) throws CannotCompileException 22 | { 23 | try { 24 | URL url = null; 25 | try { 26 | url = ctClass.getURL(); 27 | } catch (NotFoundException ignored) {} 28 | classes.put(ctClass.getName(), new Info(url, ctClass.toBytecode())); 29 | } catch (IOException e) { 30 | throw new CannotCompileException(e); 31 | } 32 | } 33 | 34 | @Override 35 | public void close() {} 36 | 37 | @Override 38 | public InputStream openClassfile(String classname) 39 | { 40 | Info classInfo = classes.get(classname); 41 | if (classInfo != null) { 42 | return new ByteArrayInputStream(classInfo.classfile); 43 | } 44 | return null; 45 | } 46 | 47 | @Override 48 | public URL find(String classname) 49 | { 50 | if (classes.containsKey(classname)) { 51 | return classes.get(classname).url; 52 | } 53 | 54 | return null; 55 | } 56 | 57 | public void printDebugInfo() 58 | { 59 | int bytes = 0; 60 | for (Info info : classes.values()) { 61 | bytes += info.classfile.length; 62 | } 63 | bytes /= 1024; 64 | 65 | System.out.printf("%d modified classes (%dKB)%n", classes.size(), bytes); 66 | } 67 | 68 | private static class Info 69 | { 70 | final URL url; 71 | final byte[] classfile; 72 | 73 | Info(URL url, byte[] classfile) 74 | { 75 | this.url = url; 76 | this.classfile = classfile; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/CyclicDependencyException.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | public class CyclicDependencyException extends Exception 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/DownloadAndRestarter.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.net.URISyntaxException; 7 | import java.net.URL; 8 | import java.nio.channels.Channels; 9 | import java.nio.channels.ReadableByteChannel; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | 13 | public class DownloadAndRestarter 14 | { 15 | private DownloadAndRestarter() {} 16 | 17 | public static final boolean DO_APPEND = false; 18 | 19 | private static String fileNameFromURL(URL url) 20 | { 21 | String uri = url.toString(); 22 | return uri.substring(uri.lastIndexOf('/') + 1, uri.length()); 23 | } 24 | 25 | private static void restartApplication() throws URISyntaxException, IOException 26 | { 27 | final String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; 28 | final File currentJar = new File(DownloadAndRestarter.class.getProtectionDomain().getCodeSource().getLocation().toURI()); 29 | 30 | /* is it a jar file? */ 31 | if (!currentJar.getName().endsWith(".jar")) { 32 | return; 33 | } 34 | 35 | /* Build command: java -jar application.jar */ 36 | final ArrayList command = new ArrayList(); 37 | command.add(javaBin); 38 | command.add("-jar"); 39 | command.add(currentJar.getPath()); 40 | command.addAll(Arrays.asList(Loader.ARGS)); 41 | 42 | final ProcessBuilder builder = new ProcessBuilder(command); 43 | builder.start(); 44 | System.exit(0); 45 | } 46 | 47 | public static void downloadOne(URL download) throws IOException 48 | { 49 | ReadableByteChannel rbc = Channels.newChannel(download.openStream()); 50 | String fileName = fileNameFromURL(download); 51 | FileOutputStream fos = new FileOutputStream(Loader.MOD_DIR + fileName, DO_APPEND); 52 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 53 | fos.close(); 54 | } 55 | 56 | public static void downloadAndRestart(URL[] downloads) throws IOException, URISyntaxException 57 | { 58 | for (URL download : downloads) { 59 | downloadOne(download); 60 | } 61 | restartApplication(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/DuplicateModIDException.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | public class DuplicateModIDException extends Exception 4 | { 5 | private ModInfo info1; 6 | private ModInfo info2; 7 | 8 | public DuplicateModIDException(ModInfo info1, ModInfo info2) 9 | { 10 | this.info1 = info1; 11 | this.info2 = info2; 12 | } 13 | 14 | @Override 15 | public String getMessage() 16 | { 17 | return "Duplicate Mod ID: '" + info1.ID + "'. Used by '" + info1.Name + "' and '" + info2.Name + "'."; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/GameBetaFinder.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | import org.objectweb.asm.*; 4 | 5 | public class GameBetaFinder extends ClassVisitor 6 | { 7 | public GameBetaFinder() 8 | { 9 | super(Opcodes.ASM5); 10 | // TODO? 11 | //super(classVisitor); 12 | } 13 | 14 | @Override 15 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) 16 | { 17 | MethodVisitor r = super.visitMethod(access, name, desc, signature, exceptions); 18 | if (name.equals("")) { 19 | r = new MyMethodAdapter(r); 20 | } 21 | return r; 22 | } 23 | 24 | class MyMethodAdapter extends MethodVisitor 25 | { 26 | private boolean isBeta = false; 27 | 28 | MyMethodAdapter(MethodVisitor methodVisitor) 29 | { 30 | super(Opcodes.ASM5); 31 | // TODO? 32 | //super(methodVisitor); 33 | } 34 | 35 | @Override 36 | public void visitFieldInsn(int opcode, String owner, String name, String descriptor) 37 | { 38 | super.visitFieldInsn(opcode, owner, name, descriptor); 39 | if (name.equals("isBeta") && opcode == Opcodes.PUTSTATIC) { 40 | Loader.STS_BETA = isBeta; 41 | } 42 | } 43 | 44 | @Override 45 | public void visitInsn(int opcode) 46 | { 47 | super.visitInsn(opcode); 48 | if (opcode == Opcodes.ICONST_0) { 49 | isBeta = false; 50 | } else if (opcode == Opcodes.ICONST_1) { 51 | isBeta = true; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/GithubUpdateChecker.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | import com.google.gson.*; 4 | import com.vdurmont.semver4j.Semver; 5 | import com.vdurmont.semver4j.SemverException; 6 | 7 | import javax.swing.*; 8 | import java.io.FileNotFoundException; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.net.HttpURLConnection; 13 | import java.net.MalformedURLException; 14 | import java.net.URL; 15 | 16 | public class GithubUpdateChecker extends UpdateChecker 17 | { 18 | public GithubUpdateChecker(String username, String reponame) throws MalformedURLException 19 | { 20 | this("https://api.github.com/repos/" + username + "/" + reponame + "/releases/latest"); 21 | } 22 | 23 | public GithubUpdateChecker(String url) throws MalformedURLException 24 | { 25 | super(url); 26 | } 27 | 28 | private static String removeLatestFromURL(String url) 29 | { 30 | if (url.endsWith("/latest")) { 31 | return url.substring(0, url.length() - "/latest".length()); 32 | } 33 | return url; 34 | } 35 | 36 | @Override 37 | protected void obtainLatestRelease() throws IOException 38 | { 39 | // If no latest release exists because all releases are pre-releases, grab the latest pre-release 40 | try { 41 | super.obtainLatestRelease(); 42 | } catch (FileNotFoundException e) { 43 | jsonURL = new URL(removeLatestFromURL(jsonURL.toString())); 44 | 45 | HttpURLConnection request = (HttpURLConnection) jsonURL.openConnection(); 46 | request.connect(); 47 | 48 | try { 49 | JsonParser jp = new JsonParser(); 50 | JsonElement root = jp.parse(new InputStreamReader((InputStream) request.getContent())); 51 | latest = root.getAsJsonArray().get(0).getAsJsonObject(); 52 | } catch (JsonSyntaxException e2) { 53 | System.out.println(jsonURL); 54 | System.out.println(e.getMessage()); 55 | } 56 | } 57 | } 58 | 59 | @Override 60 | public Semver getLatestReleaseVersion() throws IOException 61 | { 62 | try { 63 | return ModInfo.safeVersion(getElementAsString("tag_name")); 64 | } catch (SemverException e) { 65 | JOptionPane.showMessageDialog(null, 66 | getElementAsString("html_url") 67 | + "\nrelease has a missing or bad version number: \"" 68 | + getElementAsString("tag_name") 69 | + "\".\nGo yell at the author to fix it.", 70 | "Warning", JOptionPane.WARNING_MESSAGE); 71 | return null; 72 | } 73 | } 74 | 75 | @Override 76 | public URL getLatestReleaseURL() throws IOException 77 | { 78 | return new URL(getElementAsString("html_url")); 79 | } 80 | 81 | @Override 82 | public URL getLatestDownloadURL() throws IOException 83 | { 84 | JsonElement assets = getElement("assets"); 85 | 86 | if (!(assets instanceof JsonArray)) { 87 | throw new RuntimeException("Excepted assets to be of type array"); 88 | } 89 | 90 | for (JsonElement asset : (JsonArray)assets) { 91 | if (asset instanceof JsonObject) { 92 | JsonObject object = (JsonObject)asset; 93 | String url = object.get("browser_download_url").getAsString(); 94 | if (isJar(url)) { 95 | return new URL(url); 96 | } 97 | } 98 | } 99 | 100 | throw new RuntimeException("Could not find jar asset to download"); 101 | } 102 | 103 | private boolean isJar(String url) { 104 | return url.toLowerCase().endsWith(".jar"); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/MTSClassPool.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | import javassist.ClassPath; 4 | import javassist.ClassPool; 5 | import javassist.CtClass; 6 | import javassist.LoaderClassPath; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | public class MTSClassPool extends ClassPool 14 | { 15 | private ClassLoader classLoader; 16 | private List classPaths = new ArrayList<>(); 17 | private Set outJar = null; 18 | 19 | public MTSClassPool(MTSClassLoader classLoader) 20 | { 21 | appendSystemPath(); 22 | resetClassLoader(classLoader); 23 | } 24 | 25 | public void resetClassLoader(MTSClassLoader loader) 26 | { 27 | classLoader = loader; 28 | for (ClassPath classPath : classPaths) { 29 | removeClassPath(classPath); 30 | } 31 | classPaths.clear(); 32 | 33 | insertClassPath(new LoaderClassPath(loader)); 34 | loader.addStreamToClassPool(this); 35 | } 36 | 37 | @Override 38 | public ClassPath insertClassPath(ClassPath cp) 39 | { 40 | classPaths.add(cp); 41 | return super.insertClassPath(cp); 42 | } 43 | 44 | @Override 45 | public ClassPath appendClassPath(ClassPath cp) 46 | { 47 | classPaths.add(cp); 48 | return super.appendClassPath(cp); 49 | } 50 | 51 | @Override 52 | public ClassLoader getClassLoader() 53 | { 54 | return classLoader; 55 | } 56 | 57 | public void setParent(ClassPool parent) 58 | { 59 | this.parent = parent; 60 | } 61 | 62 | public Set getModifiedClasses() 63 | { 64 | Set ret = new HashSet<>(); 65 | for (Object v : classes.values()) { 66 | CtClass cls = (CtClass) v; 67 | if (cls.isModified()) { 68 | ret.add(cls); 69 | } 70 | } 71 | if (Loader.OUT_JAR || Loader.PACKAGE) { 72 | outJar = ret; 73 | } 74 | return ret; 75 | } 76 | 77 | public Set getOutJarClasses() 78 | { 79 | return outJar; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/MissingDependencyException.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | public class MissingDependencyException extends Exception 4 | { 5 | public ModInfo modInfo; 6 | public String dependency; 7 | 8 | public MissingDependencyException(ModInfo modInfo, String dependency) 9 | { 10 | this.modInfo = modInfo; 11 | this.dependency = dependency; 12 | } 13 | 14 | @Override 15 | public String getMessage() 16 | { 17 | String ret = "Unable to find dependency '" + dependency + "' for mod '"; 18 | if (modInfo.ID == null || modInfo.ID.isEmpty()) { 19 | ret += modInfo.Name; 20 | } else { 21 | ret += modInfo.ID; 22 | } 23 | ret += "'."; 24 | return ret; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/MissingModIDException.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | public class MissingModIDException extends Exception 4 | { 5 | public String modID; 6 | 7 | public MissingModIDException(String modID) 8 | { 9 | this.modID = modID; 10 | } 11 | 12 | @Override 13 | public String getMessage() 14 | { 15 | return "Unable to find Mod ID '" + modID + "'."; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/ModUpdate.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | import java.net.URL; 4 | 5 | public class ModUpdate 6 | { 7 | public ModInfo info; 8 | public URL releaseURL; 9 | public URL downloadURL; 10 | 11 | public ModUpdate(ModInfo info, URL releaseURL, URL downloadURL) 12 | { 13 | this.info = info; 14 | this.releaseURL = releaseURL; 15 | this.downloadURL = downloadURL; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/OutJar.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | import javassist.CannotCompileException; 4 | import javassist.CtClass; 5 | 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Set; 12 | import java.util.jar.JarEntry; 13 | import java.util.jar.JarOutputStream; 14 | 15 | class OutJar 16 | { 17 | public static class FilePathAndBytes 18 | { 19 | public String path; 20 | public byte[] b; 21 | 22 | public FilePathAndBytes(String path, byte[] b) 23 | { 24 | this.path = path; 25 | this.b = b; 26 | } 27 | } 28 | 29 | /* https://stackoverflow.com/questions/22591903/javassist-how-to-inject-a-method-into-a-class-in-jar?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa */ 30 | public static class JarHandler 31 | { 32 | public void writeOut(String jarPathAndName, List files) 33 | throws IOException 34 | { 35 | File jarFile = new File(jarPathAndName); 36 | 37 | try { 38 | JarOutputStream tempJar = new JarOutputStream(new FileOutputStream(jarFile)); 39 | 40 | try { 41 | // Open the given file. 42 | 43 | try { 44 | // Create a jar entry and add it to the temp jar. 45 | 46 | for (FilePathAndBytes file : files) { 47 | String fileName = file.path; 48 | byte[] fileByteCode = file.b; 49 | JarEntry entry = new JarEntry(fileName); 50 | tempJar.putNextEntry(entry); 51 | tempJar.write(fileByteCode); 52 | } 53 | 54 | } catch (Exception ex) { 55 | System.out.println(ex); 56 | 57 | // Add a stub entry here, so that the jar will close 58 | // without an 59 | // exception. 60 | 61 | tempJar.putNextEntry(new JarEntry("stub")); 62 | } 63 | 64 | } catch (Exception ex) { 65 | System.out.println(ex); 66 | 67 | // IMportant so the jar will close without an 68 | // exception. 69 | 70 | tempJar.putNextEntry(new JarEntry("stub")); 71 | } finally { 72 | tempJar.close(); 73 | } 74 | } finally { 75 | // do I need to do things here 76 | } 77 | } 78 | } 79 | 80 | public static void dumpJar(MTSClassPool pool, String jarPath) 81 | throws SecurityException, IllegalArgumentException, IOException 82 | { 83 | Set ctClasses = pool.getOutJarClasses(); 84 | List files = new ArrayList<>(); 85 | for (CtClass ctClass : ctClasses) { 86 | try { 87 | String className = ctClass.getName(); 88 | byte[] b = ctClass.toBytecode(); 89 | String classPath = className.replaceAll("\\.", "/") + ".class"; 90 | files.add(new FilePathAndBytes(classPath, b)); 91 | } catch (IOException | CannotCompileException e) { 92 | // eat it - just means this isn't a file we've loaded 93 | } 94 | } 95 | JarHandler handler = new JarHandler(); 96 | handler.writeOut(jarPath, files); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/ReflectionHelper.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire; 2 | 3 | import sun.reflect.*; 4 | import java.lang.reflect.*; 5 | 6 | public class ReflectionHelper { 7 | private static final String MODIFIERS_FIELD = "modifiers"; 8 | 9 | private static final ReflectionFactory reflection = 10 | ReflectionFactory.getReflectionFactory(); 11 | 12 | public static void setStaticFinalField( 13 | Field field, Object value) 14 | throws NoSuchFieldException, IllegalAccessException { 15 | // we mark the field to be public 16 | field.setAccessible(true); 17 | // next we change the modifier in the Field instance to 18 | // not be final anymore, thus tricking reflection into 19 | // letting us modify the static final field 20 | Field modifiersField = 21 | Field.class.getDeclaredField(MODIFIERS_FIELD); 22 | modifiersField.setAccessible(true); 23 | int modifiers = modifiersField.getInt(field); 24 | // blank out the final bit in the modifiers int 25 | modifiers &= ~Modifier.FINAL; 26 | modifiersField.setInt(field, modifiers); 27 | FieldAccessor fa = reflection.newFieldAccessor( 28 | field, false 29 | ); 30 | fa.set(null, value); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/finders/InOrderFinder.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.finders; 2 | 3 | import java.util.List; 4 | 5 | import com.evacipated.cardcrawl.modthespire.lib.Matcher; 6 | import com.evacipated.cardcrawl.modthespire.patcher.Expectation; 7 | 8 | import javassist.expr.Expr; 9 | 10 | public class InOrderFinder extends MatchFinderExprEditor { 11 | 12 | private int location; 13 | private boolean foundLocation; 14 | private int foundMatchesIndex; 15 | 16 | private Matcher finalMatch; 17 | private List expectedMatches; 18 | 19 | public InOrderFinder(List expectedMatches, Matcher finalMatch) { 20 | super(); 21 | 22 | this.expectedMatches = expectedMatches; 23 | this.finalMatch = finalMatch; 24 | 25 | this.foundMatchesIndex = 0; 26 | this.foundLocation = false; 27 | } 28 | 29 | private void foundFinalMatch(int lineNumber) { 30 | if (foundLocation) return; 31 | 32 | this.foundLocation = true; 33 | this.location = lineNumber; 34 | } 35 | 36 | private boolean finalMatch() { 37 | return foundMatchesIndex >= expectedMatches.size(); 38 | } 39 | 40 | private void foundMatch() { 41 | this.foundMatchesIndex++; 42 | } 43 | 44 | private Matcher currentMatch() { 45 | return expectedMatches.get(foundMatchesIndex); 46 | } 47 | 48 | @Override 49 | protected void doMatch(Expectation expectedType, Expr toMatch) { 50 | if (finalMatch()) { 51 | if (finalMatch.getExpectation() == expectedType && finalMatch.match(toMatch)) { 52 | foundFinalMatch(toMatch.getLineNumber()); 53 | } 54 | } else { 55 | Matcher current = currentMatch(); 56 | if (current.getExpectation() == expectedType && current.match(toMatch)) { 57 | foundMatch(); 58 | } 59 | } 60 | } 61 | 62 | @Override 63 | public boolean didFindLocation() { 64 | return foundLocation; 65 | } 66 | 67 | @Override 68 | public int[] getFoundLocations() { 69 | return new int[] {location}; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/finders/InOrderMultiFinder.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.finders; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.evacipated.cardcrawl.modthespire.lib.Matcher; 7 | import com.evacipated.cardcrawl.modthespire.patcher.Expectation; 8 | 9 | import javassist.expr.Expr; 10 | 11 | public class InOrderMultiFinder extends MatchFinderExprEditor { 12 | 13 | private List locations; 14 | private boolean foundLocation; 15 | private int foundMatchesIndex; 16 | 17 | private Matcher finalMatch; 18 | private List expectedMatches; 19 | 20 | public InOrderMultiFinder(List expectedMatches, Matcher finalMatch) { 21 | super(); 22 | 23 | this.expectedMatches = expectedMatches; 24 | this.finalMatch = finalMatch; 25 | 26 | this.foundMatchesIndex = 0; 27 | this.foundLocation = false; 28 | this.locations = new ArrayList<>(); 29 | } 30 | 31 | private void foundFinalMatch(int lineNumber) { 32 | this.foundLocation = true; 33 | this.locations.add(lineNumber); 34 | } 35 | 36 | private boolean finalMatch() { 37 | return foundMatchesIndex >= expectedMatches.size(); 38 | } 39 | 40 | private void foundMatch() { 41 | this.foundMatchesIndex++; 42 | } 43 | 44 | private Matcher currentMatch() { 45 | return expectedMatches.get(foundMatchesIndex); 46 | } 47 | 48 | @Override 49 | protected void doMatch(Expectation expectedType, Expr toMatch) { 50 | if (finalMatch()) { 51 | if (finalMatch.getExpectation() == expectedType && finalMatch.match(toMatch)) { 52 | foundFinalMatch(toMatch.getLineNumber()); 53 | } 54 | } else { 55 | Matcher current = currentMatch(); 56 | if (current.getExpectation() == expectedType && current.match(toMatch)) { 57 | foundMatch(); 58 | } 59 | } 60 | } 61 | 62 | @Override 63 | public boolean didFindLocation() { 64 | return foundLocation; 65 | } 66 | 67 | @Override 68 | public int[] getFoundLocations() { 69 | int[] asArray = new int[locations.size()]; 70 | for (int i = 0; i < asArray.length; i++) { 71 | asArray[i] = locations.get(i); 72 | } 73 | return asArray; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/finders/MatchFinderExprEditor.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.finders; 2 | 3 | import com.evacipated.cardcrawl.modthespire.patcher.Expectation; 4 | 5 | import javassist.expr.Cast; 6 | import javassist.expr.ConstructorCall; 7 | import javassist.expr.Expr; 8 | import javassist.expr.ExprEditor; 9 | import javassist.expr.FieldAccess; 10 | import javassist.expr.Handler; 11 | import javassist.expr.Instanceof; 12 | import javassist.expr.MethodCall; 13 | import javassist.expr.NewArray; 14 | import javassist.expr.NewExpr; 15 | 16 | public abstract class MatchFinderExprEditor extends ExprEditor { 17 | 18 | protected abstract void doMatch(Expectation expectedType, Expr toMatch); 19 | 20 | public abstract boolean didFindLocation(); 21 | 22 | public abstract int[] getFoundLocations(); 23 | 24 | @Override 25 | public void edit(Cast expr) { 26 | doMatch(Expectation.TYPE_CAST, expr); 27 | } 28 | 29 | @Override 30 | public void edit(ConstructorCall expr) { 31 | doMatch(Expectation.CONSTRUCTOR_CALL, expr); 32 | } 33 | 34 | @Override 35 | public void edit(FieldAccess expr) { 36 | doMatch(Expectation.FIELD_ACCESS, expr); 37 | } 38 | 39 | @Override 40 | public void edit(Handler expr) { 41 | doMatch(Expectation.CATCH_CLAUSE, expr); 42 | } 43 | 44 | @Override 45 | public void edit(Instanceof expr) { 46 | doMatch(Expectation.INSTANCEOF, expr); 47 | } 48 | 49 | @Override 50 | public void edit(MethodCall expr) { 51 | doMatch(Expectation.METHOD_CALL, expr); 52 | } 53 | 54 | @Override 55 | public void edit(NewArray expr) { 56 | doMatch(Expectation.ARRAY_CREATION, expr); 57 | } 58 | 59 | @Override 60 | public void edit(NewExpr expr) { 61 | doMatch(Expectation.NEW_EXPRESSION, expr); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/ByRef.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.PARAMETER}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ByRef { 11 | String type() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/ConfigUtils.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import org.apache.commons.lang3.SystemUtils; 4 | 5 | import java.io.File; 6 | 7 | public class ConfigUtils 8 | { 9 | private static final String APP_NAME = "ModTheSpire"; 10 | public static final String CONFIG_DIR; 11 | 12 | static { 13 | String basedir; 14 | if (SystemUtils.IS_OS_WINDOWS) { 15 | // %LOCALAPPDATA%/APP_NAME/ 16 | // Fallback to %APPDATA%/APP_NAME/ 17 | String appdata = System.getenv("LOCALAPPDATA"); 18 | if (appdata == null || appdata.isEmpty()) { 19 | appdata = System.getenv("APPDATA"); 20 | } 21 | basedir = appdata; 22 | } else if (SystemUtils.IS_OS_LINUX) { 23 | // /home/x/.config/APP_NAME/ 24 | basedir = SystemUtils.USER_HOME + File.separator 25 | + ".config" + File.separator; 26 | } else if (SystemUtils.IS_OS_MAC) { 27 | // /Users/x/Library/Preferences/APP_NAME/ 28 | basedir = SystemUtils.USER_HOME + File.separator 29 | + "Library" + File.separator 30 | + "Preferences" + File.separator; 31 | } else { 32 | // user.home/APP_NAME/ 33 | basedir = SystemUtils.USER_HOME; 34 | } 35 | CONFIG_DIR = basedir + File.separator + 36 | APP_NAME; 37 | 38 | // Make config directory 39 | File directory = new File(CONFIG_DIR); 40 | directory.mkdirs(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/LineFinder.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.evacipated.cardcrawl.modthespire.finders.InOrderFinder; 7 | import com.evacipated.cardcrawl.modthespire.finders.InOrderMultiFinder; 8 | import com.evacipated.cardcrawl.modthespire.finders.MatchFinderExprEditor; 9 | import com.evacipated.cardcrawl.modthespire.patcher.PatchingException; 10 | 11 | import javassist.CannotCompileException; 12 | import javassist.CtBehavior; 13 | 14 | public class LineFinder { 15 | 16 | private LineFinder() {} 17 | 18 | public static final int[] findAllInOrder(CtBehavior ctMethodToPatch, Matcher finalMatch) throws CannotCompileException, PatchingException { 19 | return findAllInOrder(ctMethodToPatch, new ArrayList<>(), finalMatch); 20 | } 21 | 22 | public static final int[] findAllInOrder(CtBehavior ctMethodToPatch, List expectedMatches, Matcher finalMatch) throws CannotCompileException, PatchingException { 23 | MatchFinderExprEditor editor = new InOrderMultiFinder(expectedMatches, finalMatch); 24 | ctMethodToPatch.instrument(editor); 25 | if (!editor.didFindLocation()) { 26 | throw new PatchingException(ctMethodToPatch, "Location matching given description could not be found for patch"); 27 | } 28 | return editor.getFoundLocations(); 29 | } 30 | 31 | public static final int[] findInOrder(CtBehavior ctMethodToPatch, Matcher finalMatch) throws CannotCompileException, PatchingException { 32 | return findInOrder(ctMethodToPatch, new ArrayList<>(), finalMatch); 33 | } 34 | 35 | public static final int[] findInOrder(CtBehavior ctMethodToPatch, List expectedMatches, Matcher finalMatch) throws CannotCompileException, PatchingException { 36 | MatchFinderExprEditor editor = new InOrderFinder(expectedMatches, finalMatch); 37 | ctMethodToPatch.instrument(editor); 38 | if (!editor.didFindLocation()) { 39 | throw new PatchingException(ctMethodToPatch, "Location matching given description could not be found for patch"); 40 | } 41 | return editor.getFoundLocations(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireConfig.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.io.*; 4 | import java.util.Properties; 5 | 6 | public class SpireConfig 7 | { 8 | private final static String EXTENSION = "properties"; 9 | private Properties properties; 10 | private File file; 11 | private String filePath; 12 | 13 | public static String makeFilePath(String modName, String fileName) 14 | { 15 | return makeFilePath(modName, fileName, EXTENSION); 16 | } 17 | 18 | public static String makeFilePath(String modName, String fileName, String ext) 19 | { 20 | String dirPath; 21 | if (modName == null) { 22 | dirPath = ConfigUtils.CONFIG_DIR + File.separator; 23 | } else { 24 | dirPath = ConfigUtils.CONFIG_DIR + File.separator 25 | + modName + File.separator; 26 | } 27 | String filePath = dirPath + fileName + "." + ext; 28 | File dir = new File(dirPath); 29 | dir.mkdirs(); 30 | 31 | return filePath; 32 | } 33 | 34 | public SpireConfig(String modName, String fileName) throws IOException 35 | { 36 | this(modName, fileName, new Properties()); 37 | } 38 | 39 | public SpireConfig(String modName, String fileName, Properties defaultProperties) throws IOException 40 | { 41 | properties = new Properties(defaultProperties); 42 | 43 | filePath = makeFilePath(modName, fileName); 44 | 45 | file = new File(filePath); 46 | file.createNewFile(); 47 | load(); 48 | } 49 | 50 | public void load() throws IOException 51 | { 52 | properties.load(new FileInputStream(file)); 53 | } 54 | 55 | public void save() throws IOException 56 | { 57 | properties.store(new FileOutputStream(file), null); 58 | } 59 | 60 | public boolean has(String key) 61 | { 62 | return properties.containsKey(key); 63 | } 64 | 65 | public void remove(String key) 66 | { 67 | properties.remove(key); 68 | } 69 | 70 | public void clear() 71 | { 72 | properties.clear(); 73 | } 74 | 75 | public String getString(String key) 76 | { 77 | return properties.getProperty(key); 78 | } 79 | 80 | public void setString(String key, String value) 81 | { 82 | properties.setProperty(key, value); 83 | } 84 | 85 | public boolean getBool(String key) 86 | { 87 | return Boolean.parseBoolean(getString(key)); 88 | } 89 | 90 | public void setBool(String key, boolean value) 91 | { 92 | setString(key, Boolean.toString(value)); 93 | } 94 | 95 | public int getInt(String key) 96 | { 97 | return Integer.parseInt(getString(key)); 98 | } 99 | 100 | public void setInt(String key, int value) 101 | { 102 | setString(key, Integer.toString(value)); 103 | } 104 | 105 | public float getFloat(String key) 106 | { 107 | return Float.parseFloat(getString(key)); 108 | } 109 | 110 | public void setFloat(String key, float value) 111 | { 112 | setString(key, Float.toString(value)); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireEnum.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SpireEnum { 11 | String name() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireField.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | public class SpireField 6 | { 7 | public interface DefaultValue 8 | { 9 | T get(); 10 | } 11 | 12 | private DefaultValue defaultValue; 13 | 14 | private Field field; 15 | 16 | public SpireField(DefaultValue defaultValue) 17 | { 18 | this.defaultValue = defaultValue; 19 | } 20 | 21 | public SpireField(SpireField originalSpireField) 22 | { 23 | if (originalSpireField != null) { 24 | defaultValue = originalSpireField.defaultValue; 25 | } 26 | } 27 | 28 | public void initialize(Class clz, String fieldName) throws NoSuchFieldException 29 | { 30 | field = clz.getDeclaredField(fieldName); 31 | field.setAccessible(true); 32 | } 33 | 34 | public T getDefaultValue() 35 | { 36 | return defaultValue.get(); 37 | } 38 | 39 | public T get(Object __instance) 40 | { 41 | // This should never be called, but serves as a fallback 42 | try { 43 | return (T) field.get(__instance); 44 | } catch (IllegalAccessException e) { 45 | e.printStackTrace(); 46 | } 47 | return null; 48 | } 49 | 50 | public void set(Object __instance, T value) 51 | { 52 | // This should never be called, but serves as a fallback 53 | try { 54 | field.set(__instance, value); 55 | } catch (IllegalAccessException e) { 56 | e.printStackTrace(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireInitializer.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SpireInitializer { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireInsertLocator.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import javassist.CtBehavior; 4 | 5 | public abstract class SpireInsertLocator 6 | { 7 | public abstract int[] Locate(CtBehavior ctMethodToPatch) throws Exception; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireInsertPatch.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import javassist.CtBehavior; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.METHOD}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface SpireInsertPatch { 13 | Class locator() default NONE.class; 14 | int loc() default -1; 15 | int rloc() default -1; 16 | int[] locs() default {}; 17 | int[] rlocs() default {}; 18 | String[] localvars() default {}; 19 | 20 | final class NONE extends SpireInsertLocator 21 | { 22 | @Override 23 | public int[] Locate(CtBehavior ctMethodToPatch) throws Exception 24 | { 25 | return null; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireInstrumentPatch.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SpireInstrumentPatch { } 11 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireOverride.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Target({ElementType.METHOD}) 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface SpireOverride 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpirePatch.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Repeatable(SpirePatches.class) 6 | @Target({ElementType.TYPE}) 7 | @Retention(RetentionPolicy.RUNTIME) 8 | public @interface SpirePatch 9 | { 10 | Class clz() default void.class; 11 | String cls() default ""; 12 | String method(); 13 | Class[] paramtypez() default {void.class}; 14 | String[] paramtypes() default {"DEFAULT"}; 15 | String requiredModId() default ""; 16 | boolean optional() default false; 17 | 18 | String CONSTRUCTOR = ""; 19 | String STATICINITIALIZER = ""; 20 | String CLASS = ""; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpirePatch2.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Repeatable(SpirePatches2.class) 6 | @Target({ElementType.TYPE}) 7 | @Retention(RetentionPolicy.RUNTIME) 8 | public @interface SpirePatch2 9 | { 10 | Class clz() default void.class; 11 | String cls() default ""; 12 | String method(); 13 | Class[] paramtypez() default {void.class}; 14 | String[] paramtypes() default {"DEFAULT"}; 15 | String requiredModId() default ""; 16 | boolean optional() default false; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpirePatches.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SpirePatches 11 | { 12 | SpirePatch[] value(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpirePatches2.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SpirePatches2 11 | { 12 | SpirePatch2[] value(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpirePostfixPatch.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SpirePostfixPatch 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpirePrefixPatch.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SpirePrefixPatch 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireRawPatch.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SpireRawPatch { } 11 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireReturn.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.util.NoSuchElementException; 4 | 5 | public final class SpireReturn 6 | { 7 | private static final SpireReturn EMPTY = new SpireReturn<>(); 8 | private static final SpireReturn PLACEHOLDER = new SpireReturn<>(null); 9 | 10 | private final boolean hasValue; 11 | private T value; 12 | 13 | private SpireReturn() 14 | { 15 | hasValue = false; 16 | value = null; 17 | } 18 | 19 | public static SpireReturn Continue() 20 | { 21 | @SuppressWarnings("unchecked") 22 | SpireReturn t = (SpireReturn) EMPTY; 23 | return t; 24 | } 25 | 26 | private SpireReturn(T value) 27 | { 28 | hasValue = true; 29 | this.value = value; 30 | } 31 | 32 | public static SpireReturn Return(T value) 33 | { 34 | PLACEHOLDER.value = value; 35 | @SuppressWarnings("unchecked") 36 | SpireReturn ret = (SpireReturn) PLACEHOLDER; 37 | return ret; 38 | } 39 | 40 | public static SpireReturn Return() 41 | { 42 | return Return(null); 43 | } 44 | 45 | public T get() 46 | { 47 | if (!isPresent()) { 48 | throw new NoSuchElementException("No value present"); 49 | } 50 | return value; 51 | } 52 | 53 | public boolean isPresent() 54 | { 55 | return hasValue; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireSideload.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SpireSideload 11 | { 12 | String[] modIDs(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/SpireSuper.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | import org.apache.commons.lang3.NotImplementedException; 4 | 5 | public class SpireSuper 6 | { 7 | public static R call(Object... params) 8 | { 9 | throw new NotImplementedException("This shouldn't happen."); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/lib/StaticSpireField.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.lib; 2 | 3 | public class StaticSpireField extends SpireField 4 | { 5 | public StaticSpireField(DefaultValue defaultValue) 6 | { 7 | super(defaultValue); 8 | } 9 | 10 | public StaticSpireField(SpireField originalSpireField) 11 | { 12 | super(originalSpireField); 13 | } 14 | 15 | public T get() 16 | { 17 | return get(null); 18 | } 19 | 20 | public void set(T value) 21 | { 22 | set(null, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/ByRefParameterNotArrayException.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | public class ByRefParameterNotArrayException extends PatchingException 4 | { 5 | public ByRefParameterNotArrayException(int index) 6 | { 7 | super(Integer.toString(index)); 8 | } 9 | 10 | public ByRefParameterNotArrayException(String name) 11 | { 12 | super(name); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/Expectation.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | public enum Expectation { 4 | TYPE_CAST, CONSTRUCTOR_CALL, FIELD_ACCESS, 5 | CATCH_CLAUSE, INSTANCEOF, METHOD_CALL, 6 | ARRAY_CREATION, NEW_EXPRESSION 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/InstrumentPatchInfo.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | import javassist.CannotCompileException; 4 | import javassist.CtBehavior; 5 | import javassist.expr.ExprEditor; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | 10 | public class InstrumentPatchInfo extends PatchInfo 11 | { 12 | private Method method; 13 | 14 | public InstrumentPatchInfo(CtBehavior ctMethodToPatch, Method method) 15 | { 16 | super(ctMethodToPatch, null); 17 | this.method = method; 18 | } 19 | 20 | @Override 21 | protected String patchClassName() 22 | { 23 | return method.getDeclaringClass().getName(); 24 | } 25 | 26 | @Override 27 | protected String debugMsg() 28 | { 29 | return "Adding Instrument..."; 30 | } 31 | 32 | @Override 33 | public int patchOrdering() 34 | { 35 | return -1; 36 | } 37 | 38 | @Override 39 | public void doPatch() throws PatchingException 40 | { 41 | try { 42 | ExprEditor exprEditor = (ExprEditor) method.invoke(null); 43 | ctMethodToPatch.instrument(exprEditor); 44 | } catch (IllegalAccessException | CannotCompileException | InvocationTargetException e) { 45 | throw new PatchingException(e); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/LocatorInfo.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpireInsertLocator; 4 | import javassist.CtBehavior; 5 | 6 | import java.lang.reflect.Constructor; 7 | 8 | public class LocatorInfo { 9 | 10 | private CtBehavior ctMethodToPatch; 11 | private Class finderClass; 12 | 13 | public LocatorInfo(CtBehavior ctMethodToPatch, Class finderClass) { 14 | this.ctMethodToPatch = ctMethodToPatch; 15 | this.finderClass = finderClass; 16 | } 17 | 18 | public int[] findLines() throws Exception { 19 | Constructor ctor = finderClass.getDeclaredConstructor(); 20 | ctor.setAccessible(true); 21 | SpireInsertLocator obj = (SpireInsertLocator)ctor.newInstance(); 22 | return obj.Locate(ctMethodToPatch); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/MissingParamTypesException.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 4 | import javassist.CtClass; 5 | 6 | public class MissingParamTypesException extends Exception 7 | { 8 | public MissingParamTypesException(CtClass patchClass, SpirePatch patch) 9 | { 10 | super(String.format("Patch %s\nPatching %s.%s:\nHas overloads and no paramtypes defined", patchClass.getName(), patch.cls(), patch.method())); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/NonStaticPatchMethodException.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | import javassist.CtMethod; 4 | 5 | public class NonStaticPatchMethodException extends Exception 6 | { 7 | public NonStaticPatchMethodException(CtMethod patchMethod) 8 | { 9 | super(patchMethod.getLongName()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/ParamInfo.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.ByRef; 4 | import javassist.CtBehavior; 5 | import javassist.CtClass; 6 | import javassist.Modifier; 7 | import javassist.NotFoundException; 8 | import javassist.bytecode.LocalVariableAttribute; 9 | import javassist.bytecode.MethodInfo; 10 | 11 | class ParamInfo 12 | { 13 | private CtBehavior ctBehavior; 14 | private int position; 15 | private String name; 16 | private boolean isPrivate = false; 17 | 18 | private static String findName(CtBehavior ctBehavior, int position) 19 | { 20 | MethodInfo methodInfo = ctBehavior.getMethodInfo2(); 21 | LocalVariableAttribute table = (LocalVariableAttribute) methodInfo.getCodeAttribute().getAttribute(LocalVariableAttribute.tag); 22 | if (table != null) { 23 | int j = 0; 24 | for (int i=0; i ctBehavior.getParameterTypes().length) { 50 | this.position = -1; 51 | name = null; 52 | } 53 | } catch (NotFoundException e) { 54 | e.printStackTrace(); 55 | } 56 | 57 | if (name != null) { 58 | if (name.startsWith("___")) { 59 | isPrivate = true; 60 | name = name.replaceFirst("___", ""); 61 | } 62 | } 63 | } 64 | 65 | boolean isPrivateCapture() 66 | { 67 | return isPrivate; 68 | } 69 | 70 | CtClass getPrivateCaptureType(ParamInfo destInfo) throws PatchingException 71 | { 72 | if (!destInfo.isPrivateCapture()) { 73 | throw new PatchingException("Not a private capture, this shouldn't have been called"); 74 | } 75 | 76 | CtClass declaringClass = ctBehavior.getDeclaringClass(); 77 | try { 78 | return declaringClass.getDeclaredField(destInfo.getName()).getType(); 79 | } catch (NotFoundException e) { 80 | return null; 81 | } 82 | } 83 | 84 | CtClass getDestByRefType(ParamInfo destInfo) throws ClassNotFoundException 85 | { 86 | String typename = null; 87 | for (Object o : destInfo.getAnnotations()) { 88 | if (o instanceof ByRef && !((ByRef) o).type().isEmpty()) { 89 | typename = ((ByRef) o).type(); 90 | } 91 | } 92 | 93 | try { 94 | return ctBehavior.getDeclaringClass().getClassPool().get(typename); 95 | } catch (NotFoundException e) { 96 | return null; 97 | } 98 | } 99 | 100 | int getParamCount() 101 | { 102 | return ctBehavior.getAvailableParameterAnnotations().length; 103 | } 104 | 105 | int getPosition() 106 | { 107 | return position; 108 | } 109 | 110 | String getName() 111 | { 112 | return name; 113 | } 114 | 115 | CtClass getType() throws NotFoundException 116 | { 117 | if (position < 0) { 118 | return null; 119 | } 120 | if (position == 0) { 121 | return ctBehavior.getDeclaringClass(); 122 | } 123 | return ctBehavior.getParameterTypes()[position - 1]; 124 | } 125 | 126 | String getTypename() throws NotFoundException 127 | { 128 | CtClass type = getType(); 129 | if (type == null) { 130 | return ""; 131 | } 132 | return type.getName(); 133 | } 134 | 135 | Object[] getAnnotations() throws ClassNotFoundException 136 | { 137 | return ctBehavior.getParameterAnnotations()[position - 1]; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/PatchInfo.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.ByRef; 4 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 5 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch2; 6 | import javassist.*; 7 | import javassist.bytecode.annotation.AnnotationImpl; 8 | 9 | import java.lang.reflect.Proxy; 10 | 11 | public abstract class PatchInfo 12 | { 13 | private static int modNum = 0; 14 | 15 | private int modOrder; 16 | SpirePatch patch; 17 | CtBehavior ctMethodToPatch; 18 | CtMethod patchMethod; 19 | 20 | public PatchInfo(CtBehavior ctMethodToPatch, CtMethod patchMethod) 21 | { 22 | this.ctMethodToPatch = ctMethodToPatch; 23 | this.patchMethod = patchMethod; 24 | this.modOrder = modNum; 25 | } 26 | 27 | public PatchInfo setSpirePatch(SpirePatch patch) 28 | { 29 | this.patch = patch; 30 | return this; 31 | } 32 | 33 | public void debugPrint() 34 | { 35 | System.out.println("Patch Class: [" + patchClassName() + "]"); 36 | if (isSpirePatch2()) { 37 | System.out.println(" - SpirePatch2"); 38 | } 39 | System.out.println(" - Patching [" + ctMethodToPatch.getLongName() + "]"); 40 | System.out.print(" - "); 41 | System.out.println(debugMsg()); 42 | } 43 | 44 | protected String patchClassName() 45 | { 46 | return patchMethod.getDeclaringClass().getName(); 47 | } 48 | 49 | protected abstract String debugMsg(); 50 | 51 | public static void nextMod() 52 | { 53 | ++modNum; 54 | } 55 | 56 | final public int modOrdering() 57 | { 58 | return modOrder; 59 | } 60 | 61 | final public boolean isSpirePatch2() 62 | { 63 | if (patch == null) { 64 | return false; 65 | } 66 | 67 | try { 68 | AnnotationImpl impl = (AnnotationImpl) Proxy.getInvocationHandler(patch); 69 | return SpirePatch2.class.getName().equals(impl.getTypeName()); 70 | } catch (IllegalArgumentException | ClassCastException ignored) { 71 | return false; 72 | } 73 | } 74 | 75 | // Lower is earlier 76 | public abstract int patchOrdering(); 77 | 78 | public abstract void doPatch() throws PatchingException; 79 | 80 | protected static boolean paramByRef(Object[] annotations) 81 | { 82 | for (Object o : annotations) { 83 | if (o instanceof ByRef) { 84 | return true; 85 | } 86 | } 87 | return false; 88 | } 89 | 90 | // Gets the typename from the ByRef annotation 91 | protected static String paramByRefTypename(Object[] annotations) 92 | { 93 | for (Object o : annotations) { 94 | if (o instanceof ByRef) { 95 | return ((ByRef) o).type(); 96 | } 97 | } 98 | return ""; 99 | } 100 | 101 | // Gets the typename from the patched method's marameter types 102 | protected static String paramByRefTypename2(CtBehavior ctMethodToPatch, int index) throws NotFoundException 103 | { 104 | if (!Modifier.isStatic(ctMethodToPatch.getModifiers())) { 105 | --index; 106 | } 107 | try { 108 | return ctMethodToPatch.getParameterTypes()[index].getName(); 109 | } catch (ArrayIndexOutOfBoundsException e) { 110 | return null; 111 | } 112 | } 113 | 114 | protected static String paramByRefTypenamePrivateCapture(CtBehavior ctMethodToPatch, String paramName) throws NotFoundException 115 | { 116 | CtClass ctClass = ctMethodToPatch.getDeclaringClass(); 117 | CtField ctField = ctClass.getField(paramName); 118 | return ctField.getType().getName(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/PatchInfoComparator.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | import java.util.Comparator; 4 | 5 | public class PatchInfoComparator implements Comparator 6 | { 7 | // Ordering: 8 | // Insert, Instrument, Replace, Prefix, Postfix, Raw 9 | // Then sorted by mod load order 10 | @Override 11 | public int compare(PatchInfo o1, PatchInfo o2) 12 | { 13 | int patchOrdering = Integer.compare(o1.patchOrdering(), o2.patchOrdering()); 14 | if (patchOrdering != 0) { 15 | return patchOrdering; 16 | } 17 | 18 | int modOrdering = Integer.compare(o1.modOrdering(), o2.modOrdering()); 19 | if (modOrdering != 0) { 20 | return modOrdering; 21 | } 22 | 23 | return o1.toString().compareTo(o2.toString()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/PatchingException.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | import javassist.CtBehavior; 4 | import javassist.CtClass; 5 | 6 | public class PatchingException extends Exception 7 | { 8 | public PatchingException(String msg) 9 | { 10 | super(msg); 11 | } 12 | 13 | public PatchingException(String msg, Throwable cause) 14 | { 15 | super(msg, cause); 16 | } 17 | 18 | public PatchingException(Throwable cause) 19 | { 20 | super(cause); 21 | } 22 | 23 | public PatchingException(CtClass ctClass, String msg) 24 | { 25 | super(ctClass.getName() + ": " + msg); 26 | } 27 | 28 | public PatchingException(CtBehavior m, String msg) 29 | { 30 | super(m.getLongName() + ": " + msg); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/PrefixPatchInfo.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | import javassist.CannotCompileException; 4 | import javassist.CtBehavior; 5 | import javassist.CtConstructor; 6 | import javassist.CtMethod; 7 | 8 | public class PrefixPatchInfo extends ParameterPatchInfo 9 | { 10 | public PrefixPatchInfo(CtBehavior ctMethodToPatch, CtMethod patchMethod) 11 | { 12 | super(ctMethodToPatch, patchMethod); 13 | canSpireReturn = true; 14 | canByRefParams = true; 15 | } 16 | 17 | @Override 18 | protected String debugMsg() 19 | { 20 | return "Adding Prefix..."; 21 | } 22 | 23 | @Override 24 | public int patchOrdering() 25 | { 26 | return 1; 27 | } 28 | 29 | @Override 30 | protected void applyPatch(String src) throws CannotCompileException 31 | { 32 | if (ctMethodToPatch instanceof CtConstructor && !((CtConstructor) ctMethodToPatch).isClassInitializer()) { 33 | ((CtConstructor) ctMethodToPatch).insertBeforeBody(src); 34 | } else { 35 | ctMethodToPatch.insertBefore(src); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/RawPatchInfo.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | import javassist.CtBehavior; 4 | 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.lang.reflect.Method; 7 | 8 | public class RawPatchInfo extends PatchInfo 9 | { 10 | private Method method; 11 | 12 | public RawPatchInfo(CtBehavior ctMethodToPatch, Method method) 13 | { 14 | super(ctMethodToPatch, null); 15 | this.method = method; 16 | } 17 | 18 | @Override 19 | protected String patchClassName() 20 | { 21 | return method.getDeclaringClass().getName(); 22 | } 23 | 24 | @Override 25 | protected String debugMsg() 26 | { 27 | return "Raw Javassist..."; 28 | } 29 | 30 | @Override 31 | public int patchOrdering() 32 | { 33 | return 5; 34 | } 35 | 36 | @Override 37 | public void doPatch() throws PatchingException 38 | { 39 | try { 40 | method.invoke(null, ctMethodToPatch); 41 | } catch (IllegalAccessException | InvocationTargetException e) { 42 | throw new PatchingException(e); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/ReplacePatchInfo.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher; 2 | 3 | import javassist.CannotCompileException; 4 | import javassist.CtBehavior; 5 | import javassist.CtMethod; 6 | 7 | public class ReplacePatchInfo extends PatchInfo 8 | { 9 | public ReplacePatchInfo(CtBehavior ctMethodToPatch, CtMethod replaceMethod) 10 | { 11 | super(ctMethodToPatch, replaceMethod); 12 | } 13 | 14 | @Override 15 | protected String debugMsg() 16 | { 17 | return "Replacing..."; 18 | } 19 | 20 | @Override 21 | public int patchOrdering() 22 | { 23 | return 0; 24 | } 25 | 26 | @Override 27 | public void doPatch() throws PatchingException 28 | { 29 | try { 30 | ((CtMethod) ctMethodToPatch).setBody(patchMethod, null); 31 | } catch (CannotCompileException e) { 32 | throw new PatchingException(e); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/javassist/MyCodeConverter.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher.javassist; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpireOverride; 4 | import com.evacipated.cardcrawl.modthespire.patcher.javassist.convert.TransformSpecialCallVirtual; 5 | import javassist.CannotCompileException; 6 | import javassist.CodeConverter; 7 | import javassist.CtMethod; 8 | import javassist.Modifier; 9 | 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | public class MyCodeConverter extends CodeConverter 14 | { 15 | private static Set done = new HashSet<>(); 16 | 17 | public static void reset() 18 | { 19 | done.clear(); 20 | } 21 | 22 | public void redirectSpecialMethodCall(CtMethod origMethod) throws CannotCompileException 23 | { 24 | int mod1 = origMethod.getModifiers(); 25 | if (( 26 | Modifier.isStatic(mod1) 27 | || !Modifier.isPrivate(mod1) 28 | || origMethod.getDeclaringClass().isInterface() 29 | ) 30 | && !done.contains(origMethod.getLongName()) 31 | ) { 32 | if (!Modifier.isPrivate(mod1) && !origMethod.hasAnnotation(SpireOverride.class)) { 33 | throw new CannotCompileException("invoke-type not special/private " + origMethod.getLongName()); 34 | } 35 | } 36 | 37 | origMethod.setModifiers(Modifier.setProtected(origMethod.getModifiers())); 38 | done.add(origMethod.getLongName()); 39 | 40 | transformers = new TransformSpecialCallVirtual(transformers, origMethod); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patcher/javassist/convert/TransformSpecialCallVirtual.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patcher.javassist.convert; 2 | 3 | import com.evacipated.cardcrawl.modthespire.Loader; 4 | import javassist.ClassPool; 5 | import javassist.CtClass; 6 | import javassist.CtMethod; 7 | import javassist.NotFoundException; 8 | import javassist.bytecode.*; 9 | import javassist.convert.Transformer; 10 | 11 | public class TransformSpecialCallVirtual extends Transformer 12 | { 13 | protected String classname, methodname, methodDescriptor; 14 | protected CodeAttribute codeAttr; 15 | 16 | /* cache */ 17 | protected int newIndex; 18 | protected ConstPool constPool; 19 | 20 | public TransformSpecialCallVirtual(Transformer next, CtMethod origMethod) 21 | { 22 | super(next); 23 | methodname = origMethod.getName(); 24 | methodDescriptor = origMethod.getMethodInfo2().getDescriptor(); 25 | classname = origMethod.getDeclaringClass().getName(); 26 | constPool = null; 27 | } 28 | 29 | @Override 30 | public void initialize(ConstPool cp, CodeAttribute attr) { 31 | if (constPool != cp) { 32 | newIndex = 0; 33 | } 34 | codeAttr = attr; 35 | } 36 | 37 | /** 38 | * Modify INVOKESPECIAL to use INVOKEVIRTUAL. 39 | */ 40 | @Override 41 | public int transform(CtClass clazz, int pos, CodeIterator iterator, 42 | ConstPool cp) throws BadBytecode 43 | { 44 | int c = iterator.byteAt(pos); 45 | if (c == INVOKESPECIAL) { 46 | int index = iterator.u16bitAt(pos + 1); 47 | String cname = cp.eqMember(methodname, methodDescriptor, index); 48 | if (cname != null && matchClass(cname, clazz.getClassPool())) { 49 | int ntinfo = cp.getMemberNameAndType(index); 50 | pos = match(c, pos, iterator, cp.getNameAndTypeDescriptor(ntinfo), cp); 51 | } 52 | } 53 | 54 | return pos; 55 | } 56 | 57 | private boolean matchClass(String name, ClassPool pool) { 58 | if (classname.equals(name)) 59 | return true; 60 | 61 | try { 62 | CtClass clazz = pool.get(name); 63 | CtClass declClazz = pool.get(classname); 64 | if (clazz.subtypeOf(declClazz)) 65 | try { 66 | CtMethod m = clazz.getMethod(methodname, methodDescriptor); 67 | return m.getDeclaringClass().getName().equals(classname); 68 | } 69 | catch (NotFoundException e) { 70 | // maybe the original method has been removed. 71 | return true; 72 | } 73 | } 74 | catch (NotFoundException e) { 75 | return false; 76 | } 77 | 78 | return false; 79 | } 80 | 81 | protected int match(int c, int pos, CodeIterator iterator, 82 | int typedesc, ConstPool cp) throws BadBytecode 83 | { 84 | LineNumberAttribute ainfo = (LineNumberAttribute) codeAttr.getAttribute(LineNumberAttribute.tag); 85 | 86 | int nt = cp.addNameAndTypeInfo(cp.addUtf8Info(methodname), typedesc); 87 | int ci = cp.addClassInfo(classname); 88 | if (c == INVOKEINTERFACE) { 89 | newIndex = cp.addInterfaceMethodrefInfo(ci, nt); 90 | } else { 91 | if (c == INVOKESPECIAL) { 92 | if (Loader.DEBUG) { 93 | System.out.println(" @ " + ainfo.toLineNumber(pos)); 94 | } 95 | iterator.writeByte(INVOKEVIRTUAL, pos); 96 | } 97 | 98 | newIndex = cp.addMethodrefInfo(ci, nt); 99 | } 100 | 101 | constPool = cp; 102 | 103 | iterator.write16bit(newIndex, pos + 1); 104 | return pos; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/AllowWatcherUnlock.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch2; 4 | import com.evacipated.cardcrawl.modthespire.lib.SpireReturn; 5 | import com.megacrit.cardcrawl.characters.AbstractPlayer; 6 | import com.megacrit.cardcrawl.core.CardCrawlGame; 7 | import com.megacrit.cardcrawl.screens.DeathScreen; 8 | import com.megacrit.cardcrawl.screens.stats.CharStat; 9 | import com.megacrit.cardcrawl.unlock.UnlockTracker; 10 | import com.megacrit.cardcrawl.unlock.misc.DefectUnlock; 11 | import com.megacrit.cardcrawl.unlock.misc.WatcherUnlock; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | @SpirePatch2( 17 | clz=DeathScreen.class, 18 | method="willWatcherUnlock" 19 | ) 20 | public class AllowWatcherUnlock 21 | { 22 | public static SpireReturn Prefix(boolean ___defectUnlockedThisRun) 23 | { 24 | if (___defectUnlockedThisRun || !UnlockTracker.isCharacterLocked(WatcherUnlock.KEY)) { 25 | return SpireReturn.Return(false); 26 | } 27 | List baseCharacters = new ArrayList<>(); 28 | for (AbstractPlayer c : CardCrawlGame.characterManager.getAllCharacters()) { 29 | switch (c.chosenClass) { 30 | case IRONCLAD: 31 | case THE_SILENT: 32 | case DEFECT: 33 | baseCharacters.add(c.getCharStat()); 34 | break; 35 | } 36 | } 37 | return SpireReturn.Return( 38 | !UnlockTracker.isCharacterLocked(DefectUnlock.KEY) 39 | && baseCharacters.stream().anyMatch(stats -> stats.getVictoryCount() > 0) 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/AlwaysEnableCustomMode.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 4 | import com.megacrit.cardcrawl.screens.mainMenu.MenuPanelScreen; 5 | import javassist.CannotCompileException; 6 | import javassist.expr.ExprEditor; 7 | import javassist.expr.FieldAccess; 8 | 9 | @SpirePatch( 10 | clz=MenuPanelScreen.class, 11 | method="initializePanels" 12 | ) 13 | public class AlwaysEnableCustomMode 14 | { 15 | public static ExprEditor Instrument() 16 | { 17 | return new ExprEditor() { 18 | @Override 19 | public void edit(FieldAccess f) throws CannotCompileException 20 | { 21 | if (f.getClassName().equals("com.megacrit.cardcrawl.screens.stats.CharStat") 22 | && f.getFieldName().equals("highestDaily")) { 23 | f.replace("$_ = 1;"); 24 | } 25 | } 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/CatchCrash.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch2; 4 | import com.megacrit.cardcrawl.core.ExceptionHandler; 5 | 6 | public class CatchCrash 7 | { 8 | @SpirePatch2( 9 | clz=ExceptionHandler.class, 10 | method="handleException" 11 | ) 12 | public static class MegaCritHandler 13 | { 14 | public static void Prefix(Exception e) 15 | { 16 | HandleCrash.crash = e; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/ChangeWindowTitle.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches; 2 | 3 | import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; 4 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 5 | import com.megacrit.cardcrawl.desktop.DesktopLauncher; 6 | import javassist.CannotCompileException; 7 | import javassist.expr.ExprEditor; 8 | import javassist.expr.FieldAccess; 9 | 10 | @SpirePatch( 11 | clz=DesktopLauncher.class, 12 | method="main" 13 | ) 14 | public class ChangeWindowTitle 15 | { 16 | public static ExprEditor Instrument() 17 | { 18 | return new ExprEditor() { 19 | @Override 20 | public void edit(FieldAccess f) throws CannotCompileException 21 | { 22 | if (f.isWriter() && f.getClassName().equals(LwjglApplicationConfiguration.class.getName()) 23 | && f.getFieldName().equals("title")) { 24 | f.replace("$proceed(\"Modded \" + $1);"); 25 | } 26 | } 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/CreditsModList.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches; 2 | 3 | import com.evacipated.cardcrawl.modthespire.Loader; 4 | import com.evacipated.cardcrawl.modthespire.ModInfo; 5 | import com.evacipated.cardcrawl.modthespire.ReflectionHelper; 6 | import com.evacipated.cardcrawl.modthespire.lib.ByRef; 7 | import com.evacipated.cardcrawl.modthespire.lib.SpireInsertPatch; 8 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 9 | import com.megacrit.cardcrawl.core.Settings; 10 | import com.megacrit.cardcrawl.credits.CreditLine; 11 | import com.megacrit.cardcrawl.credits.CreditsScreen; 12 | 13 | import java.lang.reflect.Field; 14 | import java.util.ArrayList; 15 | 16 | @SpirePatch( 17 | clz=CreditsScreen.class, 18 | method=SpirePatch.CONSTRUCTOR 19 | ) 20 | public class CreditsModList { 21 | private static String[] MTS_AUTHORS = new String[] { 22 | "kiooeht", 23 | "t-larson", 24 | "test447" 25 | }; 26 | 27 | @SpireInsertPatch( 28 | rloc=5, 29 | localvars={"tmpY"} 30 | ) 31 | public static void Insert(CreditsScreen __instance, @ByRef float[] tmpY) 32 | { 33 | try { 34 | Field f = CreditsScreen.class.getDeclaredField("lines"); 35 | f.setAccessible(true); 36 | ArrayList lines = (ArrayList)f.get(__instance); 37 | 38 | // ModTheSpire 39 | lines.add(new CreditLine("ModTheSpire", tmpY[0] -= 150.0F, true)); 40 | // ModTheSpire authors 41 | for (String author : MTS_AUTHORS) { 42 | lines.add(new CreditLine(author, tmpY[0] -= 45.0F, false)); 43 | } 44 | // Loaded mods 45 | for (ModInfo info : Loader.MODINFOS) { 46 | lines.add(new CreditLine(info.Name, tmpY[0] -= 150.0F, true)); 47 | for (String author : info.Authors) { 48 | lines.add(new CreditLine(author, tmpY[0] -= 45.0F, false)); 49 | } 50 | } 51 | } catch (NoSuchFieldException | IllegalAccessException e) { 52 | e.printStackTrace(); 53 | } 54 | } 55 | 56 | public static void Postfix(CreditsScreen __instance) 57 | { 58 | try { 59 | Field f = CreditsScreen.class.getDeclaredField("lines"); 60 | f.setAccessible(true); 61 | ArrayList lines = (ArrayList) f.get(__instance); 62 | 63 | int headers = 0; 64 | for (CreditLine line : lines) { 65 | Field color = CreditLine.class.getDeclaredField("color"); 66 | color.setAccessible(true); 67 | if (color.get(line).equals(Settings.GOLD_COLOR)) { 68 | ++headers; 69 | } 70 | } 71 | 72 | float NEW_END_OF_CREDITS_Y = 85.0F + (headers * 150.0F) + ((lines.size() - headers) * 45.0F); 73 | NEW_END_OF_CREDITS_Y *= Settings.scale; 74 | Field END_OF_CREDITS_Y = CreditsScreen.class.getDeclaredField("END_OF_CREDITS_Y"); 75 | ReflectionHelper.setStaticFinalField(END_OF_CREDITS_Y, NEW_END_OF_CREDITS_Y); 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/HandleCrash.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches; 2 | 3 | import com.evacipated.cardcrawl.modthespire.Loader; 4 | 5 | import java.net.URL; 6 | import java.util.Arrays; 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | import java.util.stream.Collectors; 10 | 11 | public class HandleCrash 12 | { 13 | public static Throwable crash = null; 14 | 15 | public static void maybeExit() 16 | { 17 | if (crash != null) { 18 | System.err.println("Game crashed."); 19 | Loader.printMTSInfo(System.err); 20 | tryPrintModsInStacktrace(crash); 21 | System.err.println("Cause:"); 22 | crash.printStackTrace(); 23 | Loader.restoreWindowOnCrash(); 24 | } else { 25 | System.out.println("Game closed."); 26 | if (!Loader.DEBUG) { 27 | Loader.closeWindow(); 28 | } 29 | } 30 | } 31 | 32 | private static void tryPrintModsInStacktrace(Throwable exception) { 33 | try { 34 | printModsInStacktrace(exception); 35 | } catch (Exception e) { 36 | // ignore 37 | } 38 | } 39 | 40 | private static void printModsInStacktrace(Throwable exception) { 41 | Set classes = new HashSet<>(); 42 | addClassesInThrowable(exception, classes); 43 | 44 | Set urls = new HashSet<>(); 45 | for (String className : classes) { 46 | try { 47 | Class cls = Class.forName(className); 48 | urls.add(cls.getProtectionDomain().getCodeSource().getLocation()); 49 | } catch (ClassNotFoundException | NoClassDefFoundError ignore) { 50 | // ignore 51 | } 52 | } 53 | 54 | Set modInfoLines = new HashSet<>(); 55 | for (URL url : urls) { 56 | if (url == null) { 57 | continue; 58 | } 59 | 60 | Arrays.stream(Loader.MODINFOS).filter(m -> m.jarURL.equals(url)).findFirst().ifPresent(modInfo -> { 61 | if (!modInfo.ID.equals("basemod")) { 62 | modInfoLines.add(String.format("%s (%s)", modInfo.ID, modInfo.ModVersion)); 63 | } 64 | }); 65 | } 66 | 67 | if (modInfoLines.size() > 0) { 68 | System.err.println("Mods in stacktrace:"); 69 | for (String modId : modInfoLines.stream().sorted().collect(Collectors.toList())) { 70 | System.err.println(" - " + modId); 71 | } 72 | } 73 | } 74 | 75 | private static void addClassesInThrowable(Throwable exception, Set classes) { 76 | for (StackTraceElement stackTraceElement : exception.getStackTrace()) { 77 | classes.add(stackTraceElement.getClassName()); 78 | } 79 | 80 | for (Throwable suppressed : exception.getSuppressed()) { 81 | if (suppressed != null) { 82 | addClassesInThrowable(suppressed, classes); 83 | } 84 | } 85 | 86 | Throwable cause = exception.getCause(); 87 | if (cause != null) { 88 | addClassesInThrowable(cause, classes); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/HeapSize.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches; 2 | 3 | import com.badlogic.gdx.graphics.Color; 4 | import com.badlogic.gdx.graphics.g2d.SpriteBatch; 5 | import com.evacipated.cardcrawl.modthespire.lib.SpireInsertPatch; 6 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch2; 7 | import com.megacrit.cardcrawl.core.CardCrawlGame; 8 | import com.megacrit.cardcrawl.core.Settings; 9 | import com.megacrit.cardcrawl.helpers.FontHelper; 10 | 11 | @SpirePatch2( 12 | clz = CardCrawlGame.class, 13 | method = "render" 14 | ) 15 | public class HeapSize 16 | { 17 | @SpireInsertPatch( 18 | loc = 450 19 | ) 20 | public static void Insert(SpriteBatch ___sb) 21 | { 22 | // for testing heap usage, change to true 23 | if (false) { 24 | Runtime runtime = Runtime.getRuntime(); 25 | long heapSize = runtime.totalMemory() / 1024; 26 | long heapMaxSize = runtime.maxMemory(); 27 | long heapFreeSize = runtime.freeMemory(); 28 | 29 | FontHelper.renderFontLeftTopAligned( 30 | ___sb, 31 | FontHelper.tipBodyFont, 32 | "Heap size: " + formatSize(heapSize) + "\n" + 33 | "Max Heap size: " + formatSize(heapMaxSize) + "\n" + 34 | "Free size: " + formatSize(heapFreeSize), 35 | 10, 36 | Settings.HEIGHT - 130, 37 | Color.WHITE 38 | ); 39 | } 40 | } 41 | 42 | public static String formatSize(long v) { 43 | if (v < 1024) return v + " B"; 44 | int z = (63 - Long.numberOfLeadingZeros(v)) / 10; 45 | return String.format("%.1f %sB", (double)v / (1L << (z*10)), " KMGTPE".charAt(z)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/IsModded.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 4 | import com.megacrit.cardcrawl.core.Settings; 5 | import javassist.CannotCompileException; 6 | import javassist.expr.ExprEditor; 7 | import javassist.expr.FieldAccess; 8 | 9 | @SpirePatch( 10 | clz=Settings.class, 11 | method=SpirePatch.STATICINITIALIZER 12 | ) 13 | public class IsModded 14 | { 15 | public static ExprEditor Instrument() 16 | { 17 | return new ExprEditor() { 18 | @Override 19 | public void edit(FieldAccess f) throws CannotCompileException 20 | { 21 | if (f.isWriter() && f.getFieldName().equals("isModded")) { 22 | f.replace("$proceed(true);"); 23 | } else if (f.isWriter() && f.getFieldName().equals("isDev")) { 24 | f.replace("$proceed(false);"); 25 | } 26 | } 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/MainMenuModList.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches; 2 | 3 | import com.badlogic.gdx.graphics.g2d.BitmapFont; 4 | import com.evacipated.cardcrawl.modthespire.Loader; 5 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 6 | import com.megacrit.cardcrawl.core.CardCrawlGame; 7 | import com.megacrit.cardcrawl.helpers.FontHelper; 8 | import com.megacrit.cardcrawl.screens.mainMenu.MainMenuScreen; 9 | import javassist.CannotCompileException; 10 | import javassist.expr.ExprEditor; 11 | import javassist.expr.FieldAccess; 12 | import javassist.expr.MethodCall; 13 | 14 | import java.util.regex.Matcher; 15 | import java.util.regex.Pattern; 16 | 17 | @SpirePatch( 18 | clz=MainMenuScreen.class, 19 | method="render" 20 | ) 21 | public class MainMenuModList 22 | { 23 | public static ExprEditor Instrument() 24 | { 25 | return new ExprEditor() { 26 | @Override 27 | public void edit(FieldAccess f) throws CannotCompileException 28 | { 29 | if (f.getFieldName().equals("VERSION_INFO")) { 30 | f.replace(String.format("$_ = %s.alterVersion($proceed($$));", MainMenuModList.class.getName())); 31 | } 32 | } 33 | 34 | @Override 35 | public void edit(MethodCall m) throws CannotCompileException 36 | { 37 | if (m.getMethodName().equals("renderSmartText")) { 38 | m.replace( 39 | "{" + 40 | String.format("$5 = %s.getSmartHeight($2, $3, $6, $7);", MainMenuModList.class.getName()) + 41 | "$proceed($$);" + 42 | "}" 43 | ); 44 | } 45 | } 46 | }; 47 | } 48 | 49 | private static Pattern re = Pattern.compile("(\\[.+] \\(.+\\)(?:v\\d)*) \\[(ModTheSpire .+)]( BETA)?"); 50 | 51 | public static String alterVersion(String version) 52 | { 53 | Matcher m = re.matcher(version); 54 | if (!m.find()) { 55 | return version; 56 | } 57 | 58 | String ver = m.group(1); 59 | String mtsver = m.group(2); 60 | String beta = m.group(3); 61 | return ver + (beta == null ? "" : beta) + " NL " + makeMTSVersionModCount(mtsver); 62 | } 63 | 64 | public static String alterVersion2(String version) 65 | { 66 | Matcher m = re.matcher(version); 67 | if (!m.find()) { 68 | return version; 69 | } 70 | 71 | String ver = m.group(1); 72 | String beta = m.group(3); 73 | return ver + (beta == null ? "" : beta); 74 | } 75 | 76 | public static String makeMTSVersionModCount(String version) 77 | { 78 | return version + " - " + Loader.MODINFOS.length + " mod" + (Loader.MODINFOS.length > 1 ? "s" : ""); 79 | } 80 | 81 | public static float getSmartHeight(BitmapFont font, String msg, float lineWidth, float lineSpacing) 82 | { 83 | return -FontHelper.getSmartHeight(font, msg, lineWidth, lineSpacing) + lineSpacing; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/SkipIntro.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches; 2 | 3 | import com.evacipated.cardcrawl.modthespire.Loader; 4 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 5 | import com.evacipated.cardcrawl.modthespire.lib.SpireReturn; 6 | import com.megacrit.cardcrawl.screens.splash.SplashScreen; 7 | 8 | @SpirePatch( 9 | clz=SplashScreen.class, 10 | method="update" 11 | ) 12 | public class SkipIntro 13 | { 14 | public static SpireReturn Prefix(SplashScreen __instance) 15 | { 16 | if (Loader.SKIP_INTRO) { 17 | __instance.isDone = true; 18 | return SpireReturn.Return(); 19 | } 20 | return SpireReturn.Continue(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/lwjgl2/CatchCrash.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.lwjgl2; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 4 | import com.evacipated.cardcrawl.modthespire.patches.HandleCrash; 5 | import javassist.CannotCompileException; 6 | import javassist.expr.ExprEditor; 7 | import javassist.expr.MethodCall; 8 | 9 | @SpirePatch( 10 | cls="com.badlogic.gdx.backends.lwjgl.LwjglApplication$1", 11 | method="run" 12 | ) 13 | public class CatchCrash 14 | { 15 | public static ExprEditor Instrument() 16 | { 17 | return new ExprEditor() 18 | { 19 | @Override 20 | public void edit(MethodCall m) throws CannotCompileException 21 | { 22 | if (m.getMethodName().equals("setCursorCatched")) { 23 | m.replace( 24 | HandleCrash.class.getName() + ".crash = t;" + 25 | "$_ = $proceed($$);" 26 | ); 27 | } 28 | } 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/lwjgl2/Lwjgl2DisableGdxForceExit.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.lwjgl2; 2 | 3 | import com.badlogic.gdx.backends.lwjgl.LwjglApplication; 4 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 5 | import com.evacipated.cardcrawl.modthespire.patches.HandleCrash; 6 | import javassist.CannotCompileException; 7 | import javassist.expr.ExprEditor; 8 | import javassist.expr.FieldAccess; 9 | 10 | @SpirePatch( 11 | clz= LwjglApplication.class, 12 | method="mainLoop" 13 | ) 14 | public class Lwjgl2DisableGdxForceExit 15 | { 16 | public static ExprEditor Instrument() 17 | { 18 | return new ExprEditor() { 19 | @Override 20 | public void edit(FieldAccess f) throws CannotCompileException 21 | { 22 | if (f.isReader() && f.getFieldName().equals("forceExit")) { 23 | f.replace( 24 | HandleCrash.class.getName() + ".maybeExit();" + 25 | "$_ = false;" 26 | ); 27 | } 28 | } 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/lwjgl3/CatchCrash.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.lwjgl3; 2 | 3 | import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; 4 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 5 | import com.evacipated.cardcrawl.modthespire.patches.HandleCrash; 6 | import javassist.CannotCompileException; 7 | import javassist.NotFoundException; 8 | import javassist.expr.ExprEditor; 9 | import javassist.expr.Instanceof; 10 | 11 | @SpirePatch( 12 | clz=Lwjgl3Application.class, 13 | method=SpirePatch.CONSTRUCTOR 14 | ) 15 | public class CatchCrash 16 | { 17 | public static ExprEditor Instrument() 18 | { 19 | return new ExprEditor() 20 | { 21 | @Override 22 | public void edit(Instanceof i) throws CannotCompileException 23 | { 24 | try { 25 | if (i.getType().getName().equals(RuntimeException.class.getName())) { 26 | i.replace( 27 | HandleCrash.class.getName() + ".crash = $1;" + 28 | "$_ = $proceed($$);" 29 | ); 30 | } 31 | } catch (NotFoundException e) { 32 | throw new CannotCompileException(e); 33 | } 34 | } 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/lwjgl3/CloseMTSWindow.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.lwjgl3; 2 | 3 | import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; 4 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch2; 5 | import com.evacipated.cardcrawl.modthespire.patches.HandleCrash; 6 | 7 | @SpirePatch2( 8 | clz = Lwjgl3Application.class, 9 | method = "cleanup" 10 | ) 11 | public class CloseMTSWindow 12 | { 13 | public static void Postfix() 14 | { 15 | HandleCrash.maybeExit(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/lwjgl3/FixController.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.lwjgl3; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch2; 4 | import com.evacipated.cardcrawl.modthespire.lib.SpireRawPatch; 5 | import com.megacrit.cardcrawl.helpers.controller.CInputHelper; 6 | import javassist.*; 7 | import javassist.bytecode.CodeAttribute; 8 | import javassist.bytecode.CodeIterator; 9 | import javassist.bytecode.ConstPool; 10 | import javassist.convert.Transformer; 11 | 12 | // In addition to this patch, gdx-controllers-lwjgl3 has to be shaded 13 | @SpirePatch2( 14 | clz = CInputHelper.class, 15 | method = "initializeIfAble" 16 | ) 17 | public class FixController 18 | { 19 | // We do it this way because a normal instrument won't work because the Display class 20 | // reference we're trying to remove gets ignored by the instrument 21 | @SpireRawPatch 22 | public static void RemoveDisplayReference(CtBehavior ctBehavior) throws CannotCompileException 23 | { 24 | ctBehavior.instrument(new MyCodeConverter()); 25 | } 26 | 27 | private static class MyCodeConverter extends CodeConverter 28 | { 29 | public MyCodeConverter() 30 | { 31 | transformers = new TransformDisplayIsActive(transformers); 32 | } 33 | } 34 | 35 | private static class TransformDisplayIsActive extends Transformer 36 | { 37 | private static final String className = "org.lwjgl.opengl.Display"; 38 | private static final String methodName = "isActive"; 39 | private static final String methodDescriptor = "()Z"; 40 | 41 | 42 | /* cache */ 43 | protected CodeAttribute codeAttr; 44 | protected int newIndex; 45 | protected ConstPool constPool; 46 | 47 | public TransformDisplayIsActive(Transformer next) 48 | { 49 | super(next); 50 | } 51 | 52 | @Override 53 | public void initialize(ConstPool cp, CodeAttribute attr) { 54 | if (constPool != cp) { 55 | newIndex = 0; 56 | } 57 | codeAttr = attr; 58 | } 59 | 60 | @Override 61 | public int transform(CtClass clazz, int pos, CodeIterator it, ConstPool cp) 62 | { 63 | int c = it.byteAt(pos); 64 | if (c == INVOKESTATIC) { 65 | int index = it.u16bitAt(pos + 1); 66 | String cname = cp.eqMember(methodName, methodDescriptor, index); 67 | if (cname != null && matchClass(cname, clazz.getClassPool())) { 68 | it.writeByte(ICONST_1, pos); 69 | it.write16bit(NOP, pos + 1); 70 | } 71 | } 72 | 73 | return pos; 74 | } 75 | 76 | private boolean matchClass(String name, ClassPool pool) { 77 | if (className.equals(name)) 78 | return true; 79 | 80 | try { 81 | CtClass clazz = pool.get(name); 82 | CtClass declClazz = pool.get(className); 83 | if (clazz.subtypeOf(declClazz)) 84 | try { 85 | CtMethod m = clazz.getMethod(methodName, methodDescriptor); 86 | return m.getDeclaringClass().getName().equals(className); 87 | } 88 | catch (NotFoundException e) { 89 | // maybe the original method has been removed. 90 | return true; 91 | } 92 | } 93 | catch (NotFoundException e) { 94 | return false; 95 | } 96 | 97 | return false; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/lwjgl3/FramerateLimiter.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.lwjgl3; 2 | 3 | import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application; 4 | import com.evacipated.cardcrawl.modthespire.lib.*; 5 | import com.megacrit.cardcrawl.characters.AbstractPlayer; 6 | import javassist.CtBehavior; 7 | 8 | @SpirePatch2( 9 | clz = Lwjgl3Application.class, 10 | method = "loop" 11 | ) 12 | public class FramerateLimiter 13 | { 14 | public static int fps = 60; 15 | private static final Sync sync = new Sync(); 16 | 17 | @SpireInsertPatch( 18 | loc = 180 19 | ) 20 | public static void Insert() 21 | { 22 | sync.sync(fps); 23 | } 24 | 25 | private static class Locator extends SpireInsertLocator 26 | { 27 | @Override 28 | public int[] Locate(CtBehavior ctBehavior) throws Exception 29 | { 30 | Matcher matcher = new Matcher.FieldAccessMatcher(AbstractPlayer.class, "powers"); 31 | return LineFinder.findInOrder(ctBehavior, matcher); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/modsscreen/BaseMod/DisableBaseModBadges.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.modsscreen.BaseMod; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 4 | 5 | import java.lang.reflect.Field; 6 | import java.util.ArrayList; 7 | 8 | @SpirePatch( 9 | cls="basemod.BaseMod", 10 | method="publishPostInitialize", 11 | optional=true 12 | ) 13 | public class DisableBaseModBadges 14 | { 15 | public static void Postfix() 16 | { 17 | try { 18 | Class basemod = DisableBaseModBadges.class.getClassLoader().loadClass("basemod.BaseMod"); 19 | Field f = basemod.getDeclaredField("renderSubscribers"); 20 | f.setAccessible(true); 21 | ArrayList renderSubscribers = (ArrayList)f.get(null); 22 | renderSubscribers.removeIf(o -> o.getClass().getName().equals("basemod.ModBadge")); 23 | 24 | f = basemod.getDeclaredField("preUpdateSubscribers"); 25 | f.setAccessible(true); 26 | ArrayList preUpdateSubscribers = (ArrayList)f.get(null); 27 | preUpdateSubscribers.removeIf(o -> o.getClass().getName().equals("basemod.ModBadge")); 28 | } catch (ClassNotFoundException e) { 29 | e.printStackTrace(); 30 | } catch (NoSuchFieldException e) { 31 | e.printStackTrace(); 32 | } catch (IllegalAccessException e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/modsscreen/BaseMod/ModBadgeOnClick.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.modsscreen.BaseMod; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 4 | import javassist.CannotCompileException; 5 | import javassist.CtBehavior; 6 | 7 | @SpirePatch( 8 | cls="basemod.ModBadge", 9 | method="onClick", 10 | optional=true 11 | ) 12 | public class ModBadgeOnClick 13 | { 14 | public static void Raw(CtBehavior ctMethodToPatch) 15 | { 16 | try { 17 | ctMethodToPatch.setBody( 18 | "if (modPanel != null) {" + 19 | "modPanel.oldInputProcessor = com.badlogic.gdx.Gdx.input.getInputProcessor();" + 20 | "basemod.BaseMod.modSettingsUp = true;" + 21 | "modPanel.isUp = true;" + 22 | "modPanel.onCreate();" + 23 | "}" 24 | ); 25 | } catch (CannotCompileException e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/modsscreen/MainMenuItem.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.modsscreen; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.*; 4 | import com.evacipated.cardcrawl.modthespire.patcher.PatchingException; 5 | import com.megacrit.cardcrawl.screens.compendium.CardLibraryScreen; 6 | import com.megacrit.cardcrawl.screens.mainMenu.MainMenuScreen; 7 | import com.megacrit.cardcrawl.screens.mainMenu.MenuButton; 8 | import javassist.CannotCompileException; 9 | import javassist.CtBehavior; 10 | 11 | import java.util.Arrays; 12 | 13 | @SpirePatch( 14 | clz=MainMenuScreen.class, 15 | method="setMainMenuButtons" 16 | ) 17 | public class MainMenuItem 18 | { 19 | @SpireInsertPatch( 20 | locator=Locator.class, 21 | localvars={"index"} 22 | ) 23 | public static void Insert(Object __obj_instance, @ByRef int[] index) 24 | { 25 | MainMenuScreen __instance = (MainMenuScreen)__obj_instance; 26 | __instance.buttons.add(new MenuButton(ModMenuButton.MODS, index[0]++)); 27 | } 28 | 29 | private static class Locator extends SpireInsertLocator 30 | { 31 | @Override 32 | public int[] Locate(CtBehavior ctMethodToPatch) throws CannotCompileException, PatchingException 33 | { 34 | Matcher finalMatcher = new Matcher.FieldAccessMatcher(MenuButton.ClickResult.class, "PATCH_NOTES"); 35 | return LineFinder.findInOrder(ctMethodToPatch, finalMatcher); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/modsscreen/ModMenuButton.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.modsscreen; 2 | 3 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 4 | import com.evacipated.cardcrawl.modthespire.lib.SpireEnum; 5 | import com.megacrit.cardcrawl.screens.mainMenu.MenuButton; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | public class ModMenuButton 10 | { 11 | @SpireEnum 12 | static MenuButton.ClickResult MODS; 13 | 14 | static ModsScreen modsScreen = null; 15 | 16 | @SpirePatch( 17 | clz=MenuButton.class, 18 | method="setLabel" 19 | ) 20 | public static class SetLabel 21 | { 22 | public static void Postfix(MenuButton __instance) 23 | { 24 | try { 25 | if (__instance.result == MODS) { 26 | Field f_label = MenuButton.class.getDeclaredField("label"); 27 | f_label.setAccessible(true); 28 | f_label.set(__instance, "Mods"); 29 | } 30 | } catch (Exception e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | } 35 | 36 | @SpirePatch( 37 | clz=MenuButton.class, 38 | method="buttonEffect" 39 | ) 40 | public static class ButtonEffect 41 | { 42 | public static void Postfix(MenuButton __instance) 43 | { 44 | if (__instance.result == MODS) { 45 | if (modsScreen == null) { 46 | modsScreen = new ModsScreen(); 47 | } 48 | modsScreen.open(); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/modsscreen/ModsScreenUpdateRender.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.modsscreen; 2 | 3 | import com.badlogic.gdx.graphics.g2d.SpriteBatch; 4 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 5 | import com.megacrit.cardcrawl.screens.mainMenu.MainMenuScreen; 6 | 7 | public class ModsScreenUpdateRender 8 | { 9 | @SpirePatch( 10 | clz=MainMenuScreen.class, 11 | method="update" 12 | ) 13 | public static class Update 14 | { 15 | public static void Postfix(MainMenuScreen __instance) 16 | { 17 | if (__instance.screen == ModsScreen.Enum.MODS_LIST) { 18 | ModMenuButton.modsScreen.update(); 19 | } 20 | } 21 | } 22 | 23 | @SpirePatch( 24 | clz=MainMenuScreen.class, 25 | method="render" 26 | ) 27 | public static class Render 28 | { 29 | public static void Postfix(MainMenuScreen __instance, SpriteBatch sb) 30 | { 31 | if (__instance.screen == ModsScreen.Enum.MODS_LIST) { 32 | ModMenuButton.modsScreen.render(sb); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/patches/modsscreen/SaveBaseModBadges.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.patches.modsscreen; 2 | 3 | import com.badlogic.gdx.graphics.Texture; 4 | import com.evacipated.cardcrawl.modthespire.Loader; 5 | import com.evacipated.cardcrawl.modthespire.lib.SpireInsertPatch; 6 | import com.evacipated.cardcrawl.modthespire.lib.SpirePatch; 7 | import javassist.CannotCompileException; 8 | import javassist.ClassPool; 9 | import javassist.CtClass; 10 | import javassist.NotFoundException; 11 | import javassist.expr.ExprEditor; 12 | import javassist.expr.MethodCall; 13 | 14 | import java.net.MalformedURLException; 15 | import java.net.URISyntaxException; 16 | import java.net.URL; 17 | import java.util.HashMap; 18 | 19 | @SpirePatch( 20 | cls="basemod.BaseMod", 21 | method="registerModBadge", 22 | optional=true 23 | ) 24 | public class SaveBaseModBadges 25 | { 26 | @SpireInsertPatch( 27 | rloc=8, 28 | localvars={"badge"} 29 | ) 30 | public static void Insert(Texture t, String name, String author, String desc, Object settingsPanel, Object badge) 31 | { 32 | if (ModsScreen.baseModBadges == null) { 33 | ModsScreen.baseModBadges = new HashMap<>(); 34 | } 35 | 36 | StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); 37 | try { 38 | Class cls = SaveBaseModBadges.class.getClassLoader().loadClass(stacktrace[3].getClassName()); 39 | URL location = cls.getProtectionDomain().getCodeSource().getLocation(); 40 | if (location == null) { 41 | try { 42 | ClassPool pool = Loader.getClassPool(); 43 | CtClass ctCls = pool.get(cls.getName()); 44 | String url = ctCls.getURL().getFile(); 45 | int i = url.lastIndexOf('!'); 46 | url = url.substring(0, i); 47 | location = new URL(url); 48 | } catch (NotFoundException | MalformedURLException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | if (location != null) { 53 | URL modFile = location.toURI().toURL(); 54 | ModsScreen.baseModBadges.put(modFile, badge); 55 | } 56 | } catch (ClassNotFoundException | URISyntaxException | MalformedURLException e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | 61 | public static ExprEditor Instrument() 62 | { 63 | return new ExprEditor() { 64 | @Override 65 | public void edit(MethodCall m) throws CannotCompileException 66 | { 67 | if (m.getMethodName().equals("add")) { 68 | m.replace("$_ = true;"); 69 | } 70 | } 71 | }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/ui/TextFieldWithPlaceholder.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.ui; 2 | 3 | import javax.swing.*; 4 | import java.awt.*; 5 | import java.awt.event.FocusEvent; 6 | import java.awt.event.FocusListener; 7 | 8 | public class TextFieldWithPlaceholder extends JTextField { 9 | private String placeholder; 10 | 11 | public TextFieldWithPlaceholder() { 12 | this.addFocusListener(new FocusListener() { 13 | @Override 14 | public void focusGained(FocusEvent e) { 15 | TextFieldWithPlaceholder.this.repaint(); 16 | } 17 | 18 | @Override 19 | public void focusLost(FocusEvent e) { 20 | TextFieldWithPlaceholder.this.repaint(); 21 | } 22 | }); 23 | } 24 | 25 | @Override 26 | protected void paintComponent(Graphics g) { 27 | super.paintComponent(g); 28 | 29 | if(getText().isEmpty() && FocusManager.getCurrentKeyboardFocusManager().getFocusOwner() != this){ 30 | Graphics2D g2 = (Graphics2D)g.create(); 31 | g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 32 | g2.setColor(Color.gray); 33 | g2.setFont(getFont().deriveFont(Font.ITALIC)); 34 | g2.drawString(placeholder, 5, getHeight() - 10); 35 | g2.dispose(); 36 | } 37 | } 38 | 39 | public void setPlaceholder(String placeholder) { 40 | this.placeholder = placeholder; 41 | this.revalidate(); 42 | this.repaint(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/evacipated/cardcrawl/modthespire/ui/UpdateWindow.java: -------------------------------------------------------------------------------- 1 | package com.evacipated.cardcrawl.modthespire.ui; 2 | 3 | import com.evacipated.cardcrawl.modthespire.DownloadAndRestarter; 4 | import com.evacipated.cardcrawl.modthespire.ModUpdate; 5 | 6 | import javax.swing.*; 7 | import javax.swing.border.EmptyBorder; 8 | import java.awt.*; 9 | import java.awt.event.ActionEvent; 10 | import java.io.IOException; 11 | import java.net.URISyntaxException; 12 | import java.net.URL; 13 | 14 | class UpdateWindow extends JDialog 15 | { 16 | UpdateWindow(JFrame parent) 17 | { 18 | super(parent); 19 | setModal(true); 20 | initUI(); 21 | } 22 | 23 | private void initUI() 24 | { 25 | if (ModSelectWindow.MODUPDATES.size() == 1) { 26 | setTitle("Update Available"); 27 | } else { 28 | setTitle("Updates Available"); 29 | } 30 | setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 31 | setResizable(true); 32 | 33 | getContentPane().setPreferredSize(new Dimension(300, 200)); 34 | 35 | rootPane.setBorder(new EmptyBorder(5, 5, 5, 5)); 36 | 37 | setLayout(new BorderLayout()); 38 | 39 | DefaultListModel model = new DefaultListModel<>(); 40 | JList list = new JList<>(model); 41 | JScrollPane modScroller = new JScrollPane(list); 42 | getContentPane().add(modScroller, BorderLayout.CENTER); 43 | 44 | for (ModUpdate update : ModSelectWindow.MODUPDATES) { 45 | model.addElement(update.info.Name); 46 | } 47 | 48 | String tmp; 49 | if (ModSelectWindow.MODUPDATES.size() == 1) { 50 | tmp = "The following mod has an update available:"; 51 | } else { 52 | tmp = "The following mods have updates available:"; 53 | } 54 | getContentPane().add(new JLabel(tmp), BorderLayout.NORTH); 55 | 56 | JPanel btnPanel = new JPanel(new GridBagLayout()); 57 | JButton downloadBtn = new JButton("Download Updates and Restart ModTheSpire"); 58 | JButton browserBtn = new JButton("Open Releases in Browser"); 59 | GridBagConstraints c = new GridBagConstraints(); 60 | c.fill = GridBagConstraints.HORIZONTAL; 61 | c.weightx = 0.5; 62 | btnPanel.add(downloadBtn, c); 63 | c.gridy = 1; 64 | btnPanel.add(browserBtn, c); 65 | getContentPane().add(btnPanel, BorderLayout.SOUTH); 66 | 67 | // Open each update's release url in browser 68 | browserBtn.addActionListener((ActionEvent event) -> { 69 | if (Desktop.isDesktopSupported()) { 70 | for (ModUpdate update : ModSelectWindow.MODUPDATES) { 71 | try { 72 | Desktop.getDesktop().browse(update.releaseURL.toURI()); 73 | } catch (IOException | URISyntaxException e) { 74 | e.printStackTrace(); 75 | } 76 | } 77 | } 78 | }); 79 | 80 | // Download each update 81 | downloadBtn.addActionListener((ActionEvent event) -> { 82 | getContentPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 83 | URL[] downloadURLs = new URL[ModSelectWindow.MODUPDATES.size()]; 84 | for (int i=0; iBill Burke 31 | * @author Shigeru Chiba 32 | */ 33 | public abstract class MemberValue { 34 | ConstPool cp; 35 | char tag; 36 | 37 | MemberValue(char tag, ConstPool cp) { 38 | this.cp = cp; 39 | this.tag = tag; 40 | } 41 | 42 | /** 43 | * Returns the value. If the value type is a primitive type, the 44 | * returned value is boxed. 45 | */ 46 | abstract Object getValue(ClassLoader cl, ClassPool cp, Method m) 47 | throws ClassNotFoundException; 48 | 49 | abstract Class getType(ClassLoader cl) throws ClassNotFoundException; 50 | 51 | static Class loadClass(ClassLoader cl, String classname) 52 | throws ClassNotFoundException, NoSuchClassError 53 | { 54 | try { 55 | //return Class.forName(convertFromArray(classname), true, cl); 56 | return Class.forName(convertFromArray(classname), false, cl); 57 | } 58 | catch (LinkageError e) { 59 | throw new NoSuchClassError(classname, e); 60 | } 61 | } 62 | 63 | private static String convertFromArray(String classname) 64 | { 65 | int index = classname.indexOf("[]"); 66 | if (index != -1) { 67 | String rawType = classname.substring(0, index); 68 | StringBuffer sb = new StringBuffer(Descriptor.of(rawType)); 69 | while (index != -1) { 70 | sb.insert(0, "["); 71 | index = classname.indexOf("[]", index + 1); 72 | } 73 | return sb.toString().replace('/', '.'); 74 | } 75 | return classname; 76 | } 77 | 78 | /** 79 | * Accepts a visitor. 80 | */ 81 | public abstract void accept(MemberValueVisitor visitor); 82 | 83 | /** 84 | * Writes the value. 85 | */ 86 | public abstract void write(AnnotationsWriter w) throws IOException; 87 | } 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/classutil/AbstractClassFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.classutil; 2 | 3 | import java.lang.reflect.Modifier; 4 | 5 | /** 6 | *

AbstractClassFilter implements a {@link ClassFilter} 7 | * that matches class names that (a) can be loaded and (b) are abstract. It 8 | * relies on the pool of classes read by a {@link ClassFinder}; it's 9 | * not really useful by itself.

10 | * 11 | *

This class is really just a convenient specialization of the 12 | * {@link ClassModifiersClassFilter} class.

13 | * 14 | * @see ClassFilter 15 | * @see ClassModifiersClassFilter 16 | * @see ClassFinder 17 | * @see Modifier 18 | */ 19 | public class AbstractClassFilter 20 | extends ClassModifiersClassFilter 21 | { 22 | /*----------------------------------------------------------------------*\ 23 | Constructor 24 | \*----------------------------------------------------------------------*/ 25 | 26 | /** 27 | * Construct a new AbstractClassFilter that will accept 28 | * only abstract classes. 29 | */ 30 | public AbstractClassFilter() 31 | { 32 | super (Modifier.ABSTRACT); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/classutil/ClassFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.classutil; 2 | 3 | /** 4 | * Instances of classes that implement this interface are used, with a 5 | * {@link ClassFinder} object, to filter class names. This interface is 6 | * deliberately reminiscent of the java.io.FilenameFilter 7 | * interface. 8 | * 9 | * @see ClassFinder 10 | */ 11 | public interface ClassFilter 12 | { 13 | /** 14 | * Tests whether a class name should be included in a class name 15 | * list. 16 | * 17 | * @param classInfo the loaded information about the class 18 | * @param classFinder the {@link ClassFinder} that called this filter 19 | * (mostly for access to ClassFinder 20 | * utility methods) 21 | * 22 | * @return true if and only if the name should be included 23 | * in the list; false otherwise 24 | */ 25 | public boolean accept (ClassInfo classInfo, ClassFinder classFinder); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/classutil/ClassModifiersClassFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.classutil; 2 | 3 | /** 4 | *

ClassModifiersClassFilter is a {@link ClassFilter} that 5 | * matches class names that (a) can be loaded and (b) match a set of class 6 | * modifiers (as defined by the constants in the 7 | * java.lang.reflect.Modifier class). For instance, the the 8 | * following code fragment defines a filter that will match only public 9 | * final classes:

10 | * 11 | *
12 |  * import java.lang.reflect.Modifier;
13 |  *
14 |  * ...
15 |  *
16 |  * ClassFilter = new ClassModifiersClassFilter (Modifier.PUBLIC | Modifier.FINAL);
17 |  * 
18 | * 19 | *

This class relies on the pool of classes read by a 20 | * {@link ClassFinder}; it's not really useful by itself.

21 | * 22 | * @see ClassFilter 23 | * @see ClassFinder 24 | * @see java.lang.reflect.Modifier 25 | */ 26 | public class ClassModifiersClassFilter implements ClassFilter 27 | { 28 | /*----------------------------------------------------------------------*\ 29 | Private Data Items 30 | \*----------------------------------------------------------------------*/ 31 | 32 | private int modifiers = 0; 33 | 34 | /*----------------------------------------------------------------------*\ 35 | Constructor 36 | \*----------------------------------------------------------------------*/ 37 | 38 | /** 39 | * Construct a new ClassModifiersClassFilter that will accept 40 | * any classes with the specified modifiers. 41 | * 42 | * @param modifiers the bit-field of modifier flags. See the 43 | * java.lang.reflect.Modifier class for 44 | * legal values. 45 | */ 46 | public ClassModifiersClassFilter (int modifiers) 47 | { 48 | super(); 49 | this.modifiers = modifiers; 50 | } 51 | 52 | /** 53 | * Tests whether a class name should be included in a class name 54 | * list. 55 | * 56 | * @param classInfo the loaded information about the class 57 | * @param classFinder the {@link ClassFinder} that called this filter 58 | * (mostly for access to ClassFinder 59 | * utility methods) 60 | * 61 | * @return true if and only if the name should be included 62 | * in the list; false otherwise 63 | */ 64 | public boolean accept (ClassInfo classInfo, ClassFinder classFinder) 65 | { 66 | return ((classInfo.getModifier() & modifiers) != 0); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/classutil/InterfaceOnlyClassFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.classutil; 2 | 3 | import java.lang.reflect.Modifier; 4 | 5 | /** 6 | *

InterfaceOnlyClassFilter implements a {@link ClassFilter} 7 | * that matches class names that (a) can be loaded and (b) are interfaces. It 8 | * relies on the pool of classes read by a {@link ClassFinder}; it's 9 | * not really useful by itself.

10 | * 11 | *

This class is really just a convenient specialization of the 12 | * {@link ClassModifiersClassFilter} class.

13 | */ 14 | public class InterfaceOnlyClassFilter 15 | extends ClassModifiersClassFilter 16 | { 17 | /*----------------------------------------------------------------------*\ 18 | Constructor 19 | \*----------------------------------------------------------------------*/ 20 | 21 | /** 22 | * Construct a new InterfaceOnlyClassFilter that will accept 23 | * only classes that are interfaces. 24 | */ 25 | public InterfaceOnlyClassFilter() 26 | { 27 | super (Modifier.INTERFACE); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/classutil/NotClassFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.classutil; 2 | 3 | /** 4 | * NotClassFilter is a {@link ClassFilter} that 5 | * wraps another {@link ClassFilter} and negates the sense of the 6 | * wrapped filter's {@link ClassFilter#accept accept()} method. This 7 | * class conceptually provides a logical "NOT" operator for class name 8 | * filters. For example, the following code fragment will create a filter 9 | * that finds all classes that are not interfaces. 10 | * 11 | *
12 |  * NotClassFilter filter = new NotClassFilter (new InterfaceOnlyClassFilter());
13 |  * 
14 | * 15 | * @see ClassFilter 16 | * @see AndClassFilter 17 | * @see OrClassFilter 18 | * @see ClassFinder 19 | * @see InterfaceOnlyClassFilter 20 | */ 21 | public class NotClassFilter implements ClassFilter 22 | { 23 | /*----------------------------------------------------------------------*\ 24 | Private Data Items 25 | \*----------------------------------------------------------------------*/ 26 | 27 | private ClassFilter filter; 28 | 29 | /*----------------------------------------------------------------------*\ 30 | Constructor 31 | \*----------------------------------------------------------------------*/ 32 | 33 | /** 34 | * Create a new NotClassFilter that wraps the 35 | * specified {@link ClassFilter}. 36 | * 37 | * @param filter The {@link ClassFilter} to wrap. 38 | */ 39 | public NotClassFilter (ClassFilter filter) 40 | { 41 | this.filter = filter; 42 | } 43 | 44 | /*----------------------------------------------------------------------*\ 45 | Public Methods 46 | \*----------------------------------------------------------------------*/ 47 | 48 | /** 49 | * Tests whether a class name should be included in a class name 50 | * list. 51 | * 52 | * @param classInfo the {@link ClassInfo} object to test 53 | * @param classFinder the invoking {@link ClassFinder} object 54 | * 55 | * @return true if and only if the name should be included 56 | * in the list; false otherwise 57 | */ 58 | public boolean accept (ClassInfo classInfo, ClassFinder classFinder) 59 | { 60 | return ! this.filter.accept (classInfo, classFinder); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/classutil/RegexClassFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.classutil; 2 | 3 | import java.util.regex.Pattern; 4 | import java.util.regex.PatternSyntaxException; 5 | 6 | /** 7 | *

RegexClassFilter is a {@link ClassFilter} that matches class 8 | * names using a regular expression. Multiple regular expression filters 9 | * can be combined using {@link AndClassFilter} and/or 10 | * {@link OrClassFilter} objects.

11 | * 12 | *

This class does not have to load the classes it's filtering; it 13 | * matches on the class name only.

14 | * 15 | *

RegexClassFilter uses the java.util.regex 16 | * regular expression classes.

17 | * 18 | * @see ClassFilter 19 | * @see AndClassFilter 20 | * @see OrClassFilter 21 | * @see NotClassFilter 22 | * @see ClassFinder 23 | */ 24 | public class RegexClassFilter 25 | implements ClassFilter 26 | { 27 | /*----------------------------------------------------------------------*\ 28 | Private Data Items 29 | \*----------------------------------------------------------------------*/ 30 | 31 | private Pattern pattern; 32 | 33 | /*----------------------------------------------------------------------*\ 34 | Constructor 35 | \*----------------------------------------------------------------------*/ 36 | 37 | /** 38 | * Construct a new RegexClassFilter using the specified 39 | * pattern. 40 | * 41 | * @param regex the regular expression to add 42 | * 43 | * @throws PatternSyntaxException bad regular expression 44 | */ 45 | public RegexClassFilter (String regex) 46 | throws PatternSyntaxException 47 | { 48 | pattern = Pattern.compile (regex); 49 | } 50 | 51 | /** 52 | * Construct a new RegexClassFilter using the specified 53 | * pattern. 54 | * 55 | * @param regex the regular expression to add 56 | * @param regexFlags regular expression compilation flags (e.g., 57 | * Pattern.CASE_INSENSITIVE). See 58 | * the Javadocs for java.util.regex for 59 | * legal values. 60 | * 61 | * @throws PatternSyntaxException bad regular expression 62 | */ 63 | public RegexClassFilter (String regex, int regexFlags) 64 | throws PatternSyntaxException 65 | { 66 | pattern = Pattern.compile (regex, regexFlags); 67 | } 68 | 69 | /*----------------------------------------------------------------------*\ 70 | Public Methods 71 | \*----------------------------------------------------------------------*/ 72 | 73 | /** 74 | * Determine whether a class name is to be accepted or not, based on 75 | * the regular expression specified to the constructor. 76 | * 77 | * @param classInfo the {@link ClassInfo} object to test 78 | * @param classFinder the invoking {@link ClassFinder} object 79 | * 80 | * @return true if the class name matches, 81 | * false if it doesn't 82 | */ 83 | public boolean accept (ClassInfo classInfo, ClassFinder classFinder) 84 | { 85 | return pattern.matcher (classInfo.getClassName()).find(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/classutil/SubclassClassFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.classutil; 2 | 3 | import java.util.Map; 4 | import java.util.HashMap; 5 | 6 | /** 7 | *

SubclassClassFilter is a {@link ClassFilter} that matches 8 | * class names that (a) can be loaded and (b) extend a given subclass or 9 | * implement a specified interface, directly or indirectly. It uses the 10 | * java.lang.Class.isAssignableFrom() method, so it actually has to 11 | * load each class it tests. For maximum flexibility, a 12 | * SubclassClassFilter can be configured to use a specific class 13 | * loader.

14 | */ 15 | public class SubclassClassFilter implements ClassFilter 16 | { 17 | /*----------------------------------------------------------------------*\ 18 | Private Data Items 19 | \*----------------------------------------------------------------------*/ 20 | 21 | private Class baseClass; 22 | 23 | /*----------------------------------------------------------------------*\ 24 | Constructor 25 | \*----------------------------------------------------------------------*/ 26 | 27 | /** 28 | * Construct a new SubclassClassFilter that will accept 29 | * only classes that extend the specified class or implement the 30 | * specified interface. 31 | * 32 | * @param baseClassOrInterface the base class or interface 33 | */ 34 | public SubclassClassFilter (Class baseClassOrInterface) 35 | { 36 | this.baseClass = baseClassOrInterface; 37 | } 38 | 39 | /*----------------------------------------------------------------------*\ 40 | Public Methods 41 | \*----------------------------------------------------------------------*/ 42 | 43 | /** 44 | * Perform the acceptance test on the loaded Class object. 45 | * 46 | * @param classInfo the {@link ClassInfo} object to test 47 | * @param classFinder the invoking {@link ClassFinder} object 48 | * 49 | * @return true if the class name matches, 50 | * false if it doesn't 51 | */ 52 | public boolean accept (ClassInfo classInfo, ClassFinder classFinder) 53 | { 54 | Map superClasses = new HashMap(); 55 | 56 | if (baseClass.isInterface()) 57 | classFinder.findAllInterfaces (classInfo, superClasses); 58 | else 59 | classFinder.findAllSuperClasses (classInfo, superClasses); 60 | 61 | return superClasses.keySet().contains (baseClass.getName()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/cmdline/OptionComparator.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.cmdline; 2 | 3 | import java.util.Comparator; 4 | 5 | /** 6 | * Used solely by UsageInfo, an instance of this class compares 7 | * OptionInfo items, by short option name or long option name, in 8 | * a case-insensitive manner. 9 | * 10 | * @see UsageInfo 11 | * @see OptionInfo 12 | */ 13 | final class OptionComparator implements Comparator 14 | { 15 | private boolean ignoreCase = false; 16 | 17 | public OptionComparator() 18 | { 19 | // Nothing to do 20 | } 21 | 22 | public OptionComparator (boolean ignoreCase) 23 | { 24 | this.ignoreCase = ignoreCase; 25 | } 26 | 27 | public int compare (OptionInfo o1, OptionInfo o2) 28 | { 29 | String s1 = getComparisonString (o1); 30 | String s2 = getComparisonString (o2); 31 | 32 | return ignoreCase ? s1.compareToIgnoreCase (s2) : s1.compareTo (s2); 33 | } 34 | 35 | public boolean equals (Object o) 36 | { 37 | return (this.getClass().isInstance (o)); 38 | } 39 | 40 | public int hashCode() // NOPMD 41 | { 42 | return super.hashCode(); 43 | } 44 | 45 | private String getComparisonString (OptionInfo opt) 46 | { 47 | String result = ""; 48 | 49 | if (opt.shortOption != UsageInfo.NO_SHORT_OPTION) 50 | result = String.valueOf (opt.shortOption); 51 | else if (opt.longOption != null) 52 | result = opt.longOption; 53 | 54 | return result; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/cmdline/OptionInfo.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.cmdline; 2 | 3 | /** 4 | * Used solely by UsageInfo, this class contains information about 5 | * an option. 6 | * 7 | * @see UsageInfo 8 | */ 9 | final class OptionInfo 10 | { 11 | char shortOption; 12 | String longOption; 13 | String argToken; 14 | String explanation; 15 | 16 | OptionInfo (char shortOption, 17 | String longOption, 18 | String argToken, 19 | String explanation) 20 | { 21 | this.shortOption = shortOption; 22 | this.longOption = longOption; 23 | this.argToken = argToken; 24 | this.explanation = explanation; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/cmdline/Package.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.cmdline; 2 | 3 | /** 4 | * Package-specific shared utility methods. Used only within this package. 5 | */ 6 | final class Package 7 | { 8 | /*----------------------------------------------------------------------*\ 9 | Constants 10 | \*----------------------------------------------------------------------*/ 11 | 12 | /** 13 | * The name of the resource bundle for this package. 14 | */ 15 | static final String BUNDLE_NAME = "org.clapper.util.cmdline.Bundle"; 16 | 17 | /*----------------------------------------------------------------------*\ 18 | Constructor 19 | \*----------------------------------------------------------------------*/ 20 | 21 | private Package() 22 | { 23 | // Can't be built 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/cmdline/ParameterHandler.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.cmdline; 2 | 3 | import java.util.Iterator; 4 | import java.util.NoSuchElementException; 5 | 6 | /** 7 | * Thie interface defines the callback methods used by a 8 | * {@link ParameterParser} object, when its 9 | * {@link ParameterParser#parse parse()} method is called. 10 | * 11 | * @see ParameterParser 12 | * @see CommandLineUtility 13 | */ 14 | public interface ParameterHandler 15 | { 16 | /*----------------------------------------------------------------------*\ 17 | Constants 18 | \*----------------------------------------------------------------------*/ 19 | 20 | /*----------------------------------------------------------------------*\ 21 | Public Methods 22 | \*----------------------------------------------------------------------*/ 23 | 24 | /** 25 | * Handles a parsed option. 26 | * 27 | * @param shortOption the (character) short option, if any; otherwise, 28 | * the constant {@link UsageInfo#NO_SHORT_OPTION}. 29 | * @param longOption the (string) long option, if any; otherwise, 30 | * null. 31 | * @param it An Iterator from which to retrieve any 32 | * value(s) for the option 33 | * 34 | * @throws CommandLineUsageException on error 35 | * @throws NoSuchElementException attempt to iterate past end of args; 36 | * {@link ParameterParser#parse} 37 | * automatically handles this exception, 38 | * so it's safe for implementations of 39 | * this method not to handle it 40 | */ 41 | public void parseOption(char shortOption, 42 | String longOption, 43 | Iterator it) 44 | throws CommandLineUsageException, 45 | NoSuchElementException; 46 | 47 | /** 48 | * Handles all parameters that appear after the end of the options. If there 49 | * are no such parameters, the implementation of this method should just 50 | * return without doing anything. 51 | * 52 | * @param it the Iterator containing the parameters 53 | * 54 | * @throws CommandLineUsageException on error 55 | * @throws NoSuchElementException attempt to iterate past end of args; 56 | * {@link ParameterParser#parse} 57 | * automatically handles this exception, 58 | * so it's safe for implementations of 59 | * this method not to handle it 60 | */ 61 | public void parsePostOptionParameters(Iterator it) 62 | throws CommandLineUsageException, 63 | NoSuchElementException; 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/config/EnvSection.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.config; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Implements the special "env" section 8 | * 9 | * @see Section 10 | */ 11 | class EnvSection extends Section 12 | { 13 | /*----------------------------------------------------------------------*\ 14 | Constructor 15 | \*----------------------------------------------------------------------*/ 16 | 17 | /** 18 | * Allocate a new EnvSection object, loading its values from 19 | * the environment. 20 | * 21 | * @param name the section name 22 | * @param id the ID 23 | */ 24 | EnvSection (String name, int id) 25 | { 26 | super (name, id); 27 | 28 | // Need a modifiable copy of the environment map, so we can 29 | // escape any embedded backslashes. (That's necessary because the 30 | // values from the environment will be subsituted pre-parse.) 31 | 32 | Map env = new HashMap (System.getenv()); 33 | super.addVariables (escapeEmbeddedBackslashes (env)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/config/NoSuchSectionException.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.config; 2 | 3 | /** 4 | * A NoSuchSectionException is thrown by the 5 | * {@link Configuration} class to signify that a requested configuration 6 | * section does not exist. 7 | * 8 | * @see ConfigurationException 9 | */ 10 | public class NoSuchSectionException extends ConfigurationException 11 | { 12 | /*----------------------------------------------------------------------*\ 13 | Private Static Variables 14 | \*----------------------------------------------------------------------*/ 15 | 16 | /** 17 | * See JDK 1.5 version of java.io.Serializable 18 | */ 19 | private static final long serialVersionUID = 1L; 20 | 21 | /*----------------------------------------------------------------------*\ 22 | Private Data Items 23 | \*----------------------------------------------------------------------*/ 24 | 25 | private String sectionName = null; 26 | 27 | /*----------------------------------------------------------------------*\ 28 | Constructor 29 | \*----------------------------------------------------------------------*/ 30 | 31 | /** 32 | * Constructs an exception. 33 | * 34 | * @param sectionName the section name to which the exception pertains 35 | */ 36 | public NoSuchSectionException (String sectionName) 37 | { 38 | super (Package.BUNDLE_NAME, 39 | "noSuchSection", 40 | "Configuration section \"{0}\" does not exist", 41 | new Object[] {sectionName}); 42 | 43 | this.sectionName = sectionName; 44 | } 45 | 46 | /*----------------------------------------------------------------------*\ 47 | Public Methods 48 | \*----------------------------------------------------------------------*/ 49 | 50 | /** 51 | * Gets the section name associated with this exception. 52 | * 53 | * @return the section name 54 | */ 55 | public String getSectionName() 56 | { 57 | return sectionName; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/config/NoSuchVariableException.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.config; 2 | 3 | /** 4 | * A NoSuchVariableException is thrown by the 5 | * {@link Configuration} class to signify that a requested configuration 6 | * variable does not exist. 7 | * 8 | * @see ConfigurationException 9 | */ 10 | public class NoSuchVariableException extends ConfigurationException 11 | { 12 | /*----------------------------------------------------------------------*\ 13 | Private Static Variables 14 | \*----------------------------------------------------------------------*/ 15 | 16 | /** 17 | * See JDK 1.5 version of java.io.Serializable 18 | */ 19 | private static final long serialVersionUID = 1L; 20 | 21 | /*----------------------------------------------------------------------*\ 22 | Private Data Items 23 | \*----------------------------------------------------------------------*/ 24 | 25 | private String variableName = null; 26 | private String sectionName = null; 27 | 28 | /*----------------------------------------------------------------------*\ 29 | Constructor 30 | \*----------------------------------------------------------------------*/ 31 | 32 | /** 33 | * Constructs an exception. 34 | * 35 | * @param sectionName the section that doesn't have the variable 36 | * @param variableName the variable name to which the exception pertains 37 | */ 38 | public NoSuchVariableException (String sectionName, String variableName) 39 | { 40 | super (Package.BUNDLE_NAME, 41 | "noSuchVariable", 42 | "Variable \"{0}\" does not exist in configuration section " + 43 | "\"{1}\"", 44 | new Object[] {variableName, sectionName}); 45 | 46 | this.sectionName = sectionName; 47 | this.variableName = variableName; 48 | } 49 | 50 | /*----------------------------------------------------------------------*\ 51 | Public Methods 52 | \*----------------------------------------------------------------------*/ 53 | 54 | /** 55 | * Gets the section name associated with this exception. 56 | * 57 | * @return the section name 58 | */ 59 | public String getSectionName() 60 | { 61 | return sectionName; 62 | } 63 | 64 | /** 65 | * Gets the variable name associated with this exception. 66 | * 67 | * @return the variable name 68 | */ 69 | public String getVariableName() 70 | { 71 | return variableName; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/config/Package.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.config; 2 | 3 | /** 4 | * Package-specific shared utility methods. Used only within this package. 5 | */ 6 | final class Package 7 | { 8 | /*----------------------------------------------------------------------*\ 9 | Constants 10 | \*----------------------------------------------------------------------*/ 11 | 12 | /** 13 | * The name of the resource bundle for this package. 14 | */ 15 | static final String BUNDLE_NAME = "org.clapper.util.config.Bundle"; 16 | 17 | /*----------------------------------------------------------------------*\ 18 | Constructor 19 | \*----------------------------------------------------------------------*/ 20 | 21 | private Package() 22 | { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/config/SectionExistsException.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.config; 2 | 3 | /** 4 | * A SectionExistsException is thrown by the 5 | * {@link Configuration} class to signify that a requested configuration 6 | * section already exists and cannot be created. 7 | * 8 | * @see ConfigurationException 9 | */ 10 | public class SectionExistsException extends ConfigurationException 11 | { 12 | /*----------------------------------------------------------------------*\ 13 | Private Static Variables 14 | \*----------------------------------------------------------------------*/ 15 | 16 | /** 17 | * See JDK 1.5 version of java.io.Serializable 18 | */ 19 | private static final long serialVersionUID = 1L; 20 | 21 | /*----------------------------------------------------------------------*\ 22 | Private Data Items 23 | \*----------------------------------------------------------------------*/ 24 | 25 | private String sectionName = null; 26 | 27 | /*----------------------------------------------------------------------*\ 28 | Constructor 29 | \*----------------------------------------------------------------------*/ 30 | 31 | /** 32 | * Constructs an exception. 33 | * 34 | * @param sectionName the section name to which the exception pertains 35 | */ 36 | public SectionExistsException (String sectionName) 37 | { 38 | super (NoSuchSectionException.class.getName() + 39 | ": section " + 40 | sectionName); 41 | 42 | this.sectionName = sectionName; 43 | } 44 | 45 | /*----------------------------------------------------------------------*\ 46 | Public Methods 47 | \*----------------------------------------------------------------------*/ 48 | 49 | /** 50 | * Gets the section name associated with this exception. 51 | * 52 | * @return the section name 53 | */ 54 | public String getSectionName() 55 | { 56 | return sectionName; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/config/SystemSection.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.config; 2 | 3 | import java.util.Map; 4 | import org.clapper.util.misc.PropertiesMap; 5 | 6 | /** 7 | * Implements the special "system" section 8 | * 9 | * @see Section 10 | */ 11 | class SystemSection extends Section 12 | { 13 | /*----------------------------------------------------------------------*\ 14 | Constructor 15 | \*----------------------------------------------------------------------*/ 16 | 17 | /** 18 | * Allocate a new SystemSection object, loading its values from 19 | * the system properties list. 20 | * 21 | * @param name the section name 22 | * @param id the ID 23 | */ 24 | SystemSection (String name, int id) 25 | { 26 | super (name, id); 27 | 28 | // Escape any embedded backslashes in variable values. 29 | 30 | Map propMap = new PropertiesMap (System.getProperties()); 31 | super.addVariables (escapeEmbeddedBackslashes (propMap)); 32 | } 33 | 34 | /*----------------------------------------------------------------------*\ 35 | Package-visible Methods 36 | \*----------------------------------------------------------------------*/ 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/config/ValueSegment.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.config; 2 | 3 | import org.clapper.util.text.XStringBuilder; 4 | 5 | /** 6 | * A variable value segment. This is just a substring, with a flag that 7 | * indicates whether the segment is literal, white space-escaped or not. 8 | */ 9 | class ValueSegment 10 | { 11 | XStringBuilder segmentBuf = new XStringBuilder(); 12 | boolean isLiteral = false; 13 | boolean isWhiteSpaceEscaped = false; 14 | 15 | ValueSegment() 16 | { 17 | // Nothing to do 18 | } 19 | 20 | void append (char ch) 21 | { 22 | segmentBuf.append (ch); 23 | } 24 | 25 | int length() 26 | { 27 | return segmentBuf.length(); 28 | } 29 | 30 | public String toString() 31 | { 32 | return segmentBuf.toString(); 33 | } 34 | 35 | public ValueSegment makeCopy() 36 | { 37 | ValueSegment copy = new ValueSegment(); 38 | copy.segmentBuf = new XStringBuilder(this.segmentBuf.toString()); 39 | copy.isLiteral = this.isLiteral; 40 | copy.isWhiteSpaceEscaped = this.isWhiteSpaceEscaped; 41 | return copy; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/config/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

org.clapper.util.config - A Configuration File Parser.

7 |

This package contains classes to parse an extended .INI-style 8 | configuration file.

9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/AndFileFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | import java.io.FileFilter; 7 | import java.io.File; 8 | 9 | /** 10 | *

An AndFileFilter contains logically ANDs other 11 | * java.io.FileFilter objects. When its {@link #accept accept()} 12 | * method is called, the AndFileFilter object passes the file 13 | * through the contained filters. The file is only accepted if it is 14 | * accepted by all contained filters. This class conceptually provides a 15 | * logical "AND" operator for file filters.

16 | * 17 | *

The contained filters are applied in the order they were added to the 18 | * AndFileFilter object. This class's {@link #accept accept()} 19 | * method stops looping over the contained filters as soon as it encounters 20 | * one whose accept() method returns false (implementing 21 | * a "short-circuited AND" operation.)

22 | * 23 | * @see FileFilter 24 | * @see OrFileFilter 25 | * @see NotFileFilter 26 | * @see AndFilenameFilter 27 | * @see RegexFileFilter 28 | */ 29 | public final class AndFileFilter implements FileFilter 30 | { 31 | /*----------------------------------------------------------------------*\ 32 | Private Data Items 33 | \*----------------------------------------------------------------------*/ 34 | 35 | private List filters = new LinkedList(); 36 | 37 | /*----------------------------------------------------------------------*\ 38 | Constructor 39 | \*----------------------------------------------------------------------*/ 40 | 41 | /** 42 | * Construct a new AndFileFilter with no contained filters. 43 | */ 44 | public AndFileFilter() 45 | { 46 | // nothing to do 47 | } 48 | 49 | /** 50 | * Construct a new AndFileFilter with a set of contained filters. 51 | * Additional filters may be added later, via calls to the 52 | * {@link #addFilter addFilter()} method. 53 | * 54 | * @param filters filters to add 55 | */ 56 | public AndFileFilter (FileFilter... filters) 57 | { 58 | for (FileFilter filter : filters) 59 | addFilter (filter); 60 | } 61 | 62 | /*----------------------------------------------------------------------*\ 63 | Public Methods 64 | \*----------------------------------------------------------------------*/ 65 | 66 | /** 67 | * Add a filter to the set of contained filters. 68 | * 69 | * @param filter the FileFilter to add. 70 | * 71 | * @return this object, to permit chained calls. 72 | * 73 | * @see #removeFilter 74 | */ 75 | public AndFileFilter addFilter (FileFilter filter) 76 | { 77 | filters.add (filter); 78 | return this; 79 | } 80 | 81 | /** 82 | * Remove a filter from the set of contained filters. 83 | * 84 | * @param filter the FileFilter to remove. 85 | * 86 | * @see #addFilter 87 | */ 88 | public void removeFilter (FileFilter filter) 89 | { 90 | filters.remove (filter); 91 | } 92 | 93 | /** 94 | *

Determine whether a file is to be accepted or not, based on the 95 | * contained filters. The file is accepted if any one of the contained 96 | * filters accepts it. This method stops looping over the contained 97 | * filters as soon as it encounters one whose accept() method 98 | * returns false (implementing a "short-circuited AND" 99 | * operation.)

100 | * 101 | *

If the set of contained filters is empty, then this method 102 | * returns true.

103 | * 104 | * @param file The file to check for acceptance 105 | * 106 | * @return true if the file matches, false if it doesn't 107 | */ 108 | public boolean accept (File file) 109 | { 110 | boolean accepted = true; 111 | 112 | for (FileFilter filter : filters) 113 | { 114 | accepted = filter.accept (file); 115 | if (! accepted) 116 | break; 117 | } 118 | 119 | return accepted; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/AndFilenameFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | import java.util.Collection; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import java.util.Iterator; 7 | 8 | import java.io.FilenameFilter; 9 | import java.io.File; 10 | 11 | /** 12 | *

An AndFilenameFilter logically ANDs other 13 | * java.io.FilenameFilter objects. When its 14 | * {@link #accept accept()} method is called, the AndFilenameFilter 15 | * object passes the file through the contained filters. The file is only 16 | * accepted if it is accepted by all contained filters. This class 17 | * conceptually provides a logical "AND" operator for file filters.

18 | * 19 | *

The contained filters are applied in the order they were added to the 20 | * AndFilenameFilter object. This class's {@link #accept accept()} 21 | * method stops looping over the contained filters as soon as it encounters 22 | * one whose accept() method returns false (implementing 23 | * a "short-circuited AND" operation.)

24 | * 25 | * @see FilenameFilter 26 | * @see OrFilenameFilter 27 | * @see NotFilenameFilter 28 | * @see RegexFilenameFilter 29 | * @see AndFileFilter 30 | */ 31 | public final class AndFilenameFilter implements FilenameFilter 32 | { 33 | /*----------------------------------------------------------------------*\ 34 | Private Data Items 35 | \*----------------------------------------------------------------------*/ 36 | 37 | private List filters = new LinkedList(); 38 | 39 | /*----------------------------------------------------------------------*\ 40 | Constructor 41 | \*----------------------------------------------------------------------*/ 42 | 43 | /** 44 | * Construct a new AndFilenameFilter with no contained filters. 45 | */ 46 | public AndFilenameFilter() 47 | { 48 | // nothing to do 49 | } 50 | 51 | /** 52 | * Construct a new AndFilenameFilter with a set of contained 53 | * filters. Additional filters may be added later, via calls to the 54 | * {@link #addFilter addFilter()} method. 55 | * 56 | * @param filters filters to use 57 | */ 58 | public AndFilenameFilter (FilenameFilter... filters) 59 | { 60 | for (FilenameFilter filter : filters) 61 | addFilter (filter); 62 | } 63 | 64 | /*----------------------------------------------------------------------*\ 65 | Public Methods 66 | \*----------------------------------------------------------------------*/ 67 | 68 | /** 69 | * Add a filter to the set of contained filters. 70 | * 71 | * @param filter the FilenameFilter to add. 72 | * 73 | * @return this object, to permit chained calls. 74 | * 75 | * @see #removeFilter 76 | */ 77 | public AndFilenameFilter addFilter (FilenameFilter filter) 78 | { 79 | filters.add (filter); 80 | return this; 81 | } 82 | 83 | /** 84 | * Remove a filter from the set of contained filters. 85 | * 86 | * @param filter the FilenameFilter to remove. 87 | * 88 | * @see #addFilter 89 | */ 90 | public void removeFilter (FilenameFilter filter) 91 | { 92 | filters.remove (filter); 93 | } 94 | 95 | /** 96 | *

Determine whether a file is to be accepted or not, based on the 97 | * contained filters. The file is accepted if any one of the contained 98 | * filters accepts it. This method stops looping over the contained 99 | * filters as soon as it encounters one whose accept() method 100 | * returns false (implementing a "short-circuited AND" 101 | * operation.)

102 | * 103 | *

If the set of contained filters is empty, then this method 104 | * returns true.

105 | * 106 | * @param dir The directory containing the file. 107 | * @param name the file name 108 | * 109 | * @return true if the file matches, false if it doesn't 110 | */ 111 | public boolean accept (File dir, String name) 112 | { 113 | boolean accepted = true; 114 | 115 | for (FilenameFilter filter : filters) 116 | { 117 | accepted = filter.accept (dir, name); 118 | if (! accepted) 119 | break; 120 | } 121 | 122 | return accepted; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/CombinationFilterMode.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | /** 4 | *

Used solely to define type-safe mode values for 5 | * {@link CombinationFilenameFilter} and {@link CombinationFileFilter}. 6 | * 7 | * @see CombinationFileFilter 8 | * @see CombinationFilenameFilter 9 | */ 10 | public enum CombinationFilterMode 11 | { 12 | /** 13 | * Mode setting that instructs the filter to AND all the 14 | * contained filters. 15 | */ 16 | AND_FILTERS, 17 | 18 | /** 19 | * Mode setting that instructs the filter to OR all the 20 | * contained filters. 21 | */ 22 | OR_FILTERS 23 | }; 24 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/DirectoryFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * DirectoryFilter implements a 7 | * java.io.FileFilter that matches only directories. 8 | */ 9 | public class DirectoryFilter implements FileFilter 10 | { 11 | /*----------------------------------------------------------------------*\ 12 | Constructor 13 | \*----------------------------------------------------------------------*/ 14 | 15 | public DirectoryFilter() 16 | { 17 | // Nothing to do 18 | } 19 | 20 | /*----------------------------------------------------------------------*\ 21 | Public Methods 22 | \*----------------------------------------------------------------------*/ 23 | 24 | /** 25 | * Determine whether the specified file is a directory or not. 26 | * 27 | * @return true if the file is a directory, false if not 28 | */ 29 | public boolean accept (File f) 30 | { 31 | return f.isDirectory(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/FileFilterMatchType.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | /** 4 | *

The FileFilterMatchType enumeration spells out the legal 5 | * match types for {@link RegexFilenameFilter} and {@link RegexFileFilter}. 6 | * 7 | * @see RegexFileFilter 8 | * @see RegexFilenameFilter 9 | */ 10 | public enum FileFilterMatchType 11 | { 12 | FILENAME, PATH 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/FileOnlyFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * FileOnlyFilter implements a 7 | * java.io.FileFilter that matches anything that isn't a directory. 8 | * Note that the following two lines produce semantically equivalent filters: 9 | * 10 | *

11 |  * FileFilter f1 = new FileOnlyFilter();
12 |  * FileFilter f1 = new NotFileFilter (new DirectoryFilter());
13 |  * 
14 | */ 15 | public class FileOnlyFilter implements FileFilter 16 | { 17 | /*----------------------------------------------------------------------*\ 18 | Constructor 19 | \*----------------------------------------------------------------------*/ 20 | 21 | public FileOnlyFilter() 22 | { 23 | // Nothing to do 24 | } 25 | 26 | /*----------------------------------------------------------------------*\ 27 | Public Methods 28 | \*----------------------------------------------------------------------*/ 29 | 30 | /** 31 | * Determine whether the specified file is a directory or not. 32 | * 33 | * @return true if the file is a directory, false if not 34 | */ 35 | public boolean accept (File f) 36 | { 37 | return f.isFile(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/JustifyStyle.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | /** 4 | *

The JustifyStyle enumeration spells out the legal field 5 | * justification values for classes such as {@link JustifyTextWriter}. It 6 | * resides in a separate class for readability.

7 | * 8 | * @see JustifyTextWriter 9 | * @see org.clapper.util.text.TextUtil#rightJustifyString(String,int) 10 | * @see org.clapper.util.text.TextUtil#leftJustifyString(String,int) 11 | * @see org.clapper.util.text.TextUtil#centerString(String,int) 12 | */ 13 | public enum JustifyStyle 14 | { 15 | RIGHT_JUSTIFY, LEFT_JUSTIFY, CENTER 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/NotFileFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | import java.io.FileFilter; 4 | import java.io.File; 5 | 6 | /** 7 | * NotFileFilter is a FileFilter that wraps another 8 | * FileFilter and negates the sense of the wrapped filter's 9 | * accept() method. This class conceptually provides a logical 10 | * "NOT" operator for file filters. For example, the following code 11 | * fragment will create a filter that finds all files that are not 12 | * directories. 13 | * 14 | *
15 |  * NotFileFilter filter = new NotFileFilter (new DirectoryFilter());
16 |  * 
17 | * 18 | * @see FileFilter 19 | * @see AndFileFilter 20 | * @see OrFileFilter 21 | * @see DirectoryFilter 22 | */ 23 | public class NotFileFilter implements FileFilter 24 | { 25 | /*----------------------------------------------------------------------*\ 26 | Private Data Items 27 | \*----------------------------------------------------------------------*/ 28 | 29 | private FileFilter filter; 30 | 31 | /*----------------------------------------------------------------------*\ 32 | Constructor 33 | \*----------------------------------------------------------------------*/ 34 | 35 | /** 36 | * Create a new NotFileFilter that wraps the 37 | * specified {@link FileFilter}. 38 | * 39 | * @param filter The {@link FileFilter} to wrap. 40 | */ 41 | public NotFileFilter (FileFilter filter) 42 | { 43 | this.filter = filter; 44 | } 45 | 46 | /*----------------------------------------------------------------------*\ 47 | Constructor 48 | \*----------------------------------------------------------------------*/ 49 | 50 | /** 51 | * Tests whether a file should be included in a file list. 52 | * 53 | * @param file The file to check for acceptance 54 | * 55 | * @return true if the file matches, false if it doesn't 56 | */ 57 | public boolean accept (File file) 58 | { 59 | return ! this.filter.accept (file); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/NotFilenameFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | import java.io.FilenameFilter; 4 | import java.io.File; 5 | 6 | /** 7 | * NotFilenameFilter is a FilenameFilter that wraps another 8 | * FilenameFilter and negates the sense of the wrapped filter's 9 | * accept() method. This class conceptually provides a logical 10 | * "NOT" operator for file filters. For example, the following code 11 | * fragment will create a filter that finds all files that do not start with 12 | * the letter "A". 13 | * 14 | *
15 |  * NotFilenameFilter filter = new NotFilenameFilter (new RegexFilenameFilter ("^[Aa]", FileFilterMatchType.NAME));
16 |  * 
17 | * 18 | * @see FilenameFilter 19 | * @see AndFilenameFilter 20 | * @see OrFilenameFilter 21 | */ 22 | public class NotFilenameFilter implements FilenameFilter 23 | { 24 | /*----------------------------------------------------------------------*\ 25 | Private Data Items 26 | \*----------------------------------------------------------------------*/ 27 | 28 | private FilenameFilter filter; 29 | 30 | /*----------------------------------------------------------------------*\ 31 | Constructor 32 | \*----------------------------------------------------------------------*/ 33 | 34 | /** 35 | * Create a new NotFilenameFilter that wraps the 36 | * specified {@link FilenameFilter}. 37 | * 38 | * @param filter The {@link FilenameFilter} to wrap. 39 | */ 40 | public NotFilenameFilter (FilenameFilter filter) 41 | { 42 | this.filter = filter; 43 | } 44 | 45 | /*----------------------------------------------------------------------*\ 46 | Constructor 47 | \*----------------------------------------------------------------------*/ 48 | 49 | /** 50 | * Tests whether a file should be included in a file list. 51 | * 52 | * @param dir The directory containing the file. 53 | * @param name the file name 54 | * 55 | * @return true if the file matches, false if it doesn't 56 | */ 57 | public boolean accept (File dir, String name) 58 | { 59 | return ! this.filter.accept (dir, name); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/OrFileFilter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | import java.util.Collection; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import java.util.Iterator; 7 | 8 | import java.io.FileFilter; 9 | import java.io.File; 10 | 11 | /** 12 | *

An OrFileFilter logically ORs other java.io.FileFilter 13 | * objects. When its {@link #accept accept()} method is called, the 14 | * OrFileFilter object passes the file through the contained 15 | * filters. The file is accepted if it is accepted by any of the contained 16 | * filters. This class conceptually provides a logical "OR" operator for 17 | * file filters.

18 | * 19 | *

The contained filters are applied in the order they were added to the 20 | * OrFileFilter object. This class's {@link #accept accept()} 21 | * method stops looping over the contained filters as soon as it encounters 22 | * one whose accept() method returns true (implementing 23 | * a "short-circuited OR" operation.)

24 | * 25 | * @see FileFilter 26 | * @see AndFileFilter 27 | * @see NotFileFilter 28 | * @see OrFilenameFilter 29 | * @see RegexFileFilter 30 | */ 31 | public final class OrFileFilter implements FileFilter 32 | { 33 | /*----------------------------------------------------------------------*\ 34 | Private Data Items 35 | \*----------------------------------------------------------------------*/ 36 | 37 | private List filters = new LinkedList(); 38 | 39 | /*----------------------------------------------------------------------*\ 40 | Constructor 41 | \*----------------------------------------------------------------------*/ 42 | 43 | /** 44 | * Construct a new OrFileFilter with no contained filters. 45 | */ 46 | public OrFileFilter() 47 | { 48 | // Nothing to do 49 | } 50 | 51 | /** 52 | * Construct a new OrFileFilter with two contained filters. 53 | * Additional filters may be added later, via calls to the 54 | * {@link #addFilter addFilter()} method. 55 | * 56 | * @param filters filters to add 57 | */ 58 | public OrFileFilter (FileFilter... filters) 59 | { 60 | for (FileFilter filter : filters) 61 | addFilter (filter); 62 | } 63 | 64 | /*----------------------------------------------------------------------*\ 65 | Public Methods 66 | \*----------------------------------------------------------------------*/ 67 | 68 | /** 69 | * Add a filter to the set of contained filters. 70 | * 71 | * @param filter the FileFilter to add. 72 | * 73 | * @return this object, to permit chained calls. 74 | * 75 | * @see #removeFilter 76 | */ 77 | public OrFileFilter addFilter (FileFilter filter) 78 | { 79 | filters.add (filter); 80 | return this; 81 | } 82 | 83 | /** 84 | * Remove a filter from the set of contained filters. 85 | * 86 | * @param filter the FileFilter to remove. 87 | * 88 | * @see #addFilter 89 | */ 90 | public void removeFilter (FileFilter filter) 91 | { 92 | filters.remove (filter); 93 | } 94 | 95 | /** 96 | *

Determine whether a file is to be accepted or not, based on the 97 | * contained filters. The file is accepted if any one of the contained 98 | * filters accepts it. This method stops looping over the contained 99 | * filters as soon as it encounters one whose accept() method 100 | * returns false (implementing a "short-circuited AND" 101 | * operation.)

102 | * 103 | *

If the set of contained filters is empty, then this method 104 | * returns true.

105 | * 106 | * @param file The file to check for acceptance 107 | * 108 | * @return true if the file matches, false if it doesn't 109 | */ 110 | public boolean accept (File file) 111 | { 112 | boolean accepted = false; 113 | 114 | if (filters.size() == 0) 115 | accepted = true; 116 | 117 | else 118 | { 119 | for (FileFilter filter : filters) 120 | { 121 | accepted = filter.accept (file); 122 | if (accepted) 123 | break; 124 | } 125 | } 126 | 127 | return accepted; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/Package.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | /** 4 | * Package-specific shared utility methods. Used only within this package. 5 | */ 6 | final class Package 7 | { 8 | /*----------------------------------------------------------------------*\ 9 | Constants 10 | \*----------------------------------------------------------------------*/ 11 | 12 | /** 13 | * The name of the resource bundle for this package. 14 | */ 15 | static final String BUNDLE_NAME = "org.clapper.util.io.Bundle"; 16 | 17 | /*----------------------------------------------------------------------*\ 18 | Constructor 19 | \*----------------------------------------------------------------------*/ 20 | 21 | private Package() 22 | { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/RecursiveFileFinder.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.io; 2 | 3 | import java.io.File; 4 | import java.io.FileFilter; 5 | import java.io.FilenameFilter; 6 | 7 | import java.util.Collection; 8 | 9 | /** 10 | * A RecursiveFileFinder walks a directory tree and finds all 11 | * files (or directories) that satisfy caller-supplied criteria. The 12 | * match criteria are specified via either a FileFilter or 13 | * FilenameFilter object. 14 | */ 15 | public class RecursiveFileFinder 16 | { 17 | /*----------------------------------------------------------------------*\ 18 | Constructors 19 | \*----------------------------------------------------------------------*/ 20 | 21 | /** 22 | * Construct a new RecursiveFileFinder object. The various 23 | * find() methods actually provide the searching capabilities. 24 | */ 25 | public RecursiveFileFinder() 26 | { 27 | } 28 | 29 | /*----------------------------------------------------------------------*\ 30 | Public Methods 31 | \*----------------------------------------------------------------------*/ 32 | 33 | /** 34 | * Find all files beneath a given directory. This version of 35 | * find() takes no filter, so every file and directory is 36 | * matched. 37 | * 38 | * @param directory the starting directory 39 | * @param collection where to store the found File objects 40 | * 41 | * @return the number of File objects found 42 | */ 43 | public int findFiles (File directory, Collection collection) 44 | { 45 | return findFiles (directory, (FileFilter) null, collection); 46 | } 47 | 48 | /** 49 | * Find all files beneath a given directory, filtered by the specified 50 | * FilenameFilter. 51 | * 52 | * @param directory the starting directory 53 | * @param filter the FilenameFilter to use to filter the 54 | * file names, or null to accept all files 55 | * @param collection where to store the found File objects 56 | * 57 | * @return the number of File objects found 58 | */ 59 | public int findFiles (File directory, 60 | FilenameFilter filter, 61 | Collection collection) 62 | { 63 | int total = 0; 64 | File[] files = directory.listFiles (filter); 65 | 66 | if (files != null) 67 | { 68 | for (int i = 0; i < files.length; i++) 69 | collection.add (files[i]); 70 | 71 | total = files.length; 72 | } 73 | 74 | File[] dirs = directory.listFiles (new DirectoryFilter()); 75 | if (dirs != null) 76 | { 77 | for (int i = 0; i < dirs.length; i++) 78 | total += findFiles (dirs[i], filter, collection); 79 | } 80 | 81 | return total; 82 | } 83 | 84 | /** 85 | * Find all files beneath a given directory, filtered by the specified 86 | * FilenameFilter. 87 | * 88 | * @param directory the starting directory 89 | * @param filter the FilenameFilter to use to filter the 90 | * file names, or null to accept all files 91 | * @param collection where to store the found File objects 92 | * 93 | * @return the number of File objects found 94 | */ 95 | public int findFiles (File directory, 96 | FileFilter filter, 97 | Collection collection) 98 | { 99 | int total = 0; 100 | File[] files = directory.listFiles (filter); 101 | 102 | if (files != null) 103 | { 104 | for (int i = 0; i < files.length; i++) 105 | collection.add (files[i]); 106 | 107 | total = files.length; 108 | } 109 | 110 | File[] dirs = directory.listFiles (new DirectoryFilter()); 111 | if (dirs != null) 112 | { 113 | for (int i = 0; i < dirs.length; i++) 114 | total += findFiles (dirs[i], filter, collection); 115 | } 116 | 117 | return total; 118 | } 119 | 120 | /*----------------------------------------------------------------------*\ 121 | Private Methods 122 | \*----------------------------------------------------------------------*/ 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/io/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

org.clapper.util.io - Input/Output Classes.

7 |

This package contains input- and output-related Java classes; the package 8 | is a conceptual extension of the standard 9 | java.io package.

10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/logging/JavaUtilLoggingTextFormatter.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.logging; 2 | 3 | import java.util.logging.Formatter; 4 | import java.util.logging.LogRecord; 5 | import java.util.Date; 6 | import java.text.SimpleDateFormat; 7 | import java.io.PrintWriter; 8 | import java.io.StringWriter; 9 | 10 | /** 11 | * Implements a log formatter for the java.util.logging 12 | * infrastructure that formats each non-exception log record as a simple, 13 | * single-line text string. (Exception stack traces will cause a message to 14 | * consume multiple lines.) This formatter currently cannot be configured 15 | * in any way, though that may change in subsequent releases. The output 16 | * format is similar to 17 | * Log4J's 18 | * PatternLayout class, with a format of "%d %-5p (%c{1}): %m%n". 19 | */ 20 | public class JavaUtilLoggingTextFormatter extends Formatter 21 | { 22 | /*----------------------------------------------------------------------*\ 23 | Private Constants 24 | \*----------------------------------------------------------------------*/ 25 | 26 | private static final SimpleDateFormat DATE_FORMAT = 27 | new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss.SSS"); 28 | 29 | /*----------------------------------------------------------------------*\ 30 | Private Instance Data 31 | \*----------------------------------------------------------------------*/ 32 | 33 | /*----------------------------------------------------------------------*\ 34 | Constructor 35 | \*----------------------------------------------------------------------*/ 36 | 37 | /** 38 | * Create a new JavaUtilLoggingTextLogFormatter. 39 | */ 40 | public JavaUtilLoggingTextFormatter() 41 | { 42 | super(); 43 | } 44 | 45 | /*----------------------------------------------------------------------*\ 46 | Public Methods 47 | \*----------------------------------------------------------------------*/ 48 | 49 | /** 50 | *

Format the given log record and return the formatted string.

51 | * 52 | *

The resulting formatted String will normally include a localized 53 | * and formated version of the LogRecord's message field.

54 | * 55 | * @param record the log record to be formatted 56 | * 57 | * @return the formatted log record 58 | */ 59 | public String format (LogRecord record) 60 | { 61 | StringWriter sw = new StringWriter(); 62 | PrintWriter pw = new PrintWriter (sw); 63 | String loggerName = record.getLoggerName(); 64 | Throwable ex = record.getThrown(); 65 | String message = super.formatMessage (record); 66 | 67 | pw.print (DATE_FORMAT.format (new Date (record.getMillis()))); 68 | pw.print (' '); 69 | pw.print (record.getLevel().toString()); 70 | pw.print (" ("); 71 | 72 | // Logger name is a class name. Strip all but the last part of it. 73 | 74 | int i = loggerName.lastIndexOf ("."); 75 | if ((i != -1) && (i < (loggerName.length() - 1))) 76 | loggerName = loggerName.substring (i + 1); 77 | 78 | pw.print (loggerName); 79 | pw.print (") "); 80 | 81 | if ((message != null) && (message.trim().length() > 0)) 82 | pw.println (message); 83 | 84 | if (ex != null) 85 | ex.printStackTrace (pw); 86 | 87 | return sw.toString(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/logging/LogLevel.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.logging; 2 | 3 | /** 4 | * Encodes legal logging level constants for the {@link Logger} class. 5 | */ 6 | public enum LogLevel 7 | { 8 | /*----------------------------------------------------------------------*\ 9 | Enumeration Constant Values 10 | \*----------------------------------------------------------------------*/ 11 | 12 | /** 13 | * Log message at "debug" level 14 | */ 15 | DEBUG, 16 | 17 | /** 18 | * Log message at "error" level 19 | */ 20 | ERROR, 21 | 22 | /** 23 | * Log message at "fatal error" level 24 | */ 25 | FATAL, 26 | 27 | /** 28 | * Log message at "informational message" level 29 | */ 30 | INFO, 31 | 32 | /** 33 | * Log message at "trace" level 34 | */ 35 | TRACE, 36 | 37 | /** 38 | * Log message at "warning" level 39 | */ 40 | WARNING; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/logging/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

org.clapper.util.logging - Logging Classes.

7 |

This package contains logging-related classes. The 8 | Logger class provides a front-end to 9 | the 10 | Jakarta Commons Logging 11 | API, 12 | insulating calling applications from the specifics of the underlying 13 | logging layer, while providing a simpler, thinner interface than 14 | Commons Logging. Other classes, such as 15 | JavaUtilLoggingTextFormatter, 16 | provide useful formatters for the java.util.logging framework.

17 | 18 |

WARNING: If your application installs its own class loader (e.g., 19 | as the thread context class loader), you may have problems with 20 | both Jakarta Commons Logging and java.util.logging (which 21 | Jakarta Commons Logging uses as its default logging implementation).

22 | 23 |
    24 |
  • The java.util.logging layer explicitly bootstraps 25 | itself using the system class loader (i.e., the class loader 26 | that examines the CLASSPATH setting). In some cases, this can 27 | cause problems. For instance, an application that installs its 28 | own class loader and expects to use that class loader to find 29 | a custom-built java.util.logging formatter will not 30 | end up using its own formatter, because the formatter class 31 | is not available in the CLASSPATH, and that's where 32 | java.util.logging expects to find it. In situations 33 | like that, the best bet is to substitute a better-behaved 34 | underlying logging layer such as 35 | Log4J. 36 | 37 |
  • Jakarta Commons Logging uses a "discovery" process that plays 38 | class loader games. You can solve the problem with Commons Logging 39 | by explicitly specifying the underlying logging API to use. For 40 | example: 41 | 42 |
    43 | java -Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger -Dlog4j.configuration=file:/path/to/log4j-debug.properties
    44 |
45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/misc/EnumerationIterator.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.misc; 2 | 3 | import java.util.Iterator; 4 | import java.util.Enumeration; 5 | import java.util.NoSuchElementException; 6 | 7 | /** 8 | *

The EnumerationIterator class is an adapter that makes a 9 | * java.util.Enumeration object look and behave like a 10 | * java.util.Iterator objects. The EnumerationIterator 11 | * class implements the Iterator interface and wraps an existing 12 | * Enumeration object. This class is the conceptual opposite of 13 | * the Collections.enumeration() method in the java.util 14 | * package.

15 | * 16 | *

You can also use an instance of this class to wrap an 17 | * Enumeration for use in a JDK 1.5-style for each loop. 18 | * For instance:

19 | * 20 | *
 21 |  * {@code
 22 |  * Vector v = ...
 23 |  * for (String s : new EnumerationIterator (v.elements()))
 24 |  *     ...
 25 |  * }
 26 |  * 
27 | * 28 | * @see java.util.Iterator 29 | * @see java.util.Enumeration 30 | */ 31 | public class EnumerationIterator implements Iterator, Iterable 32 | { 33 | /*----------------------------------------------------------------------*\ 34 | Private Data Elements 35 | \*----------------------------------------------------------------------*/ 36 | 37 | /** 38 | * The underlying Enumeration. 39 | */ 40 | private Enumeration enumeration = null; 41 | 42 | /*----------------------------------------------------------------------*\ 43 | Constructor 44 | \*----------------------------------------------------------------------*/ 45 | 46 | /** 47 | * Allocate a new EnumerationIterator object that will 48 | * forward its calls to the specified Enumeration. 49 | * 50 | * @param enumeration The Enumeration to which to forward calls 51 | */ 52 | public EnumerationIterator (Enumeration enumeration) 53 | { 54 | this.enumeration = enumeration; 55 | } 56 | 57 | /*----------------------------------------------------------------------*\ 58 | Public Methods 59 | \*----------------------------------------------------------------------*/ 60 | 61 | /** 62 | * Determine whether the underlying Enumeration has more 63 | * elements. 64 | * 65 | * @return true if and only if a call to 66 | * next() will return an element, 67 | * false otherwise. 68 | * 69 | * @see #next() 70 | * @see Enumeration#hasMoreElements 71 | */ 72 | public boolean hasNext() 73 | { 74 | return enumeration.hasMoreElements(); 75 | } 76 | 77 | /** 78 | * Returns this iterator. Necessary for the Iterable interface. 79 | * 80 | * @return this object 81 | */ 82 | public Iterator iterator() 83 | { 84 | return this; 85 | } 86 | 87 | /** 88 | * Get the next element from the underlying Enumeration. 89 | * 90 | * @return the next element from the underlying Enumeration 91 | * 92 | * @exception NoSuchElementException No more elements exist 93 | * 94 | * @see Iterator#next 95 | */ 96 | public T next() throws NoSuchElementException 97 | { 98 | return enumeration.nextElement(); 99 | } 100 | 101 | /** 102 | * Removes from the underlying collection the last element returned by 103 | * the iterator. Not supported by this class. 104 | * 105 | * @throws IllegalStateException doesn't 106 | * @throws UnsupportedOperationException unconditionally 107 | */ 108 | public void remove() 109 | throws IllegalStateException, 110 | UnsupportedOperationException 111 | { 112 | throw new UnsupportedOperationException(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/misc/ObjectRemovalEvent.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.misc; 2 | 3 | import java.util.EventObject; 4 | 5 | /** 6 | *

An ObjectRemovalEvent is an event that is propagated to 7 | * certain event listeners when an object is removed from some kind of 8 | * a store or data structure. For instance, the {@link LRUMap} class supports 9 | * this event through its {@link LRUMap#addRemovalListener} method.

10 | * 11 | * @see ObjectRemovalListener 12 | * @see LRUMap#addRemovalListener 13 | */ 14 | public class ObjectRemovalEvent extends EventObject 15 | { 16 | /*----------------------------------------------------------------------*\ 17 | Private Static Variables 18 | \*----------------------------------------------------------------------*/ 19 | 20 | /** 21 | * See JDK 1.5 version of java.io.Serializable 22 | */ 23 | private static final long serialVersionUID = 1L; 24 | 25 | /*----------------------------------------------------------------------*\ 26 | Constructors 27 | \*----------------------------------------------------------------------*/ 28 | 29 | /** 30 | * Construct a ObjectRemovalEvent event to announce the removal 31 | * of an object from a data store. 32 | * 33 | * @param source the object being removed 34 | */ 35 | public ObjectRemovalEvent (Object source) 36 | { 37 | super (source); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/misc/ObjectRemovalListener.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.misc; 2 | 3 | import java.util.EventListener; 4 | 5 | /** 6 | *

An ObjectRemovalListener is an EventListener that 7 | * can be registered with certain data store objects to receive an 8 | * {@link ObjectRemovalEvent} whenever an object is removed from the data 9 | * store.

10 | * 11 | * @see ObjectRemovalEvent 12 | * @see LRUMap#addRemovalListener 13 | */ 14 | public interface ObjectRemovalListener extends EventListener 15 | { 16 | /*----------------------------------------------------------------------*\ 17 | Public Methods 18 | \*----------------------------------------------------------------------*/ 19 | 20 | /** 21 | * This method gets called when an object is removed from a store. 22 | * 23 | * @param event the ObjectRemovalEvent event 24 | */ 25 | public void objectRemoved (ObjectRemovalEvent event); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/misc/Package.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.misc; 2 | 3 | /** 4 | * Package-specific shared utility methods. Used only within this package. 5 | */ 6 | final class Package 7 | { 8 | /*----------------------------------------------------------------------*\ 9 | Constants 10 | \*----------------------------------------------------------------------*/ 11 | 12 | /** 13 | * The name of the resource bundle for this package. 14 | */ 15 | static final String BUNDLE_NAME = "org.clapper.util.misc.Bundle"; 16 | 17 | /*----------------------------------------------------------------------*\ 18 | Constructor 19 | \*----------------------------------------------------------------------*/ 20 | 21 | private Package() 22 | { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/misc/Version.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.misc; 2 | 3 | /** 4 | *

Contains the software version for the org.clapper.util 5 | * library. Also contains a main program which, invoked, displays the name 6 | * of the API, the version, and detailed build information on standard 7 | * output.

8 | */ 9 | public final class Version extends VersionBase 10 | { 11 | /*----------------------------------------------------------------------*\ 12 | Public Constants 13 | \*----------------------------------------------------------------------*/ 14 | 15 | /** 16 | * The name of the resource bundle containing the build info. 17 | */ 18 | public static final String BUILD_INFO_BUNDLE_NAME 19 | = "org.clapper.util.misc.BuildInfoBundle"; 20 | 21 | 22 | /*----------------------------------------------------------------------*\ 23 | Private Data Items 24 | \*----------------------------------------------------------------------*/ 25 | 26 | private static Version instance = new Version(); 27 | 28 | /*----------------------------------------------------------------------*\ 29 | Constructor 30 | \*----------------------------------------------------------------------*/ 31 | 32 | private Version() 33 | { 34 | } 35 | 36 | /*----------------------------------------------------------------------*\ 37 | Public Methods 38 | \*----------------------------------------------------------------------*/ 39 | 40 | /** 41 | * Get an instance of this class. 42 | * 43 | * @return a singleton instance of this class. 44 | */ 45 | public static Version getInstance() 46 | { 47 | return new Version(); 48 | } 49 | 50 | /*----------------------------------------------------------------------*\ 51 | Protected Methods 52 | \*----------------------------------------------------------------------*/ 53 | 54 | 55 | /** 56 | * Get the class name of the version resource bundle, which contains 57 | * values for the product version, copyright, etc. 58 | * 59 | * @return the name of the version resource bundle 60 | */ 61 | protected String getVersionBundleName() 62 | { 63 | return "org.clapper.util.misc.Bundle"; 64 | } 65 | 66 | /** 67 | * Get the class name of the build info resource bundle, which contains 68 | * data about when the product was built, generated (presumably) 69 | * during the build by 70 | * {@link BuildInfo#makeBuildInfoBundle BuildInfo.makeBuildInfoBundle()}. 71 | * 72 | * @return the name of the build info resource bundle 73 | */ 74 | protected String getBuildInfoBundleName() 75 | { 76 | return BUILD_INFO_BUNDLE_NAME; 77 | } 78 | 79 | /** 80 | * Get the key for the version string. This key is presumed to be 81 | * in the version resource bundle. 82 | * 83 | * @return the version string key 84 | */ 85 | protected String getVersionKey() 86 | { 87 | return "api.version"; 88 | } 89 | 90 | /** 91 | * Get the key for the copyright string. This key is presumed to be 92 | * in the version resource bundle. 93 | * 94 | * @return the copyright string key 95 | */ 96 | protected String getCopyrightKey() 97 | { 98 | return "api.copyright"; 99 | } 100 | 101 | /** 102 | * Get the key for the name of the utility or application. 103 | * 104 | * @return the key 105 | */ 106 | protected String getApplicationNameKey() 107 | { 108 | return "api.name"; 109 | } 110 | 111 | /*----------------------------------------------------------------------*\ 112 | Main Program 113 | \*----------------------------------------------------------------------*/ 114 | 115 | /** 116 | * Display the build information 117 | * 118 | * @param args command-line parameters (ignored) 119 | */ 120 | public static void main (String[] args) 121 | { 122 | System.out.println(getInstance().getVersionDisplay()); 123 | System.exit (0); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/misc/XDate.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.misc; 2 | 3 | import java.text.DateFormat; 4 | import java.text.SimpleDateFormat; 5 | 6 | import java.util.Calendar; 7 | import java.util.GregorianCalendar; 8 | import java.util.Date; 9 | import java.util.TimeZone; 10 | 11 | /** 12 | * Version of java.util.Date that provides some extra utility 13 | * functions. 14 | */ 15 | public class XDate extends Date 16 | { 17 | /*----------------------------------------------------------------------*\ 18 | Constructor 19 | \*----------------------------------------------------------------------*/ 20 | 21 | /** 22 | * Create a new XDate so that it represents the time the 23 | * object was constructed, measure to the nearest millisecond. 24 | */ 25 | public XDate() 26 | { 27 | super(); 28 | } 29 | 30 | /** 31 | * Create a new XDate object and initialize it to represent the 32 | * specified number of milliseconds since the epoch. 33 | * 34 | * @param millis the milliseconds 35 | */ 36 | public XDate (long millis) 37 | { 38 | super (millis); 39 | } 40 | 41 | /** 42 | * Create a new XDate object and initialize it to represent the 43 | * time contained in the specified, existing Date object (which 44 | * may, itself, be an XDate). 45 | * 46 | * @param date the date 47 | */ 48 | public XDate (Date date) 49 | { 50 | super (date.getTime()); 51 | } 52 | 53 | /*----------------------------------------------------------------------*\ 54 | Public Methods 55 | \*----------------------------------------------------------------------*/ 56 | 57 | /** 58 | * Convert this date from its time zone to another. Sample use: 59 | * 60 | *
 61 |      * // Convert time in default time zone to UTC
 62 |      *
 63 |      * XDate now = new XDate();
 64 |      * TimeZone tzUTC = TimeZone.getTimeZone ("UTC");
 65 |      * Date utc = now.convertToTimeZone (tzUTC);
 66 |      * DateFormat fmt = new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss z");
 67 |      * fmt.setTimeZone (tzUTC);
 68 |      * System.out.println (fmt.format (utc));
 69 |      * 
70 | * 71 | *

Or, more simply:

72 | * 73 | *
 74 |      * XDate now = new XDate();
 75 |      * System.out.println (now.formatInTimeZone ("yyyy/MM/dd HH:mm:ss z",
 76 |      *                                           TimeZone.getTimeZone ("UTC")));
 77 |      * 
78 | * 79 | * @param tz the time zone to convert the date to 80 | * 81 | * @return a new XDate object, appropriately converted. This 82 | * result can safely be stored in a java.util.Date 83 | * reference. 84 | * 85 | * @see #formatInTimeZone 86 | */ 87 | public XDate convertToTimeZone (TimeZone tz) 88 | { 89 | Calendar calFrom = new GregorianCalendar (TimeZone.getDefault()); 90 | Calendar calTo = new GregorianCalendar (tz); 91 | calFrom.setTimeInMillis (getTime()); 92 | calTo.setTimeInMillis (calFrom.getTimeInMillis()); 93 | return new XDate (calTo.getTime()); 94 | } 95 | 96 | /** 97 | * Convenience method to produce a printable date in a specified time 98 | * zone, using a SimpleDateFormat. Calling this method is 99 | * roughly equivalent to: 100 | * 101 | *
102 |      * XDate now = new XDate();
103 |      * TimeZone tzUTC = TimeZone.getTimeZone ("UTC");
104 |      * Date utc = now.convertToTimeZone (tzUTC);
105 |      * DateFormat fmt = new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss z");
106 |      * fmt.setTimeZone (tzUTC);
107 |      * String formattedDate = fmt.format (utc);
108 |      * 
109 | * 110 | * @param dateFormat the date format string to use, in a form that's 111 | * compatible with java.text.SimpleDateFormat 112 | * @param tz the desired time zone 113 | * 114 | * @return the formatted date string 115 | */ 116 | public String formatInTimeZone (String dateFormat, TimeZone tz) 117 | { 118 | Date tzDate = convertToTimeZone (tz); 119 | DateFormat fmt = new SimpleDateFormat (dateFormat); 120 | fmt.setTimeZone (tz); 121 | return fmt.format (tzDate); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/misc/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

org.clapper.util.misc - Miscellaneous Utility Classes.

7 |

This package contains miscellaneous utility class.

8 | It is a conceptual extension of the standard java.util package.

9 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/regex/Package.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.regex; 2 | 3 | /** 4 | * Package-specific shared utility methods. Used only within this package. 5 | */ 6 | final class Package 7 | { 8 | /*----------------------------------------------------------------------*\ 9 | Constants 10 | \*----------------------------------------------------------------------*/ 11 | 12 | /** 13 | * The name of the resource bundle for this package. 14 | */ 15 | static final String BUNDLE_NAME = "org.clapper.util.regex.Bundle"; 16 | 17 | /*----------------------------------------------------------------------*\ 18 | Constructor 19 | \*----------------------------------------------------------------------*/ 20 | 21 | private Package() 22 | { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/text/MultipleMapVariableDereferencer.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.text; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Map; 6 | 7 | /** 8 | 9 | /** 10 | *

The MultipleMapVariableDereferencer class implements the 11 | * VariableDereferencer interface and resolves variable 12 | * references by looking them up in one or more supplied Map 13 | * objects.

14 | * 15 | *

The keys and values in the supplied Map objects 16 | * must be String objects.

17 | * 18 | *

If more than one contained map has a value for a specific key, the 19 | * first map's value will be used. ("First", in this case, means the first 20 | * map added to the MultipleMapVariableDereference object.)

21 | * 22 | * @see VariableDereferencer 23 | * @see VariableSubstituter 24 | * @see MapVariableDereferencer 25 | */ 26 | public class MultipleMapVariableDereferencer implements VariableDereferencer 27 | { 28 | /*----------------------------------------------------------------------*\ 29 | Private Constants 30 | \*----------------------------------------------------------------------*/ 31 | 32 | /*----------------------------------------------------------------------*\ 33 | Private Instance Data 34 | \*----------------------------------------------------------------------*/ 35 | 36 | private Collection> mapList = 37 | new ArrayList>(); 38 | 39 | /*----------------------------------------------------------------------*\ 40 | Constructor 41 | \*----------------------------------------------------------------------*/ 42 | 43 | /** 44 | * Creates a new instance of MultipleMapVariableDereferencer 45 | * that has no associated Map objects. Map objects 46 | * can be added with the {@link #addMap} method. 47 | */ 48 | public MultipleMapVariableDereferencer() 49 | { 50 | } 51 | 52 | /** 53 | * Creates a new instance of MultipleMapVariableDereferencer 54 | * that is initialized with a specified set of maps. 55 | * 56 | * @param maps the maps to use 57 | */ 58 | public MultipleMapVariableDereferencer(Map... maps) 59 | { 60 | for (Map map : maps) 61 | addMap(map); 62 | } 63 | 64 | /*----------------------------------------------------------------------*\ 65 | Public Methods 66 | \*----------------------------------------------------------------------*/ 67 | 68 | /** 69 | * Add a map to the list of maps to use when dereferencing variable values. 70 | * The map is added to the end of the list. 71 | * 72 | * @param map The map to add 73 | */ 74 | public final void addMap(Map map) 75 | { 76 | mapList.add(map); 77 | } 78 | 79 | /** 80 | * Get the value associated with a given variable. 81 | * 82 | * @param varName The name of the variable for which the value is 83 | * desired. 84 | * @param context a context object, passed through from the caller 85 | * to the dereferencer, or null if there isn't one. 86 | * Ignored here. 87 | * 88 | * @return The variable's value. If the variable has no value, this 89 | * method must return null. 90 | */ 91 | public String getVariableValue (String varName, Object context) 92 | { 93 | String result = null; 94 | 95 | for (Map map : mapList) 96 | { 97 | result = map.get(varName); 98 | if (result != null) 99 | break; 100 | } 101 | 102 | return result; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/text/Package.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.text; 2 | 3 | /** 4 | * Package-specific shared utility methods. Used only within this package. 5 | */ 6 | final class Package 7 | { 8 | /*----------------------------------------------------------------------*\ 9 | Constants 10 | \*----------------------------------------------------------------------*/ 11 | 12 | /** 13 | * The name of the resource bundle for this package. 14 | */ 15 | static final String BUNDLE_NAME = "org.clapper.util.text.Bundle"; 16 | 17 | /*----------------------------------------------------------------------*\ 18 | Constructor 19 | \*----------------------------------------------------------------------*/ 20 | 21 | private Package() 22 | { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/text/VariableDereferencer.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.text; 2 | 3 | /** 4 | *

The VariableDereferencer interface defines the behavior 5 | * of classes that can look up variables by name, returning their values. 6 | * It is used primarily to mark classes that can work hand-in-hand with 7 | * VariableSubstituter objects to resolve variable references 8 | * in strings.

9 | * 10 | *

The values for referenced variables can come from anywhere (in a 11 | * Properties object, via direct method calls, from a symbol 12 | * table, etc.), provided the values can be located using only the 13 | * variable's name.

14 | * 15 | * @see MapVariableDereferencer 16 | * @see VariableSubstituter 17 | */ 18 | public interface VariableDereferencer 19 | { 20 | /*----------------------------------------------------------------------*\ 21 | Public Methods 22 | \*----------------------------------------------------------------------*/ 23 | 24 | /** 25 | * Get the value associated with a given variable. 26 | * 27 | * @param varName The name of the variable for which the value is 28 | * desired. 29 | * @param context a context object, passed through from the caller 30 | * to the dereferencer, or null if there isn't one. 31 | * 32 | * @return The variable's value. If the variable has no value, this 33 | * method must return null. 34 | * 35 | * @throws VariableSubstitutionException substitution error 36 | */ 37 | public String getVariableValue (String varName, Object context) 38 | throws VariableSubstitutionException; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/text/VariableNameChecker.java: -------------------------------------------------------------------------------- 1 | package org.clapper.util.text; 2 | 3 | /** 4 | *

This interface defines the methods for a class that checks characters 5 | * to determine whether they're legal for a variable name that's to be 6 | * substituted by a {@link VariableSubstituter} object. It has a single 7 | * method, {@link #legalVariableCharacter}, which determines whether a 8 | * specified character is a legal part of a variable name or not. This 9 | * capability provides additional flexibility by allowing callers to define 10 | * precisely what characters constitute legal variable names.

11 | * 12 | * @see VariableSubstituter 13 | * @see VariableDereferencer 14 | */ 15 | public interface VariableNameChecker 16 | { 17 | /*----------------------------------------------------------------------*\ 18 | Required Methods 19 | \*----------------------------------------------------------------------*/ 20 | 21 | /** 22 | *

Determine whether a character may legally be used in a variable 23 | * name or not.

24 | * 25 | * @param c The character to test 26 | * 27 | * @return true if the character may be part of a variable name, 28 | * false otherwise 29 | * 30 | * @see VariableSubstituter#substitute 31 | */ 32 | public boolean legalVariableCharacter (char c); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/clapper/util/text/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

org.clapper.util.text - Text and String Classes.

7 |

This package contains text and string-related Java classes; the package 8 | is a conceptual extension of the standard 9 | java.text package.

10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/version.prop: -------------------------------------------------------------------------------- 1 | version=${project.version} 2 | -------------------------------------------------------------------------------- /src/main/resources/ModTheSpire.json: -------------------------------------------------------------------------------- 1 | { 2 | "modid": "modthespire", 3 | "name": "ModTheSpire", 4 | "author_list": [], 5 | "description": "YOU SHOULD NOT BE SEEING MODTHESPIRE HERE. If you see this, remove ModTheSpire from your 'mods/' directory.", 6 | "mts_version": "999.999.999" 7 | } 8 | -------------------------------------------------------------------------------- /src/main/resources/assets/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiooeht/ModTheSpire/c6201995b8f5d97620cdb00f28a724a95b7742a6/src/main/resources/assets/ajax-loader.gif -------------------------------------------------------------------------------- /src/main/resources/assets/download.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiooeht/ModTheSpire/c6201995b8f5d97620cdb00f28a724a95b7742a6/src/main/resources/assets/download.gif -------------------------------------------------------------------------------- /src/main/resources/assets/error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiooeht/ModTheSpire/c6201995b8f5d97620cdb00f28a724a95b7742a6/src/main/resources/assets/error.gif -------------------------------------------------------------------------------- /src/main/resources/assets/good.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiooeht/ModTheSpire/c6201995b8f5d97620cdb00f28a724a95b7742a6/src/main/resources/assets/good.gif -------------------------------------------------------------------------------- /src/main/resources/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiooeht/ModTheSpire/c6201995b8f5d97620cdb00f28a724a95b7742a6/src/main/resources/assets/icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/update.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiooeht/ModTheSpire/c6201995b8f5d97620cdb00f28a724a95b7742a6/src/main/resources/assets/update.gif -------------------------------------------------------------------------------- /src/main/resources/assets/warning.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiooeht/ModTheSpire/c6201995b8f5d97620cdb00f28a724a95b7742a6/src/main/resources/assets/warning.gif -------------------------------------------------------------------------------- /src/main/resources/assets/workshop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiooeht/ModTheSpire/c6201995b8f5d97620cdb00f28a724a95b7742a6/src/main/resources/assets/workshop.gif -------------------------------------------------------------------------------- /src/main/resources/steam_appid.txt: -------------------------------------------------------------------------------- 1 | 646570 --------------------------------------------------------------------------------