diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..c9c926e --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,27 @@ +version: 2 + +jobs: + build: + docker: + - image: circleci/openjdk:8-jdk-stretch + + steps: + - checkout + + - restore_cache: + keys: + - v1-dependencies-{{ checksum "pom.xml" }} + + - run: + command: | + mvn dependency:go-offline + mvn clean package + mkdir artifact + cp target/*.jar artifact + - save_cache: + paths: + - ~/.m2 + key: v1-dependencies-{{ checksum "pom.xml" }} + + - store_artifacts: + path: artifact diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8147bf8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +*.java text=auto eol=lf diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..643f9f3 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,35 @@ +name: Java CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Cache dependencies + uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Build with Maven + run: mvn clean package + + - name: Copy artifacts + run: | + mkdir artifact + cp target/*.jar artifact + - name: Archive artifacts + uses: actions/upload-artifact@v1 + with: + name: artifacts + path: artifact diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a92ac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,225 @@ +# Created by https://www.gitignore.io/api/java,maven,eclipse,netbeans,intellij+all +# Edit at https://www.gitignore.io/?templates=java,maven,eclipse,netbeans,intellij+all + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +### Eclipse Patch ### +# Eclipse Core +.project + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Annotation Processing +.apt_generated + +.sts4-cache/ + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +### NetBeans ### +**/nbproject/private/ +**/nbproject/Makefile-*.mk +**/nbproject/Package-*.bash +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +# End of https://www.gitignore.io/api/java,maven,eclipse,netbeans,intellij+all + +# Windows +desktop.ini +*/desktop.ini +Thumbs.db +*/Thumbs.db +ehthumbs.db +*/ehthumbs.db + +# Mac +.DS_Store +*/.DS_Store +__MACOSX +__MACOSX/* +*/__MACOSX +*/__MACOSX/* + +# Java +*.MF diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..af9aba3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: java + +jdk: + - openjdk8 + +install: + - mvn clean package + +cache: + directories: + - '$HOME/.m2/repository' diff --git a/README.md b/README.md index 1fead45..4248118 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,76 @@ -# GeoIP -[![](https://i.loli.net/2019/08/11/g9PU5ufFoqmeKjp.png)](http://www.mcbbs.net/thread-900823-1-1.html "IP定位") - -GeoIP plugin for Nukkit. +# GeoIP for Nukkit +[![Nukkit](https://img.shields.io/badge/Nukkit-1.0-green)](https://github.com/NukkitX/Nukkit) +[![Build](https://img.shields.io/circleci/build/github/wode490390/GeoIP/master)](https://circleci.com/gh/wode490390/GeoIP/tree/master) +[![Release](https://img.shields.io/github/v/release/wode490390/GeoIP)](https://github.com/wode490390/GeoIP/releases) +[![Release date](https://img.shields.io/github/release-date/wode490390/GeoIP)](https://github.com/wode490390/GeoIP/releases) + GeoIP provides an approximate lookup of where your players come from, based on their public IP and public geographical databases. -Please see [mcbbs](http://www.mcbbs.net/thread-900823-1-1.html) for more information. -## Permissions +If you found any bugs or have any suggestions, please open an issue on [GitHub Issues](https://github.com/wode490390/GeoIP/issues). + +If you like this plugin, please star it on [GitHub](https://github.com/wode490390/GeoIP). + +## Commands | Command | Permission | Description | Default | | - | - | - | - | | `/geoip` | geoip.show | Shows the GeoIP location of a player. | OP | | `/geoip` | geoip.show.fullip | Shows the full ip address of a player. | false | | | geoip.hide | Allows player to hide player's country and city from people who have permission geoip.show | false | -## config.yml + +## Configuration +
+config.yml + ```yaml database: show-cities: false download-if-missing: true # Url for country - download-url: "https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz" + lzma-download-url: "https://cdn.jsdelivr.net/gh/wodeBot/geoipdb@lzma/country.mmdb.lzma" # Url for cities - download-url-city: "https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz" - update: - enable: true - by-every-x-days: 30 + lzma-download-url-city: "https://cdn.jsdelivr.net/gh/wodeBot/geoipdb@lzma/city.mmdb.lzma" show-on-login: true # "enable-locale" enables locale on geolocation display. enable-locale: true # Not all languages are supported. See https://dev.maxmind.com/geoip/geoip2/web-services/#Languages locale: en ``` +
+ +## Download +- [Releases](https://github.com/wode490390/GeoIP/releases) +- [Snapshots](https://circleci.com/gh/wode490390/GeoIP) + ## API Usage +
+example code + ```java -import cn.nukkit.Player; -import cn.nukkit.Server; import cn.wode490390.nukkit.geoip.GeoIP; +import java.util.UUID; class Example { Example() { - Player player = Server.getInstance().getPlayer("wode490390"); - if (player != null) { - String geoLocation = GeoIP.query(player); //Our API :) - player.sendMessage("Your location: " + geoLocation); - } + UUID uuid = UUID.fromString("ecb32467-6cee-4a59-b3c0-5468fec58ed4"); + String geoLocation = GeoIP.query(uuid); //Our API :) + System.out.println("Location: " + geoLocation); } } ``` +
+ +## Compiling +1. Install [Maven](https://maven.apache.org/). +2. Fork and clone the repo. +3. Run `mvn clean package`. The compiled JAR can be found in the `target/` directory. + +## Metrics Collection + +This plugin uses [bStats](https://github.com/wode490390/bStats-Nukkit). You can opt out using the global bStats config; see the [official website](https://bstats.org/getting-started) for more details. + + + +###### If I have any grammar and/or term errors, please correct them :) diff --git a/pom.xml b/pom.xml index ccedd43..d8dc85d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,14 +1,18 @@ - + 4.0.0 + cn.wode490390.nukkit geoip - 1.0.0 + jar + 1.1.0 GeoIP GeoIP plugin for Nukkit - 2018 http://wode490390.cn/ - jar + 2018 + GNU General Public License, Version 3.0 @@ -16,17 +20,44 @@ repo + + + GitHub + https://github.com/wode490390/GeoIP/issues + + + + CircleCI + https://circleci.com/gh/wode490390/GeoIP + + + + scm:git:https://github.com/wode490390/GeoIP.git + scm:git:git@github.com:wode490390/GeoIP.git + https://github.com/wode490390/GeoIP + + + + + github + github-releases + https://maven.pkg.github.com/wode490390/GeoIP + + + 1.8 1.8 UTF-8 + - nukkitx - http://repo.nukkitx.com/main/ + nukkitx-repo + https://repo.nukkitx.com/main/ + cn.nukkit @@ -37,14 +68,11 @@ com.maxmind.geoip2 geoip2 - 2.12.0 - - - javatar - javatar - 2.5 + 2.14.0 + compile + wodeGeoIP-${project.version} clean package @@ -62,7 +90,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.1 + 3.2.4 package @@ -81,6 +109,51 @@ + + pl.project13.maven + git-commit-id-plugin + 4.0.1 + + + get-the-git-infos + + revision + + + + + ${project.basedir}/.git + git + yyyy.MM.dd '@' HH:mm:ss z + ${user.timezone} + true + true + ${project.build.outputDirectory}/git.properties + properties + true + false + false + false + false + true + + git.user.* + + + false + 7 + flat + + false + false + 7 + -dirty + * + false + false + + + diff --git a/src/main/java/SevenZip/Compression/LZ/OutWindow.java b/src/main/java/SevenZip/Compression/LZ/OutWindow.java new file mode 100644 index 0000000..2fd2832 --- /dev/null +++ b/src/main/java/SevenZip/Compression/LZ/OutWindow.java @@ -0,0 +1,85 @@ +// LZ.OutWindow + +package SevenZip.Compression.LZ; + +import java.io.IOException; + +public class OutWindow +{ + byte[] _buffer; + int _pos; + int _windowSize = 0; + int _streamPos; + java.io.OutputStream _stream; + + public void Create(int windowSize) + { + if (_buffer == null || _windowSize != windowSize) + _buffer = new byte[windowSize]; + _windowSize = windowSize; + _pos = 0; + _streamPos = 0; + } + + public void SetStream(java.io.OutputStream stream) throws IOException + { + ReleaseStream(); + _stream = stream; + } + + public void ReleaseStream() throws IOException + { + Flush(); + _stream = null; + } + + public void Init(boolean solid) + { + if (!solid) + { + _streamPos = 0; + _pos = 0; + } + } + + public void Flush() throws IOException + { + int size = _pos - _streamPos; + if (size == 0) + return; + _stream.write(_buffer, _streamPos, size); + if (_pos >= _windowSize) + _pos = 0; + _streamPos = _pos; + } + + public void CopyBlock(int distance, int len) throws IOException + { + int pos = _pos - distance - 1; + if (pos < 0) + pos += _windowSize; + for (; len != 0; len--) + { + if (pos >= _windowSize) + pos = 0; + _buffer[_pos++] = _buffer[pos++]; + if (_pos >= _windowSize) + Flush(); + } + } + + public void PutByte(byte b) throws IOException + { + _buffer[_pos++] = b; + if (_pos >= _windowSize) + Flush(); + } + + public byte GetByte(int distance) + { + int pos = _pos - distance - 1; + if (pos < 0) + pos += _windowSize; + return _buffer[pos]; + } +} diff --git a/src/main/java/SevenZip/Compression/LZMA/Base.java b/src/main/java/SevenZip/Compression/LZMA/Base.java new file mode 100644 index 0000000..b4f2fb5 --- /dev/null +++ b/src/main/java/SevenZip/Compression/LZMA/Base.java @@ -0,0 +1,88 @@ +// Base.java + +package SevenZip.Compression.LZMA; + +public class Base +{ + public static final int kNumRepDistances = 4; + public static final int kNumStates = 12; + + public static final int StateInit() + { + return 0; + } + + public static final int StateUpdateChar(int index) + { + if (index < 4) + return 0; + if (index < 10) + return index - 3; + return index - 6; + } + + public static final int StateUpdateMatch(int index) + { + return (index < 7 ? 7 : 10); + } + + public static final int StateUpdateRep(int index) + { + return (index < 7 ? 8 : 11); + } + + public static final int StateUpdateShortRep(int index) + { + return (index < 7 ? 9 : 11); + } + + public static final boolean StateIsCharState(int index) + { + return index < 7; + } + + public static final int kNumPosSlotBits = 6; + public static final int kDicLogSizeMin = 0; + // public static final int kDicLogSizeMax = 28; + // public static final int kDistTableSizeMax = kDicLogSizeMax * 2; + + public static final int kNumLenToPosStatesBits = 2; // it's for speed optimization + public static final int kNumLenToPosStates = 1 << kNumLenToPosStatesBits; + + public static final int kMatchMinLen = 2; + + public static final int GetLenToPosState(int len) + { + len -= kMatchMinLen; + if (len < kNumLenToPosStates) + return len; + return (int)(kNumLenToPosStates - 1); + } + + public static final int kNumAlignBits = 4; + public static final int kAlignTableSize = 1 << kNumAlignBits; + public static final int kAlignMask = (kAlignTableSize - 1); + + public static final int kStartPosModelIndex = 4; + public static final int kEndPosModelIndex = 14; + public static final int kNumPosModels = kEndPosModelIndex - kStartPosModelIndex; + + public static final int kNumFullDistances = 1 << (kEndPosModelIndex / 2); + + public static final int kNumLitPosStatesBitsEncodingMax = 4; + public static final int kNumLitContextBitsMax = 8; + + public static final int kNumPosStatesBitsMax = 4; + public static final int kNumPosStatesMax = (1 << kNumPosStatesBitsMax); + public static final int kNumPosStatesBitsEncodingMax = 4; + public static final int kNumPosStatesEncodingMax = (1 << kNumPosStatesBitsEncodingMax); + + public static final int kNumLowLenBits = 3; + public static final int kNumMidLenBits = 3; + public static final int kNumHighLenBits = 8; + public static final int kNumLowLenSymbols = 1 << kNumLowLenBits; + public static final int kNumMidLenSymbols = 1 << kNumMidLenBits; + public static final int kNumLenSymbols = kNumLowLenSymbols + kNumMidLenSymbols + + (1 << kNumHighLenBits); + public static final int kMatchMaxLen = kMatchMinLen + kNumLenSymbols - 1; +} diff --git a/src/main/java/SevenZip/Compression/LZMA/Decoder.java b/src/main/java/SevenZip/Compression/LZMA/Decoder.java new file mode 100644 index 0000000..16ee249 --- /dev/null +++ b/src/main/java/SevenZip/Compression/LZMA/Decoder.java @@ -0,0 +1,329 @@ +package SevenZip.Compression.LZMA; + +import SevenZip.Compression.RangeCoder.BitTreeDecoder; +import SevenZip.Compression.LZMA.Base; +import SevenZip.Compression.LZ.OutWindow; +import java.io.IOException; + +public class Decoder +{ + class LenDecoder + { + short[] m_Choice = new short[2]; + BitTreeDecoder[] m_LowCoder = new BitTreeDecoder[Base.kNumPosStatesMax]; + BitTreeDecoder[] m_MidCoder = new BitTreeDecoder[Base.kNumPosStatesMax]; + BitTreeDecoder m_HighCoder = new BitTreeDecoder(Base.kNumHighLenBits); + int m_NumPosStates = 0; + + public void Create(int numPosStates) + { + for (; m_NumPosStates < numPosStates; m_NumPosStates++) + { + m_LowCoder[m_NumPosStates] = new BitTreeDecoder(Base.kNumLowLenBits); + m_MidCoder[m_NumPosStates] = new BitTreeDecoder(Base.kNumMidLenBits); + } + } + + public void Init() + { + SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_Choice); + for (int posState = 0; posState < m_NumPosStates; posState++) + { + m_LowCoder[posState].Init(); + m_MidCoder[posState].Init(); + } + m_HighCoder.Init(); + } + + public int Decode(SevenZip.Compression.RangeCoder.Decoder rangeDecoder, int posState) throws IOException + { + if (rangeDecoder.DecodeBit(m_Choice, 0) == 0) + return m_LowCoder[posState].Decode(rangeDecoder); + int symbol = Base.kNumLowLenSymbols; + if (rangeDecoder.DecodeBit(m_Choice, 1) == 0) + symbol += m_MidCoder[posState].Decode(rangeDecoder); + else + symbol += Base.kNumMidLenSymbols + m_HighCoder.Decode(rangeDecoder); + return symbol; + } + } + + class LiteralDecoder + { + class Decoder2 + { + short[] m_Decoders = new short[0x300]; + + public void Init() + { + SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_Decoders); + } + + public byte DecodeNormal(SevenZip.Compression.RangeCoder.Decoder rangeDecoder) throws IOException + { + int symbol = 1; + do + symbol = (symbol << 1) | rangeDecoder.DecodeBit(m_Decoders, symbol); + while (symbol < 0x100); + return (byte)symbol; + } + + public byte DecodeWithMatchByte(SevenZip.Compression.RangeCoder.Decoder rangeDecoder, byte matchByte) throws IOException + { + int symbol = 1; + do + { + int matchBit = (matchByte >> 7) & 1; + matchByte <<= 1; + int bit = rangeDecoder.DecodeBit(m_Decoders, ((1 + matchBit) << 8) + symbol); + symbol = (symbol << 1) | bit; + if (matchBit != bit) + { + while (symbol < 0x100) + symbol = (symbol << 1) | rangeDecoder.DecodeBit(m_Decoders, symbol); + break; + } + } + while (symbol < 0x100); + return (byte)symbol; + } + } + + Decoder2[] m_Coders; + int m_NumPrevBits; + int m_NumPosBits; + int m_PosMask; + + public void Create(int numPosBits, int numPrevBits) + { + if (m_Coders != null && m_NumPrevBits == numPrevBits && m_NumPosBits == numPosBits) + return; + m_NumPosBits = numPosBits; + m_PosMask = (1 << numPosBits) - 1; + m_NumPrevBits = numPrevBits; + int numStates = 1 << (m_NumPrevBits + m_NumPosBits); + m_Coders = new Decoder2[numStates]; + for (int i = 0; i < numStates; i++) + m_Coders[i] = new Decoder2(); + } + + public void Init() + { + int numStates = 1 << (m_NumPrevBits + m_NumPosBits); + for (int i = 0; i < numStates; i++) + m_Coders[i].Init(); + } + + Decoder2 GetDecoder(int pos, byte prevByte) + { + return m_Coders[((pos & m_PosMask) << m_NumPrevBits) + ((prevByte & 0xFF) >>> (8 - m_NumPrevBits))]; + } + } + + OutWindow m_OutWindow = new OutWindow(); + SevenZip.Compression.RangeCoder.Decoder m_RangeDecoder = new SevenZip.Compression.RangeCoder.Decoder(); + + short[] m_IsMatchDecoders = new short[Base.kNumStates << Base.kNumPosStatesBitsMax]; + short[] m_IsRepDecoders = new short[Base.kNumStates]; + short[] m_IsRepG0Decoders = new short[Base.kNumStates]; + short[] m_IsRepG1Decoders = new short[Base.kNumStates]; + short[] m_IsRepG2Decoders = new short[Base.kNumStates]; + short[] m_IsRep0LongDecoders = new short[Base.kNumStates << Base.kNumPosStatesBitsMax]; + + BitTreeDecoder[] m_PosSlotDecoder = new BitTreeDecoder[Base.kNumLenToPosStates]; + short[] m_PosDecoders = new short[Base.kNumFullDistances - Base.kEndPosModelIndex]; + + BitTreeDecoder m_PosAlignDecoder = new BitTreeDecoder(Base.kNumAlignBits); + + LenDecoder m_LenDecoder = new LenDecoder(); + LenDecoder m_RepLenDecoder = new LenDecoder(); + + LiteralDecoder m_LiteralDecoder = new LiteralDecoder(); + + int m_DictionarySize = -1; + int m_DictionarySizeCheck = -1; + + int m_PosStateMask; + + public Decoder() + { + for (int i = 0; i < Base.kNumLenToPosStates; i++) + m_PosSlotDecoder[i] = new BitTreeDecoder(Base.kNumPosSlotBits); + } + + boolean SetDictionarySize(int dictionarySize) + { + if (dictionarySize < 0) + return false; + if (m_DictionarySize != dictionarySize) + { + m_DictionarySize = dictionarySize; + m_DictionarySizeCheck = Math.max(m_DictionarySize, 1); + m_OutWindow.Create(Math.max(m_DictionarySizeCheck, (1 << 12))); + } + return true; + } + + boolean SetLcLpPb(int lc, int lp, int pb) + { + if (lc > Base.kNumLitContextBitsMax || lp > 4 || pb > Base.kNumPosStatesBitsMax) + return false; + m_LiteralDecoder.Create(lp, lc); + int numPosStates = 1 << pb; + m_LenDecoder.Create(numPosStates); + m_RepLenDecoder.Create(numPosStates); + m_PosStateMask = numPosStates - 1; + return true; + } + + void Init() throws IOException + { + m_OutWindow.Init(false); + + SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsMatchDecoders); + SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsRep0LongDecoders); + SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsRepDecoders); + SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsRepG0Decoders); + SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsRepG1Decoders); + SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_IsRepG2Decoders); + SevenZip.Compression.RangeCoder.Decoder.InitBitModels(m_PosDecoders); + + m_LiteralDecoder.Init(); + int i; + for (i = 0; i < Base.kNumLenToPosStates; i++) + m_PosSlotDecoder[i].Init(); + m_LenDecoder.Init(); + m_RepLenDecoder.Init(); + m_PosAlignDecoder.Init(); + m_RangeDecoder.Init(); + } + + public boolean Code(java.io.InputStream inStream, java.io.OutputStream outStream, + long outSize) throws IOException + { + m_RangeDecoder.SetStream(inStream); + m_OutWindow.SetStream(outStream); + Init(); + + int state = Base.StateInit(); + int rep0 = 0, rep1 = 0, rep2 = 0, rep3 = 0; + + long nowPos64 = 0; + byte prevByte = 0; + while (outSize < 0 || nowPos64 < outSize) + { + int posState = (int)nowPos64 & m_PosStateMask; + if (m_RangeDecoder.DecodeBit(m_IsMatchDecoders, (state << Base.kNumPosStatesBitsMax) + posState) == 0) + { + LiteralDecoder.Decoder2 decoder2 = m_LiteralDecoder.GetDecoder((int)nowPos64, prevByte); + if (!Base.StateIsCharState(state)) + prevByte = decoder2.DecodeWithMatchByte(m_RangeDecoder, m_OutWindow.GetByte(rep0)); + else + prevByte = decoder2.DecodeNormal(m_RangeDecoder); + m_OutWindow.PutByte(prevByte); + state = Base.StateUpdateChar(state); + nowPos64++; + } + else + { + int len; + if (m_RangeDecoder.DecodeBit(m_IsRepDecoders, state) == 1) + { + len = 0; + if (m_RangeDecoder.DecodeBit(m_IsRepG0Decoders, state) == 0) + { + if (m_RangeDecoder.DecodeBit(m_IsRep0LongDecoders, (state << Base.kNumPosStatesBitsMax) + posState) == 0) + { + state = Base.StateUpdateShortRep(state); + len = 1; + } + } + else + { + int distance; + if (m_RangeDecoder.DecodeBit(m_IsRepG1Decoders, state) == 0) + distance = rep1; + else + { + if (m_RangeDecoder.DecodeBit(m_IsRepG2Decoders, state) == 0) + distance = rep2; + else + { + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + if (len == 0) + { + len = m_RepLenDecoder.Decode(m_RangeDecoder, posState) + Base.kMatchMinLen; + state = Base.StateUpdateRep(state); + } + } + else + { + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + len = Base.kMatchMinLen + m_LenDecoder.Decode(m_RangeDecoder, posState); + state = Base.StateUpdateMatch(state); + int posSlot = m_PosSlotDecoder[Base.GetLenToPosState(len)].Decode(m_RangeDecoder); + if (posSlot >= Base.kStartPosModelIndex) + { + int numDirectBits = (posSlot >> 1) - 1; + rep0 = ((2 | (posSlot & 1)) << numDirectBits); + if (posSlot < Base.kEndPosModelIndex) + rep0 += BitTreeDecoder.ReverseDecode(m_PosDecoders, + rep0 - posSlot - 1, m_RangeDecoder, numDirectBits); + else + { + rep0 += (m_RangeDecoder.DecodeDirectBits( + numDirectBits - Base.kNumAlignBits) << Base.kNumAlignBits); + rep0 += m_PosAlignDecoder.ReverseDecode(m_RangeDecoder); + if (rep0 < 0) + { + if (rep0 == -1) + break; + return false; + } + } + } + else + rep0 = posSlot; + } + if (rep0 >= nowPos64 || rep0 >= m_DictionarySizeCheck) + { + // m_OutWindow.Flush(); + return false; + } + m_OutWindow.CopyBlock(rep0, len); + nowPos64 += len; + prevByte = m_OutWindow.GetByte(0); + } + } + m_OutWindow.Flush(); + m_OutWindow.ReleaseStream(); + m_RangeDecoder.ReleaseStream(); + return true; + } + + public boolean SetDecoderProperties(byte[] properties) + { + if (properties.length < 5) + return false; + int val = properties[0] & 0xFF; + int lc = val % 9; + int remainder = val / 9; + int lp = remainder % 5; + int pb = remainder / 5; + int dictionarySize = 0; + for (int i = 0; i < 4; i++) + dictionarySize += ((int)(properties[1 + i]) & 0xFF) << (i * 8); + if (!SetLcLpPb(lc, lp, pb)) + return false; + return SetDictionarySize(dictionarySize); + } +} diff --git a/src/main/java/SevenZip/Compression/RangeCoder/BitTreeDecoder.java b/src/main/java/SevenZip/Compression/RangeCoder/BitTreeDecoder.java new file mode 100644 index 0000000..7698581 --- /dev/null +++ b/src/main/java/SevenZip/Compression/RangeCoder/BitTreeDecoder.java @@ -0,0 +1,55 @@ +package SevenZip.Compression.RangeCoder; + +public class BitTreeDecoder +{ + short[] Models; + int NumBitLevels; + + public BitTreeDecoder(int numBitLevels) + { + NumBitLevels = numBitLevels; + Models = new short[1 << numBitLevels]; + } + + public void Init() + { + Decoder.InitBitModels(Models); + } + + public int Decode(Decoder rangeDecoder) throws java.io.IOException + { + int m = 1; + for (int bitIndex = NumBitLevels; bitIndex != 0; bitIndex--) + m = (m << 1) + rangeDecoder.DecodeBit(Models, m); + return m - (1 << NumBitLevels); + } + + public int ReverseDecode(Decoder rangeDecoder) throws java.io.IOException + { + int m = 1; + int symbol = 0; + for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) + { + int bit = rangeDecoder.DecodeBit(Models, m); + m <<= 1; + m += bit; + symbol |= (bit << bitIndex); + } + return symbol; + } + + public static int ReverseDecode(short[] Models, int startIndex, + Decoder rangeDecoder, int NumBitLevels) throws java.io.IOException + { + int m = 1; + int symbol = 0; + for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++) + { + int bit = rangeDecoder.DecodeBit(Models, startIndex + m); + m <<= 1; + m += bit; + symbol |= (bit << bitIndex); + } + return symbol; + } +} diff --git a/src/main/java/SevenZip/Compression/RangeCoder/Decoder.java b/src/main/java/SevenZip/Compression/RangeCoder/Decoder.java new file mode 100644 index 0000000..85b3150 --- /dev/null +++ b/src/main/java/SevenZip/Compression/RangeCoder/Decoder.java @@ -0,0 +1,88 @@ +package SevenZip.Compression.RangeCoder; +import java.io.IOException; + +public class Decoder +{ + static final int kTopMask = ~((1 << 24) - 1); + + static final int kNumBitModelTotalBits = 11; + static final int kBitModelTotal = (1 << kNumBitModelTotalBits); + static final int kNumMoveBits = 5; + + int Range; + int Code; + + java.io.InputStream Stream; + + public final void SetStream(java.io.InputStream stream) + { + Stream = stream; + } + + public final void ReleaseStream() + { + Stream = null; + } + + public final void Init() throws IOException + { + Code = 0; + Range = -1; + for (int i = 0; i < 5; i++) + Code = (Code << 8) | Stream.read(); + } + + public final int DecodeDirectBits(int numTotalBits) throws IOException + { + int result = 0; + for (int i = numTotalBits; i != 0; i--) + { + Range >>>= 1; + int t = ((Code - Range) >>> 31); + Code -= Range & (t - 1); + result = (result << 1) | (1 - t); + + if ((Range & kTopMask) == 0) + { + Code = (Code << 8) | Stream.read(); + Range <<= 8; + } + } + return result; + } + + public int DecodeBit(short []probs, int index) throws IOException + { + int prob = probs[index]; + int newBound = (Range >>> kNumBitModelTotalBits) * prob; + if ((Code ^ 0x80000000) < (newBound ^ 0x80000000)) + { + Range = newBound; + probs[index] = (short)(prob + ((kBitModelTotal - prob) >>> kNumMoveBits)); + if ((Range & kTopMask) == 0) + { + Code = (Code << 8) | Stream.read(); + Range <<= 8; + } + return 0; + } + else + { + Range -= newBound; + Code -= newBound; + probs[index] = (short)(prob - ((prob) >>> kNumMoveBits)); + if ((Range & kTopMask) == 0) + { + Code = (Code << 8) | Stream.read(); + Range <<= 8; + } + return 1; + } + } + + public static void InitBitModels(short []probs) + { + for (int i = 0; i < probs.length; i++) + probs[i] = (kBitModelTotal >>> 1); + } +} diff --git a/src/main/java/cn/wode490390/nukkit/geoip/GeoIP.java b/src/main/java/cn/wode490390/nukkit/geoip/GeoIP.java index 301327c..684cd35 100644 --- a/src/main/java/cn/wode490390/nukkit/geoip/GeoIP.java +++ b/src/main/java/cn/wode490390/nukkit/geoip/GeoIP.java @@ -3,11 +3,15 @@ import cn.nukkit.Player; import cn.nukkit.plugin.PluginBase; import cn.nukkit.utils.Config; +import cn.wode490390.nukkit.geoip.command.GeoIPCommand; +import cn.wode490390.nukkit.geoip.util.LZMALib; +import cn.wode490390.nukkit.geoip.util.MetricsLite; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.collect.Maps; -import com.ice.tar.TarEntry; -import com.ice.tar.TarInputStream; import com.maxmind.geoip2.DatabaseReader; + +import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -19,25 +23,39 @@ import java.util.Arrays; import java.util.Date; import java.util.Map; -import java.util.zip.GZIPInputStream; +import java.util.UUID; public class GeoIP extends PluginBase { - private static final Map cache = Maps.newHashMap(); + private static final Map cache = Maps.newHashMap(); + + /** + * Querys player's geographical location. + * + * @param uuid + * @return geographical location or null + */ + public static String query(UUID uuid) { + Preconditions.checkNotNull(uuid, "UUID cannot be null"); + return cache.get(uuid); + } /** * Querys player's geographical location. * - * @param palyer + * @param player * @return geographical location or null + * + * @see #query(UUID) */ - public static String query(Player palyer) { - Preconditions.checkNotNull(palyer, "Player cannot be null"); - return cache.get(palyer); + @Deprecated + public static String query(Player player) { + Preconditions.checkNotNull(player, "Player cannot be null"); + return query(player.getUniqueId()); } - static void setGeoLocation(Player palyer, String location) { - cache.put(palyer, location); + static void setGeoLocation(UUID uuid, String location) { + cache.put(uuid, location); } Config config; @@ -46,10 +64,11 @@ static void setGeoLocation(Player palyer, String location) { @Override public void onEnable() { try { - new MetricsLite(this); - } catch (Exception ignore) { + new MetricsLite(this, 5375); + } catch (Throwable ignore) { } + this.saveDefaultConfig(); this.config = this.getConfig(); if (this.config.getBoolean("database.show-cities", false)) { @@ -94,11 +113,11 @@ private void downloadDatabase() { try { String url; if (this.config.getBoolean("database.show-cities", false)) { - url = this.config.getString("database.download-url-city"); + url = this.config.getString("database.lzma-download-url-city", "https://cdn.jsdelivr.net/gh/wodeBot/geoipdb@lzma/city.mmdb.lzma"); } else { - url = this.config.getString("database.download-url"); + url = this.config.getString("database.lzma-download-url", "https://cdn.jsdelivr.net/gh/wodeBot/geoipdb@lzma/country.mmdb.lzma"); } - if (url == null || url.isEmpty()) { + if (Strings.isNullOrEmpty(url)) { this.getLogger().warning("GeoIP download url is empty."); return; } @@ -106,33 +125,12 @@ private void downloadDatabase() { URL downloadUrl = new URL(url); URLConnection conn = downloadUrl.openConnection(); conn.setConnectTimeout(10000); + conn.setRequestProperty("User-agent", "Mozilla/5.0 (iPad; CPU OS 13_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Mobile/15E148 Safari/604.1"); conn.connect(); - InputStream input = conn.getInputStream(); + InputStream input = new BufferedInputStream(conn.getInputStream()); OutputStream output = new FileOutputStream(this.databaseFile); - byte[] buffer = new byte[2048]; - if (url.endsWith(".gz")) { - input = new GZIPInputStream(input); - if (url.endsWith(".tar.gz")) { - // The new GeoIP2 uses tar.gz to pack the db file along with some other txt. So it makes things a bit complicated here. - String filename; - TarInputStream tarInputStream = new TarInputStream(input); - TarEntry entry; - while ((entry = tarInputStream.getNextEntry()) != null) { - if (!entry.isDirectory()) { - filename = entry.getName(); - if (filename.substring(filename.length() - 5).equalsIgnoreCase(".mmdb")) { - input = tarInputStream; - break; - } - } - } - } - } - int length = input.read(buffer); - while (length >= 0) { - output.write(buffer, 0, length); - length = input.read(buffer); - } + LZMALib.decode(input, output); + output.flush(); output.close(); input.close(); } catch (MalformedURLException ex) { diff --git a/src/main/java/cn/wode490390/nukkit/geoip/GeoIPListener.java b/src/main/java/cn/wode490390/nukkit/geoip/GeoIPListener.java index f67cd37..e395a7f 100644 --- a/src/main/java/cn/wode490390/nukkit/geoip/GeoIPListener.java +++ b/src/main/java/cn/wode490390/nukkit/geoip/GeoIPListener.java @@ -84,7 +84,7 @@ private void delayedJoin(Player player) throws UnknownHostException { // GeoIP2 API forced this when address not found in their DB. jar will not complied without this. this.plugin.getLogger().warning("Failed to read GeoIP database: " + ex.getLocalizedMessage()); } - GeoIP.setGeoLocation(player, sb.toString()); + GeoIP.setGeoLocation(player.getUniqueId(), sb.toString()); if (this.plugin.config.getBoolean("show-on-login", true)) { String template = TextFormat.colorize("&6Player &c" + player.getDisplayName() + " &6comes from &c" + sb.toString() + "&6. (IP:&c%ip%&6)"); String[] ip = player.getAddress().split("\\."); diff --git a/src/main/java/cn/wode490390/nukkit/geoip/GeoIPCommand.java b/src/main/java/cn/wode490390/nukkit/geoip/command/GeoIPCommand.java similarity index 93% rename from src/main/java/cn/wode490390/nukkit/geoip/GeoIPCommand.java rename to src/main/java/cn/wode490390/nukkit/geoip/command/GeoIPCommand.java index e00faef..2b12409 100644 --- a/src/main/java/cn/wode490390/nukkit/geoip/GeoIPCommand.java +++ b/src/main/java/cn/wode490390/nukkit/geoip/command/GeoIPCommand.java @@ -1,4 +1,4 @@ -package cn.wode490390.nukkit.geoip; +package cn.wode490390.nukkit.geoip.command; import cn.nukkit.Player; import cn.nukkit.Server; @@ -10,6 +10,7 @@ import cn.nukkit.lang.TranslationContainer; import cn.nukkit.plugin.Plugin; import cn.nukkit.utils.TextFormat; +import cn.wode490390.nukkit.geoip.GeoIP; public class GeoIPCommand extends Command implements PluginIdentifiableCommand { @@ -33,7 +34,7 @@ public boolean execute(CommandSender sender, String label, String[] args) { if (args.length > 0) { Player player = Server.getInstance().getPlayer(args[0]); if (player != null) { - String geoLocation = GeoIP.query(player); + String geoLocation = GeoIP.query(player.getUniqueId()); if (geoLocation != null) { String[] ip = player.getAddress().split("\\."); try { diff --git a/src/main/java/cn/wode490390/nukkit/geoip/util/LZMALib.java b/src/main/java/cn/wode490390/nukkit/geoip/util/LZMALib.java new file mode 100644 index 0000000..b39beb0 --- /dev/null +++ b/src/main/java/cn/wode490390/nukkit/geoip/util/LZMALib.java @@ -0,0 +1,37 @@ +package cn.wode490390.nukkit.geoip.util; + +import SevenZip.Compression.LZMA.Decoder; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public final class LZMALib { + + public static void decode(InputStream inputStream, OutputStream outputStream) throws IOException { + int propertiesSize = 5; + byte[] properties = new byte[propertiesSize]; + if (inputStream.read(properties, 0, propertiesSize) != propertiesSize) { + throw new IOException("input .lzma file is too short"); + } + Decoder decoder = new Decoder(); + if (!decoder.SetDecoderProperties(properties)) { + throw new IOException("Incorrect stream properties"); + } + long outSize = 0; + for (int i = 0; i < 8; i++) { + int v = inputStream.read(); + if (v < 0) { + throw new IOException("Can't read stream size"); + } + outSize |= ((long) v) << (8 * i); + } + if (!decoder.Code(inputStream, outputStream, outSize)) { + throw new IOException("Error in data stream"); + } + } + + private LZMALib() { + + } +} diff --git a/src/main/java/cn/wode490390/nukkit/geoip/MetricsLite.java b/src/main/java/cn/wode490390/nukkit/geoip/util/MetricsLite.java similarity index 95% rename from src/main/java/cn/wode490390/nukkit/geoip/MetricsLite.java rename to src/main/java/cn/wode490390/nukkit/geoip/util/MetricsLite.java index 56eba67..02ac339 100644 --- a/src/main/java/cn/wode490390/nukkit/geoip/MetricsLite.java +++ b/src/main/java/cn/wode490390/nukkit/geoip/util/MetricsLite.java @@ -1,4 +1,4 @@ -package cn.wode490390.nukkit.geoip; +package cn.wode490390.nukkit.geoip.util; import cn.nukkit.Server; import cn.nukkit.plugin.Plugin; @@ -17,7 +17,6 @@ import java.io.DataOutputStream; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -60,7 +59,7 @@ public class MetricsLite { private static final String URL = "https://bStats.org/submitData/bukkit"; // Is bStats enabled on this server? - private boolean enabled; + private final boolean enabled; // Should failed requests be logged? private static boolean logFailedRequests; @@ -77,14 +76,20 @@ public class MetricsLite { // The plugin private final Plugin plugin; + // The plugin id + private final int pluginId; + /** * Class constructor. * * @param plugin The plugin which stats should be submitted. + * @param pluginId The id of the plugin. + * It can be found at What is my plugin id? */ - public MetricsLite(Plugin plugin) { + public MetricsLite(Plugin plugin, int pluginId) { Preconditions.checkNotNull(plugin); this.plugin = plugin; + this.pluginId = pluginId; // Get the config file File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); @@ -193,6 +198,7 @@ public JsonObject getPluginData() { String pluginVersion = plugin.getDescription().getVersion(); data.addProperty("pluginName", pluginName); // Append the name of the plugin + data.addProperty("id", pluginId); // Append the id of the plugin data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin JsonArray customCharts = new JsonArray(); @@ -258,7 +264,7 @@ private void submitData() { Field handle = NKServiceManager.class.getDeclaredField("handle"); field.setInt(handle, handle.getModifiers() & ~Modifier.FINAL); handle.setAccessible(true); - providers = ((Map, List>>) handle.get((NKServiceManager) (Server.getInstance().getServiceManager()))).get(service); + providers = ((Map, List>>) handle.get(Server.getInstance().getServiceManager())).get(service); } catch(IllegalAccessException | IllegalArgumentException | SecurityException e) { // Something went wrong! :( if (logFailedRequests) { @@ -308,7 +314,7 @@ private static void sendData(Plugin plugin, JsonObject data) throws Exception { throw new IllegalAccessException("This method must not be called from the main thread!"); } if (logSentData) { - plugin.getLogger().info("Sending data to bStats: " + data.toString()); + plugin.getLogger().info("Sending data to bStats: " + data); } HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); @@ -328,20 +334,18 @@ private static void sendData(Plugin plugin, JsonObject data) throws Exception { connection.setDoOutput(true); try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { outputStream.write(compressedData); - outputStream.flush(); } - InputStream inputStream = connection.getInputStream(); - StringBuilder builder; - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { - builder = new StringBuilder(); + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { String line; while ((line = bufferedReader.readLine()) != null) { builder.append(line); } } + if (logResponseStatusText) { - plugin.getLogger().info("Sent data to bStats and received response: " + builder.toString()); + plugin.getLogger().info("Sent data to bStats and received response: " + builder); } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e08551f..caed4cc 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -2,12 +2,9 @@ database: show-cities: false download-if-missing: true # Url for country - download-url: "https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz" + lzma-download-url: "https://cdn.jsdelivr.net/gh/wodeBot/geoipdb@lzma/country.mmdb.lzma" # Url for cities - download-url-city: "https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz" - update: - enable: true - by-every-x-days: 30 + lzma-download-url-city: "https://cdn.jsdelivr.net/gh/wodeBot/geoipdb@lzma/city.mmdb.lzma" show-on-login: true # "enable-locale" enables locale on geolocation display. enable-locale: true diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index eb94f84..b018e57 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -6,6 +6,7 @@ website: "http://wode490390.cn/" version: "${pom.version}" api: ["1.0.0"] load: POSTWORLD + permissions: geoip.hide: description: "Allows player to hide player's country and city from people who have permission geoip.show"