update
This commit is contained in:
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
||||
@@ -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.icu,https://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",
|
||||
|
||||
@@ -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(""", "\"")
|
||||
:gsub("'", "'")
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user