diff --git a/.gitignore b/.gitignore index e496407..e7a4f51 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ UserData .vscode data PaipuAnalyzer -result \ No newline at end of file +result +tempCodeRunner* +lib/config.json \ No newline at end of file diff --git a/README.md b/README.md index 67ec3d9..016992b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MajsoulPaipuAnalyzer -自制雀魂牌谱分析工具。 +自制雀魂牌谱分析工具。支持国服、日服、国际服。日服和国际服仅进行了少量测试。 目前仅支持四人麻将牌谱分析,分析项目参考天鳳の牌譜解析プログラム的项目实现。目前实现了和牌、放铳、立直、其他、终盘大类中的大部分数据,其他有待实现。 @@ -88,11 +88,11 @@ result中即为结果。 ### 牌谱获取及转换 -首先运行Electron应用MajsoulPaipuCrawler。程序会显示雀魂窗口和SimpleMahjong窗口。为了能够分析bug,默认两个页面会打开开发者工具。由于技术原因,首次登陆雀魂以后需要刷新界面(杂项-刷新)才能正常解析牌谱。 +首先运行Electron应用MajsoulPaipuCrawler。程序会显示雀魂窗口和SimpleMahjong窗口。为了能够分析bug,默认两个页面会打开开发者工具。由于技术原因,第三方账户登陆可能出现问题,请使用网页-登陆专用窗口登陆后刷新雀魂窗口。默认为国服,如果要切换到其他服请在网页菜单中选择。 在成功登陆后,可以选择牌谱-查看已有牌谱情报。如果没有出现错误那么说明已经可以开始获取牌谱。然后进入牌谱界面,此时再次查看牌谱情报应该会成功获取到约10个牌谱情报。由于雀魂牌谱采用每次加载约10个且滚到牌谱页面底部才触发加载的方式,为了获取全部牌谱需要将滚动条滚到最底部。可以采用鼠标拖动滚动条并在底部抽搐的方式。 -在确认所有牌谱已被加载出来后,点击牌谱-下载&转换牌谱来对获取到的牌谱进行下载和格式转换。转换会跳过三麻牌谱。转换进度显示在SimpleMahjong窗口的左上角。需要注意的是,由于技术原因,最近几天打完的牌谱目前并不能成功获取,请过几天再次尝试。 +在确认所有牌谱已被加载出来后,点击牌谱-下载&转换牌谱来对获取到的牌谱进行下载和格式转换。转换会跳过三麻和比赛牌谱。转换进度显示在SimpleMahjong窗口的左上角。需要注意的是,由于技术原因,最近几天打完的牌谱目前并不能成功获取,请过几天再次尝试。 每个账户会有自己的独立ID,这个ID和加好友时候的那个ID是不一样的,游戏里大概不能直接看到?如果登陆多个账户,会将每个账户的资料按照ID分别存储,不会混在一起。牌谱下载及转换内容存储于data文件夹,避免重复下载和转换,每次进行下载仅会尝试下载转换未下载的牌谱。存储方式不再赘述,感兴趣的人翻一翻大概就能明白了。 @@ -106,7 +106,7 @@ result中即为结果。 ## 已知问题 -繁体中文的系统暂时无法正确显示。 +分析国际服数据时请确认自己的网络环境可以基本流畅访问Google Facebook等网站,否则很可能无法正常获取牌谱。 Ubuntu高版本中可能会出现GUI将可执行文件当做动态链接库的情况。目前没有找到解决方法,请使用Terminal执行。 @@ -114,20 +114,20 @@ Ubuntu高版本中可能会出现GUI将可执行文件当做动态链接库的 由于设计时从未参加过比赛场,发现问题后开了个比赛场测试并发现比赛场数据和想象中有较大出入,目前会直接忽略比赛场牌谱数据,在以后做了相关实现后加入。 -出现部分牌谱的和牌数据采用新格式,出现原因和时机不明。目前先忽略这些牌谱。 - 由于本人较菜没有上圣没法打王座,相关牌谱可能会出现bug。 ## 联系 -如果发现任何bug,可以通过github或是jzjqz17@gmail.com说明。 +如果发现任何bug,可以通过[github](https://github.com/zyr17/MajsoulPaipuAnalyzer/issues)或是jzjqz17@gmail.com说明。 ## 致谢 -该项目使用或曾经使用了这些项目的代码,感谢他们。 +该项目使用或曾经使用了这些项目的资源,感谢他们。 [wsHook](https://github.com/skepticfx/wshook) [CJsonObject](https://github.com/Bwar/CJsonObject) -[wssip](https://github.com/nccgroup/wssip) \ No newline at end of file +[wssip](https://github.com/nccgroup/wssip) + +[牌面图像](https://mj-king.net/sozai/) \ No newline at end of file diff --git a/SimpleMahjong/const.js b/SimpleMahjong/const.js index 675d6fd..bb35715 100644 --- a/SimpleMahjong/const.js +++ b/SimpleMahjong/const.js @@ -47,6 +47,19 @@ const hanname = [ ['国士无双十三面', 'kokushijusan'], ['大四喜', 'daisushi'], ['小四喜', 'shousushi'], ['四杠子', 'sukantsu'], ['宝牌', 'dora'], ['里宝牌', 'ura'], ['红宝牌', 'aka'], ]; +var majsoulfanid2name = [ + '', + '门前清自摸和','立直','枪杠','岭上开花','海底摸月', + '河底捞鱼','役牌 白','役牌 发','役牌 中','役牌:门风牌', + '役牌:场风牌','断幺九','一杯口','平和','混全带幺九', + '一气通贯','三色同顺','两立直','三色同刻','三杠子', + '对对和','三暗刻','小三元','混老头','七对子', + '纯全带幺九','混一色','二杯口','清一色','一发', + '宝牌','红宝牌','里宝牌','拔北宝牌','天和', + '地和','大三元','四暗刻','字一色','绿一色', + '清老头','国士无双','小四喜','四杠子','九莲宝灯', + '八连庄','纯正九莲宝灯','四暗刻单骑','国士无双十三面','大四喜' +]; function emptymatchdata(){ return { 'title': '', diff --git a/SimpleMahjong/paipu.js b/SimpleMahjong/paipu.js index 35c9a11..d0d92ef 100644 --- a/SimpleMahjong/paipu.js +++ b/SimpleMahjong/paipu.js @@ -173,7 +173,7 @@ class majsoulpaipuanalyze{ var discardtile = String.fromCharCode(arr[start]) + String.fromCharCode(arr[start + 1]); start += 2; var reach = 0, moqie = false, naki = false, dora = ''; - var nakiwaynum = null, nakihandtile = null; + var nakiwaynum = null, nakihandtile = ''; var unknownnumber = ''; for (; start < arr.length; ){ if (arr[start] == 0x18){ @@ -187,7 +187,7 @@ class majsoulpaipuanalyze{ var nakiend = start + nakilength; var nakiwaynum = arr[start + 5]; [nakihandlength, start] = this.getnumber(arr, start + 7); - [nakihandtile, start] = this.getstring(arr, start, nakihandlength); + if (nakihandlength < 1000) [nakihandtile, start] = this.getstring(arr, start, nakihandlength); start = nakiend; } else if (arr[start] == 0x28){ @@ -415,7 +415,7 @@ class majsoulpaipuanalyze{ tiles: tiles }; //console.log(this.onepaipu.gamedata.extra.id, whonum, typename[typenum], View.gamerecord.length); - if (typenum < 1 || typenum > 3) this.sendmessage(whonum + ' LiuJu, ' + typename[typenum], result); + this.sendmessage(whonum + ' LiuJu, ' + typename[typenum], result); return whonum; } @@ -528,29 +528,41 @@ class majsoulpaipuanalyze{ } else if (arr[start] == 0x62){ //番种 - ////TODO: 出现牌谱使用编号代替役名。找到役和编号对应列表。 - let len6, len, name; + let len6, len, name, han; [len6, start] = this.getnumber(arr, start + 1); - if (arr[start] == 0x0a){ - [len, start] = this.getnumber(arr, start + 1); - [name, start] = this.decodeUTF8(arr, start, len); - let han = arr[start + 1]; - if ( - name == '四暗刻单骑' || - name == '国士无双十三面' || - name == '大四喜' || - name == '纯正九莲宝灯' - ) - han = 2; - if (yakumanyaku) - han *= 13; - agarires.han.push([name, han]); - start += 2; - } - else{ - start += len6; - this.rawdata = undefined; + for (let end6 = len6 + start; start < end6; ){ + if (arr[start] == 0x0a){ + [len, start] = this.getnumber(arr, start + 1); + [name, start] = this.decodeUTF8(arr, start, len); + } + else if (arr[start] == 0x10){ + han = arr[start + 1]; + if ( + name == '四暗刻单骑' || + name == '国士无双十三面' || + name == '大四喜' || + name == '纯正九莲宝灯' + ) + han = 2; + if (yakumanyaku) + han *= 13; + start += 2; + } + else if (arr[start] == 0x18){ + name = majsoulfanid2name[arr[start + 1]]; + start += 2; + } + else{ + let cmd; + [cmd, start] = this.getnumber(arr, start); + let addlen = cmd % 8 == 2; + unknownnumber += ' ' + cmd; + [cmd, start] = this.getnumber(arr, start); + if (addlen) + start += cmd; + } } + agarires.han.push([name, han]); } else if (arr[start] == 0x68){ //符 @@ -880,7 +892,7 @@ class majsoulpaipuanalyze{ } console.log('paipu #' + this.nowgamedata + ' complete. ' + d.toString()); if (this.rawdata == undefined){ - console.log("but it's new type paipu, skip it for now."); + console.log("but it's an unsupported paipu, skip it for now."); } } else{ diff --git a/appveyor-linux.yml b/appveyor-linux.yml index d04de19..9b7149b 100644 --- a/appveyor-linux.yml +++ b/appveyor-linux.yml @@ -1,4 +1,4 @@ -version: 0.1.5.{build} +version: 0.2.0.{build} image: Ubuntu1804 branches: only: diff --git a/appveyor-win.yml b/appveyor-win.yml index cef0768..7bdd4b3 100644 --- a/appveyor-win.yml +++ b/appveyor-win.yml @@ -1,4 +1,4 @@ -version: 0.1.5.{build} +version: 0.2.0.{build} branches: only: - master diff --git a/doc/README.txt b/doc/README.txt index 2ffefa3..776a9f5 100644 --- a/doc/README.txt +++ b/doc/README.txt @@ -4,18 +4,19 @@ MajsoulPaipuAnalyzer简易使用说明 操作流程 1. 运行MajsoulPaipuCrawler。会出现两个窗口,雀魂网页窗口和数据分析窗口。 -2. 已经自动登陆雀魂则跳过该步。未登陆则登陆雀魂,并在成功进入主界面后选择 杂项-刷新 来刷新页面重新进入。 -3. 点击牌谱,选择 牌谱-查看已有牌谱情报, 确认成功获取到了用户ID,以及部分牌谱信息。对于已经获取过的牌谱信息会缓存。 -4. 由于雀魂不会一次性将所有牌谱信息发送,请将滚动条往下拖以加载更早的牌谱,直到想要分析的牌谱全部出现在了牌谱列表中。 -5. 转到数据分析窗口,选择 牌谱-下载&转换牌谱。会弹窗展示下载牌谱数量,然后窗口左上角会给出进度。全部完成会弹窗提示。 -6. 由于技术原因,较新的牌谱(约3天内)无法下载,请以后重试。 -7. 关闭两个窗口,编辑config.json设置分析牌谱类型。常用设置在下文给出。 -8. 运行PaipuAnalyzer.exe查看结果。 +2. 选择服务器,默认进入国服,如果要进入其他服务器请选择网页-对应服务器。 +3. 登陆雀魂账号。如果使用第三方账号登陆,且发现无法成功登陆请使用网页-登陆专用窗口进行登陆,然后关闭专用窗口并刷新雀魂网页窗口。 +4. 点击牌谱,选择 牌谱-查看已有牌谱情报, 确认成功获取到了用户ID,以及部分牌谱信息。对于已经获取过的牌谱信息会缓存。 +5. 由于雀魂不会一次性将所有牌谱信息发送,请将滚动条往下拖以加载更早的牌谱,直到想要分析的牌谱全部出现在了牌谱列表中。 +6. 转到数据分析窗口,选择 牌谱-下载&转换牌谱。会弹窗展示下载牌谱数量,然后窗口左上角会给出进度。全部完成会弹窗提示。 +7. 由于技术原因,较新的牌谱(约3天内)无法下载,请以后重试。 +8. 关闭两个窗口,编辑config.json设置分析牌谱类型。常用设置在下文给出。 +9. 运行PaipuAnalyzer.exe查看结果。 config.json常用设置 --1:第二行language默认为简体中文zh-CN,若需要使用简体转的繁体中文请改成zh-TW,瞎翻的英文请改成en-US。注意繁中目前没有经过任何测试不保证可用,若不可用请等待后续版本。。 +-1:第二行language默认为简体中文zh-CN,若需要使用简体转的繁体中文请改成zh-TW,瞎翻的英文请改成en-US。 0. 啥都不改默认分析全部牌谱 diff --git a/doc/config.md b/doc/config.md index 4672e24..796a708 100644 --- a/doc/config.md +++ b/doc/config.md @@ -1,5 +1,5 @@ { - 指牌谱分析使用的语言。目前仅对应中文和基本不能读的英文 + 指牌谱分析使用的语言。目前仅对应简体中文,机翻繁体中文和基本不能读的英文 "language": "zh-CN", 指牌谱来源。目前来源均为雀魂 diff --git a/doc/release-notes.txt b/doc/release-notes.txt new file mode 100644 index 0000000..14fedc9 --- /dev/null +++ b/doc/release-notes.txt @@ -0,0 +1,6 @@ +0.2.0 + +1.对于雀魂0.5版本中和牌数据格式变化的对应 +2.增加选项支持日服和国际服 +3.针对微信登陆的BUG修复;同时为第三方登陆增加登陆专用窗口来避免第三方登陆的问题 +4.牌谱转换时途中流局BUG修复 \ No newline at end of file diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 0000000..c0f78a5 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,34 @@ +"use strict"; + +const fs = require('fs'); +const path = require('path'); + +var config = undefined; + +function loadconfig(){ + let p = path.join(__dirname, 'config.json'); + if (fs.existsSync(p)) + config = JSON.parse(fs.readFileSync(p).toString()); + else{ + config = { + DefaultURL: 'https://majsoul.union-game.com/0/' + }; + fs.writeFileSync(p, JSON.stringify(config)); + } +} + +var configF = { + get: function (id){ + if (config == undefined) + loadconfig(); + return config[id]; + }, + set: function (id, val){ + config[id] = val; + fs.writeFileSync(path.join(__dirname, 'config.json'), JSON.stringify(config)); + } +} + +module.exports = { + config: configF +}; \ No newline at end of file diff --git a/lib/majsoul/analyze.js b/lib/majsoul/analyze.js index f29ed98..e4474f6 100644 --- a/lib/majsoul/analyze.js +++ b/lib/majsoul/analyze.js @@ -1,7 +1,7 @@ "use strict"; var clientrequest = {}; -var UserID = 0; +var UserID = -1; var paipugamedata = {}; function decodeUTF8(data, start = undefined, length = undefined){ @@ -420,7 +420,7 @@ function analyzepaipu(arr, start){ } //var pp = path.join(__dirname, '../../data/gamedata.txt'); //if (valid && data.extra.id != undefined) fs.appendFileSync(pp, JSON.stringify(data) + '\n'); - if (data.extra.id != undefined) paipugamedata[data.extra.id] = data; + if (data.extra.id != undefined && data.accountid >= 0) paipugamedata[data.extra.id] = data; ss += paipulength; } } @@ -448,6 +448,9 @@ function analyze(sender, data){ if (req == '.lq.Lobby.oauth2Login'){ analyzelogin(sender, arr8, 5); } + else if (req == '.lq.Lobby.login'){ + analyzelogin(sender, arr8, 5); + } else if (req == '.lq.Lobby.fetchGameRecordList'){ analyzepaipu(arr8, 5); } diff --git a/lib/majsoul/browseinject.js b/lib/majsoul/browseinject.js index 4037223..8604dd1 100644 --- a/lib/majsoul/browseinject.js +++ b/lib/majsoul/browseinject.js @@ -1,6 +1,6 @@ console.log("try add jquery trigger"); -const electron = require("electron"); -const ipcr = electron.ipcRenderer; +var electron = require("electron"); +var ipcr = electron.ipcRenderer; function addjquery() { //console.log("check jquery"); var flag = 1; @@ -26,4 +26,5 @@ function addjquery() { } } addjqueryintervalid = setInterval(addjquery, 1000); -addjqueryintervalremain = 30; \ No newline at end of file +addjqueryintervalremain = 30; +addjquery(); \ No newline at end of file diff --git a/main.js b/main.js index 8c2445d..d908308 100644 --- a/main.js +++ b/main.js @@ -12,7 +12,8 @@ const { app, BrowserWindow, dialog, Menu, ipcMain } = require('electron'); const path = require('path').join, fs = require('fs'), url = require('url'), - { analyze, paipugamedata, getUserID } = require('./lib/majsoul/analyze') + { analyze, paipugamedata, getUserID } = require('./lib/majsoul/analyze'), + { config } = require('./lib/config.js'); var appPath = app.getAppPath(); app.setPath('userData', appPath + '/UserData'); @@ -32,6 +33,10 @@ const ready = () => { show: false }); + browseWindow.webContents.on('did-stop-loading', function (){ + browseinject(); + }); + function browseinject() { fs.readFile(path(__dirname, 'lib', 'jquery.js'), function (error, jdata) { if (error) { @@ -50,6 +55,14 @@ const ready = () => { browsedata = String(browsedata).replace(/\/\/%jqueryjs%/, jdata); browsedata = browsedata.replace(/\/\/%wshookjs%/, wsdata); browseWindow.webContents.executeJavaScript(browsedata); + if (/https:\/\/open\.weixin\.qq\.com/.test(browseWindow.webContents.getURL())) + browseWindow.webContents.executeJavaScript(` + console.log('try to run weixin script again'); + ss = $('script'); + for (let i = 0; i < ss.length; i ++ ) + if (/majsoul/.test($(ss[i]).html())) + eval(ss[i].textContent); + `); } }); @@ -57,19 +70,16 @@ const ready = () => { } }); } - - function bwindowreload() { - browseWindow.reload(); - browseinject(); - } + + const cantgetIDstr = '获取信息错误!无法获取用户ID,请确认已经进入大厅。也可尝试刷新页面,如果多次刷新依然无法进入请汇报BUG。'; function checkpaipugamedata(){ let msgstr = '', root, paipu4 = 0, paipu3 = 0, downloaded = 0, converted = 0, userid = getUserID(); - if (userid == 0){ + if (userid <= 0){ dialog.showMessageBox({ type: 'error', title: '错误', - message: '获取信息错误!无法获取用户ID。如果首次登陆请选择杂项-刷新来刷新页面。' + message: cantgetIDstr }); return; } @@ -95,11 +105,11 @@ const ready = () => { function downloadconvertpaipu(){ let userid = getUserID(); - if (userid == 0){ + if (userid <= 0){ dialog.showMessageBox({ type: 'error', title: '错误', - message: '获取信息错误!无法获取用户ID。如果首次登陆请选择杂项-刷新来刷新页面。' + message: cantgetIDstr }); return; } @@ -183,6 +193,11 @@ const ready = () => { nextdownloadconvert(); } + function gotonewpage(str){ + config.set('DefaultURL', str); + browseWindow.loadURL(str); + } + var menutemplate = [{ label: '牌谱', submenu: [{ @@ -197,13 +212,47 @@ const ready = () => { } }] }, { - label: '杂项', + label: '网页', submenu: [{ label: '刷新', click: function () { - bwindowreload(); + browseWindow.reload(); + } + }, { + label: '进入国服', + click: function () { + gotonewpage('https://majsoul.union-game.com/0/'); + } + }, { + label: '进入日服', + click: function () { + gotonewpage('https://game.mahjongsoul.com'); + } + }, { + label: '进入国际服', + click: function () { + gotonewpage('https://mahjongsoul.game.yo-star.com'); + dialog.showMessageBox({ + type: 'info', + title: '国际服提示', + message: '由于技术原因,使用国际服时请确保当前网络能够较为通畅的访问Google, FaceBook等,否则很可能无法正确获取牌谱数据。' + }); } }, { + label: '登录专用窗口', + click: function () { + var loginWindow = new BrowserWindow({ + title: `login`, + show: false + }); + let str = config.get('DefaultURL'); + loginWindow.loadURL(str); + loginWindow.show(); + } + }] + }, { + label: '关于', + submenu: [{ label: '关于', click: function() { dialog.showMessageBox({ @@ -225,7 +274,7 @@ const ready = () => { })); newWindow.show(); newWindow.openDevTools(); - browseWindow.loadURL('https://majsoul.union-game.com/0/'); + browseWindow.loadURL(config.get('DefaultURL')); browseWindow.show(); browseWindow.maximize(); browseWindow.openDevTools(); diff --git a/src/analyzer.cpp b/src/analyzer.cpp index 7f9f736..d37917e 100644 --- a/src/analyzer.cpp +++ b/src/analyzer.cpp @@ -2369,6 +2369,8 @@ bool PaipuAnalyzer::analyze(CJsonObject &paipu){ for (int i = 0; i < 4; i ++ ) if (accountid == pdata[i]["id"]) adata.me = i; + //如果玩家数据中没找到对应ID就跳过该牌谱 + if (adata.me == -1) return false; matchdata.INewGame(paipu); #ifdef SAVEMATCHDATASTEP GameStep = CJsonObject("[]");