Commit 51ebdcfb authored by fengruiyu's avatar fengruiyu

Merge branch 'master' of http://gitlab.mints-id.com/android/android_goodnews_best

 Conflicts:
	.idea/vcs.xml
	library_base/version.properties
parents 679fb355 d224d0b3
GoodNews
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
</project>
\ No newline at end of file
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>
......
...@@ -31,5 +31,10 @@ ...@@ -31,5 +31,10 @@
<option name="name" value="maven3" /> <option name="name" value="maven3" />
<option name="url" value="https://oss.jfrog.org/libs-snapshot" /> <option name="url" value="https://oss.jfrog.org/libs-snapshot" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
</component> </component>
</project> </project>
\ No newline at end of file
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" /> <configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations> </configurations>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">
......
/build /build
\ No newline at end of file mapping.txt
seeds.txt
unused.txt
\ No newline at end of file
...@@ -10,16 +10,8 @@ android { ...@@ -10,16 +10,8 @@ android {
targetSdkVersion rootProject.ext.android.targetSdkVersion targetSdkVersion rootProject.ext.android.targetSdkVersion
versionCode rootProject.ext.android.versionCode versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName versionName rootProject.ext.android.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
dataBinding { dataBinding {
enabled true enabled true
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
apply from: "config.gradle" apply from: "config.gradle"
buildscript { buildscript {
ext.kotlin_version = '1.3.61' ext.kotlin_version = '1.3.72'
repositories { repositories {
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
...@@ -10,7 +10,7 @@ buildscript { ...@@ -10,7 +10,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.0' classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
......
...@@ -8,10 +8,10 @@ ext { ...@@ -8,10 +8,10 @@ ext {
//android开发版本配置 //android开发版本配置
android = [ android = [
compileSdkVersion: 30, compileSdkVersion: 30,
buildToolsVersion: "29.0.2", buildToolsVersion: "30.0.2",
applicationId : "com.mints.goodnews", applicationId : "com.mints.goodnews",
minSdkVersion : 21, minSdkVersion : 21,
targetSdkVersion : 29, targetSdkVersion : 30,
versionCode : 1, versionCode : 1,
versionName : "1.0.0", versionName : "1.0.0",
] ]
......
...@@ -19,3 +19,21 @@ android.useAndroidX=true ...@@ -19,3 +19,21 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
isBuildModule=false isBuildModule=false
DEBUG_URL="http://test.mints-id.com/camera-api/"
RELEASE_URL="https://api.mints-id.com/gc-api/"
RELEASE_KEY_PASSWORD=mints.goodnews
RELEASE_KEY_ALIAS=mints_goodnews
RELEASE_STORE_PASSWORD=mints.goodnews
RELEASE_STORE_FILE=mints_goodnews.jks
#ShareSDK
RELEASE_SHARESDK_KEY=
RELEASE_SHARESDK_SECRET=
#友盟
RELEASE_UMENG_KEY=
#JPush
RELEASE_JPUSH_KEY=
\ No newline at end of file
...@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME ...@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
...@@ -16,6 +16,8 @@ android { ...@@ -16,6 +16,8 @@ android {
flavorDimensions '23' flavorDimensions '23'
multiDexEnabled true multiDexEnabled true
buildConfigField ("boolean","IS_DEV","false") buildConfigField ("boolean","IS_DEV","false")
buildConfigField ("String","AppKeyPre","abcd")
} }
defaultConfig { defaultConfig {
......
...@@ -22,6 +22,7 @@ android { ...@@ -22,6 +22,7 @@ android {
defaultConfig { defaultConfig {
buildConfigField ("int","BUILD_VERSION","${buildVersion}") buildConfigField ("int","BUILD_VERSION","${buildVersion}")
} }
// noArg { // noArg {
// annotation("com.jeme.base.annotation.Poko") // annotation("com.jeme.base.annotation.Poko")
// } // }
......
...@@ -6,6 +6,8 @@ import android.text.TextUtils; ...@@ -6,6 +6,8 @@ import android.text.TextUtils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.fry.base.BuildConfig; import com.fry.base.BuildConfig;
import com.fry.base.netwrok.OkHttpInterceptor;
import com.fry.base.utils.encry.AESUtils;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
...@@ -49,7 +51,7 @@ public class RetrofitClient { ...@@ -49,7 +51,7 @@ public class RetrofitClient {
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
.cookieJar(new CookieJarImpl(new PersistentCookieStore(mContext))) .cookieJar(new CookieJarImpl(new PersistentCookieStore(mContext)))
// .cache(cache) // .cache(cache)
.addInterceptor(new BaseInterceptor(mHttpConfig.getHeads())) .addInterceptor(new OkHttpInterceptor(AESUtils.getDefaultKey()))
// .addInterceptor(new CacheInterceptor(mContext)) // .addInterceptor(new CacheInterceptor(mContext))
.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager) .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
.connectTimeout(mHttpConfig.getTimeOut(), TimeUnit.SECONDS) .connectTimeout(mHttpConfig.getTimeOut(), TimeUnit.SECONDS)
......
package com.fry.base.bean
import android.annotation.SuppressLint
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
/**
* 最外层请求数据
*
* {
* "data":"SEebPNaIQqWTBAap3xfl21GzkIIVn/7W9S0Fwws6Mhl4mPb8ZjN+xEvQr5cy90px+ lUMt",
* "key":"ckPc1k8d07O5UbV5csWOT1jUsO7rXHvEgAoAVKZYP/8Kabh7HIdHL1wdZ0JHDc OKeNzoDMNo5LunUIW0hc1rLPSWtJblZSuzzN2+/2VJnj1=="
* }
*/
@SuppressLint("ParcelCreator")
@Parcelize
data class AppRequest(
var channel: String? = "",
var sign: String? = "",
// 新增1.1.9
var check: String? = "",
var data: String? = ""
) : Parcelable
package com.fry.base.netwrok
import android.text.TextUtils
import android.util.Log
import com.fry.base.bean.AppRequest
import com.fry.base.utils.encry.AESUtils
import com.fry.base.utils.encry.Base64
import com.fry.base.utils.encry.MD5
import com.google.gson.Gson
import okhttp3.*
import okio.Buffer
import org.json.JSONObject
import java.io.IOException
import java.io.UnsupportedEncodingException
import java.nio.charset.Charset
/**
* 拦截器
*/
class OkHttpInterceptor(aesKey: String) : Interceptor {
private var aesKey = ""
/**
* 实例化拦截器对象
*/
init {
this.aesKey = aesKey
}
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
// val tokenID: String = UserManager.INSTANCE.getTokenID()
val tokenID: String = ""
val time = System.currentTimeMillis()
val channel = createChannel()
var request = chain.request()
// 加密
request = encrypt(request, tokenID, time, channel)
// header
val builder = addHeader(request, tokenID, time, channel)
// 解密
var response = chain.proceed(builder)
response = decrypt(response)
return response
}
/**
* 创建渠道
*
* @return
*/
private fun createChannel(): String {
var channel: String = ""
// var channel: String = CommonUtils.getAppMetaData(App.instance, "CHANNEL_NAME")
// try {
// // 穿山甲分包渠道
// val ttChannel = HumeSDK.getChannel(App.instance)
// if (!TextUtils.isEmpty(ttChannel)) {
// // 此处方式不能修改,已和后端约定
// channel = channel + "_" + ttChannel
// }
// } catch (e: Exception) {
// e.printStackTrace()
// }
return channel
}
/**
* 添加header
*
* @param request
* @param tokenID
* @param time
* @param channel
* @return
*/
@Throws(UnsupportedEncodingException::class)
private fun addHeader(request: Request, tokenID: String, time: Long, channel: String): Request {
return request.newBuilder()
.addHeader("version", BuildConfig.VERSION_NAME)
.addHeader("token", tokenID)
.addHeader("channel", channel)
.addHeader("new-session", MD5.GetMD5Code(time.toString()))
.addHeader("last-session", Base64.encode(time.toString().toByteArray(charset("UTF-8"))))
.build()
}
/**
* json加密
*
* @param request
* @param tokenID
* @param time
* @param channel
* @return
* @throws IOException
*/
@Throws(IOException::class)
private fun encrypt(request: Request, tokenID: String, time: Long, channel: String): Request {
//获取请求body,只有@Body 参数的requestBody 才不会为 null
var request = request
val requestBody = request.body
if (requestBody != null) {
val buffer = Buffer()
requestBody.writeTo(buffer)
var charset = Charset.forName("UTF-8")
val contentType = requestBody.contentType()
if (contentType != null) {
charset = contentType.charset(charset)
}
// 原始报文
var valueStr = buffer.readString(charset!!)
val checkStr = valueStr
//加密
if (isSign(request) && !TextUtils.isEmpty(valueStr)) {
valueStr = AESUtils.encrypt(valueStr, aesKey)
}
// 渠道
val channelName = "android_$channel"
// sign
val requestSign = "$tokenID:$channelName:$time"
val sign = MD5.GetMD5Code(requestSign)
// 验证
val check = MD5.GetMD5Code("$requestSign:$checkStr")
val appRequest = AppRequest(channelName, sign, check, valueStr)
val body: RequestBody = RequestBody.create(contentType, Gson().toJson(appRequest))
request = request.newBuilder()
.post(body)
.build()
}
return request
}
/**
* json解密
*
* @param response
* @return
* @throws IOException
*/
@Throws(IOException::class)
private fun decrypt(response: Response): Response {
var response = response
if (response.isSuccessful && isSign(response.request)) {
//the response data
val body = response.body
val source = body!!.source()
source.request(Long.MAX_VALUE) // Buffer the entire body.
val buffer = source.buffer()
var charset = Charset.defaultCharset()
val contentType = body.contentType()
if (contentType != null) {
charset = contentType.charset(charset)
}
var rspString: String? = buffer.clone().readString(charset!!)
// 解密
try {
val json = JSONObject(rspString)
if (!json.isNull("data")) {
val data = json["data"]
rspString = AESUtils.detrypt(data.toString(), aesKey)
val jsonArray = JSONObject(rspString)
json.put("data", jsonArray)
rspString = json.toString()
// rspString = json.toString().replace("\\", "");
}
} catch (e: Exception) {
Log.d("OkHttpInterceptor", "json解密失败。url:${response.request.url}响应报文:${buffer.clone().readString(charset)}")
e.printStackTrace()
}
val responseBody = ResponseBody.create(contentType, rspString!!)
response = response.newBuilder().body(responseBody).build()
}
return response
}
/**
* 是否加/解密 - 接口非common都加/解密
*
* @param request
* @return true=加/解密
*/
private fun isSign(request: Request): Boolean {
val url = request.url.toString()
//是否加密标识
var isSign = true
if (!TextUtils.isEmpty(url)) {
isSign = !url.contains("common/")
}
return isSign
}
}
\ No newline at end of file
package com.fry.base.utils.encry;
import android.util.Log;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import Decoder.BASE64Decoder;
import Decoder.BASE64Encoder;
public class AESUtils {
private static final String vis = MD5.GetMD5Code("goldcamera_2021").substring(8, 24);
// public static final String key = "123456";
public static String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; // optional value AES/DES/DESede
private static SecretKeySpec getKey(String strKey) throws Exception {
byte[] arrBTmp = strKey.getBytes();
byte[] arrB = new byte[16]; // 创建一个空的16位字节数组(默认值为0)
for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) {
arrB[i] = arrBTmp[i];
}
SecretKeySpec skeySpec = new SecretKeySpec(arrB, "AES");
return skeySpec;
}
public static String encrypt(String message, String key) {
try {
SecretKeySpec skeySpec = getKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(vis.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(message.getBytes("UTF-8"));
return new BASE64Encoder().encode(encrypted);
} catch (Exception e) {
}
return "";
}
public static String detrypt(String message, String key) {
try {
byte[] res = new BASE64Decoder().decodeBuffer(message);
SecretKeySpec skeySpec = getKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(vis.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] detrypted = cipher.doFinal(res);
return new String(detrypted, "UTF-8");
} catch (Exception e) {
}
return "";
}
private static String getBK1() {
return BuildConfig.AppKeyPre;
}
private static String getBK2() {
return "nnnnnn";
}
private static String getBK3(int size) {
StringBuffer sb = new StringBuffer();
for (int i = 1; i <= size; i++) {
sb.append(i);
}
return sb.toString();
}
public static String getDefaultKey() {
StringBuffer sb = new StringBuffer();
sb.append(getBK1())
.append(getBK2())
.append(getBK3(6));
return sb.toString();
}
}
package com.fry.base.utils.encry;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Base64编码工具类
*
* @author
* @date 2012-10-11
*/
public class Base64 {
private static final char[] legalChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
public static String encode(byte[] data) {
int start = 0;
int len = data.length;
StringBuffer buf = new StringBuffer(data.length * 3 / 2);
int end = len - 3;
int i = start;
int n = 0;
while (i <= end) {
int d = ((((int) data[i]) & 0x0ff) << 16) | ((((int) data[i + 1]) & 0x0ff) << 8) | (((int) data[i + 2]) & 0x0ff);
buf.append(legalChars[(d >> 18) & 63]);
buf.append(legalChars[(d >> 12) & 63]);
buf.append(legalChars[(d >> 6) & 63]);
buf.append(legalChars[d & 63]);
i += 3;
if (n++ >= 14) {
n = 0;
buf.append(" ");
}
}
if (i == start + len - 2) {
int d = ((((int) data[i]) & 0x0ff) << 16) | ((((int) data[i + 1]) & 255) << 8);
buf.append(legalChars[(d >> 18) & 63]);
buf.append(legalChars[(d >> 12) & 63]);
buf.append(legalChars[(d >> 6) & 63]);
buf.append("=");
} else if (i == start + len - 1) {
int d = (((int) data[i]) & 0x0ff) << 16;
buf.append(legalChars[(d >> 18) & 63]);
buf.append(legalChars[(d >> 12) & 63]);
buf.append("==");
}
return buf.toString();
}
private static int decode(char c) {
if (c >= 'A' && c <= 'Z')
return ((int) c) - 65;
else if (c >= 'a' && c <= 'z')
return ((int) c) - 97 + 26;
else if (c >= '0' && c <= '9')
return ((int) c) - 48 + 26 + 26;
else
switch (c) {
case '+':
return 62;
case '/':
return 63;
case '=':
return 0;
default:
throw new RuntimeException("unexpected code: " + c);
}
}
/**
* Decodes the given Base64 encoded String to a new byte array. The byte array holding the decoded data is returned.
*/
public static byte[] decode(String s) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
decode(s, bos);
} catch (IOException e) {
throw new RuntimeException();
}
byte[] decodedBytes = bos.toByteArray();
try {
bos.close();
bos = null;
} catch (IOException ex) {
System.err.println("Error while decoding BASE64: " + ex.toString());
}
return decodedBytes;
}
private static void decode(String s, OutputStream os) throws IOException {
int i = 0;
int len = s.length();
while (true) {
while (i < len && s.charAt(i) <= ' ')
i++;
if (i == len)
break;
int tri = (decode(s.charAt(i)) << 18) + (decode(s.charAt(i + 1)) << 12) + (decode(s.charAt(i + 2)) << 6) + (decode(s.charAt(i + 3)));
os.write((tri >> 16) & 255);
if (s.charAt(i + 2) == '=')
break;
os.write((tri >> 8) & 255);
if (s.charAt(i + 3) == '=')
break;
os.write(tri & 255);
i += 4;
}
}
}
package com.fry.base.utils.encry;
import android.util.Log;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
/**
* 描述:3DES加密工具类
* 作者:孟崔广
* 时间:2018/3/29 17:51
* 邮箱:mengcga@163.com
*/
public class Des3 {
// crediteasec@lx100$#365#$
// 密钥
// private final static String secretKey = "mail.asiainfo.com@2x222$#bbb#2";
// 向量
private final static String iv = "01234567";
// 加解密统一使用的编码方式
private final static String encoding = "utf-8";
private static String TAG = "Des3";
/**
* 3DES加密
*
* @param plainText 普通文本
* @return
* @throws Exception
*/
public static String encode(String plainText, String secretKey) {
String ciphertext = "";
if (plainText != null && plainText.length() != 0) {
Key deskey = null;
byte[] encryptData = null;
try {
byte[] bytes = secretKey.getBytes();
DESedeKeySpec spec = new DESedeKeySpec(bytes);
SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede");
deskey = keyfactory.generateSecret(spec);
Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding");
IvParameterSpec ips = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, deskey, ips);
encryptData = cipher.doFinal(plainText.getBytes(encoding));
ciphertext = Base64.encode(encryptData);
//这里需要对特殊字符串进行转码
ciphertext = URLEncoder.encode(ciphertext, "utf-8");
} catch (Exception e) {
Log.e(TAG, e.getMessage().toString());
e.printStackTrace();
}
}
return ciphertext;
}
/**
* 3DES解密
*
* @param encryptText 加密文本
* @return
* @throws Exception
*/
public static String decode(String encryptText, String secretKey) {
String clearText = "";
if (encryptText != null && encryptText.length() != 0) {
Key deskey = null;
byte[] decryptData;
try {
//这里需要对特殊字符串进行转码
encryptText = URLDecoder.decode(
encryptText, "utf-8");
DESedeKeySpec spec = new DESedeKeySpec(secretKey.getBytes());
SecretKeyFactory keyfactory = SecretKeyFactory
.getInstance("desede");
deskey = keyfactory.generateSecret(spec);
Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding");
IvParameterSpec ips = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, deskey, ips);
decryptData = cipher.doFinal(Base64.decode(encryptText));
clearText = new String(decryptData, encoding);
} catch (Exception e) {
e.printStackTrace();
}
}
return clearText;
}
}
package com.fry.base.utils.encry;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/*
* MD5 算法
*/
public class MD5 {
// 全局数组
private final static String[] strDigits = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
// 返回形式为数字跟字符串
private static String byteToArrayString(byte bByte) {
int iRet = bByte;
// System.out.println("iRet="+iRet);
if (iRet < 0) {
iRet += 256;
}
int iD1 = iRet / 16;
int iD2 = iRet % 16;
return strDigits[iD1] + strDigits[iD2];
}
// 返回形式只为数字
private static String byteToNum(byte bByte) {
int iRet = bByte;
System.out.println("iRet1=" + iRet);
if (iRet < 0) {
iRet += 256;
}
return String.valueOf(iRet);
}
// 转换字节数组为16进制字串
private static String byteToString(byte[] bByte) {
StringBuffer sBuffer = new StringBuffer();
for (int i = 0; i < bByte.length; i++) {
sBuffer.append(byteToArrayString(bByte[i]));
}
return sBuffer.toString();
}
public static String GetMD5Code(String strObj) {
String resultString = null;
try {
resultString = new String(strObj);
MessageDigest md = MessageDigest.getInstance("MD5");
// md.digest() 该函数返回值为存放哈希值结果的byte数组
resultString = byteToString(md.digest(strObj.getBytes()));
} catch (NoSuchAlgorithmException ex) {
ex.printStackTrace();
}
return resultString;
}
public static void main(String[] args) {
MD5 getMD5 = new MD5();
System.out.println(GetMD5Code("136111111111231231231231e807f1fcf82d132f9bb018ca6738a19f"));
}
}
\ No newline at end of file
#Wed Jun 30 16:52:56 CST 2021 #Wed Jun 30 14:54:57 CST 2021
VERSION_BUILD=2219 VERSION_BUILD=2207
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment