32 Commits
Author SHA1 Message Date
Cookiues 0cb5320290 feat: 主页账号备份,并修好 B50 预览缩不小
账号备份(AccountBackupCard + AccountBackupService):
- 并发读 GetUserData/Extend/Option/Rating/Charge/Activity/MissionData +
  Character + Map(分页)+ KaleidxScope + 各 itemKind 的 GetUserItemApi,
  汇成一个 JSON 字符串可复制到剪贴板。舞里程与总游玩数据本就在
  userData 的 point/totalPoint/playCount 里。
- itemKind 15(KaleidxScopeKey)不列进回读清单:客户端 ExportUserItems
  从不输出它,下行也没有读它的路径,钥匙实际是
  userKaleidxScopeList[].isKeyFound。
- 全程只发 GetUser*Api,不碰 UpsertUserAllApi。
- 逐段容错:私服没实现某个接口时其余照样备出,失败段单独列出——
  「读到但是空的」和「没读到」在 userItemList 里按 kind 分开存,可区分。
- 取数与拼装分开,buildBackupDocument/encode 部分是纯函数,单测覆盖。

B50 预览:原因是 InteractiveViewer(constrained:false) 给孩子无界约束,
FittedBox(fit: fitWidth) 在无界下根本不缩放,海报按 1762x1850 原尺寸铺开;
而 minScale 又算成 1.0,于是既巨大又缩不动。改为给一个 contain 尺寸的
有限盒子让 FittedBox 真正缩放,minScale 固定 1.0(这个版本没有
initialScale 可用)。RepaintBoundary 留在 FittedBox 内侧,绘制尺寸仍是
1762x1850,导出分辨率不受影响,并加了断言守住这点。
2026-10-02 12:26:03 +08:00
Cookiues a5fb2c97cd fix: 解歌 11820 的查表结论与道具回读容错
排查「解锁 11820 机台不显示」的结果:

- 11820 = Xaleid◆scopiX(xi),lockType=4(MusicLockType.KaleidScope)、
  longMusic=1,eventName 与 subEventName 都是 26100124。
  NotesListManager.cs:251 的分支确认:KaleidScope 曲落到 IsUnlockBase →
  MusicUnlockList,也就是 itemKind 5 的行对它确实有效,包没发错。
  真正卡点是前一道门:曲池准入要 IsOpenEvent(26100124)(NotesListManager.cs:70),
  机台 mai2.ini 的 AllOpen=0,所以完全依赖服务器 GetGameEventApi 给的窗口;
  而 G:\Package 的 StreamingAssets 只装了 A000/A005/A011/A015,没有 A031,
  GetMusic(11820) 直接是 null —— 这是数据没装,不是协议问题。
- 重生成映射表时改用 D:/A031OptDump/Root 作数据根(上一版读错了根):
  现在 music 含 11820、gate 为 1~10(7=棱镜塔、9=希望之门)、
  title 3001→3031、chara 983→987。
- 解锁/收藏品路径里 GetUserItemApi 的回读改成失败可降级:私服不实现它时
  退回一律 '1'(加这段之前的行为),不要让回读把整个解锁功能拖挂。
- 按你的要求删掉 README 的「ID 映射表」一节。
2026-10-02 01:51:11 +08:00
Cookiues 2c7f9c1de7 feat: ID 映射支持叠加单独导出的数据包,并纳入 event 表
D:/A031OptDump/Root 是比 G:\Package 更新的一层增补(曲目到 12024、宴曲
111537/111765/112002),原来的 --package 只认整合包内的 A??? 目录,套不到它。

- 新增可重复的 --data-root,把一个目录直接当数据根,按传参顺序追加、后者覆盖
  同 ID 行;--package 与 --data-root 可混用。
- 新增 events.json(eventId → 活动名 / alwaysOpen / infoType / disableArea)。
  本地 event 表没有日期窗口,开没开由服务器 GetGameEventApi 的
  GameEvent{startDate,endDate} 决定,README 里写明了这一点,免得误用它判状态。
- 合并两层后:music 1367→1378、map 168→169、title 3000→3001、event 824。
  11820 在两层数据包里都不存在,结论不变。
- lockType 分布按合并后数据更正为 Unlock 1347 / Lock 18 / Challenge 5 /
  Transmission 2 / KaleidScope 6,顺带说明大多数歌 lockType==Unlock、
  本来就可玩,不查 itemKind 5。
2026-10-02 01:16:15 +08:00
Cookiues 264b1d0aa7 feat: 从机台数据包抽出 music/item/map/gate 的 ID 映射表
风险页填的全是裸 ID,而「填了表里没有的 ID」是这类功能最常见的失败方式:
服务器照样回 returnCode: 1,客户端建池时静默跳过,看起来就是没生效。

- tools/gen_id_mapping.dart:读 Sinmai_Data/StreamingAssets/A0xx/<表>/<x>.xml,
  按目录名升序合并、同 ID 后者覆盖(与 DataManager.LoadData 语义一致),
  输出 data/id_mapping/{music,items,maps,gates,meta}.json。
  ID 取 XML 的 <name><id>,不是目录名——目录名只是 dataName。
- 零依赖:自带一个只处理 XmlSerializer 那种规整结构的小扫描器,
  不为此往依赖树里加 xml 包、也不搅动 pubspec.lock。
- 产物放 data/ 不放 assets/:目前没有运行时读取方,进 assets 会白白把
  1.2MB 打进每个安装包。

用这份表顺手证实了三件事:musicId 11820 在曲表里根本不存在(11819、11821
也不在),所以它不是万花筒锁——lockType=4 的歌全表只有 6 首;角色 1001 同样
不存在,合法 ID 从 101 起;万花筒只有 gate 1~6,客户端 ForceAddMasterKey
写死的 gateId 7 在这份数据里没有对应行。
2026-10-02 01:08:48 +08:00
Cookiues 80df51c239 fix: 解歌的 isNewItemList 按主键回读再算,不再一律发 1
排查「发 11820 解歌不显示」时发现两处问题:

- applyItemListPatch 的 isNewItemList 一直硬写全 '1'(新增),从没读服务器
  已有行。真机 BuildListData 是按 (itemKind, itemId) 比对快照给 0/1 的,
  重复解锁一首已存在的歌时服务器按插入冲突丢行,表现就是上传成功但没生效。
  这和角色/地图/万花筒三处的处理不一致,现补齐。
- 新增 TitleApiService.getUserItems/getUserItemKeys。GetUserItemApi 是按
  itemKind 分页的,首次 nextIndex 要按 itemKind * 1e10 编码
  (PacketGetUserItem.cs:19),发 0 只会拿到空页。
- 解锁上传后回读复查,把「服务器没存这行道具」与「存了但曲目池不给进」分开报。
- 待解锁列表含 10000~19999 的 DX 曲时提示:Master/Re:Master 不像老曲那样
  白送(IsUnlockMaster 里 id<10000 直接放过),必须显式带 itemKind 6/7。

需要说明:一首歌进不进选曲池由 NotesListManager.cs:70 决定
(IsOpenEvent(eventName) && !IsGameNgMusicId),事件窗口来自 GetGameEventApi、
曲目本体来自机台 Music.xml。11820 若卡在这一层,任何用户包都改不动,
复查那一步就是为了把这种情况和包的问题区分开。

README 补 isNew*List 语义、曲目可见性链路与 DX 段解锁规则三条说明。
删除因此不再被引用的 unlockMusicSuccess。
2026-10-01 22:14:50 +08:00
Cookiues 6438fb2171 feat: 万花筒专区可设置门的通关状态
通关态与前两个动作一样落在 userKaleidxScopeList 同一行,是 isClear。
用三态下拉(不改动 / 已通关 / 未通关)而不是勾选框,因为通关可以往回改。

- 标记通关时补 clearDate,格式按客户端 TimeManager.GetDateString 的
  yyyy-MM-dd HH:mm:ss.f;已有首次通关日期不覆盖,撤销通关时一起清空
- 上行是整行替换,只叠加要改的位,playCount / best 成绩 / 日期保持原值
- 通关态只在 found && key 的前提下才显示得出来,缺钥匙时给一条短提示
- 校验放宽:三个动作任选其一即可执行
2026-10-01 11:04:18 +08:00
Cookiues 44c4a1daae feat: 连接信息恢复的会话在 isLogin 时直接开放票据与风险功能
- 恢复时除了 GetUserDataApi 再拉一次 GetUserPreviewApi:服务器说 isLogin 为 true
  就把这次会话当已登录,_gameLogin 落地,票据与风险页按「登录后 60 秒冷却」计时
  (loginDateTime 取恢复当下),不必再发一次 UserLoginApi
- GetUserPreviewApi 的响应里没有 loginId,而 UpsertUserAll 要拿它当 playlogId,
  所以导出连接信息时把本机的 loginId 一起带过去,恢复端优先用这个值,
  拉不到才退回 preview 里的字段(多半是 0)
- 只有 Cookie、preview 拉不到、或服务器说没在登录中时仍按只读处理,
  提示横幅与 SnackBar 文案区分「可写」和「只能读」两种恢复结果
- HomePage 的恢复入口收成 restored: SessionRestoreResult?,把 preview 与
  userData 一起当种子,省掉两次重复请求
2026-09-29 13:47:03 +08:00
Cookiues e74bb792bd fix: 登录按钮对认不出的输入给出提示,不再静默无反应
- SGWCMAID 前缀改成按粘贴原文判断:令牌取的是内容的后 64 位,粘贴内容一旦超过
  64 位,前缀就被截掉了,原先对截取结果做 startsWith 会直接 return,
  点按钮看着能点却一声不响
- 补 test/login_page_test.dart 覆盖两条输入路径:不认识的令牌给提示,
  连接信息把按钮切成「恢复会话」
2026-09-29 13:30:06 +08:00
Cookiues 5d16984e4e feat: 实验性连接信息导出与会话恢复,重写关于页
- SessionShare:把 userId / JSESSIONID / token 打成 Base64(JSON),Cookie 只保留
  JSESSIONID 一项,粘贴时容忍换行与空白,QR 令牌与乱码解不出来
- 主页底部新增「导出连接信息」卡片,标记实验性:内容未加密,字符串在手即可操作账号
- 登录页识别到连接信息时把登录按钮换成「恢复会话」,不必再扫 QR 令牌
- 恢复优先沿用导入的 JSESSIONID(不重发 UserLoginApi,免得顶掉机台上的登录),
  Cookie 失效才退回用 token 登录;验证 Cookie 时顺手拉到的 GetUserDataApi 结果
  直接交给主页展示,省一次请求
- 仅凭 Cookie 恢复的会话没有 loginId / loginDateTime,_gameLogin 保持 null,
  票据与风险页照旧提示「尚未登录游戏服务器」,主页顶部给出说明横幅
- 关于页改用 AppCard / SectionTitle 脚手架,补基本信息与风险提示,把过时的
  「jsQR (web)」致谢换成原生 MethodChannel(QR 解码早已不走 web 库)
2026-09-29 02:43:39 +08:00
Cookiues 0808e367f7 feat: 风险页新增数值修改、一键跑图与万花筒专区
- 修改总 Rating:只写 playerRating 与 userRating.rating,钳 0~99999,
  每首歌各自的 ratingList 原样带回
- 添加舞里程(UserDetail.point):完整 int32 区间,支持累加与直接设定,
  允许负值即扣减
- 一键跑图:isClear/isComplete 在客户端是从 distance 派生的,单发 flag 会被
  冲掉,故把 distance 推过 End 里程针;unlockFlag 语义是反的
- 万花筒专区:发现门与获取钥匙落在 userKaleidxScopeList 同一行的两个布尔位,
  钥匙不走 userItemList;上行是整行替换,所以先读回原行再叠加
- 旅行伙伴:修正误导性的示例 ID 1001(合法是三位数,表里没有的 ID 服务器照收
  但客户端静默跳过),编组槽位可设定等级,上传后回读复查以区分失败原因
- 补 MIT 许可证
2026-09-27 01:49:55 +08:00
Cookiues 329845f228 fix: CI 的 analyze 因 info 级别提示直接失败
flutter analyze 默认把 info 也当致命项,仓库里那条既有的
prefer_initializing_formals 让 checks 作业 exit 1,后面三个构建作业全被跳过。

两处处理:
1. 真把这条 lint 修掉——TitleApiService 改用私有初始化形参 {this._cookies},
   Dart 对外仍暴露成 cookies: 具名参数,调用点无需改动。analyze 现在 0 issue。
2. CI 改用 flutter analyze --no-fatal-infos,与 README 写的「0 error/warning」
   一致;否则依赖升级带进来的 deprecation 提示(之前 dart:html 那 5 条)
   会毫无道理地打断整条流水线。
2026-09-26 01:18:14 +08:00
Cookiues 6b2efaebba fix: 主页的系统返回手势不再退回登录页
PopScope 的 canPop 写的是 !_loggingOut,平时即为 true,所以原生返回手势会
直接把 HomePage 弹出、落回 Login Page;同时因为 didPop==true,回调里的
退登分支根本不会执行 —— 一个原因同时造成「回到登录页」和「账号没退出」,
留下一个会话仍在线的半退出状态。

改为 canPop:false 把根路由锁死在主页,返回意图交给 _exitApp:非 iOS 平台
走 SystemNavigator.pop 结束应用(iOS 不允许程序化退出,根路由也没有返回手势)。
AppBar 的退出按钮与风险操作完成后的 onExitToTitle 仍走 _performLogoutAndExit,
那两处是明确要回主标题。

新增回归用例:把 HomePage push 在占位根路由之上,触发 handlePopRoute,
断言 HomePage 仍在栈上且调用了 SystemNavigator.pop。
2026-09-26 01:09:13 +08:00
Cookiues cb50426a61 build: 移除 Web 平台支持
删掉 web/ 目录与 4 个 *_web.dart 实现(jsQR/dart:html 解码、FileUploadInputElement
选图、以及为 Web 空实现的缓存与导出)。剩下 Android/iOS/Windows/macOS/Linux。

顺带把只剩单一实现的「façade + _native + _web」条件导入结构收敛成单文件,
调用点不变。同时从 .metadata 的 migration 平台列表和 analysis_options 的
exclude 里去掉 web,避免 flutter create/migrate 再把 web/ 生成回来。

QR 从图片解码此前只有 Web 端用 jsQR 真正可用,移动端走 MethodChannel 由
MainActivity/SceneDelegate 实现,桌面端无 handler——这一点在 qr_service.dart
和 README 里都注明了。

副作用:dart:html / dart:js 带来的 5 条 analyze info 随文件消失,
flutter analyze 从 7 条降到 1 条(剩下的是 title_api_service 里既有的
prefer_initializing_formals)。
2026-09-26 01:01:03 +08:00
Cookiues b6d55bf236 feat: 修复一些东西 2026-09-26 00:51:21 +08:00
Cookiues 3683a139e2 build: Gradle wrapper 升到 9.3.1
本地 release apk 构建一直在用 9.3.1,CI 却仍按仓库里的 9.1.0 拉取,
两边不一致会让 CI 的 android 作业结果不可复现。统一提到 9.3.1。
2026-09-26 00:49:24 +08:00
Cookiues 8dda399f76 feat: 旅行伙伴发放与编组、修复退登卡死、优化宽屏布局、加 CI
自动退出登录卡死:_performLogoutAndExit 只 pop() 一层,而解锁/收藏品页是 push
在 HomePage 之上的,退登后被弹回「风险」hub,同时 _loggingOut 仍为 true,
PopScope(canPop:false) + 返回键转圈形成死局。改为 popUntil(isFirst)。

旅行伙伴发不了:它是 ItemKind.Character = 9,与搭档 Partner = 10 是两种东西,
原先收藏品的类型列表 [1,2,3,10,11,12] 里根本没有 9。按客户端实现,角色不走
userItemList(ExportUserItems 不导出 itemKind 9),而是
upsertUserAll.userCharacterList 的 {characterId, level, awakening, useCount},
配 isNewCharacterList 一行一字符。新增发放与 5 槽 charaSlot 编组页,
含「把槽 0 复制到其他槽位」,并校验所选角色确为已拥有或本次待发放。

宽屏布局:各页只有 ConstrainedBox 限宽、没有居中容器,1440 窗口下内容贴左、
右侧大片空白。抽出 responsiveBody 统一居中;B50 预览改用按宽度适配
(InteractiveViewer constrained:false + FittedBox fitWidth),横屏下不再被
contain 缩成中间一小块,导出仍是原始 1762x1850。

CI:.github/workflows/build.yml 先跑 analyze+test 作为门槛,再产出
Windows exe、Android apk、iOS 未签名 ipa(macOS runner,无证书故不可安装)。
README 从 Flutter 模板改写为实际文档,含 path_provider/jni 与 JBR jlink
两个构建环境坑的说明。
2026-09-26 00:37:54 +08:00
Cookiues ebd1fdddaa fix: UpsertUserAll 因 playSpecial 越界被服务器 500 拒绝
calcRandom() 移植的是 C# 里声明为 int 的 32 位比特反转,`<<=` 溢出按二进制
补码环绕,结果恒为合法 signed int32(高位被置起时为负)。Dart 的 int 是 64 位
且被反转值恒正,于是约一半概率产出 [2^31, 2^32) 的正数(如 3212134032),超出
UserGamePlaylog.playSpecial 的 int 声明,服务器整个 body 反序列化失败返回
HTTP 500,表现为解锁歌曲/获取收藏品不可用。按 C# 位模式重解释为 signed 32 位。

同时补上 userPlaylogList 缺少的 extBool3(UserPlaylog.cs:337 声明的 bool)。
2026-09-25 23:06:37 +08:00
Cookiues 79fa0d7a20 feat: 移植 B50 成绩图,并把项目名从 purarine 改回 ichino
海报从 Empurple 的 Best50ImageRenderer/ImageBuilder 移植:按原始像素坐标
(1762x1850 画布、300x100 卡片、5 列、行距 120) 叠加在 base.png 上,底图里
已画好的面板/药丸标签/页脚不重复绘制;页面外壳走 Ichino 的 Material 3。

RatingCalculator 的达成率分档改成单张有序表,避免偏移与评级错位;
RA 与评级由 test/rating_calculator_test.dart 的边界用例锁定。

GetUserRatingApi 只需要 userId(Cookie 可选),所以 Preview API 模式、未登录
游戏服务器时也能出图,只是没有 FC/AP/FS 标记。导出用 RepaintBoundary 抓
全尺寸 PNG:原生走 share_plus(不支持文件分享的平台回退为落盘并提示路径),
web 走下载。

BEST35 与 BEST15 改为各自独立排版。原先沿用 Empurple 的跨列表全局序号,
BEST35 没打满时 BEST15 会整体上移混进上半区。

path_provider 会经 path_provider_android 2.3.x 拖入 jni 插件,而 jni 把
windows/linux 也声明成 ffiPlugin,桌面构建因此去链接 jvm.lib 并失败,
故用 dependency_overrides 钉住 path_provider_android <2.3.0。
2026-09-25 22:25:35 +08:00
Cookiues 24ccd628d1 sync 2026-08-22 16:52:33 +08:00
Cookiues 0b3282da5c b 2026-06-26 17:06:24 +08:00
Cookiues cb10fbfc7d Merge branch 'master' of https://github.com/NightcordSekai/Ichino 2026-06-14 00:30:11 +08:00
Cookiues a616aa610a 我靠有地雷女 2026-06-14 00:30:06 +08:00
Cookiues 20724d00ae deleted ipa 2026-06-12 09:59:43 +08:00
Cookiues fe2cc2c184 delete unused ticket id 2026-06-12 09:57:29 +08:00
Cookiues 087e24f786 someshits 2026-06-11 12:59:11 +08:00
Cookiues cfff0b8b06 较新较热 2026-06-09 17:21:11 +08:00
Cookiues 2bf8030c37 todo:send upsertuserall packet 2026-05-30 00:24:31 +08:00
Cookiues 2ad6781049 claude nmsl 2026-05-29 16:36:24 +08:00
Cookiues cd6491cdc5 feat.Hatsune Miku 2026-05-29 16:21:54 +08:00
Cookiues 53e078ff91 feat.Hatsune Miku 2026-05-29 16:08:32 +08:00
Cookiues dca69d095a feat:improve logic 2026-05-29 12:51:23 +08:00
Cookiues a78a87e46b First 2026-05-28 22:11:05 +08:00