Created
March 12, 2024 10:30
-
-
Save TheDauntless/c543b7b4cb6f21e59077b31a839128c9 to your computer and use it in GitHub Desktop.
Flutter Hive Reader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// crc.dart | |
import 'dart:typed_data'; | |
/// Efficient CRC32 implementation | |
class Crc32 { | |
/// Computes the CRC32 checksum of [bytes] using a previous [crc] value. | |
static int compute( | |
Uint8List bytes, { | |
int crc = 0, | |
int offset = 0, | |
int? length, | |
}) { | |
crc = crc ^ 0xffffffff; | |
length ??= bytes.length; | |
for (var i = offset; i < offset + length; i++) { | |
crc = _table[(crc ^ bytes[i]) & 0xff] ^ (crc >> 8); | |
} | |
return crc ^ 0xffffffff; | |
} | |
/// Precalculated CRC table. | |
static const List<int> _table = [ | |
// | |
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, | |
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, | |
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, | |
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, | |
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, | |
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, | |
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, | |
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, | |
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, | |
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, | |
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, | |
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, | |
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, | |
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, | |
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, | |
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, | |
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, | |
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, | |
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, | |
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, | |
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, | |
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, | |
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, | |
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, | |
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, | |
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, | |
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, | |
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, | |
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, | |
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, | |
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, | |
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, | |
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, | |
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, | |
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, | |
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, | |
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, | |
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, | |
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, | |
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, | |
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, | |
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, | |
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d | |
]; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//generic_adapter.dart | |
import 'package:hive/hive.dart'; | |
class GenericAdapter extends TypeAdapter<List> { | |
@override | |
final int typeId; | |
GenericAdapter(this.typeId); | |
@override | |
List<dynamic> read(BinaryReader reader) { | |
final numOfFields = reader.readByte(); | |
var list = List<dynamic>.filled(numOfFields, null, growable: true); | |
for (var i = 0; i < numOfFields; i++) { | |
list[reader.readByte()] = reader.read(); | |
} | |
return list; | |
} | |
@override | |
int get hashCode => typeId.hashCode; | |
@override | |
void write(BinaryWriter writer, List obj) { | |
// No write needed | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//main.dart | |
import 'package:eu_nviso_hivetest/generic_adapter.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:hive_flutter/hive_flutter.dart'; | |
import 'package:path_provider/path_provider.dart'; | |
import 'package:file_picker/file_picker.dart'; | |
import 'package:hive/hive.dart'; | |
import 'dart:convert'; | |
import 'package:path/path.dart' as Path; | |
import 'package:flutter/services.dart'; | |
import 'package:eu_nviso_hivetest/crc.dart'; | |
import 'dart:io'; | |
import 'dart:typed_data'; | |
import 'dart:math'; | |
void main() async { | |
WidgetsFlutterBinding.ensureInitialized(); | |
final dir = await getApplicationDocumentsDirectory(); | |
Hive.init(dir.path); | |
// Register the GenericAdapter for all available typeIds | |
for(var i = 0; i<223; i++) | |
{ | |
Hive.registerAdapter(GenericAdapter(i)); | |
} | |
runApp(MaterialApp(home:MyApp())); | |
} | |
class MyApp extends StatefulWidget { | |
@override | |
_MyAppState createState() => _MyAppState(); | |
} | |
class _MyAppState extends State<MyApp> { | |
Map<dynamic, dynamic> hiveEntries = {}; | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
home: Scaffold( | |
appBar: AppBar( | |
title: Text('Hive Entries Viewer'), | |
), | |
body: Column( | |
children: [ | |
ElevatedButton( | |
onPressed: () => _pickAndReadHiveFile(), | |
child: Text('Browse Hive File'), | |
), | |
Expanded( | |
child: SingleChildScrollView( | |
child: SingleChildScrollView( | |
scrollDirection: Axis.horizontal, | |
child: DataTable( | |
columns: const [ | |
DataColumn(label: Text('Key')), | |
DataColumn(label: Text('Value')), | |
], | |
rows: hiveEntries.entries | |
.map( | |
(entry) => DataRow(cells: [ | |
DataCell( | |
InkWell( | |
onTap: () async { | |
Clipboard.setData(ClipboardData(text: entry.key.toString())); | |
ScaffoldMessenger.of(context).showSnackBar( | |
SnackBar( | |
content: Text('Key copied to clipboard'), | |
), | |
); | |
}, | |
child: Text(entry.key.toString()), | |
), | |
), | |
DataCell( | |
InkWell( | |
onTap: () { | |
Clipboard.setData(ClipboardData(text: entry.value.toString())) | |
.then((_) { | |
ScaffoldMessenger.of(context).showSnackBar( | |
SnackBar( | |
content: Text('Value copied to clipboard'), | |
), | |
); | |
}); | |
}, | |
child: Text(entry.value.toString()), | |
), | |
), | |
]), | |
) | |
.toList(), | |
), | |
), | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
Future<void> _pickAndReadHiveFile() async { | |
FilePickerResult? result = await FilePicker.platform.pickFiles(); | |
if (result != null) { | |
String? filePath = result.files.single.path; | |
if (filePath != null) { | |
TextEditingController passwordController = TextEditingController(); | |
// Show dialog to enter password | |
showDialog( | |
context: context, | |
builder: (BuildContext context) { | |
return AlertDialog( | |
title: Text('Enter Password'), | |
content: TextField( | |
controller: passwordController, | |
obscureText: true, | |
decoration: InputDecoration( | |
hintText: 'Password', | |
) | |
), | |
actions: <Widget>[ | |
TextButton( | |
child: Text('OK'), | |
onPressed: () async { | |
String password = ""; | |
if(passwordController.text.length > 0){ | |
password = passwordController.text; | |
} | |
Navigator.of(context).pop(); // Close the dialog | |
await _readHiveFile(filePath, password); // Pass the password | |
}, | |
), | |
], | |
); | |
}, | |
); | |
} | |
} | |
} | |
Future<void> _readHiveFile(String filePath, String password) async { | |
File recoveredFile = await recoverHive(filePath, password.isEmpty ? null : HiveAesCipher(base64.decode(password))); | |
String directory = Path.dirname(recoveredFile.path); | |
String boxName = Path.basenameWithoutExtension(filePath); | |
Hive.init(directory); | |
Box<dynamic> box; | |
if(password.isNotEmpty) | |
{ | |
var passwordBytes = base64.decode(password); | |
var encryptionCipher = HiveAesCipher(passwordBytes); | |
box = await Hive.openBox<dynamic>(boxName, path: directory, encryptionCipher: encryptionCipher); | |
} | |
else{ | |
box = await Hive.openBox<dynamic>(boxName, path: directory); | |
} | |
setState(() { | |
hiveEntries = Map<dynamic, dynamic>.from(box.toMap()); | |
}); | |
await box.close(); | |
} | |
} | |
Future<String> copyFileToTemp(String sourcePath) async { | |
var sourceFile = File(sourcePath); | |
// Generate a random subfolder name | |
var rng = Random(); | |
var tempSubfolderName = "temp_${rng.nextInt(10000)}"; // Random subfolder name | |
var tempDir = Directory.systemTemp.createTempSync(tempSubfolderName); | |
// Create a File instance for the destination file in the new subfolder | |
var tempFile = File('${tempDir.path}/${sourceFile.uri.pathSegments.last}'); | |
try { | |
await sourceFile.copy(tempFile.path); | |
print('File copied successfully to temporary directory: ${tempFile.path}'); | |
} catch (e) { | |
print('Failed to copy file to temporary directory: $e'); | |
} | |
return tempFile.path; | |
} | |
Future<File> recoverHive(originalFile, HiveAesCipher? cipher) async { | |
var filePath = await copyFileToTemp(originalFile); | |
var file = File(filePath); | |
var bytes = await file.readAsBytes(); | |
int offset = 0; | |
var allFrames = BytesBuilder(); | |
var keyNames = <String, int>{}; | |
var keyInts = []; | |
while (offset < bytes.length) { | |
var frameLength = ByteData.sublistView(bytes, offset, offset + 4).getUint32(0, Endian.little); | |
var keyOffset = offset + 4; // Start looking for the key name right after the frame length | |
var endOffset = offset + frameLength; | |
if (bytes.length > keyOffset + 2) { | |
Uint8List newKey; | |
int frameResize; | |
int keyLength; | |
if(bytes[keyOffset] == 0x01){ | |
// Key is String | |
keyLength = bytes[keyOffset + 1]; | |
var keyBytes = bytes.sublist(keyOffset + 2, keyOffset + 2 + keyLength); | |
var keyName = String.fromCharCodes(keyBytes); | |
if (keyNames.containsKey(keyName)) { | |
keyNames[keyName] = keyNames[keyName]! + 1; | |
keyName = "${keyName}_${keyNames[keyName]}"; | |
} else { | |
keyNames[keyName] = 1; | |
} | |
var modifiedKeyBytes = Uint8List.fromList(keyName.codeUnits); | |
var modifiedKeyLength = modifiedKeyBytes.length; | |
// get bytes for TYPE + LENGTH + VALUE | |
var bb = BytesBuilder(); | |
bb.addByte(0x01); | |
bb.addByte(modifiedKeyLength); | |
bb.add(modifiedKeyBytes); | |
newKey = bb.toBytes(); | |
frameResize = modifiedKeyLength - keyLength; | |
keyLength += 2; // add the length of the type | |
} | |
else{ | |
// Key is int | |
keyLength = 5; // type + uint32 | |
var keyIndexOffset = keyOffset + 0x01; | |
var keyInt = ByteData.sublistView(bytes, keyIndexOffset, keyIndexOffset + 4).getUint32(0, Endian.little); | |
while(keyInts.contains(keyInt)){ | |
keyInt += 1; | |
} | |
keyInts.add(keyInt); | |
var index = ByteData(4)..setUint32(0, keyInt, Endian.little); | |
// get bytes for TYPE + index | |
var bb = BytesBuilder(); | |
bb.addByte(0x00); | |
bb.add(index.buffer.asUint8List()); | |
newKey = bb.toBytes(); | |
frameResize = 0; | |
} | |
// If there is no value, it's a delete frame, so we don't bother adding it again | |
if(frameLength == keyLength + 8){ // 4 bytes for CRC, 4 bytes for frame length | |
offset = endOffset; | |
print("Dropping delete frame for " + newKey.toString()); | |
continue; | |
} | |
// Calculate new length of frame | |
frameLength += frameResize; | |
// Create a new frame bytes builder | |
var frameBytes = BytesBuilder(); | |
// Prepare the frame length in ByteData and add it to the frame | |
var frameLengthData = ByteData(4)..setUint32(0, frameLength, Endian.little); | |
frameBytes.add(frameLengthData.buffer.asUint8List()); | |
// Add the new key | |
frameBytes.add(newKey); | |
// Add the rest of the frame after the original key. Don't include the CRC | |
frameBytes.add(bytes.sublist(keyOffset + keyLength, endOffset-4)); | |
// Compute CRC using Hive's Crc32 class | |
var newCrc = Crc32.compute( | |
frameBytes.toBytes(), | |
offset: 0, | |
length: frameLength - 4, | |
crc: cipher?.calculateKeyCrc() ?? 0, | |
); | |
// Write Crc code | |
var newCrcBytes = Uint8List(4)..buffer.asByteData().setUint32(0, newCrc, Endian.little); | |
frameBytes.add(newCrcBytes); | |
// Update the overall frames with the modified frame | |
allFrames.add(frameBytes.toBytes()); | |
} | |
offset = endOffset; // Move to the next frame | |
} | |
var reconstructedBytes = allFrames.takeBytes(); | |
try { | |
await file.writeAsBytes(reconstructedBytes); | |
print('Bytes successfully written to temporary file: ${file.path}'); | |
} catch (e) { | |
print('Failed to write bytes to temporary file: $e'); | |
} | |
return file; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: eu_nviso_hivetest | |
description: "A new Flutter project." | |
publish_to: 'none' | |
version: 1.0.0+1 | |
environment: | |
sdk: '>=3.3.0 <4.0.0' | |
dependencies: | |
flutter: | |
sdk: flutter | |
hive: ^2.2.3 | |
hive_flutter: ^1.1.0 | |
isar_flutter_libs: ^3.1.0+1 | |
path_provider: ^2.1.2 | |
file_picker: ^6.1.1 | |
path: ^1.9.0 | |
flutter_secure_storage: ^9.0.0 | |
cupertino_icons: ^1.0.6 | |
dev_dependencies: | |
flutter_test: | |
sdk: flutter | |
hive_generator: ^1.1.0 | |
build_runner: ^2.0.1 | |
flutter_lints: ^3.0.0 | |
flutter: | |
uses-material-design: true | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment