Skip to content

Commit

Permalink
feat: initial implement of new data sniffer
Browse files Browse the repository at this point in the history
  • Loading branch information
KochiyaOcean committed Oct 14, 2023
1 parent 6d10a69 commit 6071309
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 13 deletions.
2 changes: 0 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,10 @@ electronRemote.initialize()

require('./lib/module-path').setAllowedPath(global.ROOT)
const config = require('./lib/config')
const proxy = require('./lib/proxy')
const shortcut = require('./lib/shortcut')
const { warn, error } = require('./lib/utils')
const dbg = require('./lib/debug')
require('./lib/updater')
proxy.setMaxListeners(30)
require('./lib/tray')
require('./lib/screenshot')

Expand Down
64 changes: 64 additions & 0 deletions assets/js/webview-preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ const config = remote.require('./lib/config')

window.ipc = remote.require('./lib/ipc')

const url = require('url')
const gameAPIBroadcaster = remote.require('./lib/game-api-broadcaster')

document.addEventListener('DOMContentLoaded', (e) => {
if (config.get('poi.misc.dmmcookie', false) && location.hostname.includes('dmm')) {
const now = new Date()
Expand Down Expand Up @@ -156,6 +159,67 @@ const handleDocumentReady = () => {

handleDocumentReady()

const hackXHR = () => {
const hack = () => {
const OriginalXMLHttpRequest = XMLHttpRequest
window.XMLHttpRequest = function () {
let method, reqUrl, reqBody
const req = new OriginalXMLHttpRequest()

// Hack open method
req.originOpen = req.open
req.open = (...params) => {
method = params[0]
reqUrl = params[1]
return req.originOpen(...params)
}

// Hack send method
req.originSend = req.send
req.send = (body) => {
reqBody = body
return req.originSend(body)
}

// Send event
req.addEventListener('load', () => {
const resUrl = req.responseURL || reqUrl
gameAPIBroadcaster.sendRequest(
method,
[undefined, url.parse(resUrl).pathname, resUrl],
reqBody,
)
})
req.addEventListener('loadend', () => {
if (!req.responseType || ['json', 'document', 'text'].includes(req.responseType)) {
gameAPIBroadcaster.sendResponse(
method,
[undefined, url.parse(req.responseURL).pathname, req.responseURL],
reqBody,
req.response,
req.responseType,
req.status,
)
}
})
req.addEventListener('error', () => {
const resUrl = req.responseURL || reqUrl
gameAPIBroadcaster.sendError([undefined, url.parse(resUrl).pathname, resUrl], req.status)
})

return req
}
}

if (document.body) {
hack()
} else {
document.addEventListener('DOMContentLoaded', hack)
}
}

hackXHR()

if (
window.location
.toString()
Expand Down
113 changes: 113 additions & 0 deletions lib/game-api-broadcaster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import EventEmitter from 'events'
import querystring from 'querystring'
import fs from 'fs-extra'
import path from 'path'
import URL from 'url'

type RequestOrigin = string | undefined
type PathName = string
type Url = string
type RequestInfo = [RequestOrigin, PathName, Url]

interface KancolleServer {
num?: number
name?: string
ip?: string
}

interface KancolleServerInfo {
[ip: string]: KancolleServer
}

class GameAPIBroadcaster extends EventEmitter {
serverList: KancolleServerInfo = fs.readJsonSync(path.join(ROOT, 'assets', 'data', 'server.json'))

serverInfo: KancolleServer = {}

sendRequest = (method: string, requestInfo: RequestInfo, rawReqBody: string) => {
this.emit(
'network.on.request',
method,
requestInfo,
JSON.stringify(querystring.parse(rawReqBody || '')),
Date.now(),
)
}

sendResponse = (
method: string,
requestInfo: RequestInfo,
rawReqBody: string,
rawResBody: unknown,
resType: XMLHttpRequestResponseType,
statusCode?: number,
) => {
this.updateKanColleServer(requestInfo)
const resBody = this.parseResponseBody(rawResBody, resType)
if (resBody && statusCode === 200) {
this.emit(
'network.on.response',
method,
requestInfo,
resBody,
JSON.stringify(querystring.parse(rawReqBody || '')),
Date.now(),
)
}
}

sendError = (requestInfo: RequestInfo, statusCode?: number) => {
this.emit('network.error', requestInfo, statusCode)
}

private parseResponseBody = (rawResBody: unknown, resType: XMLHttpRequestResponseType) => {
switch (resType) {
case 'arraybuffer':
case 'blob': {
// not parseable
return undefined
}
case 'json': {
return JSON.stringify(rawResBody)
}
case 'document':
case 'text':
default: {
try {
const bodyStr = rawResBody as string
const parsed = bodyStr?.startsWith('svdata=') ? bodyStr.substring(7) : bodyStr
JSON.parse(parsed)
return parsed
} catch (e) {
return undefined
}
}
}
}

private updateKanColleServer = (requestInfo: RequestInfo) => {
const [, pathName, reqUrl] = requestInfo
if (this.isKancolleGameApi(pathName)) {
const { hostname } = URL.parse(reqUrl)
if (hostname) {
if (this.serverList[hostname]) {
this.serverInfo = {
...this.serverList[hostname],
ip: hostname,
}
} else {
this.serverInfo = {
num: -1,
name: '__UNKNOWN',
ip: hostname,
}
}
this.emit('kancolle.server.change', this.serverInfo)
}
}
}

private isKancolleGameApi = (pathname: PathName = ''): boolean => pathname.startsWith('/kcsapi')
}

export default new GameAPIBroadcaster()
6 changes: 3 additions & 3 deletions views/env-parts/data-resolver.es
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { onGameRequest, onGameResponse } from 'views/redux/reducer-factory'
import * as remote from '@electron/remote'

const proxy = remote.require('./lib/proxy')
const gameAPIBroadcaster = remote.require('./lib/game-api-broadcaster')

const isGameApi = (pathname) => pathname.startsWith('/kcsapi')

Expand Down Expand Up @@ -168,7 +168,7 @@ const addProxyListener = () => {
if (!window.listenerStatusFlag) {
window.listenerStatusFlag = true
for (const eventName in proxyListener) {
proxy.addListener(eventName, proxyListener[eventName])
gameAPIBroadcaster.addListener(eventName, proxyListener[eventName])
}
}
}
Expand All @@ -183,7 +183,7 @@ window.addEventListener('unload', () => {
if (window.listenerStatusFlag) {
window.listenerStatusFlag = false
for (const eventName in proxyListener) {
proxy.removeListener(eventName, proxyListener[eventName])
gameAPIBroadcaster.removeListener(eventName, proxyListener[eventName])
}
}
})
1 change: 0 additions & 1 deletion views/env.es
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ Object.remoteClone = (obj) => JSON.parse(window.remote.require('./lib/utils').re
// Node modules
const originConfig = remote.require('./lib/config')
window.ipc = remote.require('./lib/ipc')
window.proxy = remote.require('./lib/proxy')
window.CONST = Object.remoteClone(remote.require('./lib/constant'))
window.config = {}
for (const key in originConfig) {
Expand Down
1 change: 1 addition & 0 deletions views/kan-game-wrapper.es
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ export class KanGameWrapper extends Component {
ref={this.webview}
disablewebsecurity
allowpopups
nodeintegrationinsubframes
webpreferences="allowRunningInsecureContent=no, backgroundThrottling=no, contextIsolation=no, sandbox=no"
preload={preloadUrl}
audioMuted={muted}
Expand Down
10 changes: 3 additions & 7 deletions views/services.es
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { observer, observe } from 'redux-observers'
import { store } from 'views/create-store'
import i18next from 'views/env-parts/i18next'

const proxy = remote.require('./lib/proxy')

const gameAPIBroadcaster = remote.require('./lib/game-api-broadcaster')
import './services/update'
import './services/layout'
import './services/welcome'
Expand All @@ -24,19 +23,16 @@ import { gameRefreshPage, gameRefreshPageIgnoringCache, gameReload } from './ser

// Update server info
const setUpdateServer = (dispatch) => {
const t = setInterval(() => {
const { ip, num: id, name } = proxy.getServerInfo()
gameAPIBroadcaster.addListener('kancolle.server.change', ({ ip, num: id, name }) => {
if (window.getStore('info.server.ip') !== ip) {
if (ip) {
dispatch({
type: '@@ServerReady',
serverInfo: { ip, id, name },
})
}
} else {
clearInterval(t)
}
}, 1000)
})
}
const serverObserver = observer(
(state) => state.info.server.ip,
Expand Down

0 comments on commit 6071309

Please sign in to comment.