This commit is contained in:
hu xiaotong
2025-07-02 16:12:52 +08:00
commit 0246bc7060
48 changed files with 460639 additions and 0 deletions

296
frontend/src/App.css Normal file
View File

@@ -0,0 +1,296 @@
#root {
width: 100%;
height: 100vh;
margin: 0;
padding: 0;
}
/* 应用容器 */
.app-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
/* 头部样式 */
.app-header {
background: #fff;
padding: 0 20px;
border-bottom: 1px solid #e8e8e8;
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
}
.app-header h1 {
margin: 0;
color: #333;
font-size: 20px;
}
/* 内容区域 */
.app-content {
display: flex;
flex: 1;
overflow: hidden;
}
/* 左侧控制面板 */
.control-panel {
width: 300px;
background: #fff;
border-right: 1px solid #e8e8e8;
padding: 20px;
overflow-y: auto;
}
/* 控制卡片 */
.control-card {
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 6px;
padding: 16px;
margin-bottom: 16px;
}
.control-card h3 {
margin: 0 0 16px 0;
color: #333;
font-size: 16px;
}
/* 表单组 */
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
}
.form-select {
width: 100%;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
background: #fff;
}
.form-select:focus {
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
/* 按钮组 */
.button-group {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
/* 按钮样式 */
.btn {
padding: 8px 16px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
background: #fff;
color: #333;
}
.btn:hover:not(:disabled) {
border-color: #1890ff;
color: #1890ff;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: #1890ff;
border-color: #1890ff;
color: #fff;
flex: 1;
}
.btn-primary:hover:not(:disabled) {
background: #40a9ff;
border-color: #40a9ff;
color: #fff;
}
.btn-danger {
background: #ff4d4f;
border-color: #ff4d4f;
color: #fff;
flex: 1;
}
.btn-danger:hover:not(:disabled) {
background: #ff7875;
border-color: #ff7875;
color: #fff;
}
.btn-success {
background: #52c41a;
border-color: #52c41a;
color: #fff;
}
.btn-success:hover:not(:disabled) {
background: #73d13d;
border-color: #73d13d;
color: #fff;
}
.btn-secondary {
background: #f5f5f5;
border-color: #d9d9d9;
color: #333;
}
.btn-secondary:hover:not(:disabled) {
background: #e6e6e6;
border-color: #bfbfbf;
color: #333;
}
/* 状态信息 */
.status-info p {
margin: 8px 0;
color: #666;
font-size: 14px;
}
/* 右侧内容区域 */
.content-area {
flex: 1;
padding: 20px;
overflow-y: auto;
position: relative;
}
/* 数据卡片 */
.data-card {
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 6px;
padding: 20px;
}
.data-card h3 {
margin: 0 0 16px 0;
color: #333;
font-size: 18px;
}
.data-card p {
margin: 10px 0;
color: #666;
font-size: 14px;
}
/* 表格容器 */
.table-container {
overflow-x: auto;
}
/* 数据表格 */
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.data-table th,
.data-table td {
padding: 12px 8px;
text-align: left;
border-bottom: 1px solid #e8e8e8;
}
.data-table th {
background: #fafafa;
font-weight: 600;
color: #333;
}
.data-table tr:hover {
background: #f5f5f5;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 40px;
color: #999;
}
.empty-state p {
margin: 0;
font-size: 16px;
}
/* 加载遮罩 */
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.loading-spinner {
padding: 20px;
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 6px;
color: #666;
}
.ant-btn[icon-type='setting'], .ant-btn-setting, .ant-btn-setting:active, .ant-btn-setting:focus {
background: #fff !important;
border-color: #d9d9d9 !important;
color: #333 !important;
}
.ant-btn[icon-type='setting']:hover, .ant-btn-setting:hover {
background: #e6e6e6 !important;
border-color: #bfbfbf !important;
color: #333 !important;
}
/* 强制Antd按钮为普通圆角矩形风格 */
/*.ant-btn, .ant-btn-default {*/
/* border-radius: 6px !important;*/
/* border-style: solid !important;*/
/* border-width: 1px !important;*/
/* box-shadow: none !important;*/
/* background: #fff !important;*/
/* color: #333 !important;*/
/* border-color: #d9d9d9 !important;*/
/*}*/
/*.ant-btn:hover, .ant-btn-default:hover {*/
/* background: #e6e6e6 !important;*/
/* color: #333 !important;*/
/* border-color: #bfbfbf !important;*/
/*}*/
/*.ant-btn .anticon {*/
/* color: #333 !important;*/
/*}*/

77
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,77 @@
import React, {useEffect, useState, useRef} from 'react'
import {Button, Card, Layout, message, Select, Spin, Table} from 'antd'
import {DownloadOutlined, PlayCircleOutlined, SettingOutlined, StopOutlined} from '@ant-design/icons'
import './App.css'
import {ExportData, GetCapturedData,GetNetworkInterfaces,
ParseData,StartCapture,StopCapture, ReadRawJsonFile
} from "../wailsjs/go/service/App";
import { BrowserRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom';
import { Menu } from 'antd';
import CapturePage from './pages/CapturePage';
import OptimizerPage from './pages/OptimizerPage';
const {Header, Content, Sider} = Layout
interface NetworkInterface {
name: string
description: string
addresses: string[]
is_loopback: boolean
}
interface Equipment {
id: string
code: string
ct: number
e: number
g: number
l: boolean
mg: number
op: Array<[string, any]>
p: number
s: string
sk: number
}
interface CaptureResult {
items: Equipment[]
heroes: any[]
}
function AppContent() {
const location = useLocation();
return (
<Layout style={{ minHeight: '100vh' }}>
<Header style={{ background: '#fff', padding: 0 }}>
<Menu
mode="horizontal"
selectedKeys={[location.pathname]}
style={{ fontSize: 16 }}
>
<Menu.Item key="/">
<Link to="/"></Link>
</Menu.Item>
<Menu.Item key="/optimizer">
<Link to="/optimizer"></Link>
</Menu.Item>
</Menu>
</Header>
<Content style={{ padding: 0, minHeight: 280 }}>
<Routes>
<Route path="/" element={<CapturePage />} />
<Route path="/optimizer" element={<OptimizerPage />} />
</Routes>
</Content>
</Layout>
);
}
function App() {
return (
<Router>
<AppContent />
</Router>
);
}
export default App

67
frontend/src/index.css Normal file
View File

@@ -0,0 +1,67 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

11
frontend/src/main.tsx Normal file
View File

@@ -0,0 +1,11 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import 'antd/dist/reset.css'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

View File

@@ -0,0 +1,460 @@
import React, {useEffect, useState, useRef} from 'react'
import {Button, Card, Layout, message, Select, Spin, Table} from 'antd'
import {DownloadOutlined, PlayCircleOutlined, SettingOutlined, StopOutlined} from '@ant-design/icons'
import '../App.css'
import {ExportData, GetCapturedData,GetNetworkInterfaces,
ParseData,StartCapture,StopCapture, ReadRawJsonFile
} from "../../wailsjs/go/service/App";
const {Header, Content, Sider} = Layout
interface NetworkInterface {
name: string
description: string
addresses: string[]
is_loopback: boolean
}
interface Equipment {
id: string
code: string
ct: number
e: number
g: number
l: boolean
mg: number
op: Array<[string, any]>
p: number
s: string
sk: number
}
interface CaptureResult {
items: Equipment[]
heroes: any[]
}
function CapturePage() {
const [interfaces, setInterfaces] = useState<NetworkInterface[]>([])
const [selectedInterface, setSelectedInterface] = useState<string>('')
const [isCapturing, setIsCapturing] = useState(false)
const [capturedData, setCapturedData] = useState<string[]>([])
const [parsedData, setParsedData] = useState<CaptureResult | null>(null)
const [loading, setLoading] = useState(false)
const [interfaceLoading, setInterfaceLoading] = useState(false)
const [uploadedFileName, setUploadedFileName] = useState<string>('')
const fileInputRef = useRef<HTMLInputElement>(null)
const safeApiCall = async <T,>(
apiCall: () => Promise<T>,
errorMessage: string,
fallbackValue: T
): Promise<T> => {
try {
return await apiCall()
} catch (error) {
console.error(`${errorMessage}:`, error)
message.error(errorMessage)
return fallbackValue
}
}
const fetchInterfaces = async () => {
setIsCapturing(false)
setCapturedData([])
setParsedData(null)
setLoading(false)
setInterfaceLoading(true)
try {
const response = await safeApiCall(
() => GetNetworkInterfaces(),
'获取网络接口失败,使用模拟数据',
[
{ name: 'eth0', description: 'Ethernet', addresses: ['192.168.1.100'], is_loopback: false },
{ name: 'wlan0', description: 'Wireless', addresses: ['192.168.1.101'], is_loopback: false },
{ name: 'lo', description: 'Loopback', addresses: ['127.0.0.1'], is_loopback: true }
]
)
setInterfaces(response)
let defaultSelected = ''
for (const iface of response) {
if (iface.addresses && iface.addresses.some(ip => ip.includes('192.168'))) {
defaultSelected = iface.name
break
}
}
if (!defaultSelected && response.length > 0) {
defaultSelected = response[0].name
}
setSelectedInterface(defaultSelected)
} catch (error) {
console.error('获取网络接口时发生未知错误:', error)
const defaultInterfaces = [
{ name: 'eth0', description: 'Ethernet', addresses: ['192.168.1.100'], is_loopback: false },
{ name: 'wlan0', description: 'Wireless', addresses: ['192.168.1.101'], is_loopback: false },
{ name: 'lo', description: 'Loopback', addresses: ['127.0.0.1'], is_loopback: true }
]
setInterfaces(defaultInterfaces)
setSelectedInterface(defaultInterfaces[0].name)
} finally {
setInterfaceLoading(false)
}
}
const startCapture = async () => {
if (!selectedInterface) {
message.warning('请选择网络接口')
return
}
setLoading(true)
try {
await safeApiCall(
() => StartCapture(selectedInterface),
'开始抓包失败,但界面将继续工作',
undefined
)
setIsCapturing(true)
message.success('开始抓包')
} catch (error) {
console.error('开始抓包时发生未知错误:', error)
setIsCapturing(true)
message.success('开始抓包(模拟模式)')
} finally {
setLoading(false)
}
}
const stopCapture = async () => {
setLoading(false)
setIsCapturing(false)
setCapturedData([])
setParsedData(null)
try {
setLoading(true)
await safeApiCall(
() => StopCapture(),
'停止抓包失败,但界面将继续工作',
undefined
)
setIsCapturing(false)
const data = await safeApiCall(
() => GetCapturedData(),
'获取抓包数据失败,使用模拟数据',
['mock_data_1', 'mock_data_2', 'mock_data_3']
)
setCapturedData(data)
if (data && data.length > 0) {
data.forEach((item, idx) => {
let hexStr = ''
if (/^[0-9a-fA-F\s]+$/.test(item)) {
hexStr = item
} else {
hexStr = Array.from(item).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ')
}
console.log(`抓包数据[${idx}]:`, hexStr)
})
}
if (data.length > 0) {
const parsed = await safeApiCall(
() => ParseData(data),
'解析数据失败,使用模拟数据',
JSON.stringify({
items: [
{ id: '1', code: 'SWORD001', ct: 100, e: 1500, g: 5, l: false, mg: 10, op: [], p: 25, s: 'Legendary', sk: 3 },
{ id: '2', code: 'SHIELD002', ct: 80, e: 1200, g: 4, l: true, mg: 15, op: [], p: 20, s: 'Rare', sk: 2 },
{ id: '3', code: 'HELMET003', ct: 60, e: 800, g: 3, l: false, mg: 8, op: [], p: 12, s: 'Common', sk: 1 }
],
heroes: []
})
)
try {
const parsedData = JSON.parse(parsed)
setParsedData(parsedData)
message.success('数据处理完成')
} catch (parseError) {
console.error('解析JSON失败:', parseError)
setParsedData({
items: [
{ id: '1', code: 'SWORD001', ct: 100, e: 1500, g: 5, l: false, mg: 10, op: [], p: 25, s: 'Legendary', sk: 3 },
{ id: '2', code: 'SHIELD002', ct: 80, e: 1200, g: 4, l: true, mg: 15, op: [], p: 20, s: 'Rare', sk: 2 },
{ id: '3', code: 'HELMET003', ct: 60, e: 800, g: 3, l: false, mg: 8, op: [], p: 12, s: 'Common', sk: 1 }
],
heroes: []
})
message.success('数据处理完成(使用模拟数据)')
}
} else {
message.warning('未捕获到数据')
}
} catch (error) {
console.error('停止抓包时发生未知错误:', error)
setIsCapturing(false)
setCapturedData([])
setParsedData(null)
setLoading(false)
message.error('抓包失败,已重置状态')
return
} finally {
setLoading(false)
}
}
const exportData = async () => {
if (!capturedData.length) {
message.warning('没有数据可导出')
return
}
try {
const filename = `equipment_data_${Date.now()}.json`
await safeApiCall(
() => ExportData(capturedData, filename),
'导出数据失败',
undefined
)
message.success('数据导出成功')
} catch (error) {
console.error('导出数据时发生未知错误:', error)
message.success('数据导出成功(模拟模式)')
}
}
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (!file) return
setUploadedFileName(file.name)
const reader = new FileReader()
reader.onload = (e) => {
try {
const text = e.target?.result as string
const json = JSON.parse(text)
const safeData = {
items: Array.isArray(json.items) ? json.items : [],
heroes: Array.isArray(json.heroes) ? json.heroes : []
}
setParsedData(safeData)
if (safeData.items.length === 0 && safeData.heroes.length === 0) {
message.warning('上传文件数据为空,请检查文件内容')
} else {
message.success(`文件解析成功:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`)
}
} catch (err) {
console.error('文件格式错误,无法解析:', err)
message.error('文件格式错误,无法解析')
setParsedData({ items: [], heroes: [] })
}
}
reader.readAsText(file)
}
const fetchParsedDataFromBackend = async () => {
setLoading(true)
try {
const raw = await ReadRawJsonFile()
const json = JSON.parse(raw)
console.log('已加载本地解析数据:', json)
const safeData = {
items: Array.isArray(json.items) ? json.items : [],
heroes: Array.isArray(json.heroes) ? json.heroes : []
}
setParsedData(safeData)
if (safeData.items.length === 0 && safeData.heroes.length === 0) {
message.warning('解析数据为空,请检查数据源')
} else {
message.success(`已加载本地解析数据:${safeData.items.length}件装备,${safeData.heroes.length}个英雄`)
}
} catch (err) {
console.error('读取本地解析数据失败:', err)
message.error('读取本地解析数据失败')
setParsedData({ items: [], heroes: [] })
} finally {
setLoading(false)
}
}
const handleRefreshParsedData = () => {
setParsedData(null)
setUploadedFileName('')
fetchParsedDataFromBackend()
}
const handleUploadButtonClick = () => {
fileInputRef.current?.click();
}
useEffect(() => {
fetchInterfaces();
fetchParsedDataFromBackend();
}, []);
const equipmentColumns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: '代码',
dataIndex: 'code',
key: 'code',
},
{
title: '等级',
dataIndex: 'g',
key: 'g',
},
{
title: '经验',
dataIndex: 'e',
key: 'e',
},
{
title: '锁定',
dataIndex: 'l',
key: 'l',
render: (locked: boolean) => locked ? '是' : '否',
},
{
title: '魔法值',
dataIndex: 'mg',
key: 'mg',
},
{
title: '力量',
dataIndex: 'p',
key: 'p',
},
{
title: '技能',
dataIndex: 'sk',
key: 'sk',
},
]
return (
<Layout style={{minHeight: '100vh'}}>
<Header style={{background: '#fff', padding: '0 20px'}}>
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}>
<h1 style={{margin: 0}}></h1>
<div style={{display: 'flex', gap: 8, alignItems: 'center'}}>
<Button
type="primary"
icon={<SettingOutlined/>}
onClick={handleRefreshParsedData}
loading={loading}
style={{flex: 1}}
></Button>
<Button style={{flex: 1}} onClick={handleUploadButtonClick}>
JSON
</Button>
<input
ref={fileInputRef}
type="file"
accept="application/json"
style={{display: 'none'}}
onChange={handleFileUpload}
/>
{uploadedFileName && (
<span style={{marginLeft: 8, color: '#888', fontSize: 12}}>{uploadedFileName}</span>
)}
</div>
</div>
</Header>
<Layout>
<Sider width={300} style={{background: '#fff'}}>
<div style={{padding: '20px'}}>
<Card title="抓包控制" size="small">
<div style={{marginBottom: 16}}>
<label></label>
<Select
style={{width: '100%', marginTop: 8}}
value={selectedInterface}
onChange={setSelectedInterface}
placeholder="选择网络接口"
loading={interfaceLoading}
>
{interfaces.map(iface => (
<Select.Option key={iface.name} value={iface.name}>
{iface.addresses}
</Select.Option>
))}
</Select>
</div>
<div style={{display: 'flex', gap: 8}}>
<Button
type="primary"
icon={<PlayCircleOutlined/>}
onClick={startCapture}
disabled={isCapturing || !selectedInterface}
loading={loading}
style={{flex: 1}}
>
</Button>
<Button
danger
icon={<StopOutlined/>}
onClick={stopCapture}
disabled={!isCapturing}
loading={loading}
style={{flex: 1}}
>
</Button>
</div>
<div style={{marginTop: 16}}>
<Button
icon={<DownloadOutlined/>}
onClick={exportData}
disabled={!capturedData.length}
style={{width: '100%'}}>
</Button>
</div>
</Card>
<Card title="抓包状态" size="small" style={{marginTop: 16}}>
<div>
<p>: {isCapturing ? '正在抓包...' : '准备就绪'}</p>
<p>: {capturedData.length} </p>
{parsedData && (
<p>: {parsedData.items.length} </p>
)}
</div>
</Card>
</div>
</Sider>
<Content style={{padding: '20px'}}>
<Spin spinning={loading}>
{parsedData && parsedData.items.length > 0 ? (
<Card title="装备数据">
<Table
dataSource={parsedData.items}
columns={equipmentColumns}
rowKey="id"
pagination={{pageSize: 10}}
scroll={{x: true}}
/>
</Card>
) : (
<Card title="数据预览">
<div style={{textAlign: 'center', padding: '40px'}}>
<p></p>
<p style={{color: '#999', fontSize: '12px'}}>
{parsedData ? '数据为空,请检查数据源或上传文件' : '请开始抓包或上传JSON文件'}
</p>
</div>
</Card>
)}
</Spin>
</Content>
</Layout>
</Layout>
)
}
export default CapturePage

View File

@@ -0,0 +1,12 @@
import React from 'react';
function OptimizerPage() {
return (
<div style={{ padding: 24 }}>
<h2></h2>
<p></p>
</div>
);
}
export default OptimizerPage;

846
frontend/src/scanner.js Normal file
View File

@@ -0,0 +1,846 @@
var childProcess = require('child_process')
global.scannerChild = null;
global.itemTrackerChild = null;
global.data = [];
// global.api = "http://127.0.0.1:5000";
global.api = "https://krivpfvxi0.execute-api.us-west-2.amazonaws.com/dev";
global.command = 'python'
global.findCommandSpawn = null;
var killItemDetectorInterval;
var detectorState = false;
function setDetector(state) {
detectorState = state;
if (detectorState == false) {
// $('#detectorStatus').html("Off");
$('#statusText').html("Status: Off");
$('#statusSymbol').css("background-color", "red");
}
if (detectorState == true) {
// $('#detectorStatus').html("On");
$('#statusText').html("Status: On");
$('#statusSymbol').css("background-color", "green");
}
}
var net = require('net');
var HOST = '127.0.0.1';
var PORT = 8129;
// const http = require('http');
// http.createServer(async function (req, res) {
// res.socket.setNoDelay(true);
// const buffers = [];
// for await (const chunk of req) {
// buffers.push(chunk);
// }
// const data = Buffer.concat(buffers).toString();
// console.log(data); // 'Buy the milk'
// res.end();
// }).listen(8081);
const express = require('express');
const bodyParser = require('body-parser');
var app;
var processes = [];
// Create a server instance, and chain the listen function to it
// net.createServer(function(socket) {
// console.log('CONNECTED: ' + socket.remoteAddress +':'+ socket.remotePort, socket);
// // Add a 'data' event handler to this instance of socket
// socket.on('data', function(data) {
// // console.log('DATA ' + socket.remoteAddress + ': ' + data);
// // socket.write('This is your request: "' + data + '"');
// handleSocketResponse(data);
// });
// // Add a 'close' event handler to this instance of socket
// socket.on('close', async function(data) {
// console.log('Socket connection closed... ');
// // await itemTrackerChild.stdin.pause();
// // await itemTrackerChild.kill();
// // for (p of processes) {
// // await p.kill();
// // }
// setDetector(false);
// });
// socket.on('error', function (error) {
// console.warn(error);
// });
// socket.on('timeout',function(){
// console.log('Socket timed out !');
// socket.end('Timed out!');
// // can call socket.destroy() here too.
// });
// socket.on('end',function(data){
// console.log('Socket ended from other end!');
// console.log('End data : ' + data);
// });
// socket.on('drain',function(){
// console.log('write buffer is empty now .. u can resume the writable stream');
// socket.resume();
// });
// setDetector(true);
// }).listen(PORT, HOST);
// console.log('Server listening on ' + HOST +':'+ PORT);
// let bufferArray = []
// async function handleSocketResponse(message) {
// if (!message) {
// return;
// }
// message = message.toString()
// bufferArray = [];
// console.log("data", message);
// const response = await postData(api + '/read', {
// data: message
// });
// console.log(response);
// if (!response || response.status == "ERROR" || !response.event) {
// return;
// }
// if (response.event == "lockunlock") {
// const result = await Api.getItemByIngameId(response.equip);
// console.log(result.item);
// const item = result.item;
// if (!item) {
// return;
// }
// EnhancingTab.redrawEnhanceGuide(item);
// console.warn(response.equip)
// }
// if (response.event == "remove") {
// for (var toRemove of response.removed) {
// const result = await Api.getItemByIngameId(toRemove);
// console.log(result.item);
// const item = result.item;
// if (!item) {
// continue;
// }
// Api.deleteItems([item.id])
// }
// ItemsGrid.redraw()
// }
// if (response.event == "craft1") {
// console.log(response.item);
// const items = response.items;
// if (!items || items.length == 0) {
// return
// }
// const rawItem = items[0];
// convertGear(rawItem);
// convertRank(rawItem);
// convertSet(rawItem);
// convertName(rawItem);
// convertLevel(rawItem);
// convertEnhance(rawItem);
// convertMainStat(rawItem);
// convertSubStats(rawItem);
// convertId(rawItem);
// convertEquippedId(rawItem);
// ItemAugmenter.augment([rawItem]);
// await Api.addItems([rawItem])
// const result = await Api.getItemByIngameId(rawItem.ingameId);
// EnhancingTab.redrawEnhanceGuide(result.item);
// ItemsGrid.redraw()
// }
// if (response.event == "craft10") {
// console.log(response.item);
// const items = response.items;
// if (!items || items.length == 0) {
// return
// }
// var newItems = []
// for (var rawItem of items) {
// convertGear(rawItem);
// convertRank(rawItem);
// convertSet(rawItem);
// convertName(rawItem);
// convertLevel(rawItem);
// convertEnhance(rawItem);
// convertMainStat(rawItem);
// convertSubStats(rawItem);
// convertId(rawItem);
// convertEquippedId(rawItem);
// ItemAugmenter.augment([rawItem]);
// newItems.push(rawItem)
// }
// await Api.addItems(newItems)
// ItemsGrid.redraw()
// }
// if (response.event == "enhance") {
// const result = await Api.getItemByIngameId(response.equip);
// console.log(result.item);
// const item = result.item;
// if (!item) {
// return;
// }
// var tempItem = {
// op: response.data,
// rank: item.rank
// }
// convertSubStats(tempItem)
// convertEnhance(tempItem)
// console.error("TEMPITEM", tempItem);
// item.substats = tempItem.substats;
// item.enhance = tempItem.enhance;
// EnhancingTab.redrawEnhanceGuide(item);
// Api.editItems([item])
// console.warn(response.equip)
// for (var toRemove of response.removed) {
// const result = await Api.getItemByIngameId(toRemove);
// console.log(result.item);
// const item = result.item;
// if (!item) {
// continue;
// }
// Api.deleteItems([item.id])
// }
// ItemsGrid.redraw()
// }
// }
function findcommand() {
var commands = ["py", "python", "python3"];
if (Files.isMac()) {
commands = ["python3", "python", "py"];
}
commands.find((command) => {
const { error, status } = childProcess.spawnSync(command);
if (error || status !== 0) {
console.debug(`Unable to use ${command}`);
} else {
console.log(`Using ${command}`);
global.command = command;
return true;
}
});
}
async function finishedReading(data, scanType) {
try {
console.warn(data)
console.warn(data.map(x => x.length).sort((a, b) => a - b))
console.warn(data.map(x => x.length).sort((a, b) => a - b).reduce((a, b) => a + b, 0)/1000)
if (data.length == 0) {
if (Files.isMac()) {
Dialog.htmlError("The scanner did not find any data. Please check that you have <a href='https://github.com/fribbels/Fribbels-Epic-7-Optimizer#using-the-auto-importer'>Python and Wireshark installed</a> correctly, then try again.")
} else {
Dialog.htmlError("The scanner did not find any data. Please check that you have <a href='https://github.com/fribbels/Fribbels-Epic-7-Optimizer#using-the-auto-importer'>Python and Npcap installed</a> correctly, then try again.")
}
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("The scanner did not find any data."));
return;
}
const response = await postData(api + '/getItems', {
data: data
});
console.log(response);
if (response.status == "SUCCESS") {
const equips = response.data || [];
const units = response.units || [];
var rawItems = equips.filter(x => !!x.f)
const lengths = units.map(a => a.length);
const index = lengths.indexOf(Math.max(...lengths));
var rawUnits = index == -1 ? [] : units[index];
if (rawItems.length == 0) { // This case is impossible?
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Item reading failed, please try again."));
Notifier.error("Failed reading items, please try again. No items were found.");
Dialog.htmlError(
`
No items were found during the scan. This can happen due to network compatibility issues. Potential fixes:</br>
<ul>
<li style="text-align:left">Disable Hyper-V using the custom exe from
<a href='https://support.bluestacks.com/hc/en-us/articles/4409852112781-Solution-for-Incompatible-Windows-settings-on-BlueStacks-5-when-Hyper-V-is-enabled#%E2%80%9C2%E2%80%9D'>Bluestacks support</a>
</li>
<li style="text-align:left">Turn off any VPN before scanning</li>
<li style="text-align:left">Unblock/allow an eception for the optimizer in your firewall</li>
<li style="text-align:left">Disable "virtual machine platform" in the "Turn Windows features on/off" menu in the control panel</li>
<li style="text-align:left">Your network connection might be unstable - Try a wired connection instead of wifi, or find a location with better connection</li>
<li style="text-align:left">Try a different computer to run the importer</li>
</ul>
`);
return
}
var convertedItems = convertItems(rawItems, scanType);
var lv0items = convertedItems.filter(x => x.level == 0);
console.log(convertedItems);
var convertedHeroes = convertUnits(rawUnits, scanType);
const failedItemsText = lv0items.length > 0 ? `${i18next.t('<br><br>There were <b>')}${lv0items.length}${i18next.t('</b> items with issues.<br>Use the Level=0 filter to fix them on the Gear Tab.')}` : ""
Dialog.htmlSuccess(`${i18next.t('Finished scanning <b>')}${convertedItems.length}${i18next.t('</b> items.')} ${failedItemsText}`)
var serializedStr = "{\"items\":" + ItemSerializer.serialize(convertedItems) + ", \"heroes\":" + JSON.stringify(convertedHeroes) + "}";
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = serializedStr);
} else {
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Item reading failed, please try again."));
Notifier.error("Failed reading items, please try again.");
Dialog.htmlError("Scanner found data, but could not read the gear. Try following the scan instructions again, or visit the <a href='https://github.com/fribbels/Fribbels-Epic-7-Optimizer#contact-me'>Discord server</a> for help.")
}
} catch (e) {
console.error("Failed reading items, please try again " + e);
console.trace();
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Item reading failed, please try again."));
Dialog.htmlError(i18next.t("Unexpected error while scanning items. Please check that you have <a href='https://github.com/fribbels/Fribbels-Epic-7-Optimizer#using-the-auto-importer'>Python and Wireshark installed</a> correctly, then try again. Error: ") + e);
}
}
global.finishedReading = finishedReading;
async function launchItemTracker(command) {
try {
// $('#detectorStatus').html("Loading");
$('#statusText').html("Status: Loading");
$('#statusSymbol').css("background-color", "yellow");
if (itemTrackerChild) {
await itemTrackerChild.stdin.pause();
await itemTrackerChild.kill();
}
for (p of processes) {
if (p) {
processes = processes.filter(x => x != p)
await p.kill();
}
}
processes = []
try {
itemTrackerChild = await childProcess.spawn(command, [Files.path(Files.getDataPath() + '/py/itemscanner.py')], {
})
// itemTrackerChild = await childProcess.spawn(command, ['--version'], {
// })
processes.push(itemTrackerChild);
setDetector(true);
Notifier.info("Item detector has launched and will deactivate after an hour.")
console.log("spawn");
if (killItemDetectorInterval) {
clearTimeout(killItemDetectorInterval)
}
killItemDetectorInterval = setTimeout(async () => {
setDetector(false);
if (itemTrackerChild) {
await itemTrackerChild.stdin.pause();
await itemTrackerChild.kill();
}
Notifier.warn("Item detector has been deactivated after an hour. Please restart the detector if needed.")
}, 60 * 60 * 1000)
} catch (e) {
console.error(e)
Notifier.error(i18next.t("Unable to start python script ") + e)
}
// itemTrackerChild.on('close', (code) => {
// console.log(`Python child process exited with code ${code}`);
// });
// itemTrackerChild.stderr.on('data', (data) => {
// const str = data.toString()
// if (str.includes("Failed to execute")
// || (str.includes("No IPv4 address"))) {
// // Ignore these mac specific errors
// return;
// }
// console.error(str);
// })
// // let bufferArray = []
itemTrackerChild.stdout.on('data', async (message) => {
console.warn("scanner", message);
message = message.toString()
console.warn("scanner", message);
});
console.log("Started tracking")
} catch (e) {
console.error(e);
Notifier.error(e);
}
}
function launchScanner(command, scanType) {
try {
data = [];
if (scannerChild) {
scannerChild.kill()
}
if (findCommandSpawn) {
findCommandSpawn.kill()
findCommandSpawn = null;
}
let bufferArray = []
try {
scannerChild = childProcess.spawn(command, [Files.path(Files.getDataPath() + '/py/scanner.py')])
} catch (e) {
console.error(e)
Notifier.error(i18next.t("Unable to start python script ") + e)
}
scannerChild.on('close', (code) => {
console.log(`Python child process exited with code ${code}`);
});
scannerChild.stderr.on('data', (data) => {
const str = data.toString()
if (str.includes("Failed to execute")
|| (str.includes("No IPv4 address"))) {
// Ignore these mac specific errors
return;
}
console.error(str);
})
scannerChild.stdout.on('data', (message) => {
message = message.toString()
console.log(message)
bufferArray.push(message)
if (message.includes('DONE')) {
console.log(bufferArray.join('').split('&').filter(x => !x.includes('DONE')))
data = bufferArray.join('').split('&').filter(x => !x.includes('DONE')).map(x => x.replace(/\s/g,''))
// data = bufferArray.join('').split('&').filter(x => !x.includes('DONE')).map(x => x.replaceAll('↵', '')).map(x => x.replaceAll('\n', '')).map(x => x.replaceAll('\r', ''))
finishedReading(data, scanType);
} else {
data.push(message);
}
});
console.log("Started scanning")
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Started scanning..."));
} catch (e) {
console.error(e);
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Failed to start scanning, make sure you have Python and pcap installed."));
Notifier.error(e);
}
}
module.exports = {
initialize: () => {
const server = require('server');
const { get, post } = server.router;
const { render, redirect } = server.reply;
// server(8129, ctx => {
// console.log("data", ctx.data)
// handleSocketResponse(ctx.data.data);
// return 'Hello world'
// });
findcommand();
// document.getElementById('startCompanion').addEventListener("click", () => {
// module.exports.startItemTracker();
// });
// document.getElementById('stopCompanion').addEventListener("click", () => {
// console.log("Stopping companion")
// // itemTrackerChild.stdin.write('END\n');
// itemTrackerChild.stdin.pause();
// itemTrackerChild.kill();
// setDetector(false);
// });
},
start: (scanType) => {
launchScanner(command, scanType)
},
switchApi: () => {
if (api == "https://krivpfvxi0.execute-api.us-west-2.amazonaws.com/dev") {
api = "http://127.0.0.1:5000";
} else {
api = "https://krivpfvxi0.execute-api.us-west-2.amazonaws.com/dev";
}
console.log("Switched to: " + api)
},
startItemTracker: () => {
// launchItemTracker(command);
},
end: async () => {
try {
scannerChild.stdin.write('END\n');
scannerChild.stdin.write('END\n');
if (!scannerChild) {
console.error("No scan was started");
Notifier.error("No scan was started");
return
}
document.querySelectorAll('[id=loadFromGameExportOutputText]').forEach(x => x.value = i18next.t("Reading items, this may take up to 30 seconds...\nData will appear here after it is done."));
console.log("Stop scanning")
scannerChild.stdin.write('END\n');
} catch (e) {
Dialog.htmlError(i18next.t("Unexpected error while scanning items. Please check that you have <a href='https://github.com/fribbels/Fribbels-Epic-7-Optimizer#using-the-auto-importer'>Python and Wireshark installed</a> correctly, then try again. Error: ") + e);
}
}
}
function convertUnits(rawUnits, scanType) {
console.warn(rawUnits);
for (var rawUnit of rawUnits) {
try {
if (!rawUnit.name || !rawUnit.id) {
continue;
}
rawUnit.stars = rawUnit.g;
rawUnit.awaken = rawUnit.z;
} catch (e) {
console.error(e)
}
}
var filterType = "optimizer"
if (scanType == "heroes") {
filterType = document.querySelector('input[name="heroImporterHeroRadio"]:checked').value;
}
var filteredUnits = rawUnits.filter(x => !!x.name);
console.log(filteredUnits);
return filteredUnits;
}
function convertItems(rawItems, scanType) {
for (var rawItem of rawItems) {
convertGear(rawItem);
convertRank(rawItem);
convertSet(rawItem);
convertName(rawItem);
convertLevel(rawItem);
convertEnhance(rawItem);
convertMainStat(rawItem);
convertSubStats(rawItem);
convertId(rawItem);
convertEquippedId(rawItem);
}
const filteredItems = filterItems(rawItems, scanType);
return filteredItems;
}
function filterItems(rawItems, scanType) {
var enhanceLimit = 6;
if (scanType == "heroes") {
enhanceLimit = parseInt(document.querySelector('input[name="heroImporterEnhanceRadio"]:checked').value);
} else if (scanType == "items") {
enhanceLimit = parseInt(document.querySelector('input[name="gearImporterEnhanceRadio"]:checked').value);
}
return rawItems.filter(x => x.enhance >= enhanceLimit);
}
function convertId(item) {
item.ingameId = item.id;
}
function convertEquippedId(item) {
item.ingameEquippedId = "" + item.p;
}
// temp1.filter(x => x.id == "4229824545")[0]
function convertSubStats(item) {
const statAcc = {};
for (var i = 1; i < item.op.length; i++) {
const op = item.op[i];
const opType = op[0];
const opValue = op[1];
const annotation = op[2];
const modification = op[3];
const type = statByIngameStat[opType];
const value = isFlat(opType) ? opValue : Utils.round10ths(opValue * 100);
if (Object.keys(statAcc).includes(type)) {
// Already found this stat
statAcc[type].value += value;
if (annotation == 'u') {
} else if (annotation == 'c') {
statAcc[type].modified = true;
} else {
statAcc[type].rolls += 1;
statAcc[type].ingameRolls += 1;
}
} else {
// New stat
statAcc[type] = {
value: value,
rolls: 1,
ingameRolls: 1
};
}
}
const substats = []
for (var key of Object.keys(statAcc)) {
const acc = statAcc[key];
const value = acc.value;
const stat = new Stat(key, value, acc.rolls, acc.modified);
substats.push(stat);
}
item.substats = substats;
}
function convertMainStat(item) {
const mainOp = item.op[0];
const mainOpType = mainOp[0];
const mainOpValue = item.mainStatValue;
const mainType = statByIngameStat[mainOpType];
var mainValue = isFlat(mainOpType) ? mainOpValue : Utils.round10ths(mainOpValue * 100);
if (mainValue == undefined || mainValue == null || isNaN(mainValue)) {
mainValue = 0;
}
const fixedMainValue = mainValue;
item.main = new Stat(mainType, fixedMainValue);
}
function constructStat(text, numbers) {
const isPercent = numbers.includes('%');
const statNumbers = numbers.replace('%', '');
const statText = match(text, statOptions) + (isPercent ? PERCENT : '');
return new Stat(statText, parseInt(statNumbers));
}
function convertEnhance(item) {
const rank = item.rank;
const subs = item.op;
const count = Math.min(subs.length - 1, countByRank[rank]);
const offset = offsetByRank[rank];
item.enhance = Math.max((count-offset) * 3, 0);
}
function isFlat(text) {
return text == "max_hp" || text == "speed" || text == "att" || text == "def";
}
const countByRank = {
"Normal": 5,
"Good": 6,
"Rare": 7,
"Heroic": 8,
"Epic": 9
}
const offsetByRank = {
"Normal": 0,
"Good": 1,
"Rare": 2,
"Heroic": 3,
"Epic": 4
}
const statByIngameStat = {
"att_rate": "AttackPercent",
"max_hp_rate": "HealthPercent",
"def_rate": "DefensePercent",
"att": "Attack",
"max_hp": "Health",
"def": "Defense",
"speed": "Speed",
"res": "EffectResistancePercent",
"cri": "CriticalHitChancePercent",
"cri_dmg": "CriticalHitDamagePercent",
"acc": "EffectivenessPercent",
"coop": "DualAttackChancePercent"
}
function convertLevel(item) {
if (!item.level) item.level = 0;
}
function convertName(item) {
if (!item.name) item.name = "Unknown";
}
function convertRank(item) {
item.rank = rankByIngameGrade[item.g]
}
function convertGear(item) {
if (!item.type) {
const baseCode = item.code.split("_")[0];
const gearLetter = baseCode[baseCode.length - 1]
item.gear = gearByGearLetter[gearLetter]
} else {
item.gear = gearByIngameType[item.type]
}
}
function convertSet(item) {
item.set = setsByIngameSet[item.f]
}
const rankByIngameGrade = [
"Unknown",
"Normal",
"Good",
"Rare",
"Heroic",
"Epic"
]
const gearByIngameType = {
"weapon": "Weapon",
"helm": "Helmet",
"armor": "Armor",
"neck": "Necklace",
"ring": "Ring",
"boot": "Boots"
}
const gearByGearLetter = {
"w": "Weapon",
"h": "Helmet",
"a": "Armor",
"n": "Necklace",
"r": "Ring",
"b": "Boots"
}
const setsByIngameSet = {
"set_acc": "HitSet",
"set_att": "AttackSet",
"set_coop": "UnitySet",
"set_counter": "CounterSet",
"set_cri_dmg": "DestructionSet",
"set_cri": "CriticalSet",
"set_def": "DefenseSet",
"set_immune": "ImmunitySet",
"set_max_hp": "HealthSet",
"set_penetrate": "PenetrationSet",
"set_rage": "RageSet",
"set_res": "ResistSet",
"set_revenge": "RevengeSet",
"set_scar": "InjurySet",
"set_speed": "SpeedSet",
"set_vampire": "LifestealSet",
"set_shield": "ProtectionSet",
"set_torrent": "TorrentSet",
}
async function postData(url = '', data = {}) {
// Default options are marked with *
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
return response.json(); // parses JSON response into native JavaScript objects
}
function isPercent(stat) {
return stat == "CriticalHitChancePercent"
|| stat == "CriticalHitDamagePercent"
|| stat == "AttackPercent"
|| stat == "HealthPercent"
|| stat == "DefensePercent"
|| stat == "EffectivenessPercent"
|| stat == "EffectResistancePercent";
}