bcsfe 3.2.2__tar.gz → 3.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. {bcsfe-3.2.2/src/bcsfe.egg-info → bcsfe-3.4.0}/PKG-INFO +6 -4
  2. {bcsfe-3.2.2 → bcsfe-3.4.0}/README.md +5 -3
  3. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/__init__.py +1 -1
  4. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/__main__.py +16 -2
  5. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/__init__.py +2 -0
  6. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/dialog_creator.py +15 -6
  7. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/cat_editor.py +6 -4
  8. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/storage.py +31 -2
  9. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/feature_handler.py +36 -40
  10. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/main.py +37 -7
  11. bcsfe-3.4.0/src/bcsfe/cli/recent_saves.py +144 -0
  12. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/save_management.py +50 -14
  13. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/server_cli.py +7 -1
  14. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/__init__.py +20 -0
  15. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/cat.py +3 -1
  16. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/gatya.py +1 -1
  17. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/login_bonuses.py +6 -5
  18. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/talent_orbs.py +31 -31
  19. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/gamoto/cat_shrine.py +3 -0
  20. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/config.py +2 -2
  21. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/git_handler.py +2 -4
  22. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/path.py +47 -7
  23. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/root_handler.py +1 -1
  24. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/save.py +24 -3
  25. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/locale_handler.py +1 -1
  26. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/log.py +11 -18
  27. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/server/game_data_getter.py +22 -4
  28. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/server/request.py +1 -1
  29. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/server/server_handler.py +8 -6
  30. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/theme_handler.py +3 -11
  31. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/core/save.properties +13 -5
  32. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/core/server.properties +2 -0
  33. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/cats.properties +2 -0
  34. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/gamototo.properties +3 -3
  35. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/special_skills.properties +1 -1
  36. bcsfe-3.4.0/src/bcsfe/files/locales/tw/core/config.properties +95 -0
  37. bcsfe-3.4.0/src/bcsfe/files/locales/tw/core/files.properties +9 -0
  38. bcsfe-3.4.0/src/bcsfe/files/locales/tw/core/input.properties +31 -0
  39. bcsfe-3.4.0/src/bcsfe/files/locales/tw/core/locale.properties +33 -0
  40. bcsfe-3.4.0/src/bcsfe/files/locales/tw/core/main.properties +111 -0
  41. bcsfe-3.4.0/src/bcsfe/files/locales/tw/core/save.properties +117 -0
  42. bcsfe-3.4.0/src/bcsfe/files/locales/tw/core/server.properties +58 -0
  43. bcsfe-3.4.0/src/bcsfe/files/locales/tw/core/theme.properties +24 -0
  44. bcsfe-3.4.0/src/bcsfe/files/locales/tw/core/updater.properties +19 -0
  45. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/bannable_items.properties +30 -0
  46. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/cats.properties +160 -0
  47. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/enemy.properties +17 -0
  48. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/fixes.properties +13 -0
  49. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/gambling.properties +3 -0
  50. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/gamototo.properties +56 -0
  51. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/gatya.properties +8 -0
  52. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/gold_pass.properties +6 -0
  53. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/items.properties +64 -0
  54. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/map.properties +178 -0
  55. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/medals.properties +8 -0
  56. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/missions.properties +7 -0
  57. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/playtime.properties +7 -0
  58. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/scheme_items.properties +6 -0
  59. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/special_skills.properties +10 -0
  60. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/talent_orbs.properties +26 -0
  61. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/treasures.properties +24 -0
  62. bcsfe-3.4.0/src/bcsfe/files/locales/tw/edits/user_rank.properties +9 -0
  63. bcsfe-3.4.0/src/bcsfe/files/locales/tw/metadata.json +4 -0
  64. bcsfe-3.4.0/src/bcsfe/files/locales/vi/core/config.properties +95 -0
  65. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/vi/core/files.properties +1 -1
  66. bcsfe-3.4.0/src/bcsfe/files/locales/vi/core/input.properties +33 -0
  67. bcsfe-3.4.0/src/bcsfe/files/locales/vi/core/locale.properties +118 -0
  68. bcsfe-3.4.0/src/bcsfe/files/locales/vi/core/main.properties +75 -0
  69. bcsfe-3.4.0/src/bcsfe/files/locales/vi/core/save.properties +84 -0
  70. bcsfe-3.4.0/src/bcsfe/files/locales/vi/core/server.properties +31 -0
  71. bcsfe-3.4.0/src/bcsfe/files/locales/vi/core/theme.properties +24 -0
  72. bcsfe-3.4.0/src/bcsfe/files/locales/vi/core/updater.properties +20 -0
  73. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/bannable_items.properties +31 -0
  74. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/cats.properties +154 -0
  75. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/enemy.properties +18 -0
  76. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/fixes.properties +13 -0
  77. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/vi/edits/gambling.properties +4 -3
  78. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/gamototo.properties +56 -0
  79. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/gatya.properties +9 -0
  80. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/gold_pass.properties +7 -0
  81. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/items.properties +65 -0
  82. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/map.properties +177 -0
  83. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/medals.properties +9 -0
  84. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/missions.properties +8 -0
  85. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/playtime.properties +8 -0
  86. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/vi/edits/scheme_items.properties +1 -1
  87. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/vi/edits/special_skills.properties +3 -3
  88. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/talent_orbs.properties +24 -0
  89. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/treasures.properties +25 -0
  90. bcsfe-3.4.0/src/bcsfe/files/locales/vi/edits/user_rank.properties +10 -0
  91. {bcsfe-3.2.2 → bcsfe-3.4.0/src/bcsfe.egg-info}/PKG-INFO +6 -4
  92. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe.egg-info/SOURCES.txt +29 -0
  93. bcsfe-3.2.2/src/bcsfe/files/locales/vi/core/config.properties +0 -97
  94. bcsfe-3.2.2/src/bcsfe/files/locales/vi/core/input.properties +0 -32
  95. bcsfe-3.2.2/src/bcsfe/files/locales/vi/core/locale.properties +0 -33
  96. bcsfe-3.2.2/src/bcsfe/files/locales/vi/core/main.properties +0 -113
  97. bcsfe-3.2.2/src/bcsfe/files/locales/vi/core/save.properties +0 -107
  98. bcsfe-3.2.2/src/bcsfe/files/locales/vi/core/server.properties +0 -51
  99. bcsfe-3.2.2/src/bcsfe/files/locales/vi/core/theme.properties +0 -25
  100. bcsfe-3.2.2/src/bcsfe/files/locales/vi/core/updater.properties +0 -20
  101. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/bannable_items.properties +0 -31
  102. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/cats.properties +0 -166
  103. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/enemy.properties +0 -18
  104. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/fixes.properties +0 -14
  105. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/gamototo.properties +0 -55
  106. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/gatya.properties +0 -9
  107. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/gold_pass.properties +0 -7
  108. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/items.properties +0 -56
  109. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/map.properties +0 -158
  110. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/medals.properties +0 -9
  111. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/missions.properties +0 -8
  112. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/playtime.properties +0 -8
  113. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/talent_orbs.properties +0 -25
  114. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/treasures.properties +0 -25
  115. bcsfe-3.2.2/src/bcsfe/files/locales/vi/edits/user_rank.properties +0 -10
  116. {bcsfe-3.2.2 → bcsfe-3.4.0}/LICENSE +0 -0
  117. {bcsfe-3.2.2 → bcsfe-3.4.0}/MANIFEST.in +0 -0
  118. {bcsfe-3.2.2 → bcsfe-3.4.0}/pyproject.toml +0 -0
  119. {bcsfe-3.2.2 → bcsfe-3.4.0}/setup.cfg +0 -0
  120. {bcsfe-3.2.2 → bcsfe-3.4.0}/setup.py +0 -0
  121. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/color.py +0 -0
  122. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/__init__.py +0 -0
  123. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/aku_realm.py +0 -0
  124. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/basic_items.py +0 -0
  125. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/clear_tutorial.py +0 -0
  126. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/enemy_editor.py +0 -0
  127. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/event_tickets.py +0 -0
  128. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/fixes.py +0 -0
  129. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/map.py +0 -0
  130. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/max_all.py +0 -0
  131. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/edits/rare_ticket_trade.py +0 -0
  132. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/cli/file_dialog.py +0 -0
  133. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/country_code.py +0 -0
  134. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/crypto.py +0 -0
  135. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/__init__.py +0 -0
  136. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/battle/__init__.py +0 -0
  137. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/battle/battle_items.py +0 -0
  138. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/battle/cleared_slots.py +0 -0
  139. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/battle/enemy.py +0 -0
  140. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/battle/slots.py +0 -0
  141. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/__init__.py +0 -0
  142. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/beacon_base.py +0 -0
  143. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/drop_chara.py +0 -0
  144. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/gambling.py +0 -0
  145. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/gatya_item.py +0 -0
  146. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/item_pack.py +0 -0
  147. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/matatabi.py +0 -0
  148. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/medals.py +0 -0
  149. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/mission.py +0 -0
  150. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/my_sale.py +0 -0
  151. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/nyanko_club.py +0 -0
  152. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/officer_pass.py +0 -0
  153. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/playtime.py +0 -0
  154. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/powerup.py +0 -0
  155. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/scheme_items.py +0 -0
  156. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/special_skill.py +0 -0
  157. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/stamp.py +0 -0
  158. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/unlock_popups.py +0 -0
  159. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/upgrade.py +0 -0
  160. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/catbase/user_rank_rewards.py +0 -0
  161. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/gamoto/__init__.py +0 -0
  162. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/gamoto/base_materials.py +0 -0
  163. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/gamoto/catamins.py +0 -0
  164. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/gamoto/gamatoto.py +0 -0
  165. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/gamoto/ototo.py +0 -0
  166. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/localizable.py +0 -0
  167. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/__init__.py +0 -0
  168. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/aku.py +0 -0
  169. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/challenge.py +0 -0
  170. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/chapters.py +0 -0
  171. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/dojo.py +0 -0
  172. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/enigma.py +0 -0
  173. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/event.py +0 -0
  174. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/ex_stage.py +0 -0
  175. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/gauntlets.py +0 -0
  176. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/item_reward_stage.py +0 -0
  177. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/legend_quest.py +0 -0
  178. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/map_names.py +0 -0
  179. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/map_option.py +0 -0
  180. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/map_reset.py +0 -0
  181. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/outbreaks.py +0 -0
  182. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/story.py +0 -0
  183. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/timed_score.py +0 -0
  184. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/tower.py +0 -0
  185. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/uncanny.py +0 -0
  186. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game/map/zero_legends.py +0 -0
  187. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/game_version.py +0 -0
  188. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/__init__.py +0 -0
  189. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/adb_handler.py +0 -0
  190. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/bc_csv.py +0 -0
  191. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/command.py +0 -0
  192. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/data.py +0 -0
  193. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/json_file.py +0 -0
  194. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/thread_helper.py +0 -0
  195. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/waydroid.py +0 -0
  196. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/io/yaml.py +0 -0
  197. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/max_value_helper.py +0 -0
  198. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/server/__init__.py +0 -0
  199. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/server/client_info.py +0 -0
  200. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/server/event_data.py +0 -0
  201. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/server/headers.py +0 -0
  202. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/server/managed_item.py +0 -0
  203. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/core/server/updater.py +0 -0
  204. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/core/config.properties +0 -0
  205. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/core/files.properties +0 -0
  206. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/core/input.properties +0 -0
  207. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/core/locale.properties +0 -0
  208. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/core/main.properties +0 -0
  209. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/core/theme.properties +0 -0
  210. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/core/updater.properties +0 -0
  211. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/bannable_items.properties +0 -0
  212. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/enemy.properties +0 -0
  213. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/fixes.properties +0 -0
  214. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/gambling.properties +0 -0
  215. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/gatya.properties +0 -0
  216. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/gold_pass.properties +0 -0
  217. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/items.properties +0 -0
  218. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/map.properties +0 -0
  219. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/medals.properties +0 -0
  220. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/missions.properties +0 -0
  221. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/playtime.properties +0 -0
  222. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/scheme_items.properties +0 -0
  223. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/talent_orbs.properties +0 -0
  224. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/treasures.properties +0 -0
  225. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/en/edits/user_rank.properties +0 -0
  226. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/locales/vi/metadata.json +0 -0
  227. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/max_values.json +0 -0
  228. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/themes/default.json +0 -0
  229. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/files/themes/discord.json +0 -0
  230. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe/py.typed +0 -0
  231. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe.egg-info/dependency_links.txt +0 -0
  232. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe.egg-info/entry_points.txt +0 -0
  233. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe.egg-info/requires.txt +0 -0
  234. {bcsfe-3.2.2 → bcsfe-3.4.0}/src/bcsfe.egg-info/top_level.txt +0 -0
  235. {bcsfe-3.2.2 → bcsfe-3.4.0}/tests/test_parse.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bcsfe
3
- Version: 3.2.2
3
+ Version: 3.4.0
4
4
  Summary: A save file editor for The Battle Cats
5
5
  Author: fieryhenry
6
6
  License-Expression: GPL-3.0-or-later
@@ -56,6 +56,8 @@ test/develop new features
56
56
 
57
57
  - HungJoesifer for Vietnamese localization
58
58
 
59
+ - LinYuAn for Traditional Chinese localization
60
+
59
61
  ### Themes
60
62
 
61
63
  - HungJoesifer for the `discord` inspired theme
@@ -330,7 +332,7 @@ Once you have installed and ran the editor, you can now begin to edit your save
330
332
 
331
333
  1. In `The Battle Cats` enter the `Change Account / Device` menu in the `Settings` on the main menu.
332
334
 
333
- 2. Then enter the `Begin Data Transfer` menu.
335
+ 2. Then enter the `Change Device` -> `Retrieve Data from Old Device` menu.
334
336
 
335
337
  3. Then click / tap `Save Data to Server`, this should give you a transfer code and a confirmation
336
338
  code.
@@ -359,8 +361,8 @@ may also fail, if it does then try again.
359
361
  11. Back in-game, tap the `Close Game` button, then tap `Cancel Data Transfer` (and also possibly
360
362
  `Start Game From Beginning`)
361
363
 
362
- 12. Go back into the `Change Account / Device` menu and then go into the `Resume Data Transfer`
363
- menu
364
+ 12. Go back into the `Change Account / Device` menu and then go into the `Resume Data Transfer` ->
365
+ `Transfer Data to New Device` menu
364
366
 
365
367
  13. Enter the new codes, and tap `Resume Transfer`
366
368
 
@@ -27,6 +27,8 @@ test/develop new features
27
27
 
28
28
  - HungJoesifer for Vietnamese localization
29
29
 
30
+ - LinYuAn for Traditional Chinese localization
31
+
30
32
  ### Themes
31
33
 
32
34
  - HungJoesifer for the `discord` inspired theme
@@ -301,7 +303,7 @@ Once you have installed and ran the editor, you can now begin to edit your save
301
303
 
302
304
  1. In `The Battle Cats` enter the `Change Account / Device` menu in the `Settings` on the main menu.
303
305
 
304
- 2. Then enter the `Begin Data Transfer` menu.
306
+ 2. Then enter the `Change Device` -> `Retrieve Data from Old Device` menu.
305
307
 
306
308
  3. Then click / tap `Save Data to Server`, this should give you a transfer code and a confirmation
307
309
  code.
@@ -330,8 +332,8 @@ may also fail, if it does then try again.
330
332
  11. Back in-game, tap the `Close Game` button, then tap `Cancel Data Transfer` (and also possibly
331
333
  `Start Game From Beginning`)
332
334
 
333
- 12. Go back into the `Change Account / Device` menu and then go into the `Resume Data Transfer`
334
- menu
335
+ 12. Go back into the `Change Account / Device` menu and then go into the `Resume Data Transfer` ->
336
+ `Transfer Data to New Device` menu
335
337
 
336
338
  13. Enter the new codes, and tap `Resume Transfer`
337
339
 
@@ -1,4 +1,4 @@
1
- __version__ = "3.2.2"
1
+ __version__ = "3.4.0"
2
2
 
3
3
  from bcsfe import core, cli
4
4
 
@@ -18,19 +18,27 @@ def main():
18
18
  parser.add_argument(
19
19
  "--input-path", "-i", type=str, help="input path to save file to edit"
20
20
  )
21
+ parser.add_argument(
22
+ "--game-data-dir", "-g", type=str, help="path to store the game data to"
23
+ )
24
+ parser.add_argument(
25
+ "--transfer-backup-path",
26
+ type=str,
27
+ help="path to save the backup SAVE_DATA after transfering to",
28
+ )
21
29
  parser.add_argument(
22
30
  "--config-path",
23
31
  "-c",
24
32
  type=str,
25
33
  default=None,
26
- help="path to the config file. If unspecified defaults to Documents/bcsfe/config.yaml",
34
+ help=f"path to the config file. If unspecified defaults to {core.Config.get_config_path()}",
27
35
  )
28
36
  parser.add_argument(
29
37
  "--log-path",
30
38
  "-l",
31
39
  type=str,
32
40
  default=None,
33
- help="path to the log file. If unspecified defaults to Documents/bcsfe/bcsfe.log",
41
+ help=f"path to the log file. If unspecified defaults to {core.Logger.get_log_path()}",
34
42
  )
35
43
 
36
44
  args = parser.parse_args()
@@ -44,6 +52,12 @@ def main():
44
52
  if args.log_path is not None:
45
53
  core.set_log_path(core.Path(args.log_path))
46
54
 
55
+ if args.transfer_backup_path is not None:
56
+ core.set_transfer_backup_path(core.Path(args.transfer_backup_path))
57
+
58
+ if args.game_data_dir is not None:
59
+ core.set_game_data_path(core.Path(args.game_data_dir))
60
+
47
61
  core.core_data.init_data()
48
62
 
49
63
  try:
@@ -7,6 +7,7 @@ from bcsfe.cli import (
7
7
  save_management,
8
8
  server_cli,
9
9
  edits,
10
+ recent_saves,
10
11
  )
11
12
 
12
13
  __all__ = [
@@ -18,4 +19,5 @@ __all__ = [
18
19
  "save_management",
19
20
  "server_cli",
20
21
  "edits",
22
+ "recent_saves",
21
23
  ]
@@ -73,16 +73,25 @@ class IntInput:
73
73
  default: int | None = None,
74
74
  signed: bool = True,
75
75
  bit_count: int = 32,
76
+ ensure_max: bool = False,
76
77
  ):
77
78
  self.signed = signed
78
79
  self.bit_count = bit_count
79
- self.max = self.get_max_value(max, signed, bit_count)
80
+ self.max = self.get_max_value(max, signed, bit_count, ensure_max)
80
81
  self.min = min
81
82
  self.default = default
82
83
 
83
84
  @staticmethod
84
- def get_max_value(max: int | None, signed: bool = True, bit_count: int = 32) -> int:
85
- disable_maxes = core.core_data.config.get_bool(core.ConfigKey.DISABLE_MAXES)
85
+ def get_max_value(
86
+ max: int | None,
87
+ signed: bool = True,
88
+ bit_count: int = 32,
89
+ ensure_max: bool = False,
90
+ ) -> int:
91
+ disable_maxes = (
92
+ core.core_data.config.get_bool(core.ConfigKey.DISABLE_MAXES)
93
+ and not ensure_max
94
+ )
86
95
  if signed:
87
96
  bit_count -= 1
88
97
  max_int = (2**bit_count) - 1
@@ -273,9 +282,9 @@ class ChoiceInput:
273
282
  start_index=self.start_index,
274
283
  localize_elements=self.localize_options,
275
284
  ).display_locale(self.remove_alias)
276
- return IntInput(self.get_max_value(), self.get_min_value()).get_input_locale(
277
- self.dialog, self.perameters
278
- )
285
+ return IntInput(
286
+ self.get_max_value(), self.get_min_value(), ensure_max=True
287
+ ).get_input_locale(self.dialog, self.perameters)
279
288
 
280
289
  def get_input_while(self) -> int | None:
281
290
  if len(self.strings) == 0:
@@ -413,8 +413,9 @@ class CatEditor:
413
413
  if should_exit:
414
414
  return
415
415
  if upgrade is not None:
416
- power_up.reset_upgrade()
417
- power_up.upgrade_by(upgrade.base)
416
+ if upgrade.base != -1:
417
+ power_up.reset_upgrade()
418
+ power_up.upgrade_by(upgrade.base)
418
419
  cat.set_upgrade(self.save_file, upgrade, True)
419
420
  color.ColoredText.localize(
420
421
  "selected_cat_upgraded",
@@ -435,8 +436,9 @@ class CatEditor:
435
436
  success = True
436
437
  for cat in cats:
437
438
  power_up = core.PowerUpHelper(cat, self.save_file)
438
- power_up.reset_upgrade()
439
- power_up.upgrade_by(upgrade.base)
439
+ if upgrade.base != -1:
440
+ power_up.reset_upgrade()
441
+ power_up.upgrade_by(upgrade.base)
440
442
  cat.set_upgrade(self.save_file, upgrade, True)
441
443
  if success:
442
444
  color.ColoredText.localize("upgrade_success")
@@ -128,6 +128,23 @@ def edit_loop(save_file: core.SaveFile) -> bool:
128
128
  if editor is None:
129
129
  return False
130
130
 
131
+ new_cats: list[core.Cat] = []
132
+ for cat in cats:
133
+ names = cat.get_names_cls(save_file)
134
+ if names is None or not names:
135
+ name = core.localize("unknown")
136
+ else:
137
+ name = names[0]
138
+ quantity, _ = dialog_creator.IntInput(default=1).get_input_locale(
139
+ "cat_quantity", {"name": name, "id": cat.id}
140
+ )
141
+ if quantity is None:
142
+ return False
143
+ for _ in range(quantity):
144
+ new_cats.append(cat)
145
+
146
+ cats = new_cats
147
+
131
148
  space = get_storage_space(storage)
132
149
  if len(cats) > len(storage):
133
150
  color.ColoredText.localize(
@@ -164,6 +181,19 @@ def edit_loop(save_file: core.SaveFile) -> bool:
164
181
  if options is None:
165
182
  return False
166
183
 
184
+ items: list[core.StorageItem] = []
185
+
186
+ for id in options:
187
+ item = core.StorageItem.from_special_skill(id)
188
+
189
+ quantity, _ = dialog_creator.IntInput(default=1).get_input_locale(
190
+ "skill_quantity", {"name": skill_names[id]}
191
+ )
192
+ if quantity is None:
193
+ return False
194
+ for _ in range(quantity):
195
+ items.append(item)
196
+
167
197
  space = get_storage_space(storage)
168
198
  if len(options) > len(storage):
169
199
  color.ColoredText.localize(
@@ -177,8 +207,7 @@ def edit_loop(save_file: core.SaveFile) -> bool:
177
207
  return False
178
208
 
179
209
  color.ColoredText.localize("added_special_skills")
180
- for choice in options:
181
- item = core.StorageItem.from_special_skill(choice)
210
+ for item in items:
182
211
  add_item(storage, item)
183
212
  display_item(item, save_file)
184
213
 
@@ -8,7 +8,7 @@ class FeatureHandler:
8
8
  def __init__(self, save_file: core.SaveFile):
9
9
  self.save_file = save_file
10
10
 
11
- def get_features(self):
11
+ def get_features(self) -> dict[str, Any]:
12
12
  cat_features = {"cats": edits.cat_editor.CatEditor.edit_cats}
13
13
  if core.core_data.config.get_bool(core.ConfigKey.SEPARATE_CAT_EDIT_OPTIONS):
14
14
  cat_features = {
@@ -33,7 +33,9 @@ class FeatureHandler:
33
33
  "save_save": save_management.SaveManagement.save_save,
34
34
  "save_upload": save_management.SaveManagement.save_upload,
35
35
  "save_save_file": save_management.SaveManagement.save_save_dialog,
36
- "save_save_documents": save_management.SaveManagement.save_save_documents,
36
+ core.localize(
37
+ "save_save_documents", path=core.SaveFile.get_save_path()
38
+ ): save_management.SaveManagement.save_save_data_dir,
37
39
  "waydroid_push": save_management.SaveManagement.waydroid_push,
38
40
  "waydroid_push_rerun": save_management.SaveManagement.waydroid_push_rerun,
39
41
  "adb_push": save_management.SaveManagement.adb_push,
@@ -142,8 +144,7 @@ class FeatureHandler:
142
144
  }
143
145
  return features
144
146
 
145
- def get_feature(self, feature_name: str):
146
- feature_path = feature_name.split(".")
147
+ def get_feature(self, feature_path: list[str]):
147
148
  feature_dict = self.get_features()
148
149
  feature = feature_dict
149
150
  for path in feature_path:
@@ -154,10 +155,10 @@ class FeatureHandler:
154
155
  def search_features(
155
156
  self,
156
157
  name: str,
157
- parent_path: str = "",
158
+ current_path: list[str],
158
159
  features: dict[str, Any] | None = None,
159
- found_features: dict[str, int] | None = None,
160
- ) -> dict[str, int]:
160
+ found_features: dict[tuple[str, ...], int] | None = None,
161
+ ) -> dict[tuple[str, ...], int]:
161
162
  name = name.lower()
162
163
  if features is None:
163
164
  features = self.get_features()
@@ -166,9 +167,8 @@ class FeatureHandler:
166
167
 
167
168
  for feature_name_key, feature in features.items():
168
169
  feature_name = core.core_data.local_manager.get_key(feature_name_key)
169
- path = (
170
- f"{parent_path}.{feature_name_key}" if parent_path else feature_name_key
171
- )
170
+ path = current_path.copy()
171
+ path.append(feature_name_key)
172
172
  if isinstance(feature, dict):
173
173
  found_features.update(
174
174
  self.search_features(
@@ -180,34 +180,36 @@ class FeatureHandler:
180
180
  )
181
181
  for alias in core.LocalManager.get_all_aliases(feature_name):
182
182
  if not name:
183
- found_features[path] = 100
183
+ found_features[*path] = 100
184
184
  break
185
185
  alias = alias.lower()
186
186
 
187
187
  name = name.replace(" ", "")
188
188
  alias = alias.replace(" ", "")
189
189
  if alias in name or name in alias:
190
- found_features[path] = 100
190
+ found_features[*path] = 100
191
191
  break
192
192
 
193
193
  return found_features
194
194
 
195
- def display_features(self, features: list[str]):
195
+ def display_features(self, features: list[list[str]]):
196
196
  feature_names: list[str] = []
197
197
  for feature_name in features:
198
- feature_names.append(feature_name.split(".")[-1])
198
+ feature_names.append(feature_name[-1])
199
199
  print()
200
200
  dialog_creator.ListOutput(feature_names, [], "features", {}).display_locale(
201
201
  remove_alias=True
202
202
  )
203
203
 
204
- def select_features(self, features: list[str], parent_path: str = "") -> list[str]:
204
+ def select_features(
205
+ self, features: list[list[str]], current_path: list[str]
206
+ ) -> list[list[str]]:
205
207
  if features != list(self.get_features().keys()):
206
- features.insert(0, "go_back")
208
+ features.insert(0, ["go_back"])
207
209
  self.display_features(features)
208
210
  print()
209
211
  usr_input = color.ColoredInput().localize("select_features").strip()
210
- selected_features: list[str] = []
212
+ selected_features: list[list[str]] = []
211
213
  if usr_input.isdigit():
212
214
  usr_input = int(usr_input)
213
215
  if usr_input > len(features):
@@ -216,59 +218,53 @@ class FeatureHandler:
216
218
  color.ColoredText.localize("invalid_input")
217
219
  else:
218
220
  feature_name_top = features[usr_input - 1]
219
- if feature_name_top == "go_back":
220
- return list(self.get_features().keys())
221
+ if feature_name_top == ["go_back"]:
222
+ return [[k] for k in self.get_features().keys()]
221
223
  feature = self.get_feature(feature_name_top)
222
224
  if isinstance(feature, dict):
223
225
  for feature_name in feature.keys(): # type: ignore
224
- feature_path = (
225
- f"{parent_path}.{feature_name_top}.{feature_name}"
226
- if parent_path
227
- else f"{feature_name_top}.{feature_name}"
228
- )
226
+ feature_path: list[str] = current_path.copy()
227
+ feature_path.extend(feature_name_top + [feature_name])
229
228
  selected_features.append(feature_path)
230
229
 
231
230
  else:
232
- feature_path = (
233
- f"{parent_path}.{feature_name_top}"
234
- if parent_path
235
- else feature_name_top
236
- )
231
+ feature_path = current_path.copy()
232
+ feature_path.extend(feature_name_top)
237
233
  selected_features.append(feature_path)
238
234
 
239
235
  else:
240
- feats = self.search_features(usr_input)
236
+ feats = self.search_features(usr_input, [])
241
237
  if not feats:
242
238
  color.ColoredText.localize("no_feature_with_name", name=usr_input)
243
239
  kv_map = list(feats.items())
244
240
  kv_map.sort(key=lambda v: v[1], reverse=True)
245
- selected_features = [v[0] for v in kv_map]
241
+ selected_features = [list(v[0]) for v in kv_map]
246
242
 
247
243
  return selected_features
248
244
 
249
245
  def select_features_run(self):
250
- features = self.get_features()
251
- features = list(features.keys())
246
+ features_dict = self.get_features()
247
+ features: list[list[str]] = [[k] for k in features_dict.keys()]
252
248
  self.save_file.to_file_thread(self.save_file.get_temp_path())
253
249
  edits.clear_tutorial.clear_tutorial(self.save_file, False)
254
250
  self.save_file.show_ban_message = False
255
251
 
256
252
  while True:
257
- features = self.select_features(features)
253
+ features = self.select_features(features, [])
258
254
 
259
- new_features: list[str] = []
255
+ new_features: list[list[str]] = []
260
256
  found_strs: list[str] = []
261
257
  for feature_ in features:
262
- if feature_.split(".")[-1] in found_strs:
258
+ if feature_[-1] in found_strs:
263
259
  continue
264
- found_strs.append(feature_.split(".")[-1])
260
+ found_strs.append(feature_[-1])
265
261
  new_features.append(feature_)
266
262
 
267
263
  features = new_features
268
264
  feature = None
269
265
  if len(features) == 1:
270
266
  feature = features[0]
271
- if len(features) == 2 and features[0] == "go_back":
267
+ if len(features) == 2 and features[0] == ["go_back"]:
272
268
  feature = features[1]
273
269
 
274
270
  if not feature:
@@ -283,8 +279,8 @@ class FeatureHandler:
283
279
 
284
280
  self.save_file.to_file_thread(self.save_file.get_temp_path())
285
281
 
286
- features = self.get_features()
287
- features = list(features.keys())
282
+ features_dict = self.get_features()
283
+ features = [[k] for k in features_dict.keys()]
288
284
 
289
285
  core.core_data.game_data_getter = None # reset game data getter so that if an old version is removed, it will download the new version
290
286
 
@@ -40,6 +40,26 @@ class Main:
40
40
  if stop:
41
41
  break
42
42
 
43
+ def version_check(self, v1: str, v2: str) -> bool:
44
+ v1_p = v1.split(".")
45
+ v2_p = v2.split(".")
46
+
47
+ for p1, p2 in zip(v1_p, v2_p):
48
+ if p1.isdigit():
49
+ p1 = int(p1)
50
+ else:
51
+ continue
52
+ if p2.isdigit():
53
+ p2 = int(p2)
54
+ else:
55
+ continue
56
+ if p1 > p2:
57
+ return True
58
+ if p1 < p2:
59
+ return False
60
+
61
+ return len(v1_p) > len(v2_p)
62
+
43
63
  def check_update(self):
44
64
  """Check for updates."""
45
65
 
@@ -64,16 +84,19 @@ class Main:
64
84
  local_no_beta = local_version.split("b")[0]
65
85
  latest_no_beta = latest_version.split("b")[0]
66
86
 
67
- if latest_no_beta > local_no_beta:
87
+ if self.version_check(latest_no_beta, local_no_beta):
68
88
  update_needed = True
69
- elif latest_no_beta < local_no_beta:
89
+ elif self.version_check(local_no_beta, latest_no_beta):
70
90
  update_needed = False
71
91
  else:
72
92
  if latest_version == local_version:
73
93
  update_needed = False
74
94
  else:
75
95
  if is_local_beta and is_latest_beta:
76
- update_needed = latest_version > local_version
96
+ update_needed = self.version_check(
97
+ latest_version.replace("b", "."),
98
+ local_version.replace("b", "."),
99
+ )
77
100
  elif is_local_beta:
78
101
  update_needed = True
79
102
  else:
@@ -168,6 +191,15 @@ class Main:
168
191
  return stop
169
192
  self.save_file = save_file
170
193
 
194
+ color.ColoredText.localize(
195
+ "current_save",
196
+ inquiry_code=save_file.inquiry_code[:4]
197
+ + "***"
198
+ + save_file.inquiry_code[-2:],
199
+ gv=save_file.game_version,
200
+ cc=save_file.cc,
201
+ )
202
+
171
203
  self.feature_handler()
172
204
  return False
173
205
 
@@ -259,10 +291,8 @@ class Main:
259
291
  return None
260
292
  try:
261
293
  json_data = core.JsonFile.from_data(path.read()).to_object()
262
- except core.JSONDecodeError:
263
- color.ColoredText.localize(
264
- "load_json_fail", error=core.core_data.logger.get_traceback()
265
- )
294
+ except (core.JSONDecodeError, UnicodeDecodeError):
295
+ color.ColoredText.localize("parse_json_fail")
266
296
  return None
267
297
  try:
268
298
  save_file = core.SaveFile.from_dict(json_data)
@@ -0,0 +1,144 @@
1
+ from __future__ import annotations
2
+ from typing import Any
3
+
4
+ from bcsfe import core
5
+ import datetime
6
+ import json
7
+ from bcsfe.cli import color, dialog_creator
8
+
9
+
10
+ class RecentSave:
11
+ def __init__(
12
+ self,
13
+ path: core.Path,
14
+ cc: core.CountryCode,
15
+ gv: core.GameVersion,
16
+ inquiry: str,
17
+ time: datetime.datetime,
18
+ name: core.Path,
19
+ ):
20
+ self.path = path
21
+ self.cc = cc
22
+ self.gv = gv
23
+ self.inquiry = inquiry
24
+ self.time = time
25
+ self.name = name
26
+
27
+ @staticmethod
28
+ def from_dict(data: dict[str, Any]) -> RecentSave | None:
29
+ path = data.get("path")
30
+ cc = data.get("cc")
31
+ gv = data.get("gv")
32
+ inquiry = data.get("inquiry")
33
+ time_stamp = data.get("timestamp")
34
+ name = data.get("name")
35
+ if (
36
+ path is None
37
+ or cc is None
38
+ or gv is None
39
+ or inquiry is None
40
+ or time_stamp is None
41
+ or name is None
42
+ ):
43
+ return None
44
+
45
+ return RecentSave(
46
+ core.Path(path),
47
+ core.CountryCode(cc),
48
+ core.GameVersion.from_string(gv),
49
+ inquiry,
50
+ datetime.datetime.fromtimestamp(time_stamp),
51
+ core.Path(name),
52
+ )
53
+
54
+ def to_dict(self) -> dict[str, Any]:
55
+ return {
56
+ "path": self.path.to_str(),
57
+ "cc": self.cc.get_code(),
58
+ "gv": self.gv.to_string(),
59
+ "inquiry": self.inquiry,
60
+ "timestamp": self.time.timestamp(),
61
+ "name": self.name.to_str(),
62
+ }
63
+
64
+
65
+ class RecentSaves:
66
+ def __init__(self, saves: list[RecentSave]):
67
+ self.saves = saves
68
+
69
+ @staticmethod
70
+ def from_json(data: list[dict[str, Any]]) -> RecentSaves:
71
+ res: list[RecentSave] = []
72
+
73
+ for item in data:
74
+ save = RecentSave.from_dict(item)
75
+ if save is not None:
76
+ res.append(save)
77
+
78
+ return RecentSaves(res)
79
+
80
+ def to_json(self) -> list[dict[str, Any]]:
81
+ return [save.to_dict() for save in self.saves][-10:] # only store 10
82
+
83
+ @staticmethod
84
+ def from_path(path: core.Path) -> RecentSaves | None:
85
+ json_data = json.loads(path.read().to_str())
86
+
87
+ return RecentSaves.from_json(json_data)
88
+
89
+ def to_path(self, path: core.Path):
90
+ data = json.dumps(self.to_json(), indent=4)
91
+
92
+ try:
93
+ path.write(core.Data(data))
94
+ except Exception as e:
95
+ print(e)
96
+
97
+ @staticmethod
98
+ def read_default() -> RecentSaves:
99
+ path = RecentSaves.get_path()
100
+ if path.exists():
101
+ return RecentSaves.from_path(path) or RecentSaves([])
102
+ return RecentSaves([])
103
+
104
+ @staticmethod
105
+ def get_path() -> core.Path:
106
+ return core.Path.get_data_folder().add("recent_saves.json")
107
+
108
+ def save_default(self):
109
+ path = RecentSaves.get_path()
110
+ self.to_path(path)
111
+
112
+ def select(self) -> RecentSave | None:
113
+ if not self.saves:
114
+ color.ColoredText.localize("no_recent_saves")
115
+ return None
116
+ items: list[str] = []
117
+ for save in self.saves:
118
+ items.append(
119
+ core.localize(
120
+ "recent_save",
121
+ path=save.path,
122
+ cc=save.cc,
123
+ gv=save.gv,
124
+ inquiry_code=save.inquiry,
125
+ year=save.time.year,
126
+ month=str(save.time.month).zfill(2),
127
+ day=str(save.time.day).zfill(2),
128
+ hour=str(save.time.hour).zfill(2),
129
+ minute=str(save.time.minute).zfill(2),
130
+ second=str(save.time.second).zfill(2),
131
+ name=save.name,
132
+ )
133
+ )
134
+ items.reverse()
135
+
136
+ resp = dialog_creator.ChoiceInput.from_reduced(
137
+ items, localize_options=False, dialog="select_recent"
138
+ ).single_choice()
139
+ if resp is None:
140
+ return None
141
+
142
+ resp = len(self.saves) - resp
143
+
144
+ return self.saves[resp]