This commit is contained in:
2026-08-15 17:50:48 +02:00
parent 22246060e6
commit 4dc6687de7
35 changed files with 4978 additions and 1509 deletions
+490 -490
View File
@@ -1,491 +1,491 @@
-- modified from https://github.com/idiomic/Lua_AES
--[[
Copyright 2019 Tyler Richard Hoyer
Copyright 2025 dyphire
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]
local unpack = unpack or table.unpack
local GF8x2 = {
[0]=0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e,
0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e,0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e,
0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e,0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e,
0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e,0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e,
0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e,
0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe,
0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce,0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde,
0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee,0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe,
0x1b,0x19,0x1f,0x1d,0x13,0x11,0x17,0x15,0x0b,0x09,0x0f,0x0d,0x03,0x01,0x07,0x05,
0x3b,0x39,0x3f,0x3d,0x33,0x31,0x37,0x35,0x2b,0x29,0x2f,0x2d,0x23,0x21,0x27,0x25,
0x5b,0x59,0x5f,0x5d,0x53,0x51,0x57,0x55,0x4b,0x49,0x4f,0x4d,0x43,0x41,0x47,0x45,
0x7b,0x79,0x7f,0x7d,0x73,0x71,0x77,0x75,0x6b,0x69,0x6f,0x6d,0x63,0x61,0x67,0x65,
0x9b,0x99,0x9f,0x9d,0x93,0x91,0x97,0x95,0x8b,0x89,0x8f,0x8d,0x83,0x81,0x87,0x85,
0xbb,0xb9,0xbf,0xbd,0xb3,0xb1,0xb7,0xb5,0xab,0xa9,0xaf,0xad,0xa3,0xa1,0xa7,0xa5,
0xdb,0xd9,0xdf,0xdd,0xd3,0xd1,0xd7,0xd5,0xcb,0xc9,0xcf,0xcd,0xc3,0xc1,0xc7,0xc5,
0xfb,0xf9,0xff,0xfd,0xf3,0xf1,0xf7,0xf5,0xeb,0xe9,0xef,0xed,0xe3,0xe1,0xe7,0xe5
}
local GF8x3 = {
[0]=0x00,0x03,0x06,0x05,0x0c,0x0f,0x0a,0x09,0x18,0x1b,0x1e,0x1d,0x14,0x17,0x12,0x11,
0x30,0x33,0x36,0x35,0x3c,0x3f,0x3a,0x39,0x28,0x2b,0x2e,0x2d,0x24,0x27,0x22,0x21,
0x60,0x63,0x66,0x65,0x6c,0x6f,0x6a,0x69,0x78,0x7b,0x7e,0x7d,0x74,0x77,0x72,0x71,
0x50,0x53,0x56,0x55,0x5c,0x5f,0x5a,0x59,0x48,0x4b,0x4e,0x4d,0x44,0x47,0x42,0x41,
0xc0,0xc3,0xc6,0xc5,0xcc,0xcf,0xca,0xc9,0xd8,0xdb,0xde,0xdd,0xd4,0xd7,0xd2,0xd1,
0xf0,0xf3,0xf6,0xf5,0xfc,0xff,0xfa,0xf9,0xe8,0xeb,0xee,0xed,0xe4,0xe7,0xe2,0xe1,
0xa0,0xa3,0xa6,0xa5,0xac,0xaf,0xaa,0xa9,0xb8,0xbb,0xbe,0xbd,0xb4,0xb7,0xb2,0xb1,
0x90,0x93,0x96,0x95,0x9c,0x9f,0x9a,0x99,0x88,0x8b,0x8e,0x8d,0x84,0x87,0x82,0x81,
0x9b,0x98,0x9d,0x9e,0x97,0x94,0x91,0x92,0x83,0x80,0x85,0x86,0x8f,0x8c,0x89,0x8a,
0xab,0xa8,0xad,0xae,0xa7,0xa4,0xa1,0xa2,0xb3,0xb0,0xb5,0xb6,0xbf,0xbc,0xb9,0xba,
0xfb,0xf8,0xfd,0xfe,0xf7,0xf4,0xf1,0xf2,0xe3,0xe0,0xe5,0xe6,0xef,0xec,0xe9,0xea,
0xcb,0xc8,0xcd,0xce,0xc7,0xc4,0xc1,0xc2,0xd3,0xd0,0xd5,0xd6,0xdf,0xdc,0xd9,0xda,
0x5b,0x58,0x5d,0x5e,0x57,0x54,0x51,0x52,0x43,0x40,0x45,0x46,0x4f,0x4c,0x49,0x4a,
0x6b,0x68,0x6d,0x6e,0x67,0x64,0x61,0x62,0x73,0x70,0x75,0x76,0x7f,0x7c,0x79,0x7a,
0x3b,0x38,0x3d,0x3e,0x37,0x34,0x31,0x32,0x23,0x20,0x25,0x26,0x2f,0x2c,0x29,0x2a,
0x0b,0x08,0x0d,0x0e,0x07,0x04,0x01,0x02,0x13,0x10,0x15,0x16,0x1f,0x1c,0x19,0x1a
}
local GF8x9 = {
[0]=0x00,0x09,0x12,0x1b,0x24,0x2d,0x36,0x3f,0x48,0x41,0x5a,0x53,0x6c,0x65,0x7e,0x77,
0x90,0x99,0x82,0x8b,0xb4,0xbd,0xa6,0xaf,0xd8,0xd1,0xca,0xc3,0xfc,0xf5,0xee,0xe7,
0x3b,0x32,0x29,0x20,0x1f,0x16,0x0d,0x04,0x73,0x7a,0x61,0x68,0x57,0x5e,0x45,0x4c,
0xab,0xa2,0xb9,0xb0,0x8f,0x86,0x9d,0x94,0xe3,0xea,0xf1,0xf8,0xc7,0xce,0xd5,0xdc,
0x76,0x7f,0x64,0x6d,0x52,0x5b,0x40,0x49,0x3e,0x37,0x2c,0x25,0x1a,0x13,0x08,0x01,
0xe6,0xef,0xf4,0xfd,0xc2,0xcb,0xd0,0xd9,0xae,0xa7,0xbc,0xb5,0x8a,0x83,0x98,0x91,
0x4d,0x44,0x5f,0x56,0x69,0x60,0x7b,0x72,0x05,0x0c,0x17,0x1e,0x21,0x28,0x33,0x3a,
0xdd,0xd4,0xcf,0xc6,0xf9,0xf0,0xeb,0xe2,0x95,0x9c,0x87,0x8e,0xb1,0xb8,0xa3,0xaa,
0xec,0xe5,0xfe,0xf7,0xc8,0xc1,0xda,0xd3,0xa4,0xad,0xb6,0xbf,0x80,0x89,0x92,0x9b,
0x7c,0x75,0x6e,0x67,0x58,0x51,0x4a,0x43,0x34,0x3d,0x26,0x2f,0x10,0x19,0x02,0x0b,
0xd7,0xde,0xc5,0xcc,0xf3,0xfa,0xe1,0xe8,0x9f,0x96,0x8d,0x84,0xbb,0xb2,0xa9,0xa0,
0x47,0x4e,0x55,0x5c,0x63,0x6a,0x71,0x78,0x0f,0x06,0x1d,0x14,0x2b,0x22,0x39,0x30,
0x9a,0x93,0x88,0x81,0xbe,0xb7,0xac,0xa5,0xd2,0xdb,0xc0,0xc9,0xf6,0xff,0xe4,0xed,
0x0a,0x03,0x18,0x11,0x2e,0x27,0x3c,0x35,0x42,0x4b,0x50,0x59,0x66,0x6f,0x74,0x7d,
0xa1,0xa8,0xb3,0xba,0x85,0x8c,0x97,0x9e,0xe9,0xe0,0xfb,0xf2,0xcd,0xc4,0xdf,0xd6,
0x31,0x38,0x23,0x2a,0x15,0x1c,0x07,0x0e,0x79,0x70,0x6b,0x62,0x5d,0x54,0x4f,0x46
}
local GF8x11 = {
[0]=0x00,0x0b,0x16,0x1d,0x2c,0x27,0x3a,0x31,0x58,0x53,0x4e,0x45,0x74,0x7f,0x62,0x69,
0xb0,0xbb,0xa6,0xad,0x9c,0x97,0x8a,0x81,0xe8,0xe3,0xfe,0xf5,0xc4,0xcf,0xd2,0xd9,
0x7b,0x70,0x6d,0x66,0x57,0x5c,0x41,0x4a,0x23,0x28,0x35,0x3e,0x0f,0x04,0x19,0x12,
0xcb,0xc0,0xdd,0xd6,0xe7,0xec,0xf1,0xfa,0x93,0x98,0x85,0x8e,0xbf,0xb4,0xa9,0xa2,
0xf6,0xfd,0xe0,0xeb,0xda,0xd1,0xcc,0xc7,0xae,0xa5,0xb8,0xb3,0x82,0x89,0x94,0x9f,
0x46,0x4d,0x50,0x5b,0x6a,0x61,0x7c,0x77,0x1e,0x15,0x08,0x03,0x32,0x39,0x24,0x2f,
0x8d,0x86,0x9b,0x90,0xa1,0xaa,0xb7,0xbc,0xd5,0xde,0xc3,0xc8,0xf9,0xf2,0xef,0xe4,
0x3d,0x36,0x2b,0x20,0x11,0x1a,0x07,0x0c,0x65,0x6e,0x73,0x78,0x49,0x42,0x5f,0x54,
0xf7,0xfc,0xe1,0xea,0xdb,0xd0,0xcd,0xc6,0xaf,0xa4,0xb9,0xb2,0x83,0x88,0x95,0x9e,
0x47,0x4c,0x51,0x5a,0x6b,0x60,0x7d,0x76,0x1f,0x14,0x09,0x02,0x33,0x38,0x25,0x2e,
0x8c,0x87,0x9a,0x91,0xa0,0xab,0xb6,0xbd,0xd4,0xdf,0xc2,0xc9,0xf8,0xf3,0xee,0xe5,
0x3c,0x37,0x2a,0x21,0x10,0x1b,0x06,0x0d,0x64,0x6f,0x72,0x79,0x48,0x43,0x5e,0x55,
0x01,0x0a,0x17,0x1c,0x2d,0x26,0x3b,0x30,0x59,0x52,0x4f,0x44,0x75,0x7e,0x63,0x68,
0xb1,0xba,0xa7,0xac,0x9d,0x96,0x8b,0x80,0xe9,0xe2,0xff,0xf4,0xc5,0xce,0xd3,0xd8,
0x7a,0x71,0x6c,0x67,0x56,0x5d,0x40,0x4b,0x22,0x29,0x34,0x3f,0x0e,0x05,0x18,0x13,
0xca,0xc1,0xdc,0xd7,0xe6,0xed,0xf0,0xfb,0x92,0x99,0x84,0x8f,0xbe,0xb5,0xa8,0xa3
}
local GF8x13 = {
[0]=0x00,0x0d,0x1a,0x17,0x34,0x39,0x2e,0x23,0x68,0x65,0x72,0x7f,0x5c,0x51,0x46,0x4b,
0xd0,0xdd,0xca,0xc7,0xe4,0xe9,0xfe,0xf3,0xb8,0xb5,0xa2,0xaf,0x8c,0x81,0x96,0x9b,
0xbb,0xb6,0xa1,0xac,0x8f,0x82,0x95,0x98,0xd3,0xde,0xc9,0xc4,0xe7,0xea,0xfd,0xf0,
0x6b,0x66,0x71,0x7c,0x5f,0x52,0x45,0x48,0x03,0x0e,0x19,0x14,0x37,0x3a,0x2d,0x20,
0x6d,0x60,0x77,0x7a,0x59,0x54,0x43,0x4e,0x05,0x08,0x1f,0x12,0x31,0x3c,0x2b,0x26,
0xbd,0xb0,0xa7,0xaa,0x89,0x84,0x93,0x9e,0xd5,0xd8,0xcf,0xc2,0xe1,0xec,0xfb,0xf6,
0xd6,0xdb,0xcc,0xc1,0xe2,0xef,0xf8,0xf5,0xbe,0xb3,0xa4,0xa9,0x8a,0x87,0x90,0x9d,
0x06,0x0b,0x1c,0x11,0x32,0x3f,0x28,0x25,0x6e,0x63,0x74,0x79,0x5a,0x57,0x40,0x4d,
0xda,0xd7,0xc0,0xcd,0xee,0xe3,0xf4,0xf9,0xb2,0xbf,0xa8,0xa5,0x86,0x8b,0x9c,0x91,
0x0a,0x07,0x10,0x1d,0x3e,0x33,0x24,0x29,0x62,0x6f,0x78,0x75,0x56,0x5b,0x4c,0x41,
0x61,0x6c,0x7b,0x76,0x55,0x58,0x4f,0x42,0x09,0x04,0x13,0x1e,0x3d,0x30,0x27,0x2a,
0xb1,0xbc,0xab,0xa6,0x85,0x88,0x9f,0x92,0xd9,0xd4,0xc3,0xce,0xed,0xe0,0xf7,0xfa,
0xb7,0xba,0xad,0xa0,0x83,0x8e,0x99,0x94,0xdf,0xd2,0xc5,0xc8,0xeb,0xe6,0xf1,0xfc,
0x67,0x6a,0x7d,0x70,0x53,0x5e,0x49,0x44,0x0f,0x02,0x15,0x18,0x3b,0x36,0x21,0x2c,
0x0c,0x01,0x16,0x1b,0x38,0x35,0x22,0x2f,0x64,0x69,0x7e,0x73,0x50,0x5d,0x4a,0x47,
0xdc,0xd1,0xc6,0xcb,0xe8,0xe5,0xf2,0xff,0xb4,0xb9,0xae,0xa3,0x80,0x8d,0x9a,0x97
}
local GF8x14 = {
[0]=0x00,0x0e,0x1c,0x12,0x38,0x36,0x24,0x2a,0x70,0x7e,0x6c,0x62,0x48,0x46,0x54,0x5a,
0xe0,0xee,0xfc,0xf2,0xd8,0xd6,0xc4,0xca,0x90,0x9e,0x8c,0x82,0xa8,0xa6,0xb4,0xba,
0xdb,0xd5,0xc7,0xc9,0xe3,0xed,0xff,0xf1,0xab,0xa5,0xb7,0xb9,0x93,0x9d,0x8f,0x81,
0x3b,0x35,0x27,0x29,0x03,0x0d,0x1f,0x11,0x4b,0x45,0x57,0x59,0x73,0x7d,0x6f,0x61,
0xad,0xa3,0xb1,0xbf,0x95,0x9b,0x89,0x87,0xdd,0xd3,0xc1,0xcf,0xe5,0xeb,0xf9,0xf7,
0x4d,0x43,0x51,0x5f,0x75,0x7b,0x69,0x67,0x3d,0x33,0x21,0x2f,0x05,0x0b,0x19,0x17,
0x76,0x78,0x6a,0x64,0x4e,0x40,0x52,0x5c,0x06,0x08,0x1a,0x14,0x3e,0x30,0x22,0x2c,
0x96,0x98,0x8a,0x84,0xae,0xa0,0xb2,0xbc,0xe6,0xe8,0xfa,0xf4,0xde,0xd0,0xc2,0xcc,
0x41,0x4f,0x5d,0x53,0x79,0x77,0x65,0x6b,0x31,0x3f,0x2d,0x23,0x09,0x07,0x15,0x1b,
0xa1,0xaf,0xbd,0xb3,0x99,0x97,0x85,0x8b,0xd1,0xdf,0xcd,0xc3,0xe9,0xe7,0xf5,0xfb,
0x9a,0x94,0x86,0x88,0xa2,0xac,0xbe,0xb0,0xea,0xe4,0xf6,0xf8,0xd2,0xdc,0xce,0xc0,
0x7a,0x74,0x66,0x68,0x42,0x4c,0x5e,0x50,0x0a,0x04,0x16,0x18,0x32,0x3c,0x2e,0x20,
0xec,0xe2,0xf0,0xfe,0xd4,0xda,0xc8,0xc6,0x9c,0x92,0x80,0x8e,0xa4,0xaa,0xb8,0xb6,
0x0c,0x02,0x10,0x1e,0x34,0x3a,0x28,0x26,0x7c,0x72,0x60,0x6e,0x44,0x4a,0x58,0x56,
0x37,0x39,0x2b,0x25,0x0f,0x01,0x13,0x1d,0x47,0x49,0x5b,0x55,0x7f,0x71,0x63,0x6d,
0xd7,0xd9,0xcb,0xc5,0xef,0xe1,0xf3,0xfd,0xa7,0xa9,0xbb,0xb5,0x9f,0x91,0x83,0x8d
}
local s = {
[0]=0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16
}
local si = {
[0]=0x52,0x09,0x6A,0xD5,0x30,0x36,0xA5,0x38,0xBF,0x40,0xA3,0x9E,0x81,0xF3,0xD7,0xFB,
0x7C,0xE3,0x39,0x82,0x9B,0x2F,0xFF,0x87,0x34,0x8E,0x43,0x44,0xC4,0xDE,0xE9,0xCB,
0x54,0x7B,0x94,0x32,0xA6,0xC2,0x23,0x3D,0xEE,0x4C,0x95,0x0B,0x42,0xFA,0xC3,0x4E,
0x08,0x2E,0xA1,0x66,0x28,0xD9,0x24,0xB2,0x76,0x5B,0xA2,0x49,0x6D,0x8B,0xD1,0x25,
0x72,0xF8,0xF6,0x64,0x86,0x68,0x98,0x16,0xD4,0xA4,0x5C,0xCC,0x5D,0x65,0xB6,0x92,
0x6C,0x70,0x48,0x50,0xFD,0xED,0xB9,0xDA,0x5E,0x15,0x46,0x57,0xA7,0x8D,0x9D,0x84,
0x90,0xD8,0xAB,0x00,0x8C,0xBC,0xD3,0x0A,0xF7,0xE4,0x58,0x05,0xB8,0xB3,0x45,0x06,
0xD0,0x2C,0x1E,0x8F,0xCA,0x3F,0x0F,0x02,0xC1,0xAF,0xBD,0x03,0x01,0x13,0x8A,0x6B,
0x3A,0x91,0x11,0x41,0x4F,0x67,0xDC,0xEA,0x97,0xF2,0xCF,0xCE,0xF0,0xB4,0xE6,0x73,
0x96,0xAC,0x74,0x22,0xE7,0xAD,0x35,0x85,0xE2,0xF9,0x37,0xE8,0x1C,0x75,0xDF,0x6E,
0x47,0xF1,0x1A,0x71,0x1D,0x29,0xC5,0x89,0x6F,0xB7,0x62,0x0E,0xAA,0x18,0xBE,0x1B,
0xFC,0x56,0x3E,0x4B,0xC6,0xD2,0x79,0x20,0x9A,0xDB,0xC0,0xFE,0x78,0xCD,0x5A,0xF4,
0x1F,0xDD,0xA8,0x33,0x88,0x07,0xC7,0x31,0xB1,0x12,0x10,0x59,0x27,0x80,0xEC,0x5F,
0x60,0x51,0x7F,0xA9,0x19,0xB5,0x4A,0x0D,0x2D,0xE5,0x7A,0x9F,0x93,0xC9,0x9C,0xEF,
0xA0,0xE0,0x3B,0x4D,0xAE,0x2A,0xF5,0xB0,0xC8,0xEB,0xBB,0x3C,0x83,0x53,0x99,0x61,
0x17,0x2B,0x04,0x7E,0xBA,0x77,0xD6,0x26,0xE1,0x69,0x14,0x63,0x55,0x21,0x0C,0x7D
}
local rcon = {
0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,
0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,
0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,
0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,
0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,
0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,
0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,
0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,
0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,
0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,
0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,
0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,
0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,
0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,
0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,
0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d
}
local xor4 = {
[0]=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
1,0,3,2,5,4,7,6,9,8,11,10,13,12,15,14,
2,3,0,1,6,7,4,5,10,11,8,9,14,15,12,13,
3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12,
4,5,6,7,0,1,2,3,12,13,14,15,8,9,10,11,
5,4,7,6,1,0,3,2,13,12,15,14,9,8,11,10,
6,7,4,5,2,3,0,1,14,15,12,13,10,11,8,9,
7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8,
8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7,
9,8,11,10,13,12,15,14,1,0,3,2,5,4,7,6,
10,11,8,9,14,15,12,13,2,3,0,1,6,7,4,5,
11,10,9,8,15,14,13,12,3,2,1,0,7,6,5,4,
12,13,14,15,8,9,10,11,4,5,6,7,0,1,2,3,
13,12,15,14,9,8,11,10,5,4,7,6,1,0,3,2,
14,15,12,13,10,11,8,9,6,7,4,5,2,3,0,1,
15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,
}
local function xor8(a, b)
local al = a % 16
local bl = b % 16
return 16 * xor4[a - al + (b - bl) / 16] + xor4[16 * al + bl]
end
local function xor_blocks_8(a, b)
local res = {}
for i = 1, 16 do
res[i] = xor8(a[i], b[i])
end
return res
end
local function addRoundKey(state, key)
for i, byte in next, state do
state[i] = xor8(byte, key[i])
end
end
local function subBytes(state, s_box)
for i, byte in next, state do
state[i] = s_box[byte]
end
end
local function shiftRows(state)
state[2], state[6], state[10], state[14] =
state[6], state[10], state[14], state[2]
state[3], state[7], state[11], state[15] =
state[11], state[15], state[3], state[7]
state[4], state[8], state[12], state[16] =
state[16], state[4], state[8], state[12]
end
local function inv_shiftRows(state)
state[2], state[6], state[10], state[14] =
state[14], state[2], state[6], state[10]
state[3], state[7], state[11], state[15] =
state[11], state[15], state[3], state[7]
state[4], state[8], state[12], state[16] =
state[8], state[12], state[16], state[4]
end
local function mixColumns(state)
for i = 0, 3 do
local cur = i*4+1
local a, b, c, d = state[cur], state[cur + 1], state[cur + 2], state[cur + 3]
state[cur + 0] = xor8(xor8(xor8(GF8x2[a], GF8x3[b]), c), d)
state[cur + 1] = xor8(xor8(xor8(a, GF8x2[b]), GF8x3[c]), d)
state[cur + 2] = xor8(xor8(xor8(a, b), GF8x2[c]), GF8x3[d])
state[cur + 3] = xor8(xor8(xor8(GF8x3[a], b), c), GF8x2[d])
end
end
local function inv_mixColumns(state) -- TODO: fix
for i = 0, 3 do
local cur = i*4+1
local a, b, c, d = state[cur], state[cur + 1], state[cur + 2], state[cur + 3]
state[cur + 0] = xor8(xor8(xor8(GF8x14[a], GF8x11[b]), GF8x13[c]), GF8x9[d])
state[cur + 1] = xor8(xor8(xor8(GF8x9[a], GF8x14[b]), GF8x11[c]), GF8x13[d])
state[cur + 2] = xor8(xor8(xor8(GF8x13[a], GF8x9[b]), GF8x14[c]), GF8x11[d])
state[cur + 3] = xor8(xor8(xor8(GF8x11[a], GF8x13[b]), GF8x9[c]), GF8x14[d])
end
end
-- 256-bit key constants
local n = 32 -- number of bytes in the 256-bit encryption key
local b = 240 -- number of bytes in 15 128-bit round keys
local function schedule256(key)
local expanded = {}
for c = 0, n-1 do
expanded[c] = key[c]
end
local i = 1
local c = n
local t1, t2, t3, t4 --t
while c < b do
t1 = expanded[c-4]
t2 = expanded[c-3]
t3 = expanded[c-2]
t4 = expanded[c-1]
if (c % n == 0) then
t1, t2, t3, t4 = xor8(rcon[i+1], s[t2]), s[t3], s[t4], s[t1]
i = i + 1
end
if (c % n == 16) then
t1 = s[t1]
t2 = s[t2]
t3 = s[t3]
t4 = s[t4]
end
t1 = xor8(t1, expanded[c - n])
expanded[c] = t1
c = c + 1
t2 = xor8(t2, expanded[c - n])
expanded[c] = t2
c = c + 1
t3 = xor8(t3, expanded[c - n])
expanded[c] = t3
c = c + 1
t4 = xor8(t4, expanded[c - n])
expanded[c] = t4
c = c + 1
end
local roundKeys = {}
for round = 0, 14 do
local roundKey = {}
for byte = 0, 15 do
roundKey[byte+1] = expanded[round * 16 + byte]
end
roundKeys[round] = roundKey
end
return roundKeys
end
local function chunks(text, i)
local first = i * 16 + 1
if first > #text then
return
end
i = i + 1
local chunk = {text:byte(first, first + 15)}
for j = #chunk + 1, 16 do
chunk[j] = 0
end
return i, chunk
end
local function pkcs7_unpad(str)
local len = #str
if len == 0 then return str end
local pad_len = string.byte(str, len)
if pad_len < 1 or pad_len > 16 then
return nil
end
for i = len - pad_len + 1, len do
if string.byte(str, i) ~= pad_len then
return nil
end
end
return string.sub(str, 1, len - pad_len)
end
local function zero_unpad(str)
local len = #str
while len > 0 and string.byte(str, len) == 0 do
len = len - 1
end
return string.sub(str, 1, len)
end
local function unpad(str)
local unpadded = pkcs7_unpad(str)
if unpadded then
return unpadded
else
return zero_unpad(str)
end
end
local function encrypt(state, roundKeys)
addRoundKey(state, roundKeys[0])
for round = 1, 13 do
subBytes(state, s)
shiftRows(state)
mixColumns(state)
addRoundKey(state, roundKeys[round])
end
subBytes(state, s)
shiftRows(state)
addRoundKey(state, roundKeys[14])
end
local function decrypt(state, roundKeys)
addRoundKey(state, roundKeys[14])
inv_shiftRows(state)
subBytes(state, si)
for round = 13, 1, -1 do
addRoundKey(state, roundKeys[round])
inv_mixColumns(state)
inv_shiftRows(state)
subBytes(state, si)
end
addRoundKey(state, roundKeys[0])
end
local function ECB_encrypt(key, originaltext)
local text = {}
local roundKeys = schedule256(key)
local i = 0
while true do
i, state = chunks(originaltext, i)
if not state then break end
encrypt(state, roundKeys)
text[i] = string.char(unpack(state))
end
return table.concat(text)
end
local function ECB_decrypt(key, ciphertext)
local text = {}
local roundKeys = schedule256(key)
local i = 0
while true do
i, state = chunks(ciphertext, i)
if not state then break end
decrypt(state, roundKeys)
text[i] = string.char(unpack(state))
end
return unpad(table.concat(text))
end
local function CBC_encrypt(key, iv, originaltext)
local roundKeys = schedule256(key)
local text = {}
local prev_block = {unpack(iv)}
local i = 0
while true do
i, block = chunks(originaltext, i)
if not block then break end
local xored = xor_blocks_8(block, prev_block)
encrypt(xored, roundKeys)
text[i] = string.char(unpack(xored))
prev_block = xored
end
return table.concat(text)
end
local function CBC_decrypt(key, iv, ciphertext)
local roundKeys = schedule256(key)
local text = {}
local prev_block = {unpack(iv)}
local i = 0
while true do
i, block = chunks(ciphertext, i)
if not block then break end
local decrypted = {unpack(block)}
decrypt(decrypted, roundKeys)
local plain_block = xor_blocks_8(decrypted, prev_block)
text[i] = string.char(unpack(plain_block))
prev_block = block
end
local result = table.concat(text)
return unpad(result)
end
return {
ECB = {
encrypt = ECB_encrypt;
decrypt = ECB_decrypt;
};
CBC = {
encrypt = CBC_encrypt;
decrypt = CBC_decrypt;
};
-- modified from https://github.com/idiomic/Lua_AES
--[[
Copyright 2019 Tyler Richard Hoyer
Copyright 2025 dyphire
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]
local unpack = unpack or table.unpack
local GF8x2 = {
[0]=0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e,
0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e,0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e,
0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e,0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e,
0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e,0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e,
0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e,
0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe,
0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce,0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde,
0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee,0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe,
0x1b,0x19,0x1f,0x1d,0x13,0x11,0x17,0x15,0x0b,0x09,0x0f,0x0d,0x03,0x01,0x07,0x05,
0x3b,0x39,0x3f,0x3d,0x33,0x31,0x37,0x35,0x2b,0x29,0x2f,0x2d,0x23,0x21,0x27,0x25,
0x5b,0x59,0x5f,0x5d,0x53,0x51,0x57,0x55,0x4b,0x49,0x4f,0x4d,0x43,0x41,0x47,0x45,
0x7b,0x79,0x7f,0x7d,0x73,0x71,0x77,0x75,0x6b,0x69,0x6f,0x6d,0x63,0x61,0x67,0x65,
0x9b,0x99,0x9f,0x9d,0x93,0x91,0x97,0x95,0x8b,0x89,0x8f,0x8d,0x83,0x81,0x87,0x85,
0xbb,0xb9,0xbf,0xbd,0xb3,0xb1,0xb7,0xb5,0xab,0xa9,0xaf,0xad,0xa3,0xa1,0xa7,0xa5,
0xdb,0xd9,0xdf,0xdd,0xd3,0xd1,0xd7,0xd5,0xcb,0xc9,0xcf,0xcd,0xc3,0xc1,0xc7,0xc5,
0xfb,0xf9,0xff,0xfd,0xf3,0xf1,0xf7,0xf5,0xeb,0xe9,0xef,0xed,0xe3,0xe1,0xe7,0xe5
}
local GF8x3 = {
[0]=0x00,0x03,0x06,0x05,0x0c,0x0f,0x0a,0x09,0x18,0x1b,0x1e,0x1d,0x14,0x17,0x12,0x11,
0x30,0x33,0x36,0x35,0x3c,0x3f,0x3a,0x39,0x28,0x2b,0x2e,0x2d,0x24,0x27,0x22,0x21,
0x60,0x63,0x66,0x65,0x6c,0x6f,0x6a,0x69,0x78,0x7b,0x7e,0x7d,0x74,0x77,0x72,0x71,
0x50,0x53,0x56,0x55,0x5c,0x5f,0x5a,0x59,0x48,0x4b,0x4e,0x4d,0x44,0x47,0x42,0x41,
0xc0,0xc3,0xc6,0xc5,0xcc,0xcf,0xca,0xc9,0xd8,0xdb,0xde,0xdd,0xd4,0xd7,0xd2,0xd1,
0xf0,0xf3,0xf6,0xf5,0xfc,0xff,0xfa,0xf9,0xe8,0xeb,0xee,0xed,0xe4,0xe7,0xe2,0xe1,
0xa0,0xa3,0xa6,0xa5,0xac,0xaf,0xaa,0xa9,0xb8,0xbb,0xbe,0xbd,0xb4,0xb7,0xb2,0xb1,
0x90,0x93,0x96,0x95,0x9c,0x9f,0x9a,0x99,0x88,0x8b,0x8e,0x8d,0x84,0x87,0x82,0x81,
0x9b,0x98,0x9d,0x9e,0x97,0x94,0x91,0x92,0x83,0x80,0x85,0x86,0x8f,0x8c,0x89,0x8a,
0xab,0xa8,0xad,0xae,0xa7,0xa4,0xa1,0xa2,0xb3,0xb0,0xb5,0xb6,0xbf,0xbc,0xb9,0xba,
0xfb,0xf8,0xfd,0xfe,0xf7,0xf4,0xf1,0xf2,0xe3,0xe0,0xe5,0xe6,0xef,0xec,0xe9,0xea,
0xcb,0xc8,0xcd,0xce,0xc7,0xc4,0xc1,0xc2,0xd3,0xd0,0xd5,0xd6,0xdf,0xdc,0xd9,0xda,
0x5b,0x58,0x5d,0x5e,0x57,0x54,0x51,0x52,0x43,0x40,0x45,0x46,0x4f,0x4c,0x49,0x4a,
0x6b,0x68,0x6d,0x6e,0x67,0x64,0x61,0x62,0x73,0x70,0x75,0x76,0x7f,0x7c,0x79,0x7a,
0x3b,0x38,0x3d,0x3e,0x37,0x34,0x31,0x32,0x23,0x20,0x25,0x26,0x2f,0x2c,0x29,0x2a,
0x0b,0x08,0x0d,0x0e,0x07,0x04,0x01,0x02,0x13,0x10,0x15,0x16,0x1f,0x1c,0x19,0x1a
}
local GF8x9 = {
[0]=0x00,0x09,0x12,0x1b,0x24,0x2d,0x36,0x3f,0x48,0x41,0x5a,0x53,0x6c,0x65,0x7e,0x77,
0x90,0x99,0x82,0x8b,0xb4,0xbd,0xa6,0xaf,0xd8,0xd1,0xca,0xc3,0xfc,0xf5,0xee,0xe7,
0x3b,0x32,0x29,0x20,0x1f,0x16,0x0d,0x04,0x73,0x7a,0x61,0x68,0x57,0x5e,0x45,0x4c,
0xab,0xa2,0xb9,0xb0,0x8f,0x86,0x9d,0x94,0xe3,0xea,0xf1,0xf8,0xc7,0xce,0xd5,0xdc,
0x76,0x7f,0x64,0x6d,0x52,0x5b,0x40,0x49,0x3e,0x37,0x2c,0x25,0x1a,0x13,0x08,0x01,
0xe6,0xef,0xf4,0xfd,0xc2,0xcb,0xd0,0xd9,0xae,0xa7,0xbc,0xb5,0x8a,0x83,0x98,0x91,
0x4d,0x44,0x5f,0x56,0x69,0x60,0x7b,0x72,0x05,0x0c,0x17,0x1e,0x21,0x28,0x33,0x3a,
0xdd,0xd4,0xcf,0xc6,0xf9,0xf0,0xeb,0xe2,0x95,0x9c,0x87,0x8e,0xb1,0xb8,0xa3,0xaa,
0xec,0xe5,0xfe,0xf7,0xc8,0xc1,0xda,0xd3,0xa4,0xad,0xb6,0xbf,0x80,0x89,0x92,0x9b,
0x7c,0x75,0x6e,0x67,0x58,0x51,0x4a,0x43,0x34,0x3d,0x26,0x2f,0x10,0x19,0x02,0x0b,
0xd7,0xde,0xc5,0xcc,0xf3,0xfa,0xe1,0xe8,0x9f,0x96,0x8d,0x84,0xbb,0xb2,0xa9,0xa0,
0x47,0x4e,0x55,0x5c,0x63,0x6a,0x71,0x78,0x0f,0x06,0x1d,0x14,0x2b,0x22,0x39,0x30,
0x9a,0x93,0x88,0x81,0xbe,0xb7,0xac,0xa5,0xd2,0xdb,0xc0,0xc9,0xf6,0xff,0xe4,0xed,
0x0a,0x03,0x18,0x11,0x2e,0x27,0x3c,0x35,0x42,0x4b,0x50,0x59,0x66,0x6f,0x74,0x7d,
0xa1,0xa8,0xb3,0xba,0x85,0x8c,0x97,0x9e,0xe9,0xe0,0xfb,0xf2,0xcd,0xc4,0xdf,0xd6,
0x31,0x38,0x23,0x2a,0x15,0x1c,0x07,0x0e,0x79,0x70,0x6b,0x62,0x5d,0x54,0x4f,0x46
}
local GF8x11 = {
[0]=0x00,0x0b,0x16,0x1d,0x2c,0x27,0x3a,0x31,0x58,0x53,0x4e,0x45,0x74,0x7f,0x62,0x69,
0xb0,0xbb,0xa6,0xad,0x9c,0x97,0x8a,0x81,0xe8,0xe3,0xfe,0xf5,0xc4,0xcf,0xd2,0xd9,
0x7b,0x70,0x6d,0x66,0x57,0x5c,0x41,0x4a,0x23,0x28,0x35,0x3e,0x0f,0x04,0x19,0x12,
0xcb,0xc0,0xdd,0xd6,0xe7,0xec,0xf1,0xfa,0x93,0x98,0x85,0x8e,0xbf,0xb4,0xa9,0xa2,
0xf6,0xfd,0xe0,0xeb,0xda,0xd1,0xcc,0xc7,0xae,0xa5,0xb8,0xb3,0x82,0x89,0x94,0x9f,
0x46,0x4d,0x50,0x5b,0x6a,0x61,0x7c,0x77,0x1e,0x15,0x08,0x03,0x32,0x39,0x24,0x2f,
0x8d,0x86,0x9b,0x90,0xa1,0xaa,0xb7,0xbc,0xd5,0xde,0xc3,0xc8,0xf9,0xf2,0xef,0xe4,
0x3d,0x36,0x2b,0x20,0x11,0x1a,0x07,0x0c,0x65,0x6e,0x73,0x78,0x49,0x42,0x5f,0x54,
0xf7,0xfc,0xe1,0xea,0xdb,0xd0,0xcd,0xc6,0xaf,0xa4,0xb9,0xb2,0x83,0x88,0x95,0x9e,
0x47,0x4c,0x51,0x5a,0x6b,0x60,0x7d,0x76,0x1f,0x14,0x09,0x02,0x33,0x38,0x25,0x2e,
0x8c,0x87,0x9a,0x91,0xa0,0xab,0xb6,0xbd,0xd4,0xdf,0xc2,0xc9,0xf8,0xf3,0xee,0xe5,
0x3c,0x37,0x2a,0x21,0x10,0x1b,0x06,0x0d,0x64,0x6f,0x72,0x79,0x48,0x43,0x5e,0x55,
0x01,0x0a,0x17,0x1c,0x2d,0x26,0x3b,0x30,0x59,0x52,0x4f,0x44,0x75,0x7e,0x63,0x68,
0xb1,0xba,0xa7,0xac,0x9d,0x96,0x8b,0x80,0xe9,0xe2,0xff,0xf4,0xc5,0xce,0xd3,0xd8,
0x7a,0x71,0x6c,0x67,0x56,0x5d,0x40,0x4b,0x22,0x29,0x34,0x3f,0x0e,0x05,0x18,0x13,
0xca,0xc1,0xdc,0xd7,0xe6,0xed,0xf0,0xfb,0x92,0x99,0x84,0x8f,0xbe,0xb5,0xa8,0xa3
}
local GF8x13 = {
[0]=0x00,0x0d,0x1a,0x17,0x34,0x39,0x2e,0x23,0x68,0x65,0x72,0x7f,0x5c,0x51,0x46,0x4b,
0xd0,0xdd,0xca,0xc7,0xe4,0xe9,0xfe,0xf3,0xb8,0xb5,0xa2,0xaf,0x8c,0x81,0x96,0x9b,
0xbb,0xb6,0xa1,0xac,0x8f,0x82,0x95,0x98,0xd3,0xde,0xc9,0xc4,0xe7,0xea,0xfd,0xf0,
0x6b,0x66,0x71,0x7c,0x5f,0x52,0x45,0x48,0x03,0x0e,0x19,0x14,0x37,0x3a,0x2d,0x20,
0x6d,0x60,0x77,0x7a,0x59,0x54,0x43,0x4e,0x05,0x08,0x1f,0x12,0x31,0x3c,0x2b,0x26,
0xbd,0xb0,0xa7,0xaa,0x89,0x84,0x93,0x9e,0xd5,0xd8,0xcf,0xc2,0xe1,0xec,0xfb,0xf6,
0xd6,0xdb,0xcc,0xc1,0xe2,0xef,0xf8,0xf5,0xbe,0xb3,0xa4,0xa9,0x8a,0x87,0x90,0x9d,
0x06,0x0b,0x1c,0x11,0x32,0x3f,0x28,0x25,0x6e,0x63,0x74,0x79,0x5a,0x57,0x40,0x4d,
0xda,0xd7,0xc0,0xcd,0xee,0xe3,0xf4,0xf9,0xb2,0xbf,0xa8,0xa5,0x86,0x8b,0x9c,0x91,
0x0a,0x07,0x10,0x1d,0x3e,0x33,0x24,0x29,0x62,0x6f,0x78,0x75,0x56,0x5b,0x4c,0x41,
0x61,0x6c,0x7b,0x76,0x55,0x58,0x4f,0x42,0x09,0x04,0x13,0x1e,0x3d,0x30,0x27,0x2a,
0xb1,0xbc,0xab,0xa6,0x85,0x88,0x9f,0x92,0xd9,0xd4,0xc3,0xce,0xed,0xe0,0xf7,0xfa,
0xb7,0xba,0xad,0xa0,0x83,0x8e,0x99,0x94,0xdf,0xd2,0xc5,0xc8,0xeb,0xe6,0xf1,0xfc,
0x67,0x6a,0x7d,0x70,0x53,0x5e,0x49,0x44,0x0f,0x02,0x15,0x18,0x3b,0x36,0x21,0x2c,
0x0c,0x01,0x16,0x1b,0x38,0x35,0x22,0x2f,0x64,0x69,0x7e,0x73,0x50,0x5d,0x4a,0x47,
0xdc,0xd1,0xc6,0xcb,0xe8,0xe5,0xf2,0xff,0xb4,0xb9,0xae,0xa3,0x80,0x8d,0x9a,0x97
}
local GF8x14 = {
[0]=0x00,0x0e,0x1c,0x12,0x38,0x36,0x24,0x2a,0x70,0x7e,0x6c,0x62,0x48,0x46,0x54,0x5a,
0xe0,0xee,0xfc,0xf2,0xd8,0xd6,0xc4,0xca,0x90,0x9e,0x8c,0x82,0xa8,0xa6,0xb4,0xba,
0xdb,0xd5,0xc7,0xc9,0xe3,0xed,0xff,0xf1,0xab,0xa5,0xb7,0xb9,0x93,0x9d,0x8f,0x81,
0x3b,0x35,0x27,0x29,0x03,0x0d,0x1f,0x11,0x4b,0x45,0x57,0x59,0x73,0x7d,0x6f,0x61,
0xad,0xa3,0xb1,0xbf,0x95,0x9b,0x89,0x87,0xdd,0xd3,0xc1,0xcf,0xe5,0xeb,0xf9,0xf7,
0x4d,0x43,0x51,0x5f,0x75,0x7b,0x69,0x67,0x3d,0x33,0x21,0x2f,0x05,0x0b,0x19,0x17,
0x76,0x78,0x6a,0x64,0x4e,0x40,0x52,0x5c,0x06,0x08,0x1a,0x14,0x3e,0x30,0x22,0x2c,
0x96,0x98,0x8a,0x84,0xae,0xa0,0xb2,0xbc,0xe6,0xe8,0xfa,0xf4,0xde,0xd0,0xc2,0xcc,
0x41,0x4f,0x5d,0x53,0x79,0x77,0x65,0x6b,0x31,0x3f,0x2d,0x23,0x09,0x07,0x15,0x1b,
0xa1,0xaf,0xbd,0xb3,0x99,0x97,0x85,0x8b,0xd1,0xdf,0xcd,0xc3,0xe9,0xe7,0xf5,0xfb,
0x9a,0x94,0x86,0x88,0xa2,0xac,0xbe,0xb0,0xea,0xe4,0xf6,0xf8,0xd2,0xdc,0xce,0xc0,
0x7a,0x74,0x66,0x68,0x42,0x4c,0x5e,0x50,0x0a,0x04,0x16,0x18,0x32,0x3c,0x2e,0x20,
0xec,0xe2,0xf0,0xfe,0xd4,0xda,0xc8,0xc6,0x9c,0x92,0x80,0x8e,0xa4,0xaa,0xb8,0xb6,
0x0c,0x02,0x10,0x1e,0x34,0x3a,0x28,0x26,0x7c,0x72,0x60,0x6e,0x44,0x4a,0x58,0x56,
0x37,0x39,0x2b,0x25,0x0f,0x01,0x13,0x1d,0x47,0x49,0x5b,0x55,0x7f,0x71,0x63,0x6d,
0xd7,0xd9,0xcb,0xc5,0xef,0xe1,0xf3,0xfd,0xa7,0xa9,0xbb,0xb5,0x9f,0x91,0x83,0x8d
}
local s = {
[0]=0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16
}
local si = {
[0]=0x52,0x09,0x6A,0xD5,0x30,0x36,0xA5,0x38,0xBF,0x40,0xA3,0x9E,0x81,0xF3,0xD7,0xFB,
0x7C,0xE3,0x39,0x82,0x9B,0x2F,0xFF,0x87,0x34,0x8E,0x43,0x44,0xC4,0xDE,0xE9,0xCB,
0x54,0x7B,0x94,0x32,0xA6,0xC2,0x23,0x3D,0xEE,0x4C,0x95,0x0B,0x42,0xFA,0xC3,0x4E,
0x08,0x2E,0xA1,0x66,0x28,0xD9,0x24,0xB2,0x76,0x5B,0xA2,0x49,0x6D,0x8B,0xD1,0x25,
0x72,0xF8,0xF6,0x64,0x86,0x68,0x98,0x16,0xD4,0xA4,0x5C,0xCC,0x5D,0x65,0xB6,0x92,
0x6C,0x70,0x48,0x50,0xFD,0xED,0xB9,0xDA,0x5E,0x15,0x46,0x57,0xA7,0x8D,0x9D,0x84,
0x90,0xD8,0xAB,0x00,0x8C,0xBC,0xD3,0x0A,0xF7,0xE4,0x58,0x05,0xB8,0xB3,0x45,0x06,
0xD0,0x2C,0x1E,0x8F,0xCA,0x3F,0x0F,0x02,0xC1,0xAF,0xBD,0x03,0x01,0x13,0x8A,0x6B,
0x3A,0x91,0x11,0x41,0x4F,0x67,0xDC,0xEA,0x97,0xF2,0xCF,0xCE,0xF0,0xB4,0xE6,0x73,
0x96,0xAC,0x74,0x22,0xE7,0xAD,0x35,0x85,0xE2,0xF9,0x37,0xE8,0x1C,0x75,0xDF,0x6E,
0x47,0xF1,0x1A,0x71,0x1D,0x29,0xC5,0x89,0x6F,0xB7,0x62,0x0E,0xAA,0x18,0xBE,0x1B,
0xFC,0x56,0x3E,0x4B,0xC6,0xD2,0x79,0x20,0x9A,0xDB,0xC0,0xFE,0x78,0xCD,0x5A,0xF4,
0x1F,0xDD,0xA8,0x33,0x88,0x07,0xC7,0x31,0xB1,0x12,0x10,0x59,0x27,0x80,0xEC,0x5F,
0x60,0x51,0x7F,0xA9,0x19,0xB5,0x4A,0x0D,0x2D,0xE5,0x7A,0x9F,0x93,0xC9,0x9C,0xEF,
0xA0,0xE0,0x3B,0x4D,0xAE,0x2A,0xF5,0xB0,0xC8,0xEB,0xBB,0x3C,0x83,0x53,0x99,0x61,
0x17,0x2B,0x04,0x7E,0xBA,0x77,0xD6,0x26,0xE1,0x69,0x14,0x63,0x55,0x21,0x0C,0x7D
}
local rcon = {
0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,
0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,
0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,
0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,
0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,
0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,
0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,
0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,
0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,
0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,
0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,
0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,
0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,
0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,
0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,
0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d
}
local xor4 = {
[0]=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
1,0,3,2,5,4,7,6,9,8,11,10,13,12,15,14,
2,3,0,1,6,7,4,5,10,11,8,9,14,15,12,13,
3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12,
4,5,6,7,0,1,2,3,12,13,14,15,8,9,10,11,
5,4,7,6,1,0,3,2,13,12,15,14,9,8,11,10,
6,7,4,5,2,3,0,1,14,15,12,13,10,11,8,9,
7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8,
8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7,
9,8,11,10,13,12,15,14,1,0,3,2,5,4,7,6,
10,11,8,9,14,15,12,13,2,3,0,1,6,7,4,5,
11,10,9,8,15,14,13,12,3,2,1,0,7,6,5,4,
12,13,14,15,8,9,10,11,4,5,6,7,0,1,2,3,
13,12,15,14,9,8,11,10,5,4,7,6,1,0,3,2,
14,15,12,13,10,11,8,9,6,7,4,5,2,3,0,1,
15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,
}
local function xor8(a, b)
local al = a % 16
local bl = b % 16
return 16 * xor4[a - al + (b - bl) / 16] + xor4[16 * al + bl]
end
local function xor_blocks_8(a, b)
local res = {}
for i = 1, 16 do
res[i] = xor8(a[i], b[i])
end
return res
end
local function addRoundKey(state, key)
for i, byte in next, state do
state[i] = xor8(byte, key[i])
end
end
local function subBytes(state, s_box)
for i, byte in next, state do
state[i] = s_box[byte]
end
end
local function shiftRows(state)
state[2], state[6], state[10], state[14] =
state[6], state[10], state[14], state[2]
state[3], state[7], state[11], state[15] =
state[11], state[15], state[3], state[7]
state[4], state[8], state[12], state[16] =
state[16], state[4], state[8], state[12]
end
local function inv_shiftRows(state)
state[2], state[6], state[10], state[14] =
state[14], state[2], state[6], state[10]
state[3], state[7], state[11], state[15] =
state[11], state[15], state[3], state[7]
state[4], state[8], state[12], state[16] =
state[8], state[12], state[16], state[4]
end
local function mixColumns(state)
for i = 0, 3 do
local cur = i*4+1
local a, b, c, d = state[cur], state[cur + 1], state[cur + 2], state[cur + 3]
state[cur + 0] = xor8(xor8(xor8(GF8x2[a], GF8x3[b]), c), d)
state[cur + 1] = xor8(xor8(xor8(a, GF8x2[b]), GF8x3[c]), d)
state[cur + 2] = xor8(xor8(xor8(a, b), GF8x2[c]), GF8x3[d])
state[cur + 3] = xor8(xor8(xor8(GF8x3[a], b), c), GF8x2[d])
end
end
local function inv_mixColumns(state) -- TODO: fix
for i = 0, 3 do
local cur = i*4+1
local a, b, c, d = state[cur], state[cur + 1], state[cur + 2], state[cur + 3]
state[cur + 0] = xor8(xor8(xor8(GF8x14[a], GF8x11[b]), GF8x13[c]), GF8x9[d])
state[cur + 1] = xor8(xor8(xor8(GF8x9[a], GF8x14[b]), GF8x11[c]), GF8x13[d])
state[cur + 2] = xor8(xor8(xor8(GF8x13[a], GF8x9[b]), GF8x14[c]), GF8x11[d])
state[cur + 3] = xor8(xor8(xor8(GF8x11[a], GF8x13[b]), GF8x9[c]), GF8x14[d])
end
end
-- 256-bit key constants
local n = 32 -- number of bytes in the 256-bit encryption key
local b = 240 -- number of bytes in 15 128-bit round keys
local function schedule256(key)
local expanded = {}
for c = 0, n-1 do
expanded[c] = key[c]
end
local i = 1
local c = n
local t1, t2, t3, t4 --t
while c < b do
t1 = expanded[c-4]
t2 = expanded[c-3]
t3 = expanded[c-2]
t4 = expanded[c-1]
if (c % n == 0) then
t1, t2, t3, t4 = xor8(rcon[i+1], s[t2]), s[t3], s[t4], s[t1]
i = i + 1
end
if (c % n == 16) then
t1 = s[t1]
t2 = s[t2]
t3 = s[t3]
t4 = s[t4]
end
t1 = xor8(t1, expanded[c - n])
expanded[c] = t1
c = c + 1
t2 = xor8(t2, expanded[c - n])
expanded[c] = t2
c = c + 1
t3 = xor8(t3, expanded[c - n])
expanded[c] = t3
c = c + 1
t4 = xor8(t4, expanded[c - n])
expanded[c] = t4
c = c + 1
end
local roundKeys = {}
for round = 0, 14 do
local roundKey = {}
for byte = 0, 15 do
roundKey[byte+1] = expanded[round * 16 + byte]
end
roundKeys[round] = roundKey
end
return roundKeys
end
local function chunks(text, i)
local first = i * 16 + 1
if first > #text then
return
end
i = i + 1
local chunk = {text:byte(first, first + 15)}
for j = #chunk + 1, 16 do
chunk[j] = 0
end
return i, chunk
end
local function pkcs7_unpad(str)
local len = #str
if len == 0 then return str end
local pad_len = string.byte(str, len)
if pad_len < 1 or pad_len > 16 then
return nil
end
for i = len - pad_len + 1, len do
if string.byte(str, i) ~= pad_len then
return nil
end
end
return string.sub(str, 1, len - pad_len)
end
local function zero_unpad(str)
local len = #str
while len > 0 and string.byte(str, len) == 0 do
len = len - 1
end
return string.sub(str, 1, len)
end
local function unpad(str)
local unpadded = pkcs7_unpad(str)
if unpadded then
return unpadded
else
return zero_unpad(str)
end
end
local function encrypt(state, roundKeys)
addRoundKey(state, roundKeys[0])
for round = 1, 13 do
subBytes(state, s)
shiftRows(state)
mixColumns(state)
addRoundKey(state, roundKeys[round])
end
subBytes(state, s)
shiftRows(state)
addRoundKey(state, roundKeys[14])
end
local function decrypt(state, roundKeys)
addRoundKey(state, roundKeys[14])
inv_shiftRows(state)
subBytes(state, si)
for round = 13, 1, -1 do
addRoundKey(state, roundKeys[round])
inv_mixColumns(state)
inv_shiftRows(state)
subBytes(state, si)
end
addRoundKey(state, roundKeys[0])
end
local function ECB_encrypt(key, originaltext)
local text = {}
local roundKeys = schedule256(key)
local i = 0
while true do
i, state = chunks(originaltext, i)
if not state then break end
encrypt(state, roundKeys)
text[i] = string.char(unpack(state))
end
return table.concat(text)
end
local function ECB_decrypt(key, ciphertext)
local text = {}
local roundKeys = schedule256(key)
local i = 0
while true do
i, state = chunks(ciphertext, i)
if not state then break end
decrypt(state, roundKeys)
text[i] = string.char(unpack(state))
end
return unpad(table.concat(text))
end
local function CBC_encrypt(key, iv, originaltext)
local roundKeys = schedule256(key)
local text = {}
local prev_block = {unpack(iv)}
local i = 0
while true do
i, block = chunks(originaltext, i)
if not block then break end
local xored = xor_blocks_8(block, prev_block)
encrypt(xored, roundKeys)
text[i] = string.char(unpack(xored))
prev_block = xored
end
return table.concat(text)
end
local function CBC_decrypt(key, iv, ciphertext)
local roundKeys = schedule256(key)
local text = {}
local prev_block = {unpack(iv)}
local i = 0
while true do
i, block = chunks(ciphertext, i)
if not block then break end
local decrypted = {unpack(block)}
decrypt(decrypted, roundKeys)
local plain_block = xor_blocks_8(decrypted, prev_block)
text[i] = string.char(unpack(plain_block))
prev_block = block
end
local result = table.concat(text)
return unpad(result)
end
return {
ECB = {
encrypt = ECB_encrypt;
decrypt = ECB_decrypt;
};
CBC = {
encrypt = CBC_encrypt;
decrypt = CBC_decrypt;
};
}
+192 -192
View File
@@ -1,192 +1,192 @@
--[[
sha256 -- public domain Lua SHA-256 implementation
no warranty implied; use at your own risk
author: dyphire
COMPATIBILITY
Lua 5.1+, LuaJIT
LICENSE: MIT License
--]]
local unpack = unpack or table.unpack
local function band(a,b)
local res = 0
local bit = 1
for i = 0,31 do
local aa = a % 2
local bb = b % 2
if aa == 1 and bb == 1 then
res = res + bit
end
a = (a - aa) / 2
b = (b - bb) / 2
bit = bit * 2
end
return res
end
local function bor(a,b)
local res = 0
local bit = 1
for i = 0,31 do
local aa = a % 2
local bb = b % 2
if aa == 1 or bb == 1 then
res = res + bit
end
a = (a - aa) / 2
b = (b - bb) / 2
bit = bit * 2
end
return res
end
local function bxor(a,b)
local res = 0
local bit = 1
for i = 0,31 do
local aa = a % 2
local bb = b % 2
if (aa + bb) == 1 then
res = res + bit
end
a = (a - aa) / 2
b = (b - bb) / 2
bit = bit * 2
end
return res
end
local function bnot(a)
return 0xFFFFFFFF - a
end
local function lshift(a,n)
return (a * 2^n) % 2^32
end
local function rshift(a,n)
return math.floor(a / 2^n) % 2^32
end
local function bit_ror(x, n)
return bor(rshift(x, n), lshift(x, 32 - n))
end
local function sha256(message)
local k = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
}
local function preprocess(msg)
local len = #msg
local bitLen = len * 8
msg = msg .. "\128"
local zeroPad = 64 - ((len + 9) % 64)
if zeroPad ~= 64 then
msg = msg .. string.rep("\0", zeroPad)
end
msg = msg .. string.char(
rshift(bitLen, 56) % 256,
rshift(bitLen, 48) % 256,
rshift(bitLen, 40) % 256,
rshift(bitLen, 32) % 256,
rshift(bitLen, 24) % 256,
rshift(bitLen, 16) % 256,
rshift(bitLen, 8) % 256,
bitLen % 256
)
return msg
end
local function chunkify(msg)
local chunks = {}
for i = 1, #msg, 64 do
table.insert(chunks, msg:sub(i, i + 63))
end
return chunks
end
local function processChunk(chunk, hash)
local w = {}
for i = 1, 64 do
if i <= 16 then
w[i] = lshift(string.byte(chunk, (i - 1) * 4 + 1), 24) +
lshift(string.byte(chunk, (i - 1) * 4 + 2), 16) +
lshift(string.byte(chunk, (i - 1) * 4 + 3), 8) +
string.byte(chunk, (i - 1) * 4 + 4)
else
local s0 = bxor(bxor(bit_ror(w[i - 15], 7), bit_ror(w[i - 15], 18)), rshift(w[i - 15], 3))
local s1 = bxor(bxor(bit_ror(w[i - 2], 17), bit_ror(w[i - 2], 19)), rshift(w[i - 2], 10))
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) % 2^32
end
end
local a, b, c, d, e, f, g, h = unpack(hash)
for i = 1, 64 do
local s1 = bxor(bxor(bit_ror(e, 6), bit_ror(e, 11)), bit_ror(e, 25))
local ch = bxor(band(e, f), band(bnot(e), g))
local temp1 = (h + s1 + ch + k[i] + w[i]) % 2^32
local s0 = bxor(bxor(bit_ror(a, 2), bit_ror(a, 13)), bit_ror(a, 22))
local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
local temp2 = (s0 + maj) % 2^32
h = g
g = f
f = e
e = (d + temp1) % 2^32
d = c
c = b
b = a
a = (temp1 + temp2) % 2^32
end
return
(hash[1] + a) % 2^32,
(hash[2] + b) % 2^32,
(hash[3] + c) % 2^32,
(hash[4] + d) % 2^32,
(hash[5] + e) % 2^32,
(hash[6] + f) % 2^32,
(hash[7] + g) % 2^32,
(hash[8] + h) % 2^32
end
message = preprocess(message)
local chunks = chunkify(message)
local hash = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
}
for _, chunk in ipairs(chunks) do
hash = {processChunk(chunk, hash)}
end
local result = ""
for _, h in ipairs(hash) do
result = result .. string.format("%08x", h)
end
return result
end
return sha256
--[[
sha256 -- public domain Lua SHA-256 implementation
no warranty implied; use at your own risk
author: dyphire
COMPATIBILITY
Lua 5.1+, LuaJIT
LICENSE: MIT License
--]]
local unpack = unpack or table.unpack
local function band(a,b)
local res = 0
local bit = 1
for i = 0,31 do
local aa = a % 2
local bb = b % 2
if aa == 1 and bb == 1 then
res = res + bit
end
a = (a - aa) / 2
b = (b - bb) / 2
bit = bit * 2
end
return res
end
local function bor(a,b)
local res = 0
local bit = 1
for i = 0,31 do
local aa = a % 2
local bb = b % 2
if aa == 1 or bb == 1 then
res = res + bit
end
a = (a - aa) / 2
b = (b - bb) / 2
bit = bit * 2
end
return res
end
local function bxor(a,b)
local res = 0
local bit = 1
for i = 0,31 do
local aa = a % 2
local bb = b % 2
if (aa + bb) == 1 then
res = res + bit
end
a = (a - aa) / 2
b = (b - bb) / 2
bit = bit * 2
end
return res
end
local function bnot(a)
return 0xFFFFFFFF - a
end
local function lshift(a,n)
return (a * 2^n) % 2^32
end
local function rshift(a,n)
return math.floor(a / 2^n) % 2^32
end
local function bit_ror(x, n)
return bor(rshift(x, n), lshift(x, 32 - n))
end
local function sha256(message)
local k = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
}
local function preprocess(msg)
local len = #msg
local bitLen = len * 8
msg = msg .. "\128"
local zeroPad = 64 - ((len + 9) % 64)
if zeroPad ~= 64 then
msg = msg .. string.rep("\0", zeroPad)
end
msg = msg .. string.char(
rshift(bitLen, 56) % 256,
rshift(bitLen, 48) % 256,
rshift(bitLen, 40) % 256,
rshift(bitLen, 32) % 256,
rshift(bitLen, 24) % 256,
rshift(bitLen, 16) % 256,
rshift(bitLen, 8) % 256,
bitLen % 256
)
return msg
end
local function chunkify(msg)
local chunks = {}
for i = 1, #msg, 64 do
table.insert(chunks, msg:sub(i, i + 63))
end
return chunks
end
local function processChunk(chunk, hash)
local w = {}
for i = 1, 64 do
if i <= 16 then
w[i] = lshift(string.byte(chunk, (i - 1) * 4 + 1), 24) +
lshift(string.byte(chunk, (i - 1) * 4 + 2), 16) +
lshift(string.byte(chunk, (i - 1) * 4 + 3), 8) +
string.byte(chunk, (i - 1) * 4 + 4)
else
local s0 = bxor(bxor(bit_ror(w[i - 15], 7), bit_ror(w[i - 15], 18)), rshift(w[i - 15], 3))
local s1 = bxor(bxor(bit_ror(w[i - 2], 17), bit_ror(w[i - 2], 19)), rshift(w[i - 2], 10))
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) % 2^32
end
end
local a, b, c, d, e, f, g, h = unpack(hash)
for i = 1, 64 do
local s1 = bxor(bxor(bit_ror(e, 6), bit_ror(e, 11)), bit_ror(e, 25))
local ch = bxor(band(e, f), band(bnot(e), g))
local temp1 = (h + s1 + ch + k[i] + w[i]) % 2^32
local s0 = bxor(bxor(bit_ror(a, 2), bit_ror(a, 13)), bit_ror(a, 22))
local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
local temp2 = (s0 + maj) % 2^32
h = g
g = f
f = e
e = (d + temp1) % 2^32
d = c
c = b
b = a
a = (temp1 + temp2) % 2^32
end
return
(hash[1] + a) % 2^32,
(hash[2] + b) % 2^32,
(hash[3] + c) % 2^32,
(hash[4] + d) % 2^32,
(hash[5] + e) % 2^32,
(hash[6] + f) % 2^32,
(hash[7] + g) % 2^32,
(hash[8] + h) % 2^32
end
message = preprocess(message)
local chunks = chunkify(message)
local hash = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
}
for _, chunk in ipairs(chunks) do
hash = {processChunk(chunk, hash)}
end
local result = ""
for _, h in ipairs(hash) do
result = result .. string.format("%08x", h)
end
return result
end
return sha256
+322
View File
@@ -0,0 +1,322 @@
--[[lit-meta
name = 'TohruMKDM/inflate'
version = '2.0.0'
homepage = 'https://github.com/TohruMKDM/lua-inflate'
description = 'ZIP archive inflation in pure Lua.'
tags = {'zlib', 'inflate', 'inflation', 'compression'}
license = 'MIT'
author = {name = 'Tohru~ (トール)', email = 'admin@ikaros.pw'}
contributors = {'zerkman', 'samhocevar'}
]]
---@diagnostic disable-next-line: undefined-global
local bitOp = bit32 or bit
if not bitOp then
error('This library requires bit operations to work property.')
end
local rshift, lshift, band, bxor, bnot = bitOp.rshift, bitOp.lshift, bitOp.band, bitOp.bxor, bitOp.bnot
local byte, char, sub, find = string.byte, string.char, string.sub, string.find
local concat, unpack = table.concat, unpack or table.unpack
local min = math.min
local ORDER = {17, 18, 19, 1, 9, 8, 10, 7, 11, 6, 12, 5, 13, 4, 14, 3, 15, 2, 16}
local CRC32 = {[0] = 0, 1996959894, -301047508, -1727442502, 124634137, 1886057615, -379345611, -1637575261, 249268274, 2044508324, -522852066, -1747789432, 162941995, 2125561021, -407360249, -1866523247, 498536548, 1789927666, -205950648, -2067906082, 450548861, 1843258603, -187386543, -2083289657, 325883990, 1684777152, -43845254,-1973040660, 335633487, 1661365465, -99664541, -1928851979, 997073096, 1281953886, -715111964, -1570279054, 1006888145, 1258607687, -770865667, -1526024853, 901097722, 1119000684, -608450090, -1396901568, 853044451, 1172266101, -589951537, -1412350631, 651767980, 1373503546, -925412992, -1076862698, 565507253, 1454621731, -809855591, -1195530993, 671266974, 1594198024, -972236366, -1324619484, 795835527, 1483230225, -1050600021, -1234817731, 1994146192, 31158534, -1731059524, -271249366, 1907459465, 112637215, -1614814043, -390540237, 2013776290, 251722036, -1777751922, -519137256, 2137656763, 141376813, -1855689577, -429695999, 1802195444, 476864866, -2056965928, -228458418, 1812370925, 453092731, -2113342271, -183516073, 1706088902, 314042704, -1950435094, -54949764, 1658658271, 366619977, -1932296973, -69972891, 1303535960, 984961486, -1547960204, -725929758, 1256170817, 1037604311, -1529756563, -740887301, 1131014506, 879679996, -1385723834, -631195440, 1141124467, 855842277, -1442165665, -586318647, 1342533948, 654459306, -1106571248, -921952122, 1466479909, 544179635, -1184443383, -832445281, 1591671054, 702138776, -1328506846, -942167884, 1504918807, 783551873, -1212326853, -1061524307, -306674912, -1698712650, 62317068, 1957810842, -355121351, -1647151185, 81470997,1943803523, -480048366, -1805370492, 225274430, 2053790376, -468791541, -1828061283, 167816743, 2097651377, -267414716, -2029476910, 503444072, 1762050814, -144550051, -2140837941, 426522225, 1852507879, -19653770, -1982649376, 282753626, 1742555852, -105259153, -1900089351, 397917763, 1622183637, -690576408, -1580100738, 953729732, 1340076626, -776247311, -1497606297, 1068828381, 1219638859, -670225446, -1358292148, 906185462, 1090812512, -547295293, -1469587627, 829329135, 1181335161, -882789492, -1134132454, 628085408, 1382605366, -871598187, -1156888829, 570562233, 1426400815, -977650754, -1296233688, 733239954, 1555261956, -1026031705, -1244606671, 752459403, 1541320221, -1687895376, -328994266, 1969922972, 40735498, -1677130071, -351390145, 1913087877, 83908371, -1782625662, -491226604, 2075208622, 213261112, -1831694693, -438977011, 2094854071, 198958881, -2032938284, -237706686, 1759359992, 534414190, -2118248755, -155638181, 1873836001, 414664567, -2012718362, -15766928, 1711684554, 285281116, -1889165569, -127750551, 1634467795, 376229701, -1609899400, -686959890, 1308918612, 956543938, -1486412191, -799009033, 1231636301, 1047427035, -1362007478, -640263460, 1088359270, 936918000, -1447252397, -558129467, 1202900863, 817233897, -1111625188, -893730166, 1404277552, 615818150, -1160759803, -841546093, 1423857449, 601450431, -1285129682, -1000256840, 1567103746, 711928724, -1274298825, -1022587231, 1510334235, 755167117}
local NBT = {2, 3, 7}
local CNT = {144, 112, 24, 8}
local DPT = {8, 9, 7, 8}
local STATIC_HUFFMAN = {[0] = 5, 261, 133, 389, 69, 325, 197, 453, 37, 293, 165, 421, 101, 357, 229, 485, 21, 277, 149, 405, 85, 341, 213, 469, 53, 309, 181, 437, 117, 373, 245, 501}
local STATIC_BITS = 5
local CHUNK_SIZE = 4096
local function generateCrc32(input)
local crc = -1
for i = 1, #input do
local c = byte(input, i)
crc = bxor(CRC32[bxor(c, band(crc, 255))], rshift(crc, 8))
end
crc = bnot(crc)
if crc < 0 then
crc = crc + 4294967296
end
return crc
end
local function flushBits(stream, int)
stream.bits = rshift(stream.bits, int)
stream.count = stream.count - int
end
local function peekBits(stream, int)
local buffer, bits, count, position = stream.buffer, stream.bits, stream.count, stream.position
while count < int do
bits = bits + lshift(byte(buffer, position), count)
position = position + 1
count = count + 8
end
stream.bits = bits
stream.position = position
stream.count = count
return band(bits, lshift(1, int) - 1)
end
local function getBits(stream, int)
local result = peekBits(stream, int)
stream.bits = rshift(stream.bits, int)
stream.count = stream.count - int
return result
end
local function getElement(stream, hufftable, int)
local element = hufftable[peekBits(stream, int)]
local length = band(element, 15)
local result = rshift(element, 4)
stream.bits = rshift(stream.bits, length)
stream.count = stream.count - length
return result
end
local function huffman(depths)
local size = #depths
local blocks, codes, hufftable = {[0] = 0}, {}, {}
local bits, code = 1, 0
for i = 1, size do
local depth = depths[i]
if depth > bits then
bits = depth
end
blocks[depth] = (blocks[depth] or 0) + 1
end
for i = 1, bits do
code = (code + (blocks[i - 1] or 0)) * 2
codes[i] = code
end
for i = 1, size do
local depth = depths[i]
if depth > 0 then
local element = (i - 1) * 16 + depth
local rcode = 0
for j = 1, depth do
rcode = rcode + lshift(band(1, rshift(codes[depth], j - 1)), depth - j)
end
for j = 0, 2 ^ bits - 1, 2 ^ depth do
hufftable[j + rcode] = element
end
codes[depth] = codes[depth] + 1
end
end
return hufftable, bits
end
local function loop(output, stream, litTable, litBits, distTable, distBits)
local index = #output + 1
local lit
repeat
lit = getElement(stream, litTable, litBits)
if lit < 256 then
output[index] = lit
index = index + 1
elseif lit > 256 then
local bits, size, dist = 0, 3, 1
if lit < 265 then
size = size + lit - 257
elseif lit < 285 then
bits = rshift(lit - 261, 2)
size = size + lshift(band(lit - 261, 3) + 4, bits)
else
size = 258
end
if bits > 0 then
size = size + getBits(stream, bits)
end
local element = getElement(stream, distTable, distBits)
if element < 4 then
dist = dist + element
else
bits = rshift(element - 2, 1)
dist = dist + lshift(band(element, 1) + 2, bits) + getBits(stream, bits)
end
local position = index - dist
repeat
output[index] = output[position]
index = index + 1
position = position + 1
size = size - 1
until size == 0
end
until lit == 256
end
local function dynamic(output, stream)
local lit, dist, length = 257 + getBits(stream, 5), 1 + getBits(stream, 5), 4 + getBits(stream, 4)
local depths = {}
for i = 1, length do
depths[ORDER[i]] = getBits(stream, 3)
end
for i = length + 1, 19 do
depths[ORDER[i]] = 0
end
local lengthTable, lengthBits = huffman(depths)
local i = 1
local total = lit + dist + 1
repeat
local element = getElement(stream, lengthTable, lengthBits)
if element < 16 then
depths[i] = element
i = i + 1
elseif element < 19 then
local int = NBT[element - 15]
local count = 0
local num = 3 + getBits(stream, int)
if element == 16 then
count = depths[i - 1]
elseif element == 18 then
num = num + 8
end
for _ = 1, num do
depths[i] = count
i = i + 1
end
end
until i == total
local litDepths, distDepths = {}, {}
for j = 1, lit do
litDepths[j] = depths[j]
end
for j = lit + 1, #depths do
distDepths[#distDepths + 1] = depths[j]
end
local litTable, litBits = huffman(litDepths)
local distTable, distBits = huffman(distDepths)
loop(output, stream, litTable, litBits, distTable, distBits)
end
local function static(output, stream)
local depths = {}
for i = 1, 4 do
local depth = DPT[i]
for _ = 1, CNT[i] do
depths[#depths + 1] = depth
end
end
local litTable, litBits = huffman(depths)
loop(output, stream, litTable, litBits, STATIC_HUFFMAN, STATIC_BITS)
end
local function uncompressed(output, stream)
flushBits(stream, band(stream.count, 7))
local length = getBits(stream, 16); getBits(stream, 16)
local buffer, position = stream.buffer, stream.position
for i = position, position + length - 1 do
output[#output + 1] = byte(buffer, i, i)
end
stream.position = position + length
end
local function int2le(buffer, position)
local a, b = byte(buffer, position, position + 1)
return b * 256 + a
end
local function int4le(buffer, position)
local a, b, c, d = byte(buffer, position, position + 3)
return ((d * 256 + c) * 256 + b) * 256 + a
end
local inflate = {}
---@class BitStream
---@field buffer string Character Buffer
---@field position integer Position in the character buffer
---@field bits integer Bits buffer
---@field count integer Number of bits in the buffer
local BitStream = {}
BitStream.__index = BitStream
---Creates a new bitstream object with the specified buffer
---@param buffer string The character buffer to use for the bitstream.
---@return BitStream
function inflate.new(buffer)
local EOCD
local pos = 1
repeat
local i, j = find(buffer, 'PK\5\6', pos, true)
if i then
EOCD = i
pos = j + 1
end
until not i
if EOCD then
buffer = sub(buffer, 1, EOCD + 19)..'\0\0'
end
local object = {buffer = buffer, position = 0, bits = 0, count = 0}
return setmetatable(object, BitStream)
end
---Update the chunk size to be used in inflation. Defaults to `4096`
---@param size integer The new chunk size, should be a power of 2.
function inflate.setChunkSize(size)
CHUNK_SIZE = size
end
---Returns an iterator that spans the list of files in the stream
---@return fun(): name: string?, offset: integer?, size: integer?, packed: boolean?, crc: integer?
function BitStream:files()
local buffer = self.buffer
local position = int4le(buffer, #buffer - 5) + 1
return function()
if int4le(buffer, position) ~= 33639248 then
return
end
local packed = int2le(buffer, position + 10) ~= 0
local crc = int4le(buffer, position + 16)
local length = int2le(buffer, position + 28)
local offset = int4le(buffer, position + 42) + 1
local name = sub(buffer, position + 46, position + 45 + length)
position = position + 46 + length + int2le(buffer, position + 30) + int2le(buffer, position + 32)
return name, offset + 30 + length + int2le(buffer, offset + 28), int4le(buffer, offset + 18), packed, crc
end
end
---Inflates the bitstream starting from the specified offset and optionally performs checksum verification
---@param offset integer The position at which to begin inflating.
---@param crc? integer The checksum value to use for verification.
---@return string
function BitStream:inflate(offset, crc)
local output, buffer = {}, {}
local last, typ
self.bits = 0
self.count = 0
self.position = offset
repeat
last, typ = getBits(self, 1), getBits(self, 2)
typ = typ == 0 and uncompressed(output, self) or typ == 1 and static(output, self) or typ == 2 and dynamic(output, self)
until last == 1
local size = #output
for i = 1, size, CHUNK_SIZE do
buffer[#buffer + 1] = char(unpack(output, i, min(i + CHUNK_SIZE - 1, size)))
end
local result = concat(buffer)
if crc and crc ~= generateCrc32(result) then
error("Checksum verification failed: the computed CRC does not match the expected value.", 2)
end
return result
end
---Extracts a specific file from the bitstream
---@param filepath string The file to unzip.
---@param verify? boolean Whether or not to perform checksum verification.
---@return string
function BitStream:unzip(filepath, verify)
for name, offset, size, packed, crc in self:files() do
if name == filepath then
return packed and self:inflate(offset, verify and crc or nil) or sub(self.buffer, offset, offset + size - 1)
end
end
error('File "'..filepath..'" not found in ZIP archive.')
end
--- Extracts unpacked contents from the bitstream at the specified offset and size
--- @param offset integer The starting position from which to extract the contents.
--- @param size integer The size of the contents to extract.
function BitStream:extract(offset, size)
return sub(self.buffer, offset, offset + size - 1)
end
return inflate
+372 -127
View File
@@ -5,84 +5,267 @@ local unpack = unpack or table.unpack
input_loaded, input = pcall(require, "mp.input")
uosc_available = false
latest_menu_anime = {}
local active_request_cancel = nil
local active_request_type = nil
local request_cancelled = false
-- 如果 latest_menu_anime 中存在首项为加载占位,移除它(兼容完整 menu props 或 items 数组)
local function strip_loading_from_latest_menu_anime()
if not latest_menu_anime or #latest_menu_anime == 0 then return end
local parsed = utils.parse_json(latest_menu_anime)
if not parsed or type(parsed) ~= "table" then return end
local function is_loading_item(it)
if not it or type(it) ~= "table" then return false end
if it.title == "加载数据中..." then return true end
if it.italic == true and it.icon == "spinner" then return true end
return false
end
if parsed.items and type(parsed.items) == "table" then
if #parsed.items > 0 and is_loading_item(parsed.items[1]) then
table.remove(parsed.items, 1)
latest_menu_anime = utils.format_json(parsed)
end
else
if #parsed > 0 and is_loading_item(parsed[1]) then
table.remove(parsed, 1)
latest_menu_anime = utils.format_json(parsed)
end
end
end
-- 统一取消并发请求:设置取消标志、剥离加载占位并调用实际取消函数
local function perform_cancel_active_request(expected_type)
if expected_type and active_request_type and tostring(expected_type) ~= tostring(active_request_type) then
return
end
if active_request_cancel then
request_cancelled = true
strip_loading_from_latest_menu_anime()
pcall(active_request_cancel)
active_request_cancel = nil
active_request_type = nil
end
end
local function make_build_args(encoded_query)
return function(server)
local url = server .. "/api/v2/search/anime"
local full_url = url .. "?keyword=" .. encoded_query
return make_danmaku_request_args("GET", full_url)
end
end
local function make_handle_response(ctx)
return function(server, err, out)
if request_cancelled then
ctx.remaining.n = math.max(0, ctx.remaining.n - 1)
return
end
local function do_final_update()
local final_items = {}
-- 按配置的 server 顺序拼接每个 server 的结果
for _, srv in ipairs(ctx.server_order or {}) do
local list = ctx.server_items and ctx.server_items[srv]
if list and type(list) == 'table' then
for _, v in ipairs(list) do table.insert(final_items, v) end
end
end
if request_cancelled then return end
if uosc_available then
latest_menu_anime = update_menu_uosc(ctx.menu_type, ctx.menu_title, final_items, ctx.footnote, ctx.menu_cmd, ctx.query)
else
latest_menu_anime = utils.format_json(final_items)
if input_loaded then
input.terminate()
mp.add_timeout(0.1, function()
open_menu_select(final_items)
end)
end
end
end
if err then
msg.debug(("search anime failed for %s: %s"):format(server, tostring(err)))
ctx.remaining.n = math.max(0, ctx.remaining.n - 1)
if ctx.remaining.n == 0 then pcall(do_final_update) end
return
end
local data = utils.parse_json(out)
if not data or not data.animes then
ctx.remaining.n = math.max(0, ctx.remaining.n - 1)
if ctx.remaining.n == 0 then pcall(do_final_update) end
return
end
for _, anime in ipairs(data.animes) do
local key = anime.bangumiId or (anime.animeTitle and anime.animeTitle:gsub("%s+", " ") or nil)
if key and not ctx.seen[key] then
ctx.seen[key] = true
ctx.server_items[server] = ctx.server_items[server] or {}
local note = (ctx.server_notes and ctx.server_notes[server]) or nil
local hint = anime.typeDescription
if note and note ~= "" then
if hint and hint ~= "" then
hint = hint .. "|" .. note
else
hint = note
end
end
table.insert(ctx.server_items[server], {
title = anime.animeTitle,
hint = hint,
value = { "script-message-to", mp.get_script_name(), "search-episodes-event", anime.animeTitle, anime.bangumiId, server },
})
ctx.total_count = (ctx.total_count or 0) + 1
end
end
local display_items = {}
if ctx.remaining.n > 0 then
local progress_msg = ctx.message or ""
if ctx.total_servers and ctx.total_servers > 1 and ctx.remaining and ctx.remaining.n then
local completed = math.max(0, (ctx.total_servers - ctx.remaining.n) + 1)
progress_msg = tostring(progress_msg):gsub("%.+$", "")
progress_msg = progress_msg .. string.format("%d/%d...", completed, ctx.total_servers)
end
table.insert(display_items, {
title = progress_msg,
value = "",
italic = true,
keep_open = true,
selectable = false,
icon = "spinner",
})
end
-- 按 server_order 拼接当前已收到的结果
for _, srv in ipairs(ctx.server_order or {}) do
local list = ctx.server_items and ctx.server_items[srv]
if list and type(list) == 'table' then
for _, v in ipairs(list) do table.insert(display_items, v) end
end
end
if uosc_available then
latest_menu_anime = update_menu_uosc(ctx.menu_type, ctx.menu_title, display_items, ctx.footnote, ctx.menu_cmd, ctx.query,
{ "script-message-to", mp.get_script_name(), "cancel-active-request", ctx.menu_type })
else
if not ctx.first_opened.val and input_loaded and (ctx.total_count or 0) > 0 then
ctx.first_opened.val = true
show_message("", 0)
input.terminate()
mp.add_timeout(0.1, function()
latest_menu_anime = utils.format_json(display_items)
open_menu_select(display_items)
end)
end
end
ctx.remaining.n = math.max(0, ctx.remaining.n - 1)
if ctx.remaining.n == 0 then
pcall(do_final_update)
end
end
end
-- 打开番剧数据匹配菜单
function get_animes(query)
local encoded_query = url_encode(query)
local url = options.api_server .. "/api/v2/search/anime"
local params = "keyword=" .. encoded_query
local full_url = url .. "?" .. params
local server_metas = get_api_server_list(options.api_server, true)
local servers = {}
local server_notes = {}
for _, m in ipairs(server_metas) do
table.insert(servers, m.url)
if m.note and m.note ~= '' then
server_notes[m.url] = m.note
end
end
local items = {}
local seen = {}
local first_opened = false
local remaining = #servers
local total_servers = remaining
local server_items = {}
local server_order = servers
local total_count = 0
request_cancelled = false
local message = "加载数据中..."
local menu_type = "menu_anime"
local menu_title = "在此处输入番剧名称"
local footnote = "使用enter或ctrl+enter进行搜索"
local menu_cmd = { "script-message-to", mp.get_script_name(), "search-anime-event" }
local function strip_trailing_dots(s)
if not s then return "" end
return tostring(s):gsub("%.+$", "")
end
local initial_message = message
if total_servers and total_servers > 1 then
initial_message = strip_trailing_dots(message) .. string.format("%d/%d...", 0, total_servers)
end
if uosc_available then
update_menu_uosc(menu_type, menu_title, message, footnote, menu_cmd, query)
active_request_type = menu_type
update_menu_uosc(menu_type, menu_title, initial_message, footnote, menu_cmd, query, "spinner",
{ "script-message-to", mp.get_script_name(), "cancel-active-request", menu_type })
else
show_message(message, 30)
end
msg.verbose("尝试获取番剧数据:" .. full_url)
local args = make_danmaku_request_args("GET", full_url)
if args == nil then
return
show_message(initial_message, 30)
end
local res = mp.command_native({ name = 'subprocess', capture_stdout = true, capture_stderr = true, args = args })
msg.verbose("尝试获取番剧数据,servers: " .. table.concat(servers, ", ") .. " query: " .. query)
if not res.status or res.status ~= 0 then
local message = "获取数据失败"
if uosc_available then
update_menu_uosc(menu_type, menu_title, message, footnote, menu_cmd, query)
else
show_message(message, 3)
local build_args = make_build_args(encoded_query)
-- 构造 ctx,用于 handle_response 闭包访问和修改共享状态
local ctx = {
items = items,
seen = seen,
first_opened = { val = first_opened },
remaining = { n = remaining },
message = message,
total_servers = total_servers,
menu_type = menu_type,
menu_title = menu_title,
footnote = footnote,
menu_cmd = menu_cmd,
query = query,
server_items = server_items,
server_order = server_order,
server_notes = server_notes,
total_count = total_count,
}
local handle_response = make_handle_response(ctx)
local cancel_fn = parallel_requests(servers, build_args, handle_response, function()
if request_cancelled then return end
local final_items = {}
for _, srv in ipairs(ctx.server_order or {}) do
local list = ctx.server_items and ctx.server_items[srv]
if list and type(list) == 'table' then
for _, v in ipairs(list) do table.insert(final_items, v) end
end
end
msg.error("HTTP 请求失败:" .. res.stderr)
end
local response = utils.parse_json(res.stdout)
if not response or not response.animes then
local message = "无结果"
if uosc_available then
update_menu_uosc(menu_type, menu_title, message, footnote, menu_cmd, query)
latest_menu_anime = update_menu_uosc(ctx.menu_type, ctx.menu_title, final_items, ctx.footnote, ctx.menu_cmd, ctx.query)
else
show_message(message, 3)
latest_menu_anime = utils.format_json(final_items)
if not ctx.first_opened.val and input_loaded and #final_items > 0 then
ctx.first_opened.val = true
show_message("", 0)
input.terminate()
mp.add_timeout(0.1, function()
open_menu_select(final_items)
end)
end
end
msg.info("无结果")
return
end
for _, anime in ipairs(response.animes) do
table.insert(items, {
title = anime.animeTitle,
hint = anime.typeDescription,
value = {
"script-message-to",
mp.get_script_name(),
"search-episodes-event",
anime.animeTitle, anime.bangumiId,
},
})
end
if uosc_available then
latest_menu_anime = update_menu_uosc(menu_type, menu_title, items, footnote, menu_cmd, query)
elseif input_loaded then
show_message("", 0)
mp.add_timeout(0.1, function()
latest_menu_anime = utils.format_json(items)
open_menu_select(items)
end)
end
end, { concurrency = 5, per_request_timeout = 30 })
active_request_cancel = cancel_fn
active_request_type = menu_type
end
function get_episodes(animeTitle, bangumiId)
local url = options.api_server .. "/api/v2/bangumi/" .. bangumiId
function get_episodes(animeTitle, bangumiId, api_server)
local url = api_server .. "/api/v2/bangumi/" .. bangumiId
local items = {}
local message = "加载数据中..."
@@ -91,7 +274,9 @@ function get_episodes(animeTitle, bangumiId)
local footnote = "使用 / 打开筛选"
if uosc_available then
update_menu_uosc(menu_type, menu_title, message, footnote)
active_request_type = menu_type
update_menu_uosc(menu_type, menu_title, message, footnote, nil, nil, "spinner",
{ "script-message-to", mp.get_script_name(), "cancel-active-request", menu_type })
else
show_message(message, 30)
end
@@ -102,60 +287,70 @@ function get_episodes(animeTitle, bangumiId)
return
end
local res = mp.command_native({ name = 'subprocess', capture_stdout = true, capture_stderr = true, args = args })
if not res.status or res.status ~= 0 then
local message = "获取数据失败"
if uosc_available then
update_menu_uosc(menu_type, menu_title, message, footnote)
else
show_message(message, 3)
request_cancelled = false
active_request_type = menu_type
active_request_cancel = call_cmd_async(args, function(err, stdout)
active_request_cancel = nil
if request_cancelled then
return
end
msg.error("HTTP 请求失败:" .. res.stderr)
end
local response = utils.parse_json(res.stdout)
if not response or not response.bangumi or not response.bangumi.episodes then
local message = "无结果"
if uosc_available then
update_menu_uosc(menu_type, menu_title, message, footnote)
else
show_message(message, 3)
if err then
local message = "获取数据失败"
if uosc_available then
update_menu_uosc(menu_type, menu_title, message, footnote)
else
show_message(message, 3)
end
msg.error("HTTP 请求失败:" .. tostring(err))
return
end
msg.info("无结果")
return
end
table.insert(items, {
title = "← 返回搜索结果",
value = { "script-message-to", mp.get_script_name(), "open-latest-menu-anime", latest_menu_anime },
keep_open = false,
selectable = true,
})
local response = utils.parse_json(stdout)
if not response or not response.bangumi or not response.bangumi.episodes then
local message = "无结果"
if uosc_available then
update_menu_uosc(menu_type, menu_title, message, footnote)
else
show_message(message, 3)
end
msg.info("无结果")
return
end
for _, episode in ipairs(response.bangumi.episodes) do
table.insert(items, {
title = episode.episodeTitle,
hint = episode.episodeNumber,
value = { "script-message-to", mp.get_script_name(), "load-danmaku",
animeTitle, episode.episodeTitle, episode.episodeId },
title = "← 返回搜索结果",
value = { "script-message-to", mp.get_script_name(), "open-latest-menu-anime", latest_menu_anime },
keep_open = false,
selectable = true,
})
end
if uosc_available then
footnote = mp.get_property("filename")
update_menu_uosc(menu_type, menu_title, items, footnote)
elseif input_loaded then
mp.add_timeout(0.1, function()
open_menu_select(items)
end)
end
for _, episode in ipairs(response.bangumi.episodes) do
table.insert(items, {
title = episode.episodeTitle,
hint = episode.episodeNumber,
value = { "script-message-to", mp.get_script_name(), "load-danmaku",
animeTitle, episode.episodeTitle, episode.episodeId, api_server },
keep_open = false,
selectable = true,
})
end
if uosc_available then
footnote = mp.get_property("filename")
update_menu_uosc(menu_type, menu_title, items, footnote)
elseif input_loaded then
show_message("", 0)
input.terminate()
mp.add_timeout(0.1, function()
open_menu_select(items)
end)
end
end)
active_request_type = menu_type
end
function update_menu_uosc(menu_type, menu_title, menu_item, menu_footnote, menu_cmd, query)
function update_menu_uosc(menu_type, menu_title, menu_item, menu_footnote, menu_cmd, query, message_icon, on_close)
local items = {}
if type(menu_item) == "string" then
table.insert(items, {
@@ -165,7 +360,7 @@ function update_menu_uosc(menu_type, menu_title, menu_item, menu_footnote, menu_
keep_open = true,
selectable = false,
align = "center",
icon = "spinner",
icon = message_icon or "",
})
else
items = menu_item
@@ -181,8 +376,19 @@ function update_menu_uosc(menu_type, menu_title, menu_item, menu_footnote, menu_
search_suggestion = query,
items = items,
}
if on_close ~= nil then
menu_props.on_close = on_close
end
local current_menu_type = mp.get_property_native('user-data/uosc/menu/type')
local cmd = "open-menu"
if current_menu_type and tostring(current_menu_type) == tostring(menu_type) then
cmd = "update-menu"
end
local json_props = utils.format_json(menu_props)
mp.commandv("script-message-to", "uosc", "open-menu", json_props)
mp.commandv("script-message-to", "uosc", cmd, json_props)
return json_props
end
@@ -200,6 +406,7 @@ function open_menu_select(menu_items, is_time)
items = item_titles,
submit = function(id)
input.terminate()
perform_cancel_active_request()
local v = item_values[id]
if type(v) == 'table' then
mp.commandv(unpack(v))
@@ -207,6 +414,11 @@ function open_menu_select(menu_items, is_time)
mp.command(v)
end
end,
closed = function()
show_message("", 0)
input.terminate()
perform_cancel_active_request()
end,
})
end
@@ -264,7 +476,9 @@ function open_input_menu()
if uosc_available then
open_input_menu_uosc()
elseif input_loaded then
open_input_menu_get()
mp.add_timeout(0.01, function()
open_input_menu_get()
end)
end
end
@@ -283,23 +497,29 @@ function open_add_menu_get()
local serial = 0
for url, source in pairs(DANMAKU.sources) do
if source.data then
serial = serial + 1
local action, text
if source.from == "api_server" then
action = source.blocked and "unblock" or "block"
text = string.format(" [%02d] %s [来源:弹幕服务器%s] ", serial, url,
serial = serial + 1
local action = source.blocked and "unblock" or "block"
local text = string.format(" [%02d] %s [来源:弹幕服务器%s] ", serial, url,
source.blocked and "(已屏蔽)" or "(未屏蔽)")
local style = (tonumber(select_num) == serial) and "{\\c&HFFDE7F&\\b1}" or (action == "unblock" and "{\\c&H4C4CC3&\\b0}" or "{\\c&HCCCCCC&\\b0}")
deal_value[serial] = {value = url, action = action}
table.insert(menu_log, {text = text, style = style})
else
action = "delete"
text = string.format(" [%02d] %s [来源:用户添加] ", serial, url)
serial = serial + 1
local action1 = source.blocked and "unblock" or "block"
local text1 = string.format(" [%02d] %s [来源:用户添加]%s ", serial, url, source.blocked and " (已屏蔽)" or "(未屏蔽)")
local style1 = (tonumber(select_num) == serial) and "{\\c&HFFDE7F&\\b1}" or (action1 == "unblock" and "{\\c&H4C4CC3&\\b0}" or "{\\c&HCCCCCC&\\b0}")
deal_value[serial] = {value = url, action = action1}
table.insert(menu_log, {text = text1, style = style1})
serial = serial + 1
local action2 = "delete"
local text2 = string.format(" [%02d] %s [来源:用户添加] (删除) ", serial, url)
local style2 = (tonumber(select_num) == serial) and "{\\c&HFFDE7F&\\b1}" or "{\\c&HCCCCCC&\\b0}"
deal_value[serial] = {value = url, action = action2}
table.insert(menu_log, {text = text2, style = style2})
end
local style = (tonumber(select_num) == serial) and
"{\\c&HFFDE7F&\\b1}" or (action == "unblock" and "{\\c&H4C4CC3&\\b0}" or "{\\c&HCCCCCC&\\b0}")
deal_value[serial] = {value = url, action = action}
table.insert(menu_log, {text = text, style = style})
end
end
@@ -386,7 +606,7 @@ function open_add_menu_uosc()
local sources = {}
for url, source in pairs(DANMAKU.sources) do
if source.data then
local item = {title = url, value = url, keep_open = true,}
local item = {title = utf8_sub(url, 1, 100), value = url, keep_open = true,}
if source.from == "api_server" then
if source.blocked then
item.hint = "来源:弹幕服务器(已屏蔽)"
@@ -397,7 +617,17 @@ function open_add_menu_uosc()
end
else
item.hint = "来源:用户添加"
item.actions = {{icon = "delete", name = "delete", label = "删除"},}
if source.blocked then
item.actions = {
{icon = "check", name = "unblock", label = "解除屏蔽"},
{icon = "delete", name = "delete", label = "删除"},
}
else
item.actions = {
{icon = "not_interested", name = "block", label = "屏蔽"},
{icon = "delete", name = "delete", label = "删除"},
}
end
end
table.insert(sources, item)
end
@@ -421,7 +651,9 @@ function open_add_menu()
if uosc_available then
open_add_menu_uosc()
elseif input_loaded then
open_add_menu_get()
mp.add_timeout(0.01, function()
open_add_menu_get()
end)
end
end
@@ -915,7 +1147,7 @@ function open_delay_menu_uosc(source_url, status)
end
end
end
local item = {title = url, value = url, keep_open = true,}
local item = {title = utf8_sub(url, 1, 100), value = url, keep_open = true,}
item.hint = "当前弹幕源延迟:" .. string.format("%.1f", delay + 1e-10) .. ""
item.active = url == source_url
table.insert(sources, item)
@@ -1013,7 +1245,10 @@ function open_add_total_menu_select()
prompt = '选择:',
items = item_titles,
submit = function(id)
mp.commandv(unpack(item_values[id]))
local cmd = item_values[id]
mp.add_timeout(0.1, function()
mp.commandv(unpack(cmd))
end)
end,
})
end
@@ -1026,6 +1261,10 @@ function open_add_total_menu()
end
end
function toggle_danmaku_switch(value)
mp.commandv("script-message-to", "uosc", "set", "show_danmaku", value)
mp.set_property_bool(DANMAKU_SWITCH_ON, value == "on")
end
mp.commandv(
"script-message-to",
@@ -1114,11 +1353,12 @@ mp.register_script_message("set", function(prop, value)
hide_danmaku_func()
end
mp.commandv("script-message-to", "uosc", "set", "show_danmaku", value)
toggle_danmaku_switch(value)
end)
-- 注册函数给 uosc 按钮使用
mp.register_script_message("search-anime-event", function(query)
perform_cancel_active_request()
if uosc_available then
mp.commandv("script-message-to", "uosc", "close-menu", "menu_danmaku")
end
@@ -1129,19 +1369,20 @@ mp.register_script_message("search-anime-event", function(query)
get_animes(query)
end
end)
mp.register_script_message("search-episodes-event", function(animeTitle, bangumiId)
mp.register_script_message("search-episodes-event", function(animeTitle, bangumiId, api_server)
perform_cancel_active_request()
if uosc_available then
mp.commandv("script-message-to", "uosc", "close-menu", "menu_anime")
end
get_episodes(animeTitle, bangumiId)
get_episodes(animeTitle, bangumiId, api_server)
end)
-- Register script message to show the input menu
mp.register_script_message("load-danmaku", function(animeTitle, episodeTitle, episodeId)
mp.register_script_message("load-danmaku", function(animeTitle, episodeTitle, episodeId, api_server)
ENABLED = true
DANMAKU.anime = animeTitle
DANMAKU.episode = episodeTitle
set_episode_id(episodeId, true)
set_episode_id(episodeId, true, api_server)
end)
mp.register_script_message("add-source-event", function(query)
@@ -1177,6 +1418,10 @@ mp.register_script_message("open-latest-menu-anime", function ()
end
end)
mp.register_script_message('cancel-active-request', function(menu_type)
perform_cancel_active_request(menu_type)
end)
mp.register_script_message("setup-danmaku-style", function(query, text)
local event = utils.parse_json(query)
if event ~= nil then
@@ -1273,7 +1518,7 @@ mp.register_script_message("setup-source-delay", function(query, text)
end
local delay = parse_delay_input(text)
if delay ~= nil then
mp.commandv("script-message", "danmaku-delay", tostring(delay), "0")
mp.commandv("script-message", "danmaku-delay", tostring(delay), "0", tostring(query))
mp.commandv("script-message-to", "uosc", "close-menu", "menu_delay")
mp.add_timeout(0.1, function()
open_delay_menu(query, "refresh")
+9 -1
View File
@@ -3,7 +3,9 @@ local opt = require("mp.options")
-- 选项
options = {
-- 指定弹幕服务器地址,自定义服务需兼容 dandanplay 的 api
api_server = "https://api.dandanplay.net",
-- 可指定多个用逗号分隔的有序 api_server 列表
-- 支持每项使用 '|' 或 '#' 分隔备注,例如: "https://a.example.com|备用A" 或 "https://b.example.com#备用B"
api_server = "https://danmaku-api.152468.xyz",
-- 指定 b 站和爱腾优的弹幕获取的兜底服务器地址,主要用于获取非动画弹幕
-- 可用: https://api.danmu.icuhttps://dmku.hls.one
fallback_server = "https://api.danmu.icu",
@@ -15,6 +17,10 @@ options = {
autoload_local_danmaku = false,
autoload_for_url = false,
save_danmaku = false,
-- 指定弹幕保存目录。为空时保存到视频同目录;目录需要用户提前创建
save_danmaku_path = "",
-- 指定 save_danmaku_path 的应用范围:local / url / all
save_danmaku_path_mode = "local",
user_agent = "mpv_danmaku/1.0",
proxy = "",
-- 可选:向 HTTP 请求传递 cookie.txt 文件路径
@@ -25,6 +31,8 @@ options = {
fps = "60/1.001",
-- 指定合并重复弹幕的时间间隔的容差值,单位为秒。默认值: -1,表示禁用
merge_tolerance = -1,
-- 合并重复弹幕时是否强制合并类型和颜色不同的弹幕。默认值: false,表示仅合并类型和颜色相同的弹幕
merge_without_style = false,
-- 指定弹幕关联历史记录文件的路径,支持绝对路径和相对路径
history_path = "~~/danmaku-history.json",
open_search_danmaku_menu_key = "Ctrl+d",
+133 -71
View File
@@ -10,6 +10,33 @@ local function ass_escape(text)
:gsub("\n", "\\N")
end
-- 构建 per-source 的延迟查询函数
local function make_delay_lookup(source)
local segments = nil
local prefix = nil
if source and source.delay_segments and #source.delay_segments > 0 then
segments = {}
for i, v in ipairs(source.delay_segments) do segments[i] = v end
table.sort(segments, function(a, b) return (a.start or 0) < (b.start or 0) end)
prefix = {}
local s = 0
for i, v in ipairs(segments) do
s = s + (v.delay or 0)
prefix[i] = s
end
end
return function(t)
local segs = segments or {}
local pre = prefix or {}
if #segs == 0 then return 0 end
local idx = binary_search(segs, t, function(s) return (s and s.start) or 0 end)
local target = idx - 1
if target < 1 then return 0 end
return pre[target] or 0
end
end
local function xml_unescape(str)
return str:gsub("&quot;", "\"")
:gsub("&apos;", "'")
@@ -154,62 +181,103 @@ local function merge_duplicate_danmaku(danmakus, threshold)
local groups = {}
for _, d in ipairs(danmakus) do
local tkey = tostring(d.type or "")
local ckey = tostring(d.color or "")
local tkey = options.merge_without_style and "any" or tostring(d.type or "")
local text = d.text or ""
groups[tkey] = groups[tkey] or {}
groups[tkey][ckey] = groups[tkey][ckey] or {}
groups[tkey][ckey][text] = groups[tkey][ckey][text] or {}
table.insert(groups[tkey][ckey][text], d)
groups[text] = groups[text] or {}
groups[text][tkey] = groups[text][tkey] or {}
table.insert(groups[text][tkey], d)
end
local final_groups = {}
-- 颜色合并
for _, type_groups in pairs(groups) do
for _, list in pairs(type_groups) do
if options.merge_without_style then
table.insert(final_groups, list)
else
local color_groups = {}
-- 利用弹幕同色大量重复的特征,缓存 颜色值 -> color_groups 索引
local exact_color_cache = {}
for _, d in ipairs(list) do
local c = d.color or 16777215
local g_idx = exact_color_cache[c]
if g_idx then
-- 命中缓存,直接插入
table.insert(color_groups[g_idx], d)
else
-- 未命中的颜色值,计算它是否落入已有代表颜色的 15 容差范围内
local found = false
for i, cg in ipairs(color_groups) do
if color_dist(cg[1].color or 16777215, c) <= 15 then
table.insert(cg, d)
-- 将该颜色值缓存为这个代表颜色的索引,后续遇到完全相同的颜色值可以直接命中
exact_color_cache[c] = i
found = true
break
end
end
-- 超出容差范围的全新颜色代表,开辟新组
if not found then
table.insert(color_groups, {d})
exact_color_cache[c] = #color_groups
end
end
end
for _, cg in ipairs(color_groups) do
table.insert(final_groups, cg)
end
end
end
end
local merged = {}
local abs = math.abs
for _, bytype in pairs(groups) do
for _, bycolor in pairs(bytype) do
for _, group in pairs(bycolor) do
table.sort(group, function(a, b) return a.time < b.time end)
for _, group in ipairs(final_groups) do
table.sort(group, function(a, b) return a.time < b.time end)
local i = 1
while i <= #group do
local base = group[i]
local times = { base.time }
local count = 1
local j = i + 1
local i = 1
while i <= #group do
local base = group[i]
local times = { base.time }
local count = 1
local j = i + 1
while j <= #group and abs(group[j].time - base.time) <= threshold do
times[#times+1] = group[j].time
count = count + 1
j = j + 1
end
while j <= #group and abs(group[j].time - base.time) <= threshold do
times[#times+1] = group[j].time
count = count + 1
j = j + 1
end
local same_time = true
for k = 2, #times do
if times[k] ~= times[1] then
same_time = false
break
end
end
local danmaku = {
time = base.time,
type = base.type,
size = base.size,
color = base.color,
text = base.text,
source = base.source,
orig_time = base.orig_time,
}
if count > 2 or not same_time then
danmaku.text = danmaku.text .. string.format("x%d", count)
end
table.insert(merged, danmaku)
i = j
local same_time = true
for k = 2, #times do
if times[k] ~= times[1] then
same_time = false
break
end
end
local danmaku = {
time = base.time,
type = base.type,
size = base.size,
color = base.color,
text = base.text,
source = base.source,
orig_time = base.orig_time,
}
if count > 2 or not same_time then
danmaku.text = danmaku.text .. string.format("x%d", count)
end
table.insert(merged, danmaku)
i = j
end
end
@@ -259,8 +327,11 @@ local function limit_danmaku(danmakus, limit)
end
-- 解析 XML 弹幕
local function parse_xml_danmaku(xml_string)
function parse_xml_danmaku(xml_string)
local danmakus = {}
if not xml_string then
return danmakus
end
-- [^>]* 匹配其他 attributes
-- %f[^%s] 确保 p= 前面是空白字符
for p_attr, text in xml_string:gmatch('<d%s+[^>]*%f[^%s]p="([^"]+)"[^>]*>([^<]+)</d>') do
@@ -287,7 +358,7 @@ local function parse_xml_danmaku(xml_string)
end
-- 解析 JSON 弹幕
local function parse_json_danmaku(json_string)
function parse_json_danmaku(json_string)
local danmakus = {}
if json_string:sub(1, 3) == "\239\187\191" then
json_string = json_string:sub(4)
@@ -469,10 +540,22 @@ end
-- 将弹幕转换为 XML 格式
function convert_danmaku_to_xml(danmaku_out)
local danmakus = {}
for _, source in pairs(DANMAKU.sources) do
for url, source in pairs(DANMAKU.sources) do
if not source.blocked and source.data then
local get_cached_delay = make_delay_lookup(source)
for _, d in ipairs(source.data) do
table.insert(danmakus, d)
local base_time = d.orig_time or d.time
local adjusted_time = base_time + get_cached_delay(base_time)
table.insert(danmakus, {
orig_time = d.orig_time,
time = adjusted_time,
type = d.type,
size = d.size,
color = d.color,
text = d.text,
source = url,
})
end
end
end
@@ -521,29 +604,7 @@ function convert_danmaku_to_ass_events(force)
local per_source_lists = {}
for url, source in pairs(DANMAKU.sources) do
if not source.blocked and source.data then
local segments = nil
local prefix = nil
if source.delay_segments and #source.delay_segments > 0 then
segments = {}
for i, v in ipairs(source.delay_segments) do segments[i] = v end
table.sort(segments, function(a, b) return (a.start or 0) < (b.start or 0) end)
prefix = {}
local s = 0
for i, v in ipairs(segments) do
s = s + (v.delay or 0)
prefix[i] = s
end
end
local function get_cached_delay(t)
local segs = segments or {}
local pre = prefix or {}
if #segs == 0 then return 0 end
local idx = binary_search(segs, t, function(s) return (s and s.start) or 0 end)
local target = idx - 1
if target < 1 then return 0 end
return pre[target] or 0
end
local get_cached_delay = make_delay_lookup(source)
local list = {}
for _, d in ipairs(source.data) do
@@ -711,6 +772,7 @@ function convert_danmaku_to_ass_events(force)
clean_text = clean_text,
pos = pos,
move = move,
layer = (style == "R2L") and 0 or 1,
source = d.source,
}
table.insert(ass_events, event)
+33 -13
View File
@@ -5,7 +5,8 @@ local unpack = unpack or table.unpack
local osd_width, osd_height, pause = 0, 0, true
local time_pos_observer_active = false
local overlay = mp.create_osd_overlay('ass-events')
local overlay_low = mp.create_osd_overlay('ass-events')
local overlay_high = mp.create_osd_overlay('ass-events')
local function realtime_position_text(event, pos, displayarea)
if not event.move then
@@ -51,7 +52,8 @@ function render(pos_arg)
end
if not pos then
overlay:remove()
overlay_low:remove()
overlay_high:remove()
return
end
@@ -67,7 +69,8 @@ function render(pos_arg)
fontsize = options.fontsize - ratio * 2
end
local ass_events = {}
local ass_events_low = {}
local ass_events_high = {}
local max_display = math.max(options.scrolltime, options.fixtime)
local window_start = pos - max_display
@@ -99,22 +102,36 @@ function render(pos_arg)
-- 构建 ASS 字符串
local ass_text = text and (ass_prefix .. text)
table.insert(ass_events, ass_text)
if ass_text then
if event.layer == nil or tonumber(event.layer) == 0 then
table.insert(ass_events_low, ass_text)
else
table.insert(ass_events_high, ass_text)
end
end
end
end
overlay.res_x = width
overlay.res_y = height
overlay.data = table.concat(ass_events, '\n')
overlay:update()
-- 写入低层(滚动)和高层(顶/底)overlay,并设置 z 值以控制堆叠
overlay_low.res_x = width
overlay_low.res_y = height
overlay_low.z = 0
overlay_low.data = table.concat(ass_events_low, '\n')
overlay_low:update()
overlay_high.res_x = width
overlay_high.res_y = height
overlay_high.z = 1
overlay_high.data = table.concat(ass_events_high, '\n')
overlay_high:update()
end
local function time_pos_callback(_, time_pos)
if time_pos then
render(time_pos)
else
overlay:remove()
overlay_low:remove()
overlay_high:remove()
end
end
@@ -137,7 +154,7 @@ function render_danmaku(from_menu, no_osd)
if not no_osd then
show_loaded(true)
end
mp.commandv("script-message-to", "uosc", "set", "show_danmaku", "on")
toggle_danmaku_switch("on")
show_danmaku_func()
else
show_message("")
@@ -179,7 +196,8 @@ function hide_danmaku_func()
stop_time_observer()
mp.set_property_bool(HAS_DANMAKU, false)
set_danmaku_visibility(false)
overlay:remove()
overlay_low:remove()
overlay_high:remove()
if filter_state("danmaku") then
mp.commandv("vf", "remove", "@danmaku")
end
@@ -235,7 +253,8 @@ end)
mp.add_hook("on_unload", 50, function()
COMMENTS, DELAY = nil, 0
stop_time_observer()
overlay:remove()
overlay_low:remove()
overlay_high:remove()
mp.set_property_native(DELAY_PROPERTY, 0)
if filter_state("danmaku") then
mp.commandv("vf", "remove", "@danmaku")
@@ -256,4 +275,5 @@ mp.add_hook("on_unload", 50, function()
end
DANMAKU = {sources = {}, count = 1}
mp.set_property_native(DANMAKU_COUNT, 0)
end)
+247 -10
View File
@@ -201,6 +201,18 @@ function is_protocol(path)
return type(path) == 'string' and (path:find('^%a[%w.+-]-://') ~= nil or path:find('^%a[%w.+-]-:%?') ~= nil)
end
function sanitize_filename(name)
if not name or name == "" then
return "danmaku"
end
name = name:gsub('[/\\:*?"<>|]', "_")
:gsub('^%s*(.-)%s*$', '%1')
if name == "" then
return "danmaku"
end
return name
end
function hex_to_bin(hexstr)
return (hexstr:gsub('..', function (cc)
return string.char(tonumber(cc, 16))
@@ -214,6 +226,9 @@ end
function hex_to_int_color(hex_color)
-- 移除颜色代码中的'#'字符
hex_color = hex_color:sub(2) -- 只保留颜色代码部分
if not hex_color:match("^%x%x%x%x%x%x$") then
return 16777215
end
-- 提取R, G, B的十六进制值并转为整数
local r = tonumber(hex_color:sub(1, 2), 16)
@@ -226,14 +241,14 @@ function hex_to_int_color(hex_color)
return color_int
end
local function get_type_from_position(position)
if position == 0 then
return 1
end
if position == 1 then
return 4
end
return 5
function color_dist(c1, c2)
local r1 = math.floor(c1 / 65536) % 256
local g1 = math.floor(c1 / 256) % 256
local b1 = c1 % 256
local r2 = math.floor(c2 / 65536) % 256
local g2 = math.floor(c2 / 256) % 256
local b2 = c2 % 256
return math.sqrt((r1-r2)^2 + (g1-g2)^2 + (b1-b2)^2)
end
-- url编码转换
@@ -424,6 +439,42 @@ function contains_any(tab, val)
return false
end
-- 将一个逗号分隔的 api_server 字符串解析为有序列表
function get_api_server_list(api_server_str, meta)
local want_meta = meta or false
local metas = {}
if not api_server_str or api_server_str == "" then
if want_meta then return metas end
return {}
end
for part in string.gmatch(api_server_str, "[^,]+") do
local s = part:gsub('^%s*(.-)%s*$', '%1')
if s ~= '' then
local u, n = s:match('^(.-)[|#](.*)$')
local url = nil
local note = nil
if u then
url = u:gsub('^%s*(.-)%s*$', '%1')
note = n and n:gsub('^%s*(.-)%s*$', '%1') or nil
else
url = s:gsub('^%s*(.-)%s*$', '%1')
note = nil
end
table.insert(metas, { url = url, note = note })
end
end
if #metas == 0 and api_server_str ~= '' then
table.insert(metas, { url = api_server_str, note = nil })
end
if want_meta then return metas end
local urls = {}
for _, m in ipairs(metas) do table.insert(urls, m.url) end
return urls
end
--读history 和 写history
function read_file(file_path)
local file = io.open(file_path, "r") -- 打开文件,"r"表示只读模式
@@ -727,10 +778,29 @@ function number_to_chinese(num)
return result
end
-- 内部的异步运行计数
local async_running_count = 0
function mark_async_start()
async_running_count = async_running_count + 1
end
function mark_async_end()
if async_running_count > 0 then
async_running_count = async_running_count - 1
end
end
function is_async_running()
return async_running_count > 0
end
-- 异步执行命令
-- 同时返回 abort 函数,用于取消异步命令
function call_cmd_async(args, callback)
async_running = true
-- 标记异步开始
mark_async_start()
local abort_signal = mp.command_native_async({
name = 'subprocess',
capture_stderr = true,
@@ -738,9 +808,14 @@ function call_cmd_async(args, callback)
playback_only = true,
args = args,
}, function(success, result, error)
-- 标记异步结束
mark_async_end()
if not success or not result or result.status ~= 0 then
local exit_code = (result and result.status or 'unknown')
local message = error or (result and result.stdout .. result.stderr) or ''
local message = error or (
result and ((result.stdout or '') .. (result.stderr or ''))
) or ''
callback('Calling failed. Exit code: ' .. exit_code .. ' Error: ' .. message, {})
return
end
@@ -752,4 +827,166 @@ function call_cmd_async(args, callback)
return function()
mp.abort_async_command(abort_signal)
end
end
local function yield_once()
local co = coroutine.running()
mp.add_timeout(0, function()
coroutine.resume(co)
end)
coroutine.yield()
end
local function make_safe_resume(co, timer_ref)
local resumed = false
return function(...)
if resumed then return end
resumed = true
if timer_ref.timer then
timer_ref.timer:kill()
timer_ref.timer = nil
end
local ok, err = coroutine.resume(co, ...)
if not ok then
mp.msg.warn("resume failed: " .. tostring(err))
end
end
end
function await_call_cmd(args, timeout, on_start)
local co = coroutine.running()
local timer_ref = { timer = nil }
local safe_resume = make_safe_resume(co, timer_ref)
local abort_fn = call_cmd_async(args, function(err, out)
safe_resume(err, out)
end)
if on_start and type(on_start) == 'function' then
pcall(on_start, abort_fn)
end
if timeout and type(timeout) == 'number' and timeout > 0 then
timer_ref.timer = mp.add_timeout(timeout, function()
if abort_fn then pcall(abort_fn) end
safe_resume("timeout", nil)
end)
end
return coroutine.yield()
end
-- 并行请求调度器
-- servers: { "url1", "url2", ... }
-- build_args_fn(server) -> args
-- per_response_cb(server, err, stdout)
-- final_cb() 可选
-- opts: { concurrency=3, per_request_timeout=10 }
function parallel_requests(servers, build_args_fn, per_response_cb, final_cb, opts)
if type(final_cb) == 'table' and opts == nil then
opts = final_cb
final_cb = nil
end
opts = opts or {}
local concurrency = opts.concurrency or 3
local timeout = opts.per_request_timeout or 10
local in_flight_abort = {}
local in_flight_count = 0
local aborted = false
local idx = 1
local monitor = nil
-- worker 协程
local function worker()
while true do
if aborted then return end
local i = idx
if i > #servers then return end
idx = idx + 1
local server = servers[i]
local args = build_args_fn(server)
if not args then
if not aborted then
pcall(per_response_cb, server, "no_args", nil)
end
yield_once()
else
local ok, err, out = pcall(function()
return await_call_cmd(args, timeout, function(ab)
in_flight_abort[i] = ab
in_flight_count = in_flight_count + 1
end)
end)
-- 请求结束
if in_flight_abort[i] then
in_flight_abort[i] = nil
in_flight_count = in_flight_count - 1
end
if aborted then return end
if not ok then
pcall(per_response_cb, server, tostring(err), nil)
else
pcall(per_response_cb, server, err, out)
end
end
end
end
-- 启动 worker
local workers = {}
for i = 1, math.min(concurrency, #servers) do
local co = coroutine.create(worker)
local ok, res = coroutine.resume(co)
if not ok then
mp.msg.warn("worker start failed: " .. tostring(res))
end
workers[#workers + 1] = co
end
-- monitor(完成检测)
monitor = mp.add_periodic_timer(0.05, function()
if aborted then
monitor:kill()
return
end
local all_assigned = (idx > #servers)
local any_inflight = (in_flight_count > 0)
if all_assigned and not any_inflight then
monitor:kill()
if final_cb then pcall(final_cb) end
end
end)
-- 返回取消函数
return function()
if aborted then return end
aborted = true
for i, abort_fn in pairs(in_flight_abort) do
if abort_fn then pcall(abort_fn) end
in_flight_abort[i] = nil
end
in_flight_count = 0
if monitor then
monitor:kill()
monitor = nil
end
end
end