From fd8fcbfd50ca0bbb51c66c51481120d770dfb49d Mon Sep 17 00:00:00 2001 From: jpirnay Date: Tue, 31 Mar 2026 22:06:21 +0200 Subject: [PATCH] Introduce >2GB compatible PR 1424 from fdkekin0 --- lib/I18n/I18nKeys.h | 3 - lib/I18n/I18nStrings.cpp | 451 ++++++--------- lib/Xtc/Xtc.cpp | 14 +- lib/Xtc/Xtc.h | 5 + lib/Xtc/Xtc/XtcParser.cpp | 600 +++++++++++++++++--- lib/Xtc/Xtc/XtcParser.h | 78 ++- lib/Xtc/Xtc/XtcTypes.h | 27 + src/activities/reader/ReaderActivity.cpp | 7 + src/activities/reader/XtcReaderActivity.cpp | 5 + 9 files changed, 801 insertions(+), 389 deletions(-) diff --git a/lib/I18n/I18nKeys.h b/lib/I18n/I18nKeys.h index daf36f6e..cb2505ec 100644 --- a/lib/I18n/I18nKeys.h +++ b/lib/I18n/I18nKeys.h @@ -353,7 +353,6 @@ enum class StrId : uint16_t { STR_OPDS_BROWSER, STR_COVER_CUSTOM, STR_PAGE_OVERLAY, - STR_RECENTS, STR_MENU_RECENT_BOOKS, STR_NO_RECENT_BOOKS, STR_CALIBRE_DESC, @@ -418,8 +417,6 @@ enum class StrId : uint16_t { STR_AUTO_TURN_ENABLED, STR_AUTO_TURN_PAGES_PER_MIN, STR_INFO, - STR_AUTHOR, - STR_SERIES, STR_FILE_SIZE, STR_SLEEP_COVER_OVERLAY, STR_OVERLAY_WHITE, diff --git a/lib/I18n/I18nStrings.cpp b/lib/I18n/I18nStrings.cpp index e2ea849e..6d792551 100644 --- a/lib/I18n/I18nStrings.cpp +++ b/lib/I18n/I18nStrings.cpp @@ -1,4 +1,5 @@ #include "I18nStrings.h" + #include // THIS FILE IS AUTO-GENERATED BY gen_i18n.py. DO NOT EDIT. @@ -1224,7 +1225,6 @@ const char STRINGS_EN_DATA[] = "OPDS Browser\0" "Cover + Custom\0" "Page overlay\0" - "Recents\0" "Recent Books\0" "No recent books\0" "Use Calibre wireless device transfers\0" @@ -1289,8 +1289,6 @@ const char STRINGS_EN_DATA[] = "Auto Turn Enabled: \0" "Auto Turn (Pages Per Minute)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -1340,15 +1338,14 @@ const uint16_t OFFSETS_EN[] = { 3526, 3534, 3541, 3549, 3554, 3563, 3569, 3573, 3576, 3581, 3586, 3589, 3593, 3601, 3606, 3612, 3615, 3620, 3623, 3649, 3658, 3679, 3698, 3723, 3736, 3759, 3764, 3771, 3777, 3782, 3790, 3801, 3812, 3820, 3826, 3834, - 3843, 3851, 3856, 3870, 3890, 3910, 3923, 3938, 3951, 3959, 3972, 3988, - 4026, 4068, 4075, 4095, 4101, 4108, 4114, 4122, 4157, 4168, 4185, 4225, - 4260, 4278, 4299, 4317, 4336, 4344, 4352, 4366, 4384, 4391, 4398, 4414, - 4424, 4436, 4443, 4478, 4507, 4523, 4552, 4586, 4614, 4641, 4671, 4693, - 4719, 4755, 4771, 4779, 4786, 4810, 4837, 4848, 4870, 4892, 4917, 4942, - 4961, 4973, 4982, 4989, 5002, 5017, 5033, 5043, 5069, 5076, 5092, 5112, - 5141, 5146, 5153, 5160, 5165, 5191, 5197, 5202, 5208, 5212, 5251, 5278, - 5306, 5325, 5349, 5374, 5385, 5390, 5394, 5398, 5407, 5416, 5426, 5437, - 5444, 5453, 5471, + 3843, 3851, 3856, 3870, 3890, 3910, 3923, 3938, 3951, 3964, 3980, 4018, + 4060, 4067, 4087, 4093, 4100, 4106, 4114, 4149, 4160, 4177, 4217, 4252, + 4270, 4291, 4309, 4328, 4336, 4344, 4358, 4376, 4383, 4390, 4406, 4416, + 4428, 4435, 4470, 4499, 4515, 4544, 4578, 4606, 4633, 4663, 4685, 4711, + 4747, 4763, 4771, 4778, 4802, 4829, 4840, 4862, 4884, 4909, 4934, 4953, + 4965, 4974, 4981, 4994, 5009, 5025, 5035, 5061, 5068, 5084, 5104, 5133, + 5138, 5143, 5169, 5175, 5180, 5186, 5190, 5229, 5256, 5284, 5303, 5327, + 5352, 5363, 5368, 5372, 5376, 5385, 5394, 5404, 5415, 5422, 5431, 5449, }; const char STRINGS_ES_DATA[] = @@ -1796,7 +1793,6 @@ const char STRINGS_ES_DATA[] = "Navegador OPDS\0" "Portada + Pers.\0" "Page overlay\0" - "Recents\0" "Libros recientes\0" "No hay libros recientes\0" "Transferir contenido a este dispositivo\0" @@ -1925,8 +1921,6 @@ const char STRINGS_ES_DATA[] = "\xA1" "ginas por minuto\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -1976,15 +1970,14 @@ const uint16_t OFFSETS_ES[] = { 4131, 4141, 4150, 4159, 4165, 4175, 4186, 4190, 4193, 4201, 4209, 4218, 4230, 4245, 4250, 4256, 4262, 4268, 4271, 4305, 4315, 4344, 4371, 4397, 4415, 4443, 4448, 4454, 4460, 4466, 4476, 4489, 4507, 4525, 4533, 4542, - 4551, 4560, 4565, 4580, 4611, 4642, 4657, 4673, 4686, 4694, 4711, 4735, - 4775, 4825, 4833, 4854, 4869, 4876, 4883, 4898, 4941, 4953, 4965, 5015, - 5061, 5084, 5111, 5132, 5154, 5161, 5184, 5216, 5240, 5247, 5254, 5278, - 5285, 5297, 5305, 5342, 5378, 5407, 5440, 5482, 5513, 5540, 5570, 5591, - 5626, 5680, 5703, 5711, 5718, 5748, 5783, 5792, 5816, 5837, 5869, 5895, - 5914, 5939, 5946, 5952, 5969, 5986, 6008, 6023, 6059, 6068, 6094, 6116, - 6141, 6146, 6153, 6160, 6165, 6191, 6197, 6202, 6208, 6212, 6251, 6278, - 6306, 6325, 6349, 6374, 6385, 6390, 6394, 6398, 6407, 6416, 6426, 6437, - 6444, 6453, 6471, + 4551, 4560, 4565, 4580, 4611, 4642, 4657, 4673, 4686, 4703, 4727, 4767, + 4817, 4825, 4846, 4861, 4868, 4875, 4890, 4933, 4945, 4957, 5007, 5053, + 5076, 5103, 5124, 5146, 5153, 5176, 5208, 5232, 5239, 5246, 5270, 5277, + 5289, 5297, 5334, 5370, 5399, 5432, 5474, 5505, 5532, 5562, 5583, 5618, + 5672, 5695, 5703, 5710, 5740, 5775, 5784, 5808, 5829, 5861, 5887, 5906, + 5931, 5938, 5944, 5961, 5978, 6000, 6015, 6051, 6060, 6086, 6108, 6133, + 6138, 6143, 6169, 6175, 6180, 6186, 6190, 6229, 6256, 6284, 6303, 6327, + 6352, 6363, 6368, 6372, 6376, 6385, 6394, 6404, 6415, 6422, 6431, 6449, }; const char STRINGS_FR_DATA[] = @@ -2547,7 +2540,6 @@ const char STRINGS_FR_DATA[] = "Navigateur OPDS\0" "Couverture + Perso\0" "Page overlay\0" - "Recents\0" "Livres r\xC3" "\xA9" "cents\0" @@ -2683,8 +2675,6 @@ const char STRINGS_FR_DATA[] = "Tourne-page auto : \0" "Tourne-page auto (pages par minute)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -2734,15 +2724,14 @@ const uint16_t OFFSETS_FR[] = { 3905, 3915, 3923, 3933, 3940, 3954, 3965, 3969, 3973, 3982, 3990, 3993, 3997, 4009, 4016, 4023, 4028, 4032, 4035, 4059, 4069, 4100, 4128, 4155, 4176, 4199, 4204, 4212, 4221, 4227, 4236, 4248, 4263, 4271, 4277, 4286, - 4303, 4313, 4318, 4332, 4365, 4392, 4408, 4427, 4440, 4448, 4464, 4484, - 4520, 4560, 4568, 4590, 4604, 4611, 4619, 4627, 4657, 4670, 4686, 4717, - 4740, 4758, 4772, 4790, 4808, 4819, 4834, 4854, 4876, 4886, 4893, 4916, - 4928, 4940, 4949, 4988, 5023, 5047, 5066, 5085, 5117, 5144, 5174, 5195, - 5213, 5255, 5278, 5289, 5297, 5319, 5344, 5354, 5385, 5412, 5440, 5468, - 5491, 5512, 5521, 5529, 5544, 5560, 5577, 5598, 5625, 5632, 5649, 5669, - 5705, 5710, 5717, 5724, 5729, 5755, 5761, 5766, 5772, 5776, 5815, 5842, - 5870, 5889, 5913, 5938, 5949, 5954, 5958, 5962, 5971, 5980, 5990, 6001, - 6008, 6017, 6035, + 4303, 4313, 4318, 4332, 4365, 4392, 4408, 4427, 4440, 4456, 4476, 4512, + 4552, 4560, 4582, 4596, 4603, 4611, 4619, 4649, 4662, 4678, 4709, 4732, + 4750, 4764, 4782, 4800, 4811, 4826, 4846, 4868, 4878, 4885, 4908, 4920, + 4932, 4941, 4980, 5015, 5039, 5058, 5077, 5109, 5136, 5166, 5187, 5205, + 5247, 5270, 5281, 5289, 5311, 5336, 5346, 5377, 5404, 5432, 5460, 5483, + 5504, 5513, 5521, 5536, 5552, 5569, 5590, 5617, 5624, 5641, 5661, 5697, + 5702, 5707, 5733, 5739, 5744, 5750, 5754, 5793, 5820, 5848, 5867, 5891, + 5916, 5927, 5932, 5936, 5940, 5949, 5958, 5968, 5979, 5986, 5995, 6013, }; const char STRINGS_DE_DATA[] = @@ -3130,7 +3119,6 @@ const char STRINGS_DE_DATA[] = "OPDS-Browser\0" "Umschlag + Eigenes\0" "Seiten-Overlay\0" - "Zuletzt\0" "Zuletzt gelesen\0" "Keine B\xC3" "\xBC" @@ -3233,8 +3221,6 @@ const char STRINGS_DE_DATA[] = "\xA4" "ttern (Seiten/Min.)\0" "Info\0" - "Autor\0" - "Reihe\0" "Gr\xC3" "\xB6" "\xC3" @@ -3290,15 +3276,14 @@ const uint16_t OFFSETS_DE[] = { 3641, 3653, 3663, 3673, 3681, 3695, 3704, 3707, 3712, 3719, 3730, 3733, 3737, 3742, 3748, 3755, 3760, 3767, 3770, 3790, 3799, 3821, 3842, 3863, 3882, 3896, 3902, 3909, 3914, 3919, 3927, 3938, 3948, 3957, 3963, 3972, - 3986, 3996, 4001, 4015, 4031, 4054, 4067, 4086, 4101, 4109, 4125, 4139, - 4167, 4203, 4213, 4232, 4245, 4252, 4259, 4268, 4303, 4308, 4327, 4354, - 4384, 4403, 4426, 4443, 4461, 4471, 4482, 4510, 4528, 4537, 4547, 4569, - 4578, 4591, 4598, 4630, 4664, 4688, 4715, 4744, 4776, 4805, 4834, 4859, - 4879, 4915, 4925, 4933, 4940, 4969, 5001, 5011, 5044, 5074, 5100, 5129, - 5142, 5157, 5167, 5177, 5187, 5206, 5222, 5232, 5265, 5272, 5293, 5318, - 5349, 5354, 5360, 5366, 5374, 5395, 5401, 5406, 5412, 5416, 5455, 5482, - 5510, 5530, 5568, 5607, 5618, 5623, 5627, 5631, 5642, 5652, 5663, 5676, - 5685, 5691, 5717, + 3986, 3996, 4001, 4015, 4031, 4054, 4067, 4086, 4101, 4117, 4131, 4159, + 4195, 4205, 4224, 4237, 4244, 4251, 4260, 4295, 4300, 4319, 4346, 4376, + 4395, 4418, 4435, 4453, 4463, 4474, 4502, 4520, 4529, 4539, 4561, 4570, + 4583, 4590, 4622, 4656, 4680, 4707, 4736, 4768, 4797, 4826, 4851, 4871, + 4907, 4917, 4925, 4932, 4961, 4993, 5003, 5036, 5066, 5092, 5121, 5134, + 5149, 5159, 5169, 5179, 5198, 5214, 5224, 5257, 5264, 5285, 5310, 5341, + 5346, 5354, 5375, 5381, 5386, 5392, 5396, 5435, 5462, 5490, 5510, 5548, + 5587, 5598, 5603, 5607, 5611, 5622, 5632, 5643, 5656, 5665, 5671, 5697, }; const char STRINGS_CS_DATA[] = @@ -4373,7 +4358,6 @@ const char STRINGS_CS_DATA[] = "\xAD" "\0" "Page overlay\0" - "Recents\0" "Ned\xC3" "\xA1" "vn\xC3" @@ -4704,8 +4688,6 @@ const char STRINGS_CS_DATA[] = "Auto Turn Enabled: \0" "Auto Turn (Pages Per Minute)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -4755,15 +4737,14 @@ const uint16_t OFFSETS_CS[] = { 4344, 4353, 4361, 4371, 4381, 4391, 4404, 4408, 4411, 4416, 4421, 4425, 4429, 4441, 4447, 4454, 4461, 4467, 4470, 4494, 4503, 4524, 4543, 4568, 4581, 4604, 4609, 4616, 4622, 4627, 4635, 4646, 4657, 4665, 4671, 4679, - 4698, 4708, 4713, 4727, 4754, 4786, 4804, 4823, 4836, 4844, 4860, 4885, - 4940, 4986, 4997, 5020, 5031, 5036, 5044, 5054, 5102, 5116, 5133, 5190, - 5239, 5261, 5286, 5308, 5331, 5345, 5360, 5383, 5408, 5415, 5422, 5438, - 5448, 5459, 5466, 5508, 5545, 5567, 5596, 5638, 5677, 5704, 5734, 5759, - 5806, 5844, 5862, 5874, 5885, 5914, 5946, 5955, 5982, 6007, 6044, 6071, - 6087, 6115, 6121, 6129, 6140, 6155, 6172, 6182, 6208, 6215, 6241, 6261, - 6290, 6295, 6302, 6309, 6314, 6340, 6346, 6351, 6357, 6361, 6400, 6427, - 6455, 6474, 6498, 6523, 6534, 6539, 6543, 6547, 6556, 6565, 6575, 6586, - 6593, 6602, 6620, + 4698, 4708, 4713, 4727, 4754, 4786, 4804, 4823, 4836, 4852, 4877, 4932, + 4978, 4989, 5012, 5023, 5028, 5036, 5046, 5094, 5108, 5125, 5182, 5231, + 5253, 5278, 5300, 5323, 5337, 5352, 5375, 5400, 5407, 5414, 5430, 5440, + 5451, 5458, 5500, 5537, 5559, 5588, 5630, 5669, 5696, 5726, 5751, 5798, + 5836, 5854, 5866, 5877, 5906, 5938, 5947, 5974, 5999, 6036, 6063, 6079, + 6107, 6113, 6121, 6132, 6147, 6164, 6174, 6200, 6207, 6233, 6253, 6282, + 6287, 6292, 6318, 6324, 6329, 6335, 6339, 6378, 6405, 6433, 6452, 6476, + 6501, 6512, 6517, 6521, 6525, 6534, 6543, 6553, 6564, 6571, 6580, 6598, }; const char STRINGS_PO_DATA[] = @@ -5239,7 +5220,6 @@ const char STRINGS_PO_DATA[] = "Navegador OPDS\0" "Capa + personalizado\0" "Page overlay\0" - "Recents\0" "Livros recentes\0" "Sem livros recentes\0" "Usar transfer\xC3" @@ -5380,8 +5360,6 @@ const char STRINGS_PO_DATA[] = "Auto Turn Enabled: \0" "Auto Turn (Pages Per Minute)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -5431,15 +5409,14 @@ const uint16_t OFFSETS_PO[] = { 4090, 4100, 4109, 4118, 4124, 4131, 4148, 4152, 4157, 4162, 4167, 4172, 4178, 4192, 4201, 4209, 4214, 4220, 4223, 4257, 4267, 4288, 4307, 4332, 4345, 4368, 4373, 4380, 4386, 4391, 4399, 4410, 4421, 4429, 4435, 4443, - 4461, 4471, 4476, 4490, 4517, 4543, 4558, 4579, 4592, 4600, 4616, 4636, - 4676, 4717, 4726, 4749, 4764, 4771, 4778, 4786, 4833, 4849, 4864, 4921, - 4970, 4990, 5013, 5035, 5056, 5066, 5084, 5106, 5129, 5137, 5144, 5160, - 5171, 5183, 5190, 5232, 5261, 5289, 5321, 5359, 5388, 5415, 5445, 5467, - 5498, 5548, 5570, 5578, 5585, 5610, 5638, 5645, 5670, 5693, 5728, 5752, - 5771, 5796, 5804, 5811, 5827, 5843, 5864, 5874, 5900, 5907, 5921, 5941, - 5970, 5975, 5982, 5989, 5994, 6020, 6026, 6031, 6037, 6041, 6080, 6107, - 6135, 6154, 6178, 6203, 6214, 6219, 6223, 6227, 6236, 6245, 6255, 6266, - 6273, 6282, 6300, + 4461, 4471, 4476, 4490, 4517, 4543, 4558, 4579, 4592, 4608, 4628, 4668, + 4709, 4718, 4741, 4756, 4763, 4770, 4778, 4825, 4841, 4856, 4913, 4962, + 4982, 5005, 5027, 5048, 5058, 5076, 5098, 5121, 5129, 5136, 5152, 5163, + 5175, 5182, 5224, 5253, 5281, 5313, 5351, 5380, 5407, 5437, 5459, 5490, + 5540, 5562, 5570, 5577, 5602, 5630, 5637, 5662, 5685, 5720, 5744, 5763, + 5788, 5796, 5803, 5819, 5835, 5856, 5866, 5892, 5899, 5913, 5933, 5962, + 5967, 5972, 5998, 6004, 6009, 6015, 6019, 6058, 6085, 6113, 6132, 6156, + 6181, 6192, 6197, 6201, 6205, 6214, 6223, 6233, 6244, 6251, 6260, 6278, }; const char STRINGS_RU_DATA[] = @@ -10999,7 +10976,6 @@ const char STRINGS_RU_DATA[] = "\xB9" "\0" "Page overlay\0" - "Recents\0" "\xD0" "\x9D" "\xD0" @@ -12998,8 +12974,6 @@ const char STRINGS_RU_DATA[] = "\x83" ")\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -13049,15 +13023,14 @@ const uint16_t OFFSETS_RU[] = { 5973, 5985, 5998, 6010, 6025, 6040, 6059, 6064, 6071, 6088, 6101, 6108, 6117, 6145, 6156, 6169, 6180, 6189, 6192, 6225, 6242, 6293, 6340, 6372, 6404, 6459, 6472, 6487, 6502, 6513, 6524, 6538, 6566, 6591, 6608, 6623, - 6653, 6678, 6683, 6697, 6741, 6798, 6818, 6844, 6857, 6865, 6893, 6926, - 7001, 7078, 7091, 7115, 7134, 7147, 7164, 7188, 7249, 7273, 7299, 7380, - 7464, 7495, 7538, 7569, 7602, 7622, 7642, 7692, 7725, 7740, 7747, 7805, - 7817, 7828, 7840, 7885, 7939, 7984, 8028, 8093, 8155, 8182, 8212, 8251, - 8306, 8373, 8404, 8424, 8444, 8483, 8506, 8515, 8570, 8625, 8679, 8729, - 8766, 8806, 8819, 8838, 8860, 8892, 8916, 8937, 9000, 9015, 9056, 9097, - 9154, 9159, 9166, 9173, 9178, 9204, 9210, 9215, 9221, 9225, 9264, 9291, - 9319, 9338, 9362, 9387, 9398, 9403, 9407, 9411, 9420, 9429, 9439, 9450, - 9457, 9466, 9484, + 6653, 6678, 6683, 6697, 6741, 6798, 6818, 6844, 6857, 6885, 6918, 6993, + 7070, 7083, 7107, 7126, 7139, 7156, 7180, 7241, 7265, 7291, 7372, 7456, + 7487, 7530, 7561, 7594, 7614, 7634, 7684, 7717, 7732, 7739, 7797, 7809, + 7820, 7832, 7877, 7931, 7976, 8020, 8085, 8147, 8174, 8204, 8243, 8298, + 8365, 8396, 8416, 8436, 8475, 8498, 8507, 8562, 8617, 8671, 8721, 8758, + 8798, 8811, 8830, 8852, 8884, 8908, 8929, 8992, 9007, 9048, 9089, 9146, + 9151, 9156, 9182, 9188, 9193, 9199, 9203, 9242, 9269, 9297, 9316, 9340, + 9365, 9376, 9381, 9385, 9389, 9398, 9407, 9417, 9428, 9435, 9444, 9462, }; const char STRINGS_SV_DATA[] = @@ -13662,7 +13635,6 @@ const char STRINGS_SV_DATA[] = "sare\0" "Omslag + Valfri\0" "Page overlay\0" - "Recents\0" "Senaste b\xC3" "\xB6" "ckerna\0" @@ -13825,8 +13797,6 @@ const char STRINGS_SV_DATA[] = "\xA4" "ndning (sidor per minut)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -13876,15 +13846,14 @@ const uint16_t OFFSETS_SV[] = { 3961, 3971, 3978, 3985, 3992, 4002, 4016, 4019, 4023, 4028, 4034, 4038, 4041, 4056, 4065, 4072, 4076, 4080, 4085, 4113, 4122, 4142, 4164, 4189, 4208, 4240, 4245, 4252, 4258, 4262, 4270, 4281, 4290, 4310, 4316, 4324, - 4350, 4359, 4364, 4377, 4403, 4423, 4440, 4456, 4469, 4477, 4495, 4516, - 4563, 4608, 4614, 4632, 4645, 4652, 4658, 4667, 4705, 4716, 4732, 4781, - 4819, 4841, 4867, 4893, 4918, 4929, 4937, 4961, 4977, 4984, 4991, 5012, - 5021, 5033, 5038, 5082, 5114, 5135, 5158, 5197, 5220, 5247, 5277, 5297, - 5323, 5364, 5380, 5388, 5396, 5419, 5445, 5457, 5480, 5506, 5532, 5562, - 5583, 5611, 5619, 5631, 5639, 5654, 5672, 5681, 5714, 5722, 5739, 5772, - 5811, 5816, 5823, 5830, 5835, 5861, 5867, 5872, 5878, 5882, 5921, 5948, - 5976, 5995, 6019, 6044, 6055, 6060, 6064, 6068, 6077, 6086, 6096, 6107, - 6114, 6123, 6141, + 4350, 4359, 4364, 4377, 4403, 4423, 4440, 4456, 4469, 4487, 4508, 4555, + 4600, 4606, 4624, 4637, 4644, 4650, 4659, 4697, 4708, 4724, 4773, 4811, + 4833, 4859, 4885, 4910, 4921, 4929, 4953, 4969, 4976, 4983, 5004, 5013, + 5025, 5030, 5074, 5106, 5127, 5150, 5189, 5212, 5239, 5269, 5289, 5315, + 5356, 5372, 5380, 5388, 5411, 5437, 5449, 5472, 5498, 5524, 5554, 5575, + 5603, 5611, 5623, 5631, 5646, 5664, 5673, 5706, 5714, 5731, 5764, 5803, + 5808, 5813, 5839, 5845, 5850, 5856, 5860, 5899, 5926, 5954, 5973, 5997, + 6022, 6033, 6038, 6042, 6046, 6055, 6064, 6074, 6085, 6092, 6101, 6119, }; const char STRINGS_RO_DATA[] = @@ -14598,7 +14567,6 @@ const char STRINGS_RO_DATA[] = "\x83" " + Personalizat\0" "Page overlay\0" - "Recents\0" "C\xC4" "\x83" "r\xC5" @@ -14791,8 +14759,6 @@ const char STRINGS_RO_DATA[] = ": \0" "Pagini pe minut\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -14842,15 +14808,14 @@ const uint16_t OFFSETS_RO[] = { 4235, 4245, 4253, 4263, 4274, 4284, 4297, 4300, 4303, 4314, 4322, 4329, 4335, 4348, 4356, 4364, 4368, 4372, 4375, 4398, 4407, 4434, 4463, 4488, 4505, 4530, 4539, 4545, 4553, 4559, 4567, 4580, 4596, 4611, 4617, 4625, - 4633, 4640, 4645, 4659, 4687, 4713, 4726, 4750, 4763, 4771, 4787, 4808, - 4869, 4918, 4926, 4946, 4958, 4964, 4974, 4983, 5029, 5040, 5054, 5104, - 5142, 5162, 5185, 5205, 5225, 5239, 5246, 5267, 5291, 5302, 5309, 5336, - 5346, 5359, 5367, 5407, 5441, 5465, 5502, 5545, 5587, 5614, 5644, 5672, - 5702, 5743, 5759, 5767, 5774, 5802, 5833, 5845, 5870, 5899, 5928, 5960, - 5980, 6002, 6013, 6025, 6041, 6058, 6074, 6089, 6111, 6118, 6133, 6155, - 6171, 6176, 6183, 6190, 6195, 6221, 6227, 6232, 6238, 6242, 6281, 6308, - 6336, 6355, 6379, 6404, 6415, 6420, 6424, 6428, 6437, 6446, 6456, 6467, - 6474, 6483, 6501, + 4633, 4640, 4645, 4659, 4687, 4713, 4726, 4750, 4763, 4779, 4800, 4861, + 4910, 4918, 4938, 4950, 4956, 4966, 4975, 5021, 5032, 5046, 5096, 5134, + 5154, 5177, 5197, 5217, 5231, 5238, 5259, 5283, 5294, 5301, 5328, 5338, + 5351, 5359, 5399, 5433, 5457, 5494, 5537, 5579, 5606, 5636, 5664, 5694, + 5735, 5751, 5759, 5766, 5794, 5825, 5837, 5862, 5891, 5920, 5952, 5972, + 5994, 6005, 6017, 6033, 6050, 6066, 6081, 6103, 6110, 6125, 6147, 6163, + 6168, 6173, 6199, 6205, 6210, 6216, 6220, 6259, 6286, 6314, 6333, 6357, + 6382, 6393, 6398, 6402, 6406, 6415, 6424, 6434, 6445, 6452, 6461, 6479, }; const char STRINGS_CA_DATA[] = @@ -15336,7 +15301,6 @@ const char STRINGS_CA_DATA[] = "Navegador OPDS\0" "Portada + Personalitzat\0" "Page overlay\0" - "Recents\0" "Llibres recents\0" "No hi ha llibres recents\0" "Usa les transfer\xC3" @@ -15479,8 +15443,6 @@ const char STRINGS_CA_DATA[] = "\xA0" "gines per minut)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -15530,15 +15492,14 @@ const uint16_t OFFSETS_CA[] = { 4469, 4478, 4489, 4498, 4503, 4514, 4525, 4529, 4532, 4540, 4547, 4550, 4554, 4567, 4576, 4582, 4588, 4594, 4597, 4626, 4635, 4665, 4700, 4735, 4753, 4783, 4788, 4796, 4805, 4812, 4821, 4833, 4851, 4865, 4872, 4880, - 4903, 4912, 4917, 4930, 4965, 4995, 5010, 5034, 5047, 5055, 5071, 5096, - 5142, 5180, 5187, 5218, 5228, 5234, 5242, 5254, 5293, 5305, 5317, 5376, - 5425, 5443, 5463, 5483, 5500, 5509, 5523, 5547, 5582, 5590, 5597, 5624, - 5635, 5650, 5659, 5697, 5733, 5767, 5809, 5853, 5891, 5918, 5948, 5978, - 6010, 6062, 6084, 6091, 6098, 6123, 6151, 6160, 6185, 6208, 6238, 6270, - 6286, 6310, 6319, 6324, 6341, 6357, 6379, 6392, 6433, 6443, 6471, 6499, - 6538, 6543, 6550, 6557, 6562, 6588, 6594, 6599, 6605, 6609, 6648, 6675, - 6703, 6722, 6746, 6771, 6782, 6787, 6791, 6795, 6804, 6813, 6823, 6834, - 6841, 6850, 6868, + 4903, 4912, 4917, 4930, 4965, 4995, 5010, 5034, 5047, 5063, 5088, 5134, + 5172, 5179, 5210, 5220, 5226, 5234, 5246, 5285, 5297, 5309, 5368, 5417, + 5435, 5455, 5475, 5492, 5501, 5515, 5539, 5574, 5582, 5589, 5616, 5627, + 5642, 5651, 5689, 5725, 5759, 5801, 5845, 5883, 5910, 5940, 5970, 6002, + 6054, 6076, 6083, 6090, 6115, 6143, 6152, 6177, 6200, 6230, 6262, 6278, + 6302, 6311, 6316, 6333, 6349, 6371, 6384, 6425, 6435, 6463, 6491, 6530, + 6535, 6540, 6566, 6572, 6577, 6583, 6587, 6626, 6653, 6681, 6700, 6724, + 6749, 6760, 6765, 6769, 6773, 6782, 6791, 6801, 6812, 6819, 6828, 6846, }; const char STRINGS_UK_DATA[] = @@ -21816,7 +21777,6 @@ const char STRINGS_UK_DATA[] = "\xB0" "\0" "Page overlay\0" - "Recents\0" "\xD0" "\x9E" "\xD1" @@ -23933,8 +23893,6 @@ const char STRINGS_UK_DATA[] = "\xB2" ")\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -23984,15 +23942,14 @@ const uint16_t OFFSETS_UK[] = { 6651, 6674, 6693, 6707, 6724, 6738, 6752, 6759, 6764, 6781, 6796, 6807, 6816, 6844, 6853, 6864, 6875, 6884, 6887, 6941, 6958, 7003, 7054, 7094, 7122, 7165, 7178, 7195, 7208, 7219, 7232, 7248, 7270, 7287, 7298, 7319, - 7349, 7366, 7371, 7385, 7445, 7496, 7516, 7566, 7579, 7587, 7613, 7650, - 7746, 7829, 7842, 7866, 7891, 7904, 7921, 7953, 8029, 8055, 8083, 8162, - 8240, 8273, 8318, 8349, 8382, 8404, 8424, 8466, 8501, 8518, 8525, 8574, - 8589, 8612, 8625, 8681, 8731, 8770, 8822, 8887, 8949, 8976, 9006, 9051, - 9105, 9195, 9228, 9250, 9270, 9313, 9359, 9372, 9431, 9488, 9550, 9604, - 9643, 9690, 9704, 9727, 9749, 9781, 9805, 9818, 9871, 9892, 9918, 9964, - 10009, 10014, 10021, 10028, 10033, 10059, 10065, 10070, 10076, 10080, 10119, 10146, - 10174, 10193, 10217, 10242, 10253, 10258, 10262, 10266, 10275, 10284, 10294, 10305, - 10312, 10321, 10339, + 7349, 7366, 7371, 7385, 7445, 7496, 7516, 7566, 7579, 7605, 7642, 7738, + 7821, 7834, 7858, 7883, 7896, 7913, 7945, 8021, 8047, 8075, 8154, 8232, + 8265, 8310, 8341, 8374, 8396, 8416, 8458, 8493, 8510, 8517, 8566, 8581, + 8604, 8617, 8673, 8723, 8762, 8814, 8879, 8941, 8968, 8998, 9043, 9097, + 9187, 9220, 9242, 9262, 9305, 9351, 9364, 9423, 9480, 9542, 9596, 9635, + 9682, 9696, 9719, 9741, 9773, 9797, 9810, 9863, 9884, 9910, 9956, 10001, + 10006, 10011, 10037, 10043, 10048, 10054, 10058, 10097, 10124, 10152, 10171, 10195, + 10220, 10231, 10236, 10240, 10244, 10253, 10262, 10272, 10283, 10290, 10299, 10317, }; const char STRINGS_BE_DATA[] = @@ -29182,7 +29139,6 @@ const char STRINGS_BE_DATA[] = "\xB9" "\0" "Page overlay\0" - "Recents\0" "\xD0" "\x9D" "\xD1" @@ -30947,8 +30903,6 @@ const char STRINGS_BE_DATA[] = "Auto Turn Enabled: \0" "Auto Turn (Pages Per Minute)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -30998,15 +30952,14 @@ const uint16_t OFFSETS_BE[] = { 6034, 6044, 6057, 6069, 6084, 6105, 6124, 6131, 6136, 6141, 6146, 6153, 6162, 6190, 6201, 6214, 6225, 6234, 6237, 6270, 6287, 6308, 6327, 6352, 6365, 6388, 6393, 6400, 6406, 6411, 6419, 6430, 6441, 6449, 6455, 6463, - 6493, 6512, 6517, 6531, 6575, 6632, 6652, 6680, 6693, 6701, 6729, 6764, - 6843, 6918, 6931, 6955, 6970, 6983, 7000, 7024, 7085, 7111, 7139, 7218, - 7302, 7333, 7376, 7407, 7440, 7464, 7486, 7530, 7565, 7572, 7579, 7595, - 7611, 7624, 7636, 7681, 7735, 7774, 7824, 7887, 7947, 7974, 8004, 8041, - 8098, 8170, 8205, 8223, 8241, 8280, 8303, 8312, 8363, 8414, 8470, 8520, - 8557, 8599, 8614, 8633, 8655, 8685, 8709, 8719, 8745, 8752, 8793, 8813, - 8842, 8847, 8854, 8861, 8866, 8892, 8898, 8903, 8909, 8913, 8952, 8979, - 9007, 9026, 9050, 9075, 9086, 9091, 9095, 9099, 9108, 9117, 9127, 9138, - 9145, 9154, 9172, + 6493, 6512, 6517, 6531, 6575, 6632, 6652, 6680, 6693, 6721, 6756, 6835, + 6910, 6923, 6947, 6962, 6975, 6992, 7016, 7077, 7103, 7131, 7210, 7294, + 7325, 7368, 7399, 7432, 7456, 7478, 7522, 7557, 7564, 7571, 7587, 7603, + 7616, 7628, 7673, 7727, 7766, 7816, 7879, 7939, 7966, 7996, 8033, 8090, + 8162, 8197, 8215, 8233, 8272, 8295, 8304, 8355, 8406, 8462, 8512, 8549, + 8591, 8606, 8625, 8647, 8677, 8701, 8711, 8737, 8744, 8785, 8805, 8834, + 8839, 8844, 8870, 8876, 8881, 8887, 8891, 8930, 8957, 8985, 9004, 9028, + 9053, 9064, 9069, 9073, 9077, 9086, 9095, 9105, 9116, 9123, 9132, 9150, }; const char STRINGS_IT_DATA[] = @@ -31310,7 +31263,6 @@ const char STRINGS_IT_DATA[] = "Browser OPDS\0" "Copertina + Personalizzato\0" "Page overlay\0" - "Recents\0" "Libri Recenti\0" "Nessun libro recente\0" "Usa i trasferimenti wireless del dispositivo di Calibre\0" @@ -31389,8 +31341,6 @@ const char STRINGS_IT_DATA[] = "Auto Turn Enabled: \0" "Auto Turn (Pages Per Minute)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -31440,15 +31390,14 @@ const uint16_t OFFSETS_IT[] = { 4219, 4228, 4236, 4245, 4250, 4258, 4266, 4270, 4273, 4278, 4283, 4286, 4290, 4304, 4312, 4318, 4321, 4326, 4329, 4362, 4372, 4393, 4412, 4437, 4450, 4473, 4478, 4485, 4491, 4496, 4504, 4515, 4526, 4534, 4540, 4548, - 4556, 4565, 4570, 4582, 4613, 4639, 4652, 4679, 4692, 4700, 4714, 4735, - 4791, 4844, 4854, 4880, 4895, 4901, 4909, 4921, 4966, 4980, 4995, 5047, - 5089, 5113, 5137, 5161, 5183, 5192, 5206, 5228, 5248, 5255, 5262, 5278, - 5289, 5302, 5310, 5346, 5380, 5407, 5442, 5483, 5512, 5539, 5569, 5594, - 5626, 5672, 5691, 5699, 5707, 5735, 5766, 5775, 5800, 5824, 5856, 5887, - 5907, 5932, 5941, 5948, 5964, 5980, 6000, 6010, 6036, 6043, 6061, 6081, - 6110, 6115, 6122, 6129, 6134, 6160, 6166, 6171, 6177, 6181, 6220, 6247, - 6275, 6294, 6318, 6343, 6354, 6359, 6363, 6367, 6376, 6385, 6395, 6406, - 6413, 6422, 6440, + 4556, 4565, 4570, 4582, 4613, 4639, 4652, 4679, 4692, 4706, 4727, 4783, + 4836, 4846, 4872, 4887, 4893, 4901, 4913, 4958, 4972, 4987, 5039, 5081, + 5105, 5129, 5153, 5175, 5184, 5198, 5220, 5240, 5247, 5254, 5270, 5281, + 5294, 5302, 5338, 5372, 5399, 5434, 5475, 5504, 5531, 5561, 5586, 5618, + 5664, 5683, 5691, 5699, 5727, 5758, 5767, 5792, 5816, 5848, 5879, 5899, + 5924, 5933, 5940, 5956, 5972, 5992, 6002, 6028, 6035, 6053, 6073, 6102, + 6107, 6112, 6138, 6144, 6149, 6155, 6159, 6198, 6225, 6253, 6272, 6296, + 6321, 6332, 6337, 6341, 6345, 6354, 6363, 6373, 6384, 6391, 6400, 6418, }; const char STRINGS_PL_DATA[] = @@ -32172,7 +32121,6 @@ const char STRINGS_PL_DATA[] = "\x82" "asne\0" "Page overlay\0" - "Recents\0" "Ostatnio czytane\0" "Brak ostatnio czytanych\0" "Przesy\xC5" @@ -32371,8 +32319,6 @@ const char STRINGS_PL_DATA[] = "Auto-kartkowanie: \0" "Auto-kartkowanie (str./min)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -32422,15 +32368,14 @@ const uint16_t OFFSETS_PL[] = { 4101, 4112, 4119, 4128, 4136, 4144, 4151, 4155, 4159, 4166, 4172, 4177, 4183, 4197, 4202, 4208, 4214, 4220, 4223, 4249, 4258, 4285, 4303, 4321, 4336, 4361, 4368, 4376, 4382, 4392, 4402, 4415, 4432, 4441, 4448, 4456, - 4467, 4475, 4480, 4494, 4536, 4567, 4580, 4599, 4612, 4620, 4637, 4661, - 4697, 4736, 4745, 4768, 4774, 4781, 4791, 4802, 4851, 4865, 4881, 4925, - 4969, 4989, 5013, 5031, 5050, 5060, 5088, 5111, 5148, 5154, 5161, 5188, - 5200, 5212, 5224, 5263, 5296, 5320, 5350, 5384, 5415, 5442, 5472, 5494, - 5527, 5563, 5583, 5591, 5600, 5628, 5659, 5668, 5692, 5718, 5751, 5778, - 5796, 5820, 5828, 5836, 5851, 5874, 5891, 5900, 5931, 5938, 5957, 5976, - 6004, 6009, 6016, 6023, 6028, 6054, 6060, 6065, 6071, 6075, 6114, 6141, - 6169, 6188, 6212, 6237, 6248, 6253, 6257, 6261, 6270, 6279, 6289, 6300, - 6307, 6316, 6334, + 4467, 4475, 4480, 4494, 4536, 4567, 4580, 4599, 4612, 4629, 4653, 4689, + 4728, 4737, 4760, 4766, 4773, 4783, 4794, 4843, 4857, 4873, 4917, 4961, + 4981, 5005, 5023, 5042, 5052, 5080, 5103, 5140, 5146, 5153, 5180, 5192, + 5204, 5216, 5255, 5288, 5312, 5342, 5376, 5407, 5434, 5464, 5486, 5519, + 5555, 5575, 5583, 5592, 5620, 5651, 5660, 5684, 5710, 5743, 5770, 5788, + 5812, 5820, 5828, 5843, 5866, 5883, 5892, 5923, 5930, 5949, 5968, 5996, + 6001, 6006, 6032, 6038, 6043, 6049, 6053, 6092, 6119, 6147, 6166, 6190, + 6215, 6226, 6231, 6235, 6239, 6248, 6257, 6267, 6278, 6285, 6294, 6312, }; const char STRINGS_FI_DATA[] = @@ -33004,7 +32949,6 @@ const char STRINGS_FI_DATA[] = "OPDS-selain\0" "Kansi + mukautettu\0" "Page overlay\0" - "Recents\0" "Viimeisimm\xC3" "\xA4" "t kirjat\0" @@ -33169,8 +33113,6 @@ const char STRINGS_FI_DATA[] = "Auto Turn Enabled: \0" "Auto Turn (Pages Per Minute)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -33220,15 +33162,14 @@ const uint16_t OFFSETS_FI[] = { 4076, 4085, 4093, 4102, 4107, 4113, 4130, 4137, 4140, 4145, 4150, 4160, 4165, 4177, 4183, 4189, 4195, 4200, 4203, 4234, 4244, 4265, 4284, 4309, 4322, 4345, 4350, 4357, 4363, 4368, 4376, 4387, 4398, 4406, 4412, 4420, - 4443, 4453, 4458, 4472, 4505, 4539, 4551, 4570, 4583, 4591, 4612, 4637, - 4680, 4726, 4733, 4762, 4772, 4777, 4787, 4794, 4835, 4851, 4867, 4909, - 4955, 4977, 4999, 5018, 5037, 5054, 5068, 5090, 5116, 5123, 5130, 5146, - 5153, 5165, 5173, 5211, 5244, 5267, 5307, 5351, 5379, 5406, 5436, 5470, - 5500, 5537, 5558, 5564, 5577, 5609, 5644, 5657, 5682, 5715, 5745, 5773, - 5797, 5823, 5829, 5838, 5851, 5866, 5889, 5899, 5925, 5932, 5949, 5969, - 5998, 6003, 6010, 6017, 6022, 6048, 6054, 6059, 6065, 6069, 6108, 6135, - 6163, 6182, 6206, 6231, 6242, 6247, 6251, 6255, 6264, 6273, 6283, 6294, - 6301, 6310, 6328, + 4443, 4453, 4458, 4472, 4505, 4539, 4551, 4570, 4583, 4604, 4629, 4672, + 4718, 4725, 4754, 4764, 4769, 4779, 4786, 4827, 4843, 4859, 4901, 4947, + 4969, 4991, 5010, 5029, 5046, 5060, 5082, 5108, 5115, 5122, 5138, 5145, + 5157, 5165, 5203, 5236, 5259, 5299, 5343, 5371, 5398, 5428, 5462, 5492, + 5529, 5550, 5556, 5569, 5601, 5636, 5649, 5674, 5707, 5737, 5765, 5789, + 5815, 5821, 5830, 5843, 5858, 5881, 5891, 5917, 5924, 5941, 5961, 5990, + 5995, 6000, 6026, 6032, 6037, 6043, 6047, 6086, 6113, 6141, 6160, 6184, + 6209, 6220, 6225, 6229, 6233, 6242, 6251, 6261, 6272, 6279, 6288, 6306, }; const char STRINGS_DA_DATA[] = @@ -33666,7 +33607,6 @@ const char STRINGS_DA_DATA[] = "OPDS Browser\0" "Omslag + Brugerdefineret\0" "Page overlay\0" - "Recents\0" "Seneste b\xC3" "\xB8" "ger\0" @@ -33765,8 +33705,6 @@ const char STRINGS_DA_DATA[] = "Auto Turn Enabled: \0" "Auto Turn (Pages Per Minute)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -33816,15 +33754,14 @@ const uint16_t OFFSETS_DA[] = { 3824, 3833, 3842, 3850, 3855, 3864, 3875, 3878, 3882, 3887, 3892, 3899, 3907, 3923, 3931, 3938, 3941, 3945, 3948, 3975, 3984, 4005, 4024, 4049, 4062, 4085, 4090, 4097, 4103, 4108, 4116, 4127, 4138, 4146, 4152, 4160, - 4184, 4193, 4198, 4212, 4234, 4256, 4269, 4294, 4307, 4315, 4330, 4351, - 4393, 4434, 4439, 4458, 4469, 4476, 4480, 4489, 4526, 4539, 4556, 4596, - 4631, 4649, 4668, 4686, 4703, 4713, 4727, 4750, 4764, 4771, 4778, 4794, - 4804, 4816, 4822, 4860, 4892, 4913, 4939, 4972, 4998, 5025, 5055, 5078, - 5122, 5160, 5179, 5188, 5195, 5218, 5244, 5254, 5277, 5302, 5331, 5359, - 5380, 5407, 5415, 5422, 5434, 5449, 5465, 5475, 5501, 5508, 5526, 5546, - 5575, 5580, 5587, 5594, 5599, 5625, 5631, 5636, 5642, 5646, 5685, 5712, - 5740, 5759, 5783, 5808, 5819, 5824, 5828, 5832, 5841, 5850, 5860, 5871, - 5878, 5887, 5905, + 4184, 4193, 4198, 4212, 4234, 4256, 4269, 4294, 4307, 4322, 4343, 4385, + 4426, 4431, 4450, 4461, 4468, 4472, 4481, 4518, 4531, 4548, 4588, 4623, + 4641, 4660, 4678, 4695, 4705, 4719, 4742, 4756, 4763, 4770, 4786, 4796, + 4808, 4814, 4852, 4884, 4905, 4931, 4964, 4990, 5017, 5047, 5070, 5114, + 5152, 5171, 5180, 5187, 5210, 5236, 5246, 5269, 5294, 5323, 5351, 5372, + 5399, 5407, 5414, 5426, 5441, 5457, 5467, 5493, 5500, 5518, 5538, 5567, + 5572, 5577, 5603, 5609, 5614, 5620, 5624, 5663, 5690, 5718, 5737, 5761, + 5786, 5797, 5802, 5806, 5810, 5819, 5828, 5838, 5849, 5856, 5865, 5883, }; const char STRINGS_NL_DATA[] = @@ -33892,10 +33829,10 @@ const char STRINGS_NL_DATA[] = "Batterij % verbergen\0" "Extra regelafstand alinea\0" "Tekst Anti-Aliasing\0" - "Images\0" - "Display\0" + "Afbeeldingen\0" + "Weergave\0" "Placeholder\0" - "Suppress\0" + "Verbergen\0" "Create fallback for invalid TOC\0" "Korte klik aan/uit-knop\0" "Leesstand\0" @@ -33908,7 +33845,7 @@ const char STRINGS_NL_DATA[] = "Uitlijning alinea lezer\0" "Woordafbreking\0" "Tijd tot slaapstand\0" - "Show Hidden Files\0" + "Toon verborgen bestanden\0" "Use Clock\0" "Clock Settings\0" "Uses more battery; clock may drift\0" @@ -34064,7 +34001,7 @@ const char STRINGS_NL_DATA[] = "\xAB" " Home\0" "Kies\0" - "Selected\0" + "Geselecteerd\0" "Wissel\0" "Bevestig\0" "Annuleer\0" @@ -34110,7 +34047,6 @@ const char STRINGS_NL_DATA[] = "OPDS-browser\0" "Omslag + Aangepast\0" "Page overlay\0" - "Recents\0" "Recente boeken\0" "Geen recente boeken\0" "Gebruik Calibre draadloze overdracht\0" @@ -34134,7 +34070,7 @@ const char STRINGS_NL_DATA[] = "Naar Home\0" "Voortgang synchroniseren\0" "Boekcache verwijderen\0" - "Delete\0" + "Verwijder\0" "Remove\0" "Pagina als QR tonen\0" "Hoofdstuk: \0" @@ -34174,11 +34110,9 @@ const char STRINGS_NL_DATA[] = "Geen voetnoten op deze pagina\0" "[link]\0" "Screenshot maken\0" - "Auto Turn Enabled: \0" - "Auto Turn (Pages Per Minute)\0" + "Automatisch omslaan ingeschakeld: \0" + "Autom. omslaan (pagina's per minuut)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -34211,32 +34145,31 @@ const uint16_t OFFSETS_NL[] = { 377, 400, 411, 424, 435, 454, 472, 511, 544, 564, 578, 616, 672, 691, 705, 746, 774, 785, 821, 838, 854, 885, 896, 916, 940, 945, 967, 979, 1018, 1052, 1084, 1125, 1132, 1138, 1148, 1156, - 1168, 1193, 1214, 1240, 1260, 1267, 1275, 1287, 1296, 1328, 1352, 1362, - 1390, 1427, 1444, 1464, 1483, 1501, 1525, 1540, 1560, 1578, 1588, 1603, - 1638, 1644, 1657, 1666, 1670, 1674, 1688, 1714, 1740, 1761, 1782, 1804, - 1825, 1855, 1879, 1892, 1914, 1927, 1944, 1960, 1982, 2002, 2012, 2028, - 2045, 2067, 2079, 2096, 2106, 2120, 2138, 2161, 2173, 2187, 2200, 2222, - 2236, 2259, 2264, 2281, 2296, 2307, 2323, 2341, 2355, 2379, 2399, 2412, - 2419, 2447, 2471, 2488, 2512, 2535, 2571, 2593, 2602, 2617, 2647, 2667, - 2690, 2696, 2733, 2767, 2809, 2841, 2857, 2870, 2887, 2895, 2916, 2951, - 2958, 2964, 2974, 2981, 2986, 2994, 3005, 3011, 3020, 3027, 3035, 3041, - 3056, 3063, 3082, 3092, 3110, 3126, 3142, 3151, 3161, 3175, 3181, 3191, - 3197, 3209, 3214, 3222, 3228, 3238, 3244, 3254, 3261, 3267, 3273, 3280, - 3287, 3294, 3303, 3314, 3326, 3338, 3350, 3357, 3382, 3409, 3426, 3442, - 3453, 3477, 3492, 3508, 3558, 3578, 3592, 3609, 3615, 3624, 3650, 3671, - 3694, 3704, 3715, 3735, 3763, 3789, 3798, 3807, 3816, 3824, 3829, 3838, - 3845, 3854, 3863, 3871, 3876, 3885, 3893, 3896, 3900, 3905, 3913, 3917, - 3921, 3936, 3942, 3949, 3956, 3963, 3966, 3992, 4001, 4022, 4048, 4074, - 4089, 4110, 4114, 4124, 4128, 4133, 4143, 4156, 4166, 4176, 4182, 4191, - 4200, 4209, 4214, 4230, 4253, 4280, 4293, 4312, 4325, 4333, 4348, 4368, - 4405, 4449, 4457, 4476, 4488, 4495, 4499, 4509, 4551, 4567, 4581, 4626, - 4662, 4678, 4697, 4713, 4730, 4740, 4750, 4775, 4797, 4804, 4811, 4831, - 4843, 4858, 4865, 4904, 4941, 4964, 4991, 5023, 5044, 5071, 5101, 5123, - 5152, 5194, 5214, 5222, 5230, 5255, 5283, 5293, 5321, 5347, 5379, 5405, - 5426, 5439, 5447, 5456, 5471, 5487, 5503, 5513, 5543, 5550, 5567, 5587, - 5616, 5621, 5628, 5635, 5640, 5666, 5672, 5677, 5683, 5687, 5726, 5753, - 5781, 5800, 5824, 5849, 5860, 5865, 5869, 5873, 5882, 5891, 5901, 5912, - 5919, 5928, 5946, + 1168, 1193, 1214, 1240, 1260, 1273, 1282, 1294, 1304, 1336, 1360, 1370, + 1398, 1435, 1452, 1472, 1491, 1509, 1533, 1548, 1568, 1593, 1603, 1618, + 1653, 1659, 1672, 1681, 1685, 1689, 1703, 1729, 1755, 1776, 1797, 1819, + 1840, 1870, 1894, 1907, 1929, 1942, 1959, 1975, 1997, 2017, 2027, 2043, + 2060, 2082, 2094, 2111, 2121, 2135, 2153, 2176, 2188, 2202, 2215, 2237, + 2251, 2274, 2279, 2296, 2311, 2322, 2338, 2356, 2370, 2394, 2414, 2427, + 2434, 2462, 2486, 2503, 2527, 2550, 2586, 2608, 2617, 2632, 2662, 2682, + 2705, 2711, 2748, 2782, 2824, 2856, 2872, 2885, 2902, 2910, 2931, 2966, + 2973, 2979, 2989, 2996, 3001, 3009, 3020, 3026, 3035, 3042, 3050, 3056, + 3071, 3078, 3097, 3107, 3125, 3141, 3157, 3166, 3176, 3190, 3196, 3206, + 3212, 3224, 3229, 3237, 3243, 3253, 3259, 3269, 3276, 3282, 3288, 3295, + 3302, 3309, 3318, 3329, 3341, 3353, 3365, 3372, 3397, 3424, 3441, 3457, + 3468, 3492, 3507, 3523, 3573, 3593, 3607, 3624, 3630, 3639, 3665, 3686, + 3709, 3719, 3730, 3750, 3778, 3804, 3813, 3822, 3831, 3839, 3844, 3857, + 3864, 3873, 3882, 3890, 3895, 3904, 3912, 3915, 3919, 3924, 3932, 3936, + 3940, 3955, 3961, 3968, 3975, 3982, 3985, 4011, 4020, 4041, 4067, 4093, + 4108, 4129, 4133, 4143, 4147, 4152, 4162, 4175, 4185, 4195, 4201, 4210, + 4219, 4228, 4233, 4249, 4272, 4299, 4312, 4331, 4344, 4359, 4379, 4416, + 4460, 4468, 4487, 4499, 4506, 4510, 4520, 4562, 4578, 4592, 4637, 4673, + 4689, 4708, 4724, 4741, 4751, 4761, 4786, 4808, 4818, 4825, 4845, 4857, + 4872, 4879, 4918, 4955, 4978, 5005, 5037, 5058, 5085, 5115, 5137, 5166, + 5208, 5228, 5236, 5244, 5269, 5297, 5307, 5335, 5361, 5393, 5419, 5440, + 5453, 5461, 5470, 5485, 5501, 5517, 5527, 5557, 5564, 5581, 5616, 5653, + 5658, 5663, 5689, 5695, 5700, 5706, 5710, 5749, 5776, 5804, 5823, 5847, + 5872, 5883, 5888, 5892, 5896, 5905, 5914, 5924, 5935, 5942, 5951, 5969, }; const char STRINGS_TR_DATA[] = @@ -35242,7 +35175,6 @@ const char STRINGS_TR_DATA[] = "\x96" "zel\0" "Page overlay\0" - "Recents\0" "Son Kitaplar\0" "Son okunan kitap yok\0" "Calibre kablosuz cihaz transferini kullan\0" @@ -35469,8 +35401,6 @@ const char STRINGS_TR_DATA[] = "\x87" "evirme (Dakikada Sayfa)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -35520,15 +35450,14 @@ const uint16_t OFFSETS_TR[] = { 4044, 4051, 4058, 4066, 4070, 4077, 4089, 4094, 4101, 4109, 4115, 4121, 4128, 4141, 4145, 4150, 4158, 4167, 4173, 4201, 4210, 4240, 4263, 4288, 4307, 4340, 4346, 4351, 4358, 4364, 4372, 4383, 4401, 4411, 4420, 4424, - 4440, 4447, 4452, 4473, 4509, 4535, 4551, 4565, 4578, 4586, 4599, 4620, - 4662, 4700, 4705, 4732, 4740, 4746, 4754, 4766, 4801, 4813, 4829, 4873, - 4910, 4925, 4942, 4956, 4971, 4980, 4996, 5030, 5054, 5058, 5065, 5092, - 5102, 5114, 5122, 5159, 5195, 5224, 5254, 5282, 5310, 5337, 5367, 5392, - 5422, 5459, 5478, 5484, 5491, 5514, 5540, 5555, 5578, 5602, 5628, 5650, - 5671, 5699, 5708, 5715, 5730, 5745, 5764, 5774, 5796, 5809, 5832, 5858, - 5893, 5898, 5905, 5912, 5917, 5943, 5949, 5954, 5960, 5964, 6003, 6030, - 6058, 6077, 6101, 6126, 6137, 6142, 6146, 6150, 6159, 6168, 6178, 6189, - 6196, 6205, 6223, + 4440, 4447, 4452, 4473, 4509, 4535, 4551, 4565, 4578, 4591, 4612, 4654, + 4692, 4697, 4724, 4732, 4738, 4746, 4758, 4793, 4805, 4821, 4865, 4902, + 4917, 4934, 4948, 4963, 4972, 4988, 5022, 5046, 5050, 5057, 5084, 5094, + 5106, 5114, 5151, 5187, 5216, 5246, 5274, 5302, 5329, 5359, 5384, 5414, + 5451, 5470, 5476, 5483, 5506, 5532, 5547, 5570, 5594, 5620, 5642, 5663, + 5691, 5700, 5707, 5722, 5737, 5756, 5766, 5788, 5801, 5824, 5850, 5885, + 5890, 5895, 5921, 5927, 5932, 5938, 5942, 5981, 6008, 6036, 6055, 6079, + 6104, 6115, 6120, 6124, 6128, 6137, 6146, 6156, 6167, 6174, 6183, 6201, }; const char STRINGS_KK_DATA[] = @@ -41178,7 +41107,6 @@ const char STRINGS_KK_DATA[] = "\xBD" "\0" "Page overlay\0" - "Recents\0" "\xD0" "\x96" "\xD1" @@ -43289,8 +43217,6 @@ const char STRINGS_KK_DATA[] = "\x8B" ")\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -43340,15 +43266,14 @@ const uint16_t OFFSETS_KK[] = { 6037, 6050, 6069, 6082, 6089, 6109, 6126, 6131, 6138, 6153, 6166, 6179, 6194, 6219, 6226, 6231, 6244, 6255, 6260, 6310, 6327, 6362, 6395, 6436, 6464, 6517, 6532, 6545, 6556, 6567, 6578, 6592, 6614, 6643, 6652, 6667, - 6703, 6726, 6731, 6759, 6809, 6869, 6889, 6927, 6940, 6948, 6993, 7045, - 7110, 7208, 7219, 7256, 7269, 7276, 7291, 7304, 7370, 7401, 7458, 7564, - 7651, 7682, 7715, 7742, 7767, 7781, 7810, 7850, 7879, 7886, 7893, 7937, - 7950, 7963, 7976, 8023, 8069, 8110, 8154, 8204, 8256, 8283, 8313, 8361, - 8438, 8514, 8545, 8565, 8587, 8616, 8648, 8667, 8720, 8782, 8835, 8904, - 8948, 8982, 8994, 9016, 9038, 9076, 9100, 9138, 9201, 9218, 9248, 9302, - 9376, 9381, 9388, 9395, 9400, 9426, 9432, 9437, 9443, 9447, 9486, 9513, - 9541, 9560, 9584, 9609, 9620, 9625, 9629, 9633, 9642, 9651, 9661, 9672, - 9679, 9688, 9706, + 6703, 6726, 6731, 6759, 6809, 6869, 6889, 6927, 6940, 6985, 7037, 7102, + 7200, 7211, 7248, 7261, 7268, 7283, 7296, 7362, 7393, 7450, 7556, 7643, + 7674, 7707, 7734, 7759, 7773, 7802, 7842, 7871, 7878, 7885, 7929, 7942, + 7955, 7968, 8015, 8061, 8102, 8146, 8196, 8248, 8275, 8305, 8353, 8430, + 8506, 8537, 8557, 8579, 8608, 8640, 8659, 8712, 8774, 8827, 8896, 8940, + 8974, 8986, 9008, 9030, 9068, 9092, 9130, 9193, 9210, 9240, 9294, 9368, + 9373, 9378, 9404, 9410, 9415, 9421, 9425, 9464, 9491, 9519, 9538, 9562, + 9587, 9598, 9603, 9607, 9611, 9620, 9629, 9639, 9650, 9657, 9666, 9684, }; const char STRINGS_PT_DATA[] = @@ -43864,7 +43789,6 @@ const char STRINGS_PT_DATA[] = "Navegador OPDS\0" "Capa + personalizado\0" "Page overlay\0" - "Recents\0" "Livros recentes\0" "Sem livros recentes\0" "Usar transfer\xC3" @@ -44013,8 +43937,6 @@ const char STRINGS_PT_DATA[] = "Auto Turn Enabled: \0" "Auto Turn (Pages Per Minute)\0" "Info\0" - "Author\0" - "Series\0" "Size\0" "Sleep Screen Info Overlay\0" "White\0" @@ -44064,15 +43986,14 @@ const uint16_t OFFSETS_PT[] = { 4185, 4195, 4204, 4210, 4216, 4227, 4242, 4246, 4251, 4256, 4261, 4266, 4272, 4286, 4295, 4303, 4308, 4314, 4317, 4352, 4362, 4383, 4402, 4427, 4440, 4463, 4468, 4475, 4481, 4486, 4494, 4505, 4516, 4524, 4530, 4538, - 4556, 4566, 4571, 4585, 4612, 4640, 4655, 4676, 4689, 4697, 4713, 4733, - 4774, 4826, 4835, 4858, 4873, 4880, 4887, 4902, 4945, 4961, 4976, 5034, - 5083, 5103, 5126, 5148, 5169, 5179, 5197, 5219, 5243, 5252, 5259, 5275, - 5286, 5298, 5305, 5343, 5372, 5394, 5426, 5464, 5492, 5519, 5549, 5571, - 5605, 5652, 5674, 5682, 5689, 5717, 5748, 5755, 5780, 5803, 5838, 5862, - 5881, 5906, 5915, 5922, 5938, 5957, 5978, 5988, 6014, 6021, 6036, 6056, - 6085, 6090, 6097, 6104, 6109, 6135, 6141, 6146, 6152, 6156, 6195, 6222, - 6250, 6269, 6293, 6318, 6329, 6334, 6338, 6342, 6351, 6360, 6370, 6381, - 6388, 6397, 6415, + 4556, 4566, 4571, 4585, 4612, 4640, 4655, 4676, 4689, 4705, 4725, 4766, + 4818, 4827, 4850, 4865, 4872, 4879, 4894, 4937, 4953, 4968, 5026, 5075, + 5095, 5118, 5140, 5161, 5171, 5189, 5211, 5235, 5244, 5251, 5267, 5278, + 5290, 5297, 5335, 5364, 5386, 5418, 5456, 5484, 5511, 5541, 5563, 5597, + 5644, 5666, 5674, 5681, 5709, 5740, 5747, 5772, 5795, 5830, 5854, 5873, + 5898, 5907, 5914, 5930, 5949, 5970, 5980, 6006, 6013, 6028, 6048, 6077, + 6082, 6087, 6113, 6119, 6124, 6130, 6134, 6173, 6200, 6228, 6247, 6271, + 6296, 6307, 6312, 6316, 6320, 6329, 6338, 6348, 6359, 6366, 6375, 6393, }; } // namespace i18n_strings diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index 53d32cac..b18a69ac 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -13,11 +13,14 @@ bool Xtc::load() { LOG_DBG("XTC", "Loading XTC: %s", filepath.c_str()); + // Ensure the per-book cache exists before the parser tries to create page_table.bin. + setupCacheDir(); + // Initialize parser parser.reset(new xtc::XtcParser()); - // Open XTC file - xtc::XtcError err = parser->open(filepath.c_str()); + // Open XTC file and initialize its cache-backed page table + xtc::XtcError err = parser->open(filepath.c_str(), cachePath.c_str()); if (err != xtc::XtcError::OK) { LOG_ERR("XTC", "Failed to load: %s", xtc::errorToString(err)); parser.reset(); @@ -619,3 +622,10 @@ xtc::XtcError Xtc::getLastError() const { } return parser->getLastError(); } + +void Xtc::prefetchPages(uint32_t pageIndex) const { + if (!loaded || !parser) { + return; + } + parser->prefetchWindow(pageIndex); +} diff --git a/lib/Xtc/Xtc.h b/lib/Xtc/Xtc.h index 9b75f586..1c79416f 100644 --- a/lib/Xtc/Xtc.h +++ b/lib/Xtc/Xtc.h @@ -50,6 +50,11 @@ class Xtc { */ void setupCacheDir() const; + /** + * Preload window around specified page (for page turn optimization) + */ + void prefetchPages(uint32_t pageIndex) const; + // Path accessors const std::string& getCachePath() const { return cachePath; } const std::string& getPath() const { return filepath; } diff --git a/lib/Xtc/Xtc/XtcParser.cpp b/lib/Xtc/Xtc/XtcParser.cpp index 52adcc76..083391eb 100644 --- a/lib/Xtc/Xtc/XtcParser.cpp +++ b/lib/Xtc/Xtc/XtcParser.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -18,6 +19,8 @@ namespace xtc { namespace { +constexpr size_t MAX_CHAPTERS = 4096; + bool canSeekToOffset(const uint64_t offset) { return offset <= static_cast(std::numeric_limits::max()); } @@ -31,6 +34,26 @@ bool seekToOffset(FsFile& file, const uint64_t offset) { } // namespace +void XtcParser::safeDeserializeHeader(const uint8_t* buf, PageTableCacheHeader& header) { + memcpy(&header.magic, buf + 0, 4); + memcpy(&header.version, buf + 4, 4); + memcpy(&header.pageCount, buf + 8, 4); + memcpy(&header.originalHash, buf + 12, 4); + memcpy(&header.originalSize, buf + 16, 8); + memcpy(&header.entrySize, buf + 24, 4); + memcpy(&header.reserved, buf + 28, 4); +} + +void XtcParser::safeSerializeHeader(uint8_t* buf, const PageTableCacheHeader& header) { + memcpy(buf + 0, &header.magic, 4); + memcpy(buf + 4, &header.version, 4); + memcpy(buf + 8, &header.pageCount, 4); + memcpy(buf + 12, &header.originalHash, 4); + memcpy(buf + 16, &header.originalSize, 8); + memcpy(buf + 24, &header.entrySize, 4); + memcpy(buf + 28, &header.reserved, 4); +} + XtcParser::XtcParser() : m_isOpen(false), m_defaultWidth(DISPLAY_WIDTH), @@ -39,17 +62,28 @@ XtcParser::XtcParser() m_hasChapters(false), m_lastError(XtcError::OK) { memset(&m_header, 0, sizeof(m_header)); + + for (auto& entry : m_l1Cache) { + entry.pageIndex = 0xFFFFFFFF; + entry.lastAccess = 0; + } } XtcParser::~XtcParser() { close(); } -XtcError XtcParser::open(const char* filepath) { - // Close if already open +XtcError XtcParser::open(const char* filepath, const char* cacheDir) { + // Close any previous file state before reopening if (m_isOpen) { close(); } - // Open file + m_originalPath = filepath; + m_cacheDir = cacheDir; + + uint32_t fileHash = calculateFileHash(filepath); + m_cacheFilePath = std::string(cacheDir) + "/xtc_" + std::to_string(fileHash) + "/page_table.bin"; + + // Open the original XTC file just long enough to read metadata and validate the header. if (!Storage.openFileForRead("XTC", filepath, m_file)) { m_lastError = XtcError::FILE_NOT_FOUND; return m_lastError; @@ -63,57 +97,416 @@ XtcError XtcParser::open(const char* filepath) { return m_lastError; } - // Read title & author if available + if (m_header.pageCount == 0) { + LOG_ERR("XTC", "File has no pages"); + m_file.close(); + m_lastError = XtcError::CORRUPTED_HEADER; + return m_lastError; + } + + // Metadata strings are small, so keep them in memory even when the page table is moved to cache. if (m_header.hasMetadata) { - m_lastError = readTitle(); + readTitle(); + readAuthor(); + m_title.shrink_to_fit(); + m_author.shrink_to_fit(); + LOG_INF("XTC", "Metadata strings: titleLen=%u cap=%u, authorLen=%u cap=%u", + static_cast(m_title.size()), static_cast(m_title.capacity()), + static_cast(m_author.size()), static_cast(m_author.capacity())); + } + + // Defer chapter parsing until the reader actually needs the table of contents. + m_pageTableOffset = m_header.pageTableOffset; + m_hasChapters = (m_header.hasChapters == 1) && (m_header.chapterOffset != 0); + LOG_INF("XTC", "Chapter metadata deferred: available=%s", m_hasChapters ? "yes" : "no"); + + m_file.close(); + + // Build or reuse the on-disk page table cache before marking the parser open. + if (!isPageTableCacheValid()) { + LOG_INF("XTC", "Building page table cache for %u pages", m_header.pageCount); + m_lastError = buildPageTableCache(); if (m_lastError != XtcError::OK) { - LOG_DBG("XTC", "Failed to read title: %s", errorToString(m_lastError)); - m_file.close(); + LOG_ERR("XTC", "Failed to build page table cache"); return m_lastError; } - m_lastError = readAuthor(); - if (m_lastError != XtcError::OK) { - LOG_DBG("XTC", "Failed to read author: %s", errorToString(m_lastError)); - m_file.close(); - return m_lastError; + const size_t heapBefore = ESP.getMaxAllocHeap(); + LOG_DBG("XTC", "Cache built, heap before defrag: free=%zu, maxAlloc=%zu", ESP.getFreeHeap(), heapBefore); + + // Defragment heap: small delay allows heap coalescing after file handles are closed + // This typically improves MaxAlloc by 10-20KB, enabling 96KB page buffer for grayscale + LOG_DBG("XTC", "Defragmenting heap (waiting 50ms)..."); + vTaskDelay(pdMS_TO_TICKS(50)); + + const size_t heapAfter = ESP.getMaxAllocHeap(); + const size_t heapGain = heapAfter > heapBefore ? (heapAfter - heapBefore) : 0; + if (heapGain > 0) { + LOG_INF("XTC", "Heap defragmented: +%zu bytes contiguous (now %zu)", heapGain, heapAfter); + } else { + LOG_DBG("XTC", "Heap after defrag: free=%zu, maxAlloc=%zu", ESP.getFreeHeap(), heapAfter); } } - // Read page table - m_lastError = readPageTable(); - if (m_lastError != XtcError::OK) { - LOG_DBG("XTC", "Failed to read page table: %s", errorToString(m_lastError)); - m_file.close(); + if (!openCacheFile()) { + LOG_ERR("XTC", "Failed to open cache file"); + m_lastError = XtcError::FILE_NOT_FOUND; return m_lastError; } - // Read chapters if present - m_lastError = readChapters(); - if (m_lastError != XtcError::OK) { - LOG_DBG("XTC", "Failed to read chapters: %s", errorToString(m_lastError)); - m_file.close(); - return m_lastError; - } + // Prime the sliding L2 window with the first chunk of page metadata. + loadL2Window(0); + + LOG_DBG("XTC", "File opened, heap: free=%zu, maxAlloc=%zu", ESP.getFreeHeap(), ESP.getMaxAllocHeap()); m_isOpen = true; - LOG_DBG("XTC", "Opened file: %s (%u pages, %dx%d)", filepath, m_header.pageCount, m_defaultWidth, m_defaultHeight); + LOG_DBG("XTC", "Opened file: %s (%u pages, cache: %s)", filepath, m_header.pageCount, m_cacheFilePath.c_str()); return XtcError::OK; } void XtcParser::close() { - if (m_isOpen) { + closeCacheFile(); + + if (m_isOpen && m_file.isOpen()) { m_file.close(); - m_isOpen = false; } - m_pageTable.clear(); + m_isOpen = false; + m_l2Valid = false; + m_l2WindowCount = 0; + m_chaptersLoaded = false; + + for (auto& entry : m_l1Cache) { + entry.pageIndex = 0xFFFFFFFF; + } m_chapters.clear(); m_title.clear(); - m_hasChapters = false; + m_author.clear(); memset(&m_header, 0, sizeof(m_header)); } +void XtcParser::ensureChaptersLoaded() { + if (m_chaptersLoaded || !m_hasChapters) { + return; + } + + // Chapter parsing allocates variable-length strings, so keep it lazy. + const XtcError err = readChapters(); + if (err != XtcError::OK) { + LOG_ERR("XTC", "Failed to lazy-load chapters: %s", errorToString(err)); + m_hasChapters = false; + m_chapters.clear(); + m_chapters.shrink_to_fit(); + } + m_chaptersLoaded = true; +} + +bool XtcParser::openCacheFile() { + if (m_cacheFile.isOpen()) { + return true; + } + return Storage.openFileForRead("XTC", m_cacheFilePath.c_str(), m_cacheFile); +} + +void XtcParser::closeCacheFile() { + if (m_cacheFile.isOpen()) { + m_cacheFile.close(); + } +} + +bool XtcParser::getPageInfo(uint32_t pageIndex, PageInfo& info) { + if (pageIndex >= m_header.pageCount) { + return false; + } + + // L1 is the hot cache for the most recently used pages. + if (lookupL1(pageIndex, info)) { + LOG_DBG("XTC", "L1 hit: page %u", pageIndex); + return true; + } + + // L2 is the sliding window around the reader's current position. + if (lookupL2(pageIndex, info)) { + updateL1(pageIndex, info); + LOG_DBG("XTC", "L2 hit: page %u", pageIndex); + return true; + } + + // Fall back to the SD-backed cache file, then refresh L2/L1. + LOG_DBG("XTC", "L3 load: page %u", pageIndex); + loadL2Window(pageIndex); + + if (lookupL2(pageIndex, info)) { + updateL1(pageIndex, info); + return true; + } + + return false; +} + +void XtcParser::prefetchWindow(uint32_t pageIndex) { + if (pageIndex >= m_header.pageCount) { + return; + } + + // Avoid reloading the same window when the requested page is already covered. + if (m_l2Valid && pageIndex >= m_l2WindowStart && pageIndex < m_l2WindowStart + m_l2WindowCount) { + return; + } + + loadL2Window(pageIndex); +} + +bool XtcParser::lookupL1(uint32_t pageIndex, PageInfo& info) { + for (const auto& entry : m_l1Cache) { + if (entry.pageIndex == pageIndex) { + info = entry.info; + return true; + } + } + return false; +} + +void XtcParser::updateL1(uint32_t pageIndex, const PageInfo& info) { + for (auto& entry : m_l1Cache) { + if (entry.pageIndex == pageIndex) { + entry.lastAccess = ++m_accessCounter; + return; + } + } + + // Replace the least-recently-used entry, or fill the first empty slot. + uint32_t oldestAccess = m_accessCounter; + size_t oldestIndex = 0; + bool foundEmpty = false; + + for (size_t i = 0; i < m_l1Cache.size(); i++) { + if (m_l1Cache[i].pageIndex == 0xFFFFFFFF) { + oldestIndex = i; + foundEmpty = true; + break; + } + if (m_l1Cache[i].lastAccess < oldestAccess) { + oldestAccess = m_l1Cache[i].lastAccess; + oldestIndex = i; + } + } + + m_l1Cache[oldestIndex].pageIndex = pageIndex; + m_l1Cache[oldestIndex].info = info; + m_l1Cache[oldestIndex].lastAccess = ++m_accessCounter; +} + +bool XtcParser::lookupL2(uint32_t pageIndex, PageInfo& info) { + if (!m_l2Valid) { + return false; + } + + if (pageIndex >= m_l2WindowStart && pageIndex < m_l2WindowStart + m_l2WindowCount) { + size_t idx = pageIndex - m_l2WindowStart; + info = m_l2Window[idx]; + return true; + } + return false; +} + +void XtcParser::loadL2Window(uint32_t centerPage) { + // Center the sliding window around the requested page when possible. + uint32_t halfWindow = L2_WINDOW_SIZE / 2; + uint32_t windowStart = (centerPage > halfWindow) ? centerPage - halfWindow : 0; + uint32_t windowEnd = windowStart + L2_WINDOW_SIZE; + + if (windowEnd > m_header.pageCount) { + windowEnd = m_header.pageCount; + windowStart = (windowEnd > L2_WINDOW_SIZE) ? windowEnd - L2_WINDOW_SIZE : 0; + } + + size_t windowSize = windowEnd - windowStart; + if (windowSize == 0) { + m_l2Valid = false; + m_l2WindowCount = 0; + return; + } + + if (!m_cacheFile.isOpen() && !openCacheFile()) { + LOG_ERR("XTC", "Cache file not available"); + m_l2Valid = false; + m_l2WindowCount = 0; + return; + } + + size_t entryOffset = sizeof(PageTableCacheHeader) + windowStart * sizeof(PageInfo); + if (!m_cacheFile.seek(entryOffset)) { + LOG_ERR("XTC", "Failed to seek in page table cache"); + m_l2Valid = false; + m_l2WindowCount = 0; + return; + } + + size_t readCount = 0; + for (size_t i = 0; i < windowSize; i++) { + PageInfo info; + if (m_cacheFile.read(reinterpret_cast(&info), sizeof(PageInfo)) != sizeof(PageInfo)) { + LOG_ERR("XTC", "Failed to read page info %zu", windowStart + i); + break; + } + m_l2Window[i] = info; + readCount++; + } + + m_l2WindowStart = windowStart; + m_l2WindowCount = readCount; + m_l2Valid = (readCount > 0); + + LOG_DBG("XTC", "L2 window loaded: [%u, %u] (%zu pages)", windowStart, windowStart + readCount - 1, readCount); +} + +bool XtcParser::isPageTableCacheValid() const { + if (!Storage.exists(m_cacheFilePath.c_str())) { + return false; + } + + FsFile cacheFile; + if (!Storage.openFileForRead("XTC", m_cacheFilePath.c_str(), cacheFile)) { + return false; + } + + uint8_t headerBuf[sizeof(PageTableCacheHeader)]; + if (cacheFile.read(headerBuf, sizeof(headerBuf)) != sizeof(headerBuf)) { + cacheFile.close(); + return false; + } + + PageTableCacheHeader header; + safeDeserializeHeader(headerBuf, header); + + if (header.magic != PAGE_TABLE_CACHE_MAGIC || header.version != PAGE_TABLE_CACHE_VERSION) { + cacheFile.close(); + return false; + } + + // The cache must match both the page count and the original file size. + if (header.pageCount != m_header.pageCount) { + cacheFile.close(); + return false; + } + + uint32_t expectedSize = sizeof(PageTableCacheHeader) + header.pageCount * sizeof(PageInfo); + if (cacheFile.size() < expectedSize) { + cacheFile.close(); + return false; + } + + if (header.originalSize > 0) { + FsFile originalFile; + if (Storage.openFileForRead("XTC", m_originalPath.c_str(), originalFile)) { + uint64_t currentSize = originalFile.size(); + originalFile.close(); + if (currentSize != header.originalSize) { + LOG_INF("XTC", "Cache invalidated: file size changed"); + cacheFile.close(); + return false; + } + } + } + + cacheFile.close(); + return true; +} + +XtcError XtcParser::buildPageTableCache() { + FsFile originalFile; + if (!Storage.openFileForRead("XTC", m_originalPath.c_str(), originalFile)) { + return XtcError::FILE_NOT_FOUND; + } + + size_t lastSlash = m_cacheFilePath.find_last_of('/'); + if (lastSlash != std::string::npos) { + std::string cacheDir = m_cacheFilePath.substr(0, lastSlash); + Storage.mkdir(cacheDir.c_str()); + } + + FsFile cacheFile; + if (!Storage.openFileForWrite("XTC", m_cacheFilePath.c_str(), cacheFile)) { + originalFile.close(); + return XtcError::WRITE_ERROR; + } + + // Persist a compact PageInfo array so we do not need to hold the full table in RAM. + PageTableCacheHeader header; + header.magic = PAGE_TABLE_CACHE_MAGIC; + header.version = PAGE_TABLE_CACHE_VERSION; + header.pageCount = m_header.pageCount; + header.originalHash = calculateFileHash(m_originalPath.c_str()); + header.originalSize = originalFile.size(); + header.entrySize = sizeof(PageInfo); + header.reserved = 0; + + uint8_t headerBuf[sizeof(PageTableCacheHeader)]; + safeSerializeHeader(headerBuf, header); + if (cacheFile.write(headerBuf, sizeof(headerBuf)) != sizeof(headerBuf)) { + cacheFile.close(); + originalFile.close(); + return XtcError::WRITE_ERROR; + } + + if (!seekToOffset(originalFile, m_pageTableOffset)) { + cacheFile.close(); + originalFile.close(); + return XtcError::READ_ERROR; + } + + // Convert the source page table entries into the cached PageInfo layout. + for (uint16_t i = 0; i < m_header.pageCount; i++) { + PageTableEntry entry; + if (originalFile.read(reinterpret_cast(&entry), sizeof(PageTableEntry)) != sizeof(PageTableEntry)) { + LOG_ERR("XTC", "Failed to read page table entry %u", i); + cacheFile.close(); + originalFile.close(); + return XtcError::READ_ERROR; + } + + PageInfo info; + info.offset = entry.dataOffset; + info.size = entry.dataSize; + info.width = entry.width; + info.height = entry.height; + info.bitDepth = m_bitDepth; + info.padding = 0; + + if (cacheFile.write(reinterpret_cast(&info), sizeof(info)) != sizeof(info)) { + cacheFile.close(); + originalFile.close(); + return XtcError::WRITE_ERROR; + } + } + + cacheFile.close(); + originalFile.close(); + + LOG_INF("XTC", "Page table cache built: %u entries", m_header.pageCount); + return XtcError::OK; +} + +uint32_t XtcParser::calculateFileHash(const char* filepath) const { + uint32_t hash = 0; + size_t len = strlen(filepath); + + for (size_t i = 0; i < len; i++) { + hash = hash * 31 + static_cast(filepath[i]); + } + + FsFile file; + if (Storage.openFileForRead("XTC", filepath, file)) { + uint64_t size = file.size(); + hash ^= static_cast(size); + hash ^= static_cast(size >> 32); + file.close(); + } + + return hash; +} + XtcError XtcParser::readHeader() { - // Read first 56 bytes of header + // Read the fixed-size XTC header first. size_t bytesRead = m_file.read(reinterpret_cast(&m_header), sizeof(XtcHeader)); if (bytesRead != sizeof(XtcHeader)) { return XtcError::READ_ERROR; @@ -179,49 +572,17 @@ XtcError XtcParser::readAuthor() { return XtcError::OK; } -XtcError XtcParser::readPageTable() { - if (m_header.pageTableOffset == 0) { - LOG_DBG("XTC", "Page table offset is 0, cannot read"); - return XtcError::CORRUPTED_HEADER; - } - - // Seek to page table - if (!seekToOffset(m_file, m_header.pageTableOffset)) { - LOG_DBG("XTC", "Failed to seek to page table at %llu", m_header.pageTableOffset); - return XtcError::READ_ERROR; - } - - m_pageTable.resize(m_header.pageCount); - - // Read page table entries - for (uint16_t i = 0; i < m_header.pageCount; i++) { - PageTableEntry entry; - size_t bytesRead = m_file.read(reinterpret_cast(&entry), sizeof(PageTableEntry)); - if (bytesRead != sizeof(PageTableEntry)) { - LOG_DBG("XTC", "Failed to read page table entry %u", i); - return XtcError::READ_ERROR; - } - - m_pageTable[i].offset = entry.dataOffset; - m_pageTable[i].size = entry.dataSize; - m_pageTable[i].width = entry.width; - m_pageTable[i].height = entry.height; - m_pageTable[i].bitDepth = m_bitDepth; - - // Update default dimensions from first page - if (i == 0) { - m_defaultWidth = entry.width; - m_defaultHeight = entry.height; - } - } - - LOG_DBG("XTC", "Read %u page table entries", m_header.pageCount); - return XtcError::OK; -} - XtcError XtcParser::readChapters() { m_hasChapters = false; m_chapters.clear(); + m_chapters.shrink_to_fit(); + + // Reopen the original file on demand because open() closes it after cache initialization. + if (!m_file.isOpen()) { + if (!Storage.openFileForRead("XTC", m_originalPath.c_str(), m_file)) { + return XtcError::FILE_NOT_FOUND; + } + } uint8_t hasChaptersFlag = 0; if (!m_file.seek(0x0B)) { @@ -248,7 +609,13 @@ XtcError XtcParser::readChapters() { } const uint64_t fileSize = m_file.size(); - if (chapterOffset < sizeof(XtcHeader) || chapterOffset >= fileSize || chapterOffset + 96 > fileSize) { + constexpr size_t chapterSize = 96; + + if (chapterOffset < sizeof(XtcHeader) || chapterOffset >= fileSize) { + return XtcError::OK; + } + + if (fileSize - chapterOffset < chapterSize) { return XtcError::OK; } @@ -265,18 +632,32 @@ XtcError XtcParser::readChapters() { return XtcError::OK; } - constexpr size_t chapterSize = 96; const uint64_t available = maxOffset - chapterOffset; - const size_t chapterCount = static_cast(available / chapterSize); + const uint64_t chapterCount64 = available / chapterSize; + if (chapterCount64 == 0) { + return XtcError::OK; + } + + if (chapterCount64 > MAX_CHAPTERS || chapterCount64 > std::numeric_limits::max()) { + LOG_ERR("XTC", "Chapter table too large: available=%llu chapterCount=%llu", + static_cast(available), static_cast(chapterCount64)); + return XtcError::CORRUPTED_HEADER; + } + + const size_t chapterCount = static_cast(chapterCount64); if (chapterCount == 0) { return XtcError::OK; } + const size_t freeHeapBefore = ESP.getFreeHeap(); + const size_t maxAllocBefore = ESP.getMaxAllocHeap(); + if (!seekToOffset(m_file, chapterOffset)) { return XtcError::READ_ERROR; } std::vector chapterBuf(chapterSize); + m_chapters.reserve(chapterCount); for (size_t i = 0; i < chapterCount; i++) { if (m_file.read(chapterBuf.data(), chapterSize) != chapterSize) { return XtcError::READ_ERROR; @@ -320,17 +701,27 @@ XtcError XtcParser::readChapters() { m_chapters.push_back(std::move(chapter)); } + m_chapters.shrink_to_fit(); m_hasChapters = !m_chapters.empty(); - LOG_DBG("XTC", "Chapters: %u", static_cast(m_chapters.size())); + size_t chapterNameBytes = 0; + for (const auto& chapter : m_chapters) { + chapterNameBytes += chapter.name.capacity() + 1; + } + const size_t chapterVectorBytes = m_chapters.capacity() * sizeof(ChapterInfo); + const size_t totalChapterBytes = chapterVectorBytes + chapterNameBytes; + const size_t freeHeapAfter = ESP.getFreeHeap(); + const size_t maxAllocAfter = ESP.getMaxAllocHeap(); + const int heapDelta = static_cast(freeHeapBefore) - static_cast(freeHeapAfter); + const int maxAllocDelta = static_cast(maxAllocBefore) - static_cast(maxAllocAfter); + LOG_INF("XTC", "Chapter metadata: count=%u, vector~=%zu, names~=%zu, total~=%zu, heapDelta=%d, maxAllocDelta=%d", + static_cast(m_chapters.size()), chapterVectorBytes, chapterNameBytes, totalChapterBytes, + heapDelta, maxAllocDelta); return XtcError::OK; } -bool XtcParser::getPageInfo(uint32_t pageIndex, PageInfo& info) const { - if (pageIndex >= m_pageTable.size()) { - return false; - } - info = m_pageTable[pageIndex]; - return true; +const std::vector& XtcParser::getChapters() { + ensureChaptersLoaded(); + return m_chapters; } size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize) { @@ -344,11 +735,23 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz return 0; } - const PageInfo& page = m_pageTable[pageIndex]; + // Resolve the page location through the cache hierarchy before touching the data file. + PageInfo info; + if (!getPageInfo(pageIndex, info)) { + m_lastError = XtcError::READ_ERROR; + return 0; + } - // Seek to page data - if (!seekToOffset(m_file, page.offset)) { - LOG_DBG("XTC", "Failed to seek to page %u at offset %llu", pageIndex, static_cast(page.offset)); + // Reopen the source file lazily because normal parser open() does not keep it pinned. + if (!m_file.isOpen()) { + if (!Storage.openFileForRead("XTC", m_originalPath.c_str(), m_file)) { + m_lastError = XtcError::FILE_NOT_FOUND; + return 0; + } + } + + if (!seekToOffset(m_file, info.offset)) { + LOG_DBG("XTC", "Failed to seek to page %u at offset %llu", pageIndex, static_cast(info.offset)); m_lastError = XtcError::READ_ERROR; return 0; } @@ -382,14 +785,14 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz bitmapSize = ((pageHeader.width + 7) / 8) * pageHeader.height; } - // Check buffer size + // The caller owns the buffer, so fail early if it is too small. if (bufferSize < bitmapSize) { LOG_DBG("XTC", "Buffer too small: need %u, have %u", bitmapSize, bufferSize); m_lastError = XtcError::MEMORY_ERROR; return 0; } - // Read bitmap data + // Read the bitmap payload into the caller-provided buffer. size_t bytesRead = m_file.read(buffer, bitmapSize); if (bytesRead != bitmapSize) { LOG_DBG("XTC", "Page read error: expected %u, got %u", bitmapSize, bytesRead); @@ -412,15 +815,25 @@ XtcError XtcParser::loadPageStreaming(uint32_t pageIndex, return XtcError::PAGE_OUT_OF_RANGE; } - const PageInfo& page = m_pageTable[pageIndex]; - - // Seek to page data - if (!seekToOffset(m_file, page.offset)) { - LOG_DBG("XTC", "Failed to seek to page %u at offset %llu", pageIndex, static_cast(page.offset)); + // Streaming uses the same cache lookup path but reads the payload in chunks. + PageInfo info; + if (!getPageInfo(pageIndex, info)) { return XtcError::READ_ERROR; } - // Read and skip page header (XTG for 1-bit, XTH for 2-bit) + // Reopen the source file on demand for streaming reads as well. + if (!m_file.isOpen()) { + if (!Storage.openFileForRead("XTC", m_originalPath.c_str(), m_file)) { + return XtcError::FILE_NOT_FOUND; + } + } + + if (!seekToOffset(m_file, info.offset)) { + LOG_DBG("XTC", "Failed to seek to page %u at offset %llu", pageIndex, static_cast(info.offset)); + return XtcError::READ_ERROR; + } + + // Read and validate the page header before yielding any bitmap bytes. XtgPageHeader pageHeader; size_t headerRead = m_file.read(reinterpret_cast(&pageHeader), sizeof(XtgPageHeader)); const uint32_t expectedMagic = (m_bitDepth == 2) ? XTH_MAGIC : XTG_MAGIC; @@ -431,6 +844,7 @@ XtcError XtcParser::loadPageStreaming(uint32_t pageIndex, // Calculate bitmap size based on bit depth // XTG (1-bit): Row-major, ((width+7)/8) * height bytes // XTH (2-bit): Two bit planes, ((width * height + 7) / 8) * 2 bytes + // Match the bitmap sizing rules used by the non-streaming path. size_t bitmapSize; if (m_bitDepth == 2) { bitmapSize = ((static_cast(pageHeader.width) * pageHeader.height + 7) / 8) * 2; @@ -438,7 +852,7 @@ XtcError XtcParser::loadPageStreaming(uint32_t pageIndex, bitmapSize = ((pageHeader.width + 7) / 8) * pageHeader.height; } - // Read in chunks + // Feed the bitmap to the callback in bounded chunks to keep peak memory low. std::vector chunk(chunkSize); size_t totalRead = 0; diff --git a/lib/Xtc/Xtc/XtcParser.h b/lib/Xtc/Xtc/XtcParser.h index 19f3b75d..38e00862 100644 --- a/lib/Xtc/Xtc/XtcParser.h +++ b/lib/Xtc/Xtc/XtcParser.h @@ -9,6 +9,8 @@ #include +#include +#include #include #include #include @@ -30,7 +32,7 @@ class XtcParser { ~XtcParser(); // File open/close - XtcError open(const char* filepath); + XtcError open(const char* filepath, const char* cacheDir); void close(); bool isOpen() const { return m_isOpen; } @@ -41,28 +43,14 @@ class XtcParser { uint16_t getHeight() const { return m_defaultHeight; } uint8_t getBitDepth() const { return m_bitDepth; } // 1 = XTC/XTG, 2 = XTCH/XTH - // Page information - bool getPageInfo(uint32_t pageIndex, PageInfo& info) const; + // Page information - three-tier cache interface + bool getPageInfo(uint32_t pageIndex, PageInfo& info); - /** - * Load page bitmap (raw 1-bit data, skipping XTG header) - * - * @param pageIndex Page index (0-based) - * @param buffer Output buffer (caller allocated) - * @param bufferSize Buffer size - * @return Number of bytes read on success, 0 on failure - */ + // Preload window around specified page (optimize sequential page turns) + void prefetchWindow(uint32_t pageIndex); + + // Load page bitmap (unchanged) size_t loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize); - - /** - * Streaming page load - * Memory-efficient method that reads page data in chunks. - * - * @param pageIndex Page index - * @param callback Callback function to receive data chunks - * @param chunkSize Chunk size (default: 1024 bytes) - * @return Error code - */ XtcError loadPageStreaming(uint32_t pageIndex, std::function callback, size_t chunkSize = 1024); @@ -72,7 +60,7 @@ class XtcParser { std::string getAuthor() const { return m_author; } bool hasChapters() const { return m_hasChapters; } - const std::vector& getChapters() const { return m_chapters; } + const std::vector& getChapters(); // Validation static bool isValidXtcFile(const char* filepath); @@ -82,24 +70,62 @@ class XtcParser { private: FsFile m_file; + FsFile m_cacheFile; bool m_isOpen; XtcHeader m_header; - std::vector m_pageTable; - std::vector m_chapters; + std::string m_cacheDir; + std::string m_cacheFilePath; + std::string m_originalPath; std::string m_title; std::string m_author; uint16_t m_defaultWidth; uint16_t m_defaultHeight; - uint8_t m_bitDepth; // 1 = XTC/XTG (1-bit), 2 = XTCH/XTH (2-bit) + uint8_t m_bitDepth; bool m_hasChapters; + bool m_chaptersLoaded = false; XtcError m_lastError; + uint32_t m_accessCounter = 0; + + // L1: Hot cache (fixed 4 entries) + std::array m_l1Cache; + + // L2: Sliding window (fixed size array) + std::array m_l2Window; + uint32_t m_l2WindowStart = 0; + size_t m_l2WindowCount = 0; + bool m_l2Valid = false; + + // Chapters (usually few, keep in memory) + std::vector m_chapters; + + // Original Page Table offset (for rebuilding cache) + uint64_t m_pageTableOffset = 0; // Internal helper functions XtcError readHeader(); - XtcError readPageTable(); XtcError readTitle(); XtcError readAuthor(); XtcError readChapters(); + void ensureChaptersLoaded(); + + // L3 cache management + bool isPageTableCacheValid() const; + XtcError buildPageTableCache(); + bool openCacheFile(); + void closeCacheFile(); + + // L1/L2 cache operations + bool lookupL1(uint32_t pageIndex, PageInfo& info); + void updateL1(uint32_t pageIndex, const PageInfo& info); + bool lookupL2(uint32_t pageIndex, PageInfo& info); + void loadL2Window(uint32_t centerPage); + + // Safe deserialization (alignment-safe for ESP32-C3) + static void safeDeserializeHeader(const uint8_t* buf, PageTableCacheHeader& header); + static void safeSerializeHeader(uint8_t* buf, const PageTableCacheHeader& header); + + // Utility functions + uint32_t calculateFileHash(const char* filepath) const; }; } // namespace xtc diff --git a/lib/Xtc/Xtc/XtcTypes.h b/lib/Xtc/Xtc/XtcTypes.h index 83dfd340..b86c8934 100644 --- a/lib/Xtc/Xtc/XtcTypes.h +++ b/lib/Xtc/Xtc/XtcTypes.h @@ -102,6 +102,33 @@ struct ChapterInfo { uint16_t endPage; }; +// Cache configuration +constexpr size_t L1_CACHE_SIZE = 4; // L1 cache entries +constexpr size_t L2_WINDOW_SIZE = 100; // L2 window size (reduced for 2-bit memory) +constexpr uint32_t PAGE_TABLE_CACHE_VERSION = 1; // Cache file version + +// Cache magic number +constexpr uint32_t PAGE_TABLE_CACHE_MAGIC = 0x50435458; // "XTCP" + +// Cache file header - NOTE: Do NOT read directly from file buffer! +// Use safeDeserializeHeader() function for alignment-safe access. +struct PageTableCacheHeader { + uint32_t magic; // 'XTCP' = 0x50435458 + uint32_t version; // Cache version + uint32_t pageCount; // Total pages + uint32_t originalHash; // Original file hash (for validation) + uint64_t originalSize; // Original file size (for validation) + uint32_t entrySize; // PageInfo size (16) + uint32_t reserved; // Reserved +}; + +// L1 cache entry +struct L1CacheEntry { + uint32_t pageIndex = 0xFFFFFFFF; // 0xFFFFFFFF = invalid + PageInfo info{}; + uint32_t lastAccess = 0; // Timestamp for LRU +}; + // Error codes enum class XtcError { OK = 0, diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index 6a62cb70..b7703252 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "CrossPointSettings.h" #include "Epub.h" @@ -12,6 +13,8 @@ #include "XtcReaderActivity.h" #include "activities/util/BmpViewerActivity.h" #include "activities/util/FullScreenMessageActivity.h" +#include "components/UITheme.h" +#include "fontIds.h" std::string ReaderActivity::extractFolderPath(const std::string& filePath) { const auto lastSlash = filePath.find_last_of('/'); @@ -116,6 +119,10 @@ void ReaderActivity::onEnter() { if (isBmpFile(initialBookPath)) { onGoToBmpViewer(initialBookPath); } else if (isXtcFile(initialBookPath)) { + renderer.clearScreen(); + renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_LOADING), true, EpdFontFamily::BOLD); + renderer.displayBuffer(); + auto xtc = loadXtc(initialBookPath); if (!xtc) { onGoBack(); diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 861b7422..35ca552c 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -42,6 +42,8 @@ void XtcReaderActivity::onEnter() { APP_STATE.saveToFile(); RECENT_BOOKS.addBook(xtc->getPath(), xtc->getTitle(), xtc->getAuthor(), "", xtc->getThumbBmpPath()); + xtc->prefetchPages(currentPage); + // Trigger first update requestUpdate(); } @@ -63,6 +65,7 @@ void XtcReaderActivity::loop() { [this](const ActivityResult& result) { if (!result.isCancelled) { currentPage = std::get(result.data).page; + xtc->prefetchPages(currentPage); } }); } @@ -118,12 +121,14 @@ void XtcReaderActivity::loop() { } else { currentPage = 0; } + xtc->prefetchPages(currentPage); requestUpdate(); } else if (nextTriggered) { currentPage += skipAmount; if (currentPage >= xtc->getPageCount()) { currentPage = xtc->getPageCount(); // Allow showing "End of book" } + xtc->prefetchPages(currentPage); requestUpdate(); } }