diff --git a/src/components/AndroidADBPullInstruction/AndroidADBPullInstruction.tsx b/src/components/AndroidADBPullInstruction/AndroidADBPullInstruction.tsx
index b489e41..e38e01a 100644
--- a/src/components/AndroidADBPullInstruction/AndroidADBPullInstruction.tsx
+++ b/src/components/AndroidADBPullInstruction/AndroidADBPullInstruction.tsx
@@ -36,10 +36,19 @@ export function AndroidADBPullInstruction({ dir, file }: AndroidADBPullInstructi
return (
<>
- 你需要 root
访问权限来访问安卓应用的私有数据。
+ 你需要
+
+ 超级管理员
+
+
+
+
+ 访问权限来访问安卓应用的私有数据。
- ⚠️ 请注意,获取 root
通常意味着你的安卓设备
+ ⚠️ 请注意,获取管理员权限通常意味着你的安卓设备
将失去保修资格。
@@ -92,7 +101,7 @@ export function AndroidADBPullInstruction({ dir, file }: AndroidADBPullInstructi
- 确保 adb
命令可用。
+ 确保 adb
命令可用。
💡 如果没有,可以
@@ -134,6 +143,11 @@ export function AndroidADBPullInstruction({ dir, file }: AndroidADBPullInstructi
+
+
+ 确保 adb
命令可用。
+
+
将安卓设备连接到电脑,并允许调试。
diff --git a/src/components/ImportSecretModal.tsx b/src/components/ImportSecretModal.tsx
index 2dd9870..cc099bc 100644
--- a/src/components/ImportSecretModal.tsx
+++ b/src/components/ImportSecretModal.tsx
@@ -35,7 +35,9 @@ export function ImportSecretModal({ clientName, children, show, onClose, onImpor
拖放或点我选择含有密钥的数据库文件
- 选择你的{clientName && <>「{clientName}」>}客户端平台以查看对应说明:
+
+ 选择你的{clientName && <>「{clientName}」>}客户端平台以查看对应说明:
+
{children}
diff --git a/src/faq/QQMusicFAQ.tsx b/src/faq/QQMusicFAQ.tsx
index 86b6018..bdadbce 100644
--- a/src/faq/QQMusicFAQ.tsx
+++ b/src/faq/QQMusicFAQ.tsx
@@ -1,7 +1,7 @@
import { Alert, AlertIcon, Container, Flex, List, ListItem, Text, chakra } from '@chakra-ui/react';
import { Header4 } from '~/components/HelpText/Header4';
import { SegmentTryOfficialPlayer } from './SegmentTryOfficialPlayer';
-import { QMCv2AllInstructions } from '~/features/settings/panels/QMCv2/QMCv2AllInstructions';
+import { QMCv2QQMusicAllInstructions } from '~/features/settings/panels/QMCv2/QMCv2QQMusicAllInstructions';
import { SegmentKeyImportInstructions } from './SegmentKeyImportInstructions';
export function QQMusicFAQ() {
@@ -35,7 +35,7 @@ export function QQMusicFAQ() {
- } />
+ } />
>
diff --git a/src/features/settings/panels/PanelQMCv2Key.tsx b/src/features/settings/panels/PanelQMCv2Key.tsx
index 802eac7..5f59502 100644
--- a/src/features/settings/panels/PanelQMCv2Key.tsx
+++ b/src/features/settings/panels/PanelQMCv2Key.tsx
@@ -14,6 +14,7 @@ import {
MenuDivider,
MenuItem,
MenuList,
+ Select,
Text,
Tooltip,
useToast,
@@ -30,13 +31,15 @@ import { StagingQMCv2Key } from '../keyFormats';
import { DatabaseKeyExtractor } from '~/util/DatabaseKeyExtractor';
import { MMKVParser } from '~/util/MMKVParser';
import { getFileName } from '~/util/pathHelper';
-import { QMCv2AllInstructions } from './QMCv2/QMCv2AllInstructions';
+import { QMCv2QQMusicAllInstructions } from './QMCv2/QMCv2QQMusicAllInstructions';
+import { QMCv2DoubanAllInstructions } from './QMCv2/QMCv2DoubanAllInstructions';
export function PanelQMCv2Key() {
const toast = useToast();
const dispatch = useDispatch();
const { keys: qmc2Keys, allowFuzzyNameSearch } = useSelector(selectStagingQMCv2Settings);
const [showImportModal, setShowImportModal] = useState(false);
+ const [secretType, setSecretType] = useState<'qm' | 'douban'>('qm');
const addKey = () => dispatch(qmc2AddKey());
const clearAll = () => dispatch(qmc2ClearKeys());
@@ -51,11 +54,11 @@ export function PanelQMCv2Key() {
let qmc2Keys: null | Omit[] = null;
- if (/[_.]db$/i.test(file.name)) {
+ if (/(player_process[_.]db|music_audio_play)(\.db)?$/i.test(file.name)) {
const extractor = await DatabaseKeyExtractor.getInstance();
- qmc2Keys = extractor.extractQmAndroidDbKeys(fileBuffer);
+ qmc2Keys = extractor.extractQmcV2KeysFromSqliteDb(fileBuffer);
if (!qmc2Keys) {
- alert(`不是支持的 SQLite 数据库文件。\n表名:${qmc2Keys}`);
+ alert(`不是支持的 SQLite 数据库文件。`);
return;
}
} else if (/MMKVStreamEncryptId|filenameEkeyMap/i.test(file.name)) {
@@ -96,8 +99,8 @@ export function PanelQMCv2Key() {
- QQ 音乐目前采用的加密方案(QMCv2)。在使用「QQ 音乐」安卓、Mac 或 iOS
- 客户端的情况下,其「离线加密文件」对应的「密钥」储存在独立的数据库文件内。
+ QQ 音乐、豆瓣 FM 目前采用的加密方案(QMCv2)。在使用「QQ 音乐」安卓、Mac 或 iOS 客户端,以及在使用「豆瓣
+ FM」安卓客户端的情况下,其「离线加密文件」对应的「密钥」储存在独立的数据库文件内。
@@ -155,16 +158,28 @@ export function PanelQMCv2Key() {
))}
- {qmc2Keys.length === 0 && 还没有添加密钥。}
+ {qmc2Keys.length === 0 && 还没有密钥。}
setSecretType(e.target.value as 'qm' | 'douban')}
+ variant="flushed"
+ display="inline"
+ css={{ paddingLeft: '0.75rem', width: 'auto' }}
+ >
+
+
+
+ }
show={showImportModal}
onClose={() => setShowImportModal(false)}
onImport={handleSecretImport}
>
-
+ {secretType === 'qm' && }
+ {secretType === 'douban' && }
);
diff --git a/src/features/settings/panels/QMCv2/QMCv2DoubanAllInstructions.tsx b/src/features/settings/panels/QMCv2/QMCv2DoubanAllInstructions.tsx
new file mode 100644
index 0000000..b3595c5
--- /dev/null
+++ b/src/features/settings/panels/QMCv2/QMCv2DoubanAllInstructions.tsx
@@ -0,0 +1,17 @@
+import { Tab, TabList, TabPanel, TabPanels } from '@chakra-ui/react';
+import { AndroidADBPullInstruction } from '~/components/AndroidADBPullInstruction/AndroidADBPullInstruction';
+
+export function QMCv2DoubanAllInstructions() {
+ return (
+ <>
+
+ 安卓
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/features/settings/panels/QMCv2/QMCv2AllInstructions.tsx b/src/features/settings/panels/QMCv2/QMCv2QQMusicAllInstructions.tsx
similarity index 95%
rename from src/features/settings/panels/QMCv2/QMCv2AllInstructions.tsx
rename to src/features/settings/panels/QMCv2/QMCv2QQMusicAllInstructions.tsx
index 3cfd9f5..239f1b6 100644
--- a/src/features/settings/panels/QMCv2/QMCv2AllInstructions.tsx
+++ b/src/features/settings/panels/QMCv2/QMCv2QQMusicAllInstructions.tsx
@@ -4,7 +4,7 @@ import { InstructionsIOS } from './InstructionsIOS';
import { InstructionsMac } from './InstructionsMac';
import { InstructionsPC } from './InstructionsPC';
-export function QMCv2AllInstructions() {
+export function QMCv2QQMusicAllInstructions() {
return (
<>
diff --git a/src/util/DatabaseKeyExtractor.ts b/src/util/DatabaseKeyExtractor.ts
index 0a4b8e9..c749463 100644
--- a/src/util/DatabaseKeyExtractor.ts
+++ b/src/util/DatabaseKeyExtractor.ts
@@ -23,16 +23,21 @@ export class DatabaseKeyExtractor {
return tables.includes(name);
}
- extractQmAndroidDbKeys(buffer: ArrayBuffer): null | QMAndroidKeyEntry[] {
+ extractQmcV2KeysFromSqliteDb(buffer: ArrayBuffer): null | QMAndroidKeyEntry[] {
let db: SQLDatabase | null = null;
try {
db = new this.SQL.Database(new Uint8Array(buffer));
- if (!this.hasTable(db, 'audio_file_ekey_table')) {
- return null;
- }
- const result = db.exec('select file_path, ekey from audio_file_ekey_table');
+ let sql: undefined | string;
+ if (this.hasTable(db, 'audio_file_ekey_table')) {
+ sql = 'select file_path, ekey from audio_file_ekey_table';
+ } else if (this.hasTable(db, 'EKeyFileInfo')) {
+ sql = 'select filePath, eKey from EKeyFileInfo';
+ }
+ if (!sql) return null;
+
+ const result = db.exec(sql);
if (result.length === 0) {
return [];
}