diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 9761d60..4cd55cf 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -15,17 +15,17 @@ if (IS_SO_BUILD) # and CMake builds them for you. When you build your app, Gradle # automatically packages shared libraries with your APK. add_library( # Specifies the name of the library. - native-lib + native # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). - src/main/cpp/native-lib.c) + src/main/cpp/native.c) # 设置编译输出路径 set_target_properties( - native-lib + native PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src/main/cpp/${ANDROID_ABI} @@ -36,18 +36,18 @@ else () SHARED src/main/cpp/nothing.c) - add_library(native-lib + add_library(native SHARED IMPORTED) set_target_properties( # Specifies the target library. - native-lib + native # Specifies the parameter you want to define. PROPERTIES IMPORTED_LOCATION # Provides the path to the library you want to import. - ${CMAKE_SOURCE_DIR}/src/main/cpp/${ANDROID_ABI}/libnative-lib.so) + ${CMAKE_SOURCE_DIR}/src/main/cpp/${ANDROID_ABI}/libnative.so) endif () add_library(libssl @@ -87,7 +87,7 @@ find_library( # Defines the name of the path variable that stores the if (IS_SO_BUILD) # Links your native library against one or more other native libraries. target_link_libraries( # Specifies the target library. - native-lib + native libssl libcrypto ${log-lib}) @@ -96,6 +96,6 @@ else () nothing libssl libcrypto - native-lib + native ${log-lib}) endif () \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 77f8e1d..6d2470e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -104,7 +104,7 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2" implementation 'androidx.core:core-ktx:1.11.0-beta02' - implementation 'androidx.leanback:leanback:1.0.0' + implementation 'androidx.leanback:leanback:1.2.0-alpha02' implementation 'com.github.bumptech.glide:glide:4.11.0' implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2" implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 511d935..379173e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ android:supportsRtl="true" android:theme="@style/Theme.MyTV"> +#include +#include +#include +#include +#include +#include + +#include + +#define APP_SIGNATURE "f0005d8a185c47e72946053a0af5c0f5620605dd9e301ef0937e477f607ab0d7" +#define APP_SIGNATURE2 "9e58fc98c3967ae80eb25541241895368a900fa488e015b08614575c18fab95e" + +#define TAG "native" +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) + +static bool signatureVerified = false; + +static const char key[33] = "48e5918a74ae21c972b90cce8af6c8be"; +static const char iv[33] = "9a7e7d23610266b1d9fbf98581384d92"; +static const char appendString[15] = "0f$IVHi9Qno?G"; + +static void toUpperCase(char *str) { + while (*str) { + *str = toupper((unsigned char) *str); + str++; + } +} + +static char *binaryToHex(const unsigned char *data, size_t dataSize) { + char *hexString = (char *) malloc(dataSize * 2 + 1); // 每个字节需要两个十六进制字符,再加上字符串结束符 + if (hexString == NULL) { + // 内存分配失败 + return NULL; + } + + for (size_t i = 0; i < dataSize; ++i) { + sprintf(hexString + i * 2, "%02X", data[i]); + } + + hexString[dataSize * 2] = '\0'; // 添加字符串结束符 + + return hexString; +} + +static unsigned char *hexStringToBinary(const char *hexString1) { + size_t inputLength = strlen(hexString1); + if (inputLength % 2 != 0) { + return NULL; + } + + size_t length = inputLength / 2; + unsigned char *result1 = (unsigned char *) malloc(length); + + for (size_t i = 0; i < length; i++) { + char byteString[3] = {hexString1[2 * i], hexString1[2 * i + 1], '\0'}; + result1[i] = (unsigned char) strtoul(byteString, NULL, 16); + } + + return result1; +} + +static char *aes(const char *plaintext) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NULL; + } + +// const char k[33] = "48e5918a74ae21c972b90cce8af6c8be"; + unsigned char *resultKey = hexStringToBinary(key); + +// const char i[33] = "9a7e7d23610266b1d9fbf98581384d92"; + unsigned char *resultIv = hexStringToBinary(iv); + + if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, resultKey, resultIv) != 1) { + free(resultKey); + free(resultIv); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + EVP_CIPHER_CTX_set_padding(ctx, 1); + + size_t dataLength = strlen(plaintext); + + size_t maxEncryptedLength = dataLength + EVP_CIPHER_block_size(EVP_aes_128_cbc()); + unsigned char *encryptedData = (unsigned char *) malloc(maxEncryptedLength); + if (encryptedData == NULL) { + free(resultKey); + free(resultIv); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + int len; + if (EVP_EncryptUpdate(ctx, encryptedData, &len, (const unsigned char *) plaintext, + (int) dataLength) != + 1) { + free(resultKey); + free(resultIv); + free(encryptedData); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + size_t encryptedLength = len; + + if (EVP_EncryptFinal_ex(ctx, encryptedData + len, &len) != 1) { + free(resultKey); + free(resultIv); + free(encryptedData); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + // 更新加密数据长度 + encryptedLength += len; + + // 创建一个 C 字符串以容纳加密后的数据 + char *encryptedString = binaryToHex(encryptedData, encryptedLength); + if (encryptedString == NULL) { + // 内存分配失败 + free(resultKey); + free(resultIv); + free(encryptedData); + EVP_CIPHER_CTX_free(ctx); + return NULL; + } + + // 释放内存和资源 + free(resultKey); + free(resultIv); + free(encryptedData); + EVP_CIPHER_CTX_free(ctx); + + return encryptedString; +} + +jbyte *appendStringToEnd(jbyte *data, size_t *dataLength) { + // 获取原始数据长度 + size_t originalLength = *dataLength; + + size_t appendStringLength = strlen(appendString); + + // 分配新的内存空间,包括原始数据和附加字符串 + data = (jbyte *) realloc(data, + originalLength + appendStringLength + 1); + + if (data == NULL) { + // 内存分配失败的处理 + return NULL; + } + + // 将附加字符串复制到新的内存空间的末尾 + memcpy(data + originalLength, appendString, appendStringLength); + + // 在新数据的末尾添加 null 终止字符 + data[originalLength + appendStringLength] = '\0'; + + // 更新数据长度 + *dataLength = originalLength + appendStringLength + 1; + + return data; +} + +JNIEXPORT jbyteArray + +JNICALL +Java_com_lizongying_mytv_Encryptor_hash(JNIEnv *env, jobject thiz, jbyteArray data_) { + if (signatureVerified == false) { + return NULL; + } + + jbyte *data = (*env)->GetByteArrayElements(env, data_, NULL); + + // 获取数据长度 + size_t dataLength = (*env)->GetArrayLength(env, data_); + +// LOGI("appendStringLength %s", data); + data = appendStringToEnd(data, &dataLength); + + dataLength = strlen((const char *) data); +// LOGI("dataLength %d", dataLength); + + // 分配内存以存储 MD5 值(16 字节) + unsigned char md5Digest[EVP_MAX_MD_SIZE]; + unsigned int md5DigestLength; + + // 创建 MD5 上下文 + EVP_MD_CTX *md5Context = EVP_MD_CTX_new(); + if (md5Context == NULL) { + // 处理错误 + (*env)->ReleaseByteArrayElements(env, data_, data, 0); + return NULL; + } + + // 初始化 MD5 上下文 + if (EVP_DigestInit_ex(md5Context, EVP_md5(), NULL) != 1) { + // 处理错误 + EVP_MD_CTX_free(md5Context); + (*env)->ReleaseByteArrayElements(env, data_, data, 0); + return NULL; + } + + // 更新 MD5 上下文 + if (EVP_DigestUpdate(md5Context, data, dataLength) != 1) { + // 处理错误 + EVP_MD_CTX_free(md5Context); + (*env)->ReleaseByteArrayElements(env, data_, data, 0); + return NULL; + } + + // 获取 MD5 值 + if (EVP_DigestFinal_ex(md5Context, md5Digest, &md5DigestLength) != 1) { + // 处理错误 + EVP_MD_CTX_free(md5Context); + (*env)->ReleaseByteArrayElements(env, data_, data, 0); + return NULL; + } + + jsize md5Length = (jsize) md5DigestLength; + + // 创建 Java 字节数组以容纳 MD5 值 + jbyteArray md5Array = (*env)->NewByteArray(env, md5Length); + if (md5Array == NULL) { + // 处理错误 + EVP_MD_CTX_free(md5Context); + (*env)->ReleaseByteArrayElements(env, data_, data, 0); + return NULL; + } + + // 将 MD5 值复制到 Java 字节数组 + (*env)->SetByteArrayRegion(env, md5Array, 0, md5Length, (jbyte *) md5Digest); + + // 释放内存和资源 + EVP_MD_CTX_free(md5Context); + (*env)->ReleaseByteArrayElements(env, data_, data, 0); + return md5Array; +} + +JNIEXPORT void JNICALL +Java_com_lizongying_mytv_Encryptor_init(JNIEnv *env, jobject thiz, jobject context) { + // 获取 Context 类 + jclass contextClass = (*env)->GetObjectClass(env, context); + + // 获取 getAppSignature 方法的 ID + jmethodID methodId = (*env)->GetMethodID(env, contextClass, "getAppSignature", + "()Ljava/lang/String;"); + + // 调用 Kotlin 函数 + jstring result = (jstring) (*env)->CallObjectMethod(env, context, methodId); + + // 检查 result 是否为 NULL + if (result == NULL) { + return; + } + + // 在 C 端处理字符串 + const char *utfChars = (*env)->GetStringUTFChars(env, result, NULL); + + // 比较签名哈希 + signatureVerified = (strcmp(utfChars, APP_SIGNATURE) == 0); + + if (signatureVerified == 0 ) { + signatureVerified = (strcmp(utfChars, APP_SIGNATURE2) == 0); + } + + // 释放字符串 + (*env)->ReleaseStringUTFChars(env, result, utfChars); +} + +JNIEXPORT jstring JNICALL +Java_com_lizongying_mytv_Encryptor_encrypt(JNIEnv *env, jobject thiz, jstring t, jstring e, + jstring r, jstring n, jstring i) { + if (signatureVerified == false) { + return NULL; + } + + const char *t_str = (*env)->GetStringUTFChars(env, t, NULL); + const char *e_str = (*env)->GetStringUTFChars(env, e, NULL); + const char *r_str = (*env)->GetStringUTFChars(env, r, NULL); + const char *n_str = (*env)->GetStringUTFChars(env, n, NULL); + const char *i_str = (*env)->GetStringUTFChars(env, i, NULL); + + char El[256]; + snprintf(El, sizeof(El), + "|%s|%s|%s|%s|%s|%s|https://www.yangshipin.c|mozilla/5.0 (macintosh; ||Mozilla|Netscape|MacIntel|", + t_str, e_str, "mg3c3b04ba", r_str, n_str, i_str); +// LOGI("EL %s", El); + + unsigned int xl = 0; + for (int Ll = 0; Ll < strlen(El); Ll++) { + xl = (xl << 5) - xl + (unsigned char) El[Ll]; + xl &= xl; + } + + char plaintext[256]; + snprintf(plaintext, sizeof(plaintext), "|%d%s", xl, El); + + // Encrypt the plaintext + char *encryptedResult = aes(plaintext); + toUpperCase(encryptedResult); + + char result[512]; + snprintf(result, sizeof(result), "--01%s", encryptedResult); + +// LOGI("result %s", result); + + // Free the memory allocated in the encrypt function + free(encryptedResult); + + (*env)->ReleaseStringUTFChars(env, t, t_str); + (*env)->ReleaseStringUTFChars(env, e, e_str); + (*env)->ReleaseStringUTFChars(env, r, r_str); + (*env)->ReleaseStringUTFChars(env, n, n_str); + (*env)->ReleaseStringUTFChars(env, i, i_str); + + // Create a Java string from the result and return it + jstring resultString = (*env)->NewStringUTF(env, result); + + return resultString; +} \ No newline at end of file diff --git a/app/src/main/java/com/lizongying/mytv/Encryptor.kt b/app/src/main/java/com/lizongying/mytv/Encryptor.kt index 04c12bc..525a651 100644 --- a/app/src/main/java/com/lizongying/mytv/Encryptor.kt +++ b/app/src/main/java/com/lizongying/mytv/Encryptor.kt @@ -11,7 +11,7 @@ class Encryptor { companion object { init { - System.loadLibrary("native-lib") + System.loadLibrary("native") } } } \ No newline at end of file diff --git a/app/src/main/java/com/lizongying/mytv/MainActivity.kt b/app/src/main/java/com/lizongying/mytv/MainActivity.kt index 9db5880..928e286 100644 --- a/app/src/main/java/com/lizongying/mytv/MainActivity.kt +++ b/app/src/main/java/com/lizongying/mytv/MainActivity.kt @@ -11,6 +11,7 @@ import android.os.Handler import android.os.Looper import android.util.Log import android.view.KeyEvent +import android.view.WindowManager import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView @@ -32,6 +33,8 @@ class MainActivity : FragmentActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + if (savedInstanceState == null) { supportFragmentManager.beginTransaction() .add(R.id.main_browse_fragment, playbackFragment) diff --git a/app/src/main/java/com/lizongying/mytv/MainFragment.kt b/app/src/main/java/com/lizongying/mytv/MainFragment.kt index 9df28f6..7bbd215 100644 --- a/app/src/main/java/com/lizongying/mytv/MainFragment.kt +++ b/app/src/main/java/com/lizongying/mytv/MainFragment.kt @@ -36,6 +36,16 @@ class MainFragment : BrowseSupportFragment() { private var sharedPref: SharedPreferences? = null +// override fun onCreateView( +// inflater: LayoutInflater, +// container: ViewGroup?, +// savedInstanceState: Bundle? +// ): View? { +// super.onCreate() +// // 使用自定义的布局文件 +// return inflater.inflate(R.layout.custom_browse_fragment, container, false) +// } + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) @@ -66,7 +76,6 @@ class MainFragment : BrowseSupportFragment() { private fun setupUIElements() { brandColor = ContextCompat.getColor(context!!, R.color.fastlane_background) -// headersState = HEADERS_DISABLED } private fun updateRows(tv: TV) { @@ -111,17 +120,29 @@ class MainFragment : BrowseSupportFragment() { adapter = rowsAdapter itemPosition = sharedPref?.getInt("position", 0)!! + if (itemPosition >= tvListViewModel.size()) { + itemPosition = 0 + savePosition(0) + } + + val tv = list2[itemPosition].item val tvModel = tvListViewModel.getTVModel(itemPosition) if (tvModel?.ysp() != null) { + Log.i(TAG, "ysp ${tvModel.getTV()}") lifecycleScope.launch(Dispatchers.IO) { tvModel.let { request?.fetchData(it) } } } else { - (activity as? MainActivity)?.play(list2[itemPosition].item) -// (activity as? MainActivity)?.switchInfoFragment(list2[itemPosition].item) + (activity as? MainActivity)?.play(tv) +// (activity as? MainActivity)?.switchInfoFragment(tv) } + Toast.makeText( + activity, + tv.title, + Toast.LENGTH_SHORT + ).show() (activity as? MainActivity)?.switchMainFragment() } diff --git a/app/src/main/java/com/lizongying/mytv/Request.kt b/app/src/main/java/com/lizongying/mytv/Request.kt index bef780f..9de7715 100644 --- a/app/src/main/java/com/lizongying/mytv/Request.kt +++ b/app/src/main/java/com/lizongying/mytv/Request.kt @@ -89,6 +89,7 @@ class Request(var context: Context) { override fun onResponse(call: Call, response: Response) { if (response.isSuccessful) { val liveInfo = response.body() + Log.i(TAG, "liveInfo $liveInfo") if (liveInfo?.data?.playurl != null) { val chanll = liveInfo.data.chanll val decodedBytes = Base64.decode( @@ -117,6 +118,7 @@ class Request(var context: Context) { } override fun onFailure(call: Call, t: Throwable) { + Log.i(TAG, "get data error") } }) } diff --git a/app/src/main/java/com/lizongying/mytv/models/TVListViewModel.kt b/app/src/main/java/com/lizongying/mytv/models/TVListViewModel.kt index 78b941a..dff5038 100644 --- a/app/src/main/java/com/lizongying/mytv/models/TVListViewModel.kt +++ b/app/src/main/java/com/lizongying/mytv/models/TVListViewModel.kt @@ -1,6 +1,5 @@ package com.lizongying.mytv.models -import android.util.Log import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.lizongying.mytv.TV @@ -41,4 +40,8 @@ class TVListViewModel : ViewModel() { fun getTVModel(id: Int): TVViewModel? { return tvModelListLiveData.value?.get(id) } + + fun size(): Int { + return tvListLiveData.value!!.size + } } \ No newline at end of file diff --git a/app/src/main/res/layout/browse_fragment.xml b/app/src/main/res/layout/browse_fragment.xml new file mode 100644 index 0000000..d18ae54 --- /dev/null +++ b/app/src/main/res/layout/browse_fragment.xml @@ -0,0 +1,19 @@ + + + + + + + +