Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions images/decode_xlog_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#!/usr/bin/python

import sys
import os
import glob
import zlib
import struct
import binascii
import traceback


MAGIC_NO_COMPRESS_START = 0x03
MAGIC_NO_COMPRESS_START1 = 0x06
MAGIC_NO_COMPRESS_NO_CRYPT_START = 0x08
MAGIC_COMPRESS_START = 0x04
MAGIC_COMPRESS_START1 = 0x05
MAGIC_COMPRESS_START2 = 0x07
MAGIC_COMPRESS_NO_CRYPT_START = 0x09

MAGIC_END = 0x00

lastseq = 0


def IsGoodLogBuffer(_buffer, _offset, count):

if _offset == len(_buffer): return (True, '')

magic_start = _buffer[_offset]
if MAGIC_NO_COMPRESS_START==magic_start or MAGIC_COMPRESS_START==magic_start or MAGIC_COMPRESS_START1==magic_start:
crypt_key_len = 4
elif MAGIC_COMPRESS_START2==magic_start or MAGIC_NO_COMPRESS_START1==magic_start or MAGIC_NO_COMPRESS_NO_CRYPT_START==magic_start or MAGIC_COMPRESS_NO_CRYPT_START==magic_start:
crypt_key_len = 64
else:
return (False, '_buffer[%d]:%d != MAGIC_NUM_START'%(_offset, _buffer[_offset]))

headerLen = 1 + 2 + 1 + 1 + 4 + crypt_key_len

if _offset + headerLen + 1 + 1 > len(_buffer): return (False, 'offset:%d > len(buffer):%d'%(_offset, len(_buffer)))
length = struct.unpack_from("I", _buffer[_offset+headerLen-4-crypt_key_len: _offset+headerLen-4-crypt_key_len+4])[0]
if _offset + headerLen + length + 1 > len(_buffer): return (False, 'log length:%d, end pos %d > len(buffer):%d'%(length, _offset + headerLen + length + 1, len(_buffer)))
if MAGIC_END!=_buffer[_offset + headerLen + length]: return (False, 'log length:%d, buffer[%d]:%d != MAGIC_END'%(length, _offset + headerLen + length, _buffer[_offset + headerLen + length]))


if (1>=count): return (True, '')
else: return IsGoodLogBuffer(_buffer, _offset+headerLen+length+1, count-1)


def GetLogStartPos(_buffer, _count):
offset = 0
while True:
if offset >= len(_buffer): break

if MAGIC_NO_COMPRESS_START==_buffer[offset] or MAGIC_NO_COMPRESS_START1==_buffer[offset] or MAGIC_COMPRESS_START==_buffer[offset] or MAGIC_COMPRESS_START1==_buffer[offset] or MAGIC_COMPRESS_START2==_buffer[offset] or MAGIC_COMPRESS_NO_CRYPT_START==_buffer[offset] or MAGIC_NO_COMPRESS_NO_CRYPT_START==_buffer[offset]:
if IsGoodLogBuffer(_buffer, offset, _count)[0]: return offset
offset+=1

return -1

def DecodeBuffer(_buffer, _offset, _outbuffer):

if _offset >= len(_buffer): return -1
# if _offset + 1 + 4 + 1 + 1 > len(_buffer): return -1
ret = IsGoodLogBuffer(_buffer, _offset, 1)
if not ret[0]:
fixpos = GetLogStartPos(_buffer[_offset:], 1)
if -1==fixpos:
return -1
else:
_outbuffer.extend("[F]decode_log_file.py decode error len=%d, result:%s \n"%(fixpos, ret[1]))
_offset += fixpos

magic_start = _buffer[_offset]
if MAGIC_NO_COMPRESS_START==magic_start or MAGIC_COMPRESS_START==magic_start or MAGIC_COMPRESS_START1==magic_start:
crypt_key_len = 4
elif MAGIC_COMPRESS_START2==magic_start or MAGIC_NO_COMPRESS_START1==magic_start or MAGIC_NO_COMPRESS_NO_CRYPT_START==magic_start or MAGIC_COMPRESS_NO_CRYPT_START==magic_start:
crypt_key_len = 64
else:
_outbuffer.extend('in DecodeBuffer _buffer[%d]:%d != MAGIC_NUM_START'%(_offset, magic_start))
return -1

headerLen = 1 + 2 + 1 + 1 + 4 + crypt_key_len
length = struct.unpack_from("I", _buffer[_offset+headerLen-4-crypt_key_len: _offset+headerLen-4-crypt_key_len+4])[0]
tmpbuffer = bytearray(length)

seq = struct.unpack_from("H", _buffer[_offset+headerLen-4-crypt_key_len-2-2: _offset+headerLen-4-crypt_key_len-2])[0]
begin_hour = struct.unpack_from("c", _buffer[_offset+headerLen-4-crypt_key_len-1-1: _offset+headerLen-4-crypt_key_len-1])[0]
end_hour = struct.unpack_from("c", _buffer[_offset+headerLen-4-crypt_key_len-1: _offset+headerLen-4-crypt_key_len])[0]

global lastseq
if seq != 0 and seq != 1 and lastseq != 0 and seq != (lastseq+1):
_outbuffer.extend("[F]decode_log_file.py log seq:%d-%d is missing\n" %(lastseq+1, seq-1))

if seq != 0:
lastseq = seq

tmpbuffer[:] = _buffer[_offset+headerLen:_offset+headerLen+length]

try:
decompressor = zlib.decompressobj(-zlib.MAX_WBITS)

if MAGIC_NO_COMPRESS_START1==_buffer[_offset] or MAGIC_COMPRESS_START2==_buffer[_offset]:
print("use wrong decode script")
elif MAGIC_COMPRESS_START==_buffer[_offset] or MAGIC_COMPRESS_NO_CRYPT_START==_buffer[_offset]:
tmpbuffer = decompressor.decompress(tmpbuffer)
elif MAGIC_COMPRESS_START1==_buffer[_offset]:
decompress_data = bytearray()
while len(tmpbuffer) > 0:
single_log_len = struct.unpack_from("H", buffer(tmpbuffer, 0, 2))[0]
decompress_data.extend(tmpbuffer[2:single_log_len+2])
tmpbuffer[:] = tmpbuffer[single_log_len+2:len(tmpbuffer)]

tmpbuffer = decompressor.decompress(str(decompress_data))

else:
pass

# _outbuffer.extend('seq:%d, hour:%d-%d len:%d decompress:%d\n' %(seq, ord(begin_hour), ord(end_hour), length, len(tmpbuffer)))
except Exception as e:
traceback.print_exc()
_outbuffer.extend("[F]decode_log_file.py decompress err, ".encode() + str(e).encode() + b"\n")
return _offset+headerLen+length+1

_outbuffer.extend(tmpbuffer)

return _offset+headerLen+length+1


def ParseFile(_file, _outfile):
fp = open(_file, "rb")
_buffer = bytearray(os.path.getsize(_file))
fp.readinto(_buffer)
fp.close()
startpos = GetLogStartPos(_buffer, 2)
if -1==startpos:
return

outbuffer = bytearray()

while True:
startpos = DecodeBuffer(_buffer, startpos, outbuffer)
if -1==startpos: break;

if 0==len(outbuffer): return

fpout = open(_outfile, "wb")
fpout.write(outbuffer)
fpout.close()

def main(args):
global lastseq

if 1==len(args):
if os.path.isdir(args[0]):
filelist = glob.glob(args[0] + "/*.xlog")
for filepath in filelist:
lastseq = 0
ParseFile(filepath, filepath+".log")
else: ParseFile(args[0], args[0]+".log")
elif 2==len(args):
ParseFile(args[0], args[1])
else:
filelist = glob.glob("*.xlog")
for filepath in filelist:
lastseq = 0
ParseFile(filepath, filepath+".log")

if __name__ == "__main__":
main(sys.argv[1:])
161 changes: 117 additions & 44 deletions lib/controller/xlog_info_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ class XlogInfoController extends GetxController {
return;
}

final privateKey = this.cryptMd5.value.trim();

if (this.isEnableCrypt.value == true &&
(this.cryptMd5.value.isEmpty || this.cryptMd5.value.length != 64)) {
(privateKey.isEmpty || privateKey.length != 64)) {
print("private key is empty");
showToast("Private Key 为空或长度不对(64位)", textPadding: EdgeInsets.all(15));
vm.updateStatus(XlogInfoStatus.fail);
Expand All @@ -88,44 +90,48 @@ class XlogInfoController extends GetxController {
return;
}

var execFileName = Platform.isWindows ? "xlog-decoder.exe" : "xlog-decoder";
var pyPath = path.joinAll([
_assetsDir.path,
execFileName,
]);
if (this.isEnableCrypt.value == false) {
pyPath = path.joinAll([_assetsDir.path, execFileName]);
}
final outputPath = path.join(
savePath.value,
"${vm.file.fileName}.log",
);

final execFileName =
Platform.isWindows ? "xlog-decoder.exe" : "xlog-decoder";
final execPath = path.join(_assetsDir.path, execFileName);

ProcessResult process;
if (this.isEnableCrypt.value == true) {
//加密
debugPrint("执行带加密key命令");
process = await Process.run(
pyPath,
[
"decode",
"-i",
vm.file.path,
"-p",
this.cryptMd5.value,
"-o",
"${vm.file.path}.log"
],
);
process = await Process.run(execPath, [
"decode",
"-i",
vm.file.path,
"-p",
privateKey.toLowerCase(),
"-o",
outputPath,
]);
} else {
//不加密
debugPrint("执行不加密命令");
process = await Process.run(
pyPath,
[
"decode",
"-i",
vm.file.path,
"-o",
"${vm.file.path}.log"
],
);
process = await Process.run(execPath, [
"decode",
"-i",
vm.file.path,
"-o",
outputPath,
]);
}

final stdoutMsg = process.stdout?.toString().trim() ?? '';
final stderrMsg = process.stderr?.toString().trim() ?? '';
debugPrint("xlog-decoder exitCode: ${process.exitCode}");
if (stdoutMsg.isNotEmpty) {
debugPrint("xlog-decoder stdout: $stdoutMsg");
}
if (stderrMsg.isNotEmpty) {
debugPrint("xlog-decoder stderr: $stderrMsg");
}

if (process.exitCode != 0) {
Expand All @@ -136,22 +142,25 @@ class XlogInfoController extends GetxController {
return;
}

var file = File(vm.file.path + ".log");
var file = File(outputPath);

var isExist = await file.exists();
if (isExist) {
Platform.isWindows
? await Process.run("xcopy", [
file.path,
savePath.value,
"/y",
])
: await Process.run("cp", [
"-f",
file.path,
savePath.value,
]);
vm.saveFile = File(path.joinAll([savePath.value, file.fileName]));
final fileLength = await file.length();
if (fileLength == 0) {
await file.delete().catchError((_) {});
if (this.isEnableCrypt.value == false &&
await _decodeWithPython(vm.file.path, outputPath)) {
file = File(outputPath);
} else {
showToast("Xlog解析失败:输出内容为空,请检查输入文件与私钥",
textPadding: EdgeInsets.all(15));
vm.updateStatus(XlogInfoStatus.fail);
taskList.refresh();
return;
}
}
vm.saveFile = file;
vm.updateStatus(XlogInfoStatus.success);
taskList.refresh();
} else {
Expand Down Expand Up @@ -209,4 +218,68 @@ class XlogInfoController extends GetxController {
return null;
}
}

Future<bool> _decodeWithPython(String inputPath, String outputPath) async {
final scriptPath = path.join(_assetsDir.path, "decode_xlog_file.py");
final pythonCandidates = Platform.isWindows
? <String>["python", "py"]
: <String>["python3", "python"];
debugPrint("尝试使用 Python 脚本解码: $scriptPath");

ProcessResult? result;
String? usedCmd;
try {
for (final cmd in pythonCandidates) {
try {
debugPrint("尝试执行 Python 命令: $cmd");
result = await Process.run(
cmd,
[
scriptPath,
inputPath,
outputPath,
],
);
usedCmd = cmd;
break;
} on ProcessException catch (e) {
debugPrint("Python 命令 $cmd 不可用: $e");
continue;
}
}
if (result == null) {
if (Platform.isWindows) {
showToast("未找到 Python 环境,请安装 Python 并将其加入 PATH",
textPadding: EdgeInsets.all(15));
}
return false;
}
final stdoutMsg = result.stdout?.toString().trim() ?? '';
final stderrMsg = result.stderr?.toString().trim() ?? '';
debugPrint(
"python decode (${usedCmd ?? 'unknown'}) exitCode: ${result.exitCode}");
if (stdoutMsg.isNotEmpty) {
debugPrint("python decode stdout: $stdoutMsg");
}
if (stderrMsg.isNotEmpty) {
debugPrint("python decode stderr: $stderrMsg");
}
if (result.exitCode != 0) {
return false;
}
final file = File(outputPath);
if (!await file.exists()) {
return false;
}
final length = await file.length();
if (length == 0) {
await file.delete().catchError((_) {});
return false;
}
return true;
} catch (e) {
debugPrint("python decode error: $e");
return false;
}
}
}
13 changes: 13 additions & 0 deletions lib/generated/assets.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
///This file is automatically generated. DO NOT EDIT, all your changes would be lost.
class Assets {
Assets._();

static const String imagesAppIcon = 'images/app_icon.png';
static const String imagesDecodeXlogFile = 'images/decode_xlog_file.py';
static const String imagesError = 'images/error.png';
static const String imagesFolder = 'images/folder.png';
static const String imagesSuccess = 'images/success.png';
static const String imagesXlogDecoder = 'images/xlog-decoder';
static const String marsXlogDecoderGuiImagesXlogDecoder = 'images/xlog-decoder.exe';

}
Loading